📖 Scenario: You are working on a FreeRTOS embedded system where two tasks share a resource. You want to understand the difference between using a mutex and a binary semaphore to control access to this shared resource.
🎯 Goal: Build a simple FreeRTOS program that creates a mutex and a binary semaphore, then uses each to protect a shared variable in separate tasks. You will see how to create, take, and give both synchronization objects.
📋 What You'll Learn
Create a mutex using xSemaphoreCreateMutex()
Create a binary semaphore using xSemaphoreCreateBinary()
Create two tasks: one uses the mutex, the other uses the binary semaphore
Each task takes and gives its synchronization object around accessing a shared variable
Print messages showing when each task takes and gives the mutex or semaphore
💡 Why This Matters
🌍 Real World
In embedded systems, controlling access to shared resources like sensors or communication buses is critical. Mutexes and semaphores help avoid conflicts and data corruption.
💼 Career
Understanding synchronization primitives like mutexes and semaphores is essential for embedded software engineers working with real-time operating systems such as FreeRTOS.
Progress0 / 4 steps
1
Create shared variable and synchronization objects
Create a global integer variable called sharedCounter and initialize it to 0. Then create a mutex called mutex using xSemaphoreCreateMutex() and a binary semaphore called binarySemaphore using xSemaphoreCreateBinary().
FreeRTOS
Hint
Use xSemaphoreCreateMutex() to create the mutex and xSemaphoreCreateBinary() to create the binary semaphore.
2
Create tasks to use mutex and binary semaphore
Create two FreeRTOS tasks: MutexTask and BinarySemaphoreTask. Each task should run a function that tries to take its synchronization object, increments sharedCounter, then gives the synchronization object back. Use xSemaphoreTake() and xSemaphoreGive() with a block time of portMAX_DELAY.
FreeRTOS
Hint
Use infinite loops in each task. Take the mutex or binary semaphore before incrementing sharedCounter, then give it back.
3
Initialize binary semaphore and create tasks
In the main function, call setupSyncObjects(). Then give the binary semaphore once using xSemaphoreGive(binarySemaphore) to initialize it. Create the two tasks MutexTask and BinarySemaphoreTask with priority 1 and stack size 1000. Finally, start the scheduler with vTaskStartScheduler().
FreeRTOS
Hint
Remember to give the binary semaphore once before starting the tasks so it is available initially.
4
Print sharedCounter updates
Modify both MutexTask and BinarySemaphoreTask to print messages when they take and give their synchronization object. Use printf() to print "Mutex taken, sharedCounter = %d\n" and "Mutex given\n" in MutexTask, and similarly "Semaphore taken, sharedCounter = %d\n" and "Semaphore given\n" in BinarySemaphoreTask. This will show the difference in usage.
FreeRTOS
Hint
Use printf() before and after giving the mutex or semaphore to show when they are taken and released.
Practice
(1/5)
1. Which of the following best describes the main difference between a mutex and a binary semaphore in FreeRTOS?
easy
A. Binary semaphores have ownership; mutexes do not.
B. Mutexes have ownership and priority inheritance; binary semaphores do not.
C. Mutexes are used only for signaling; binary semaphores are for exclusive access.
D. There is no difference; both behave exactly the same.
Solution
Step 1: Understand ownership concept
Mutexes have ownership, meaning the task that locks it must unlock it. Binary semaphores do not have ownership.
Step 2: Understand priority inheritance
Mutexes support priority inheritance to avoid priority inversion; binary semaphores do not.
Final Answer:
Mutexes have ownership and priority inheritance; binary semaphores do not. -> Option B
B. Taking the semaphore with portMAX_DELAY is invalid.
C. Binary semaphore must be created with xSemaphoreCreateMutex().
D. Giving the semaphore twice causes incorrect behavior.
Solution
Step 1: Understand binary semaphore usage
Binary semaphores act like flags with max count 1. Giving twice without taking in between can cause logic errors.
Step 2: Analyze the code's semaphore give calls
The code gives the semaphore twice without an intervening take, which can lead to unexpected behavior or overflow.
Final Answer:
Giving the semaphore twice causes incorrect behavior. -> Option D
Quick Check:
Binary semaphore max count = 1, double give is wrong [OK]
Hint: Binary semaphore max count 1, don't give twice without take [OK]
Common Mistakes:
Thinking portMAX_DELAY is invalid
Confusing binary semaphore with mutex creation
Ignoring semaphore initialization
5. You have two tasks: Task A (high priority) and Task B (low priority). Both need exclusive access to a shared resource. Which synchronization method should you use to avoid priority inversion and why?
A) Use a mutex because it supports priority inheritance.
B) Use a binary semaphore because it is simpler.
C) Use a counting semaphore to allow multiple accesses.
D) Use no synchronization to improve performance.
hard
A. Use a counting semaphore to allow multiple accesses.
B. Use a binary semaphore because it is simpler.
C. Use a mutex because it supports priority inheritance.
D. Use no synchronization to improve performance.
Solution
Step 1: Identify need for exclusive access with priority handling
Both tasks need exclusive access, so synchronization is required. Priority inversion can occur if a low priority task holds the resource.
Step 2: Choose synchronization with priority inheritance
Mutexes provide priority inheritance, temporarily boosting the low priority task's priority to avoid blocking the high priority task.
Final Answer:
Use a mutex because it supports priority inheritance. -> Option C
Quick Check:
Priority inversion avoidance = mutex [OK]
Hint: Use mutex for priority inheritance and exclusive access [OK]