Real-time Operating Systems (RTOS) has become a popular tool used in nearly two-thirds of all microcontroller-based embedded systems . The chances that you are currently using an RTOS or will use one in the near future are high. An RTOS allows you to break your system up into tasks, set priorities, abstract low-level timing and hardware, and provide tools to manage the application. Two commonly used tools within an RTOS application are Semaphores and Mutexes. Unfortunately, developers can often get confused about what these tools are for, leading to long and costly debug sessions.
In this post, we will explore everything that you need to know about Semaphores and Mutexes. You’ll learn what they are, where to use them, and how to avoid common pitfalls.Everything You Need To Know About Semaphores And Mutexes Click To Tweet
Mutual Exclusion through Mutexes
A Mutex is a mutual exclusion object used in multithreading applications to restrict access to a shared resource. A Mutex provides the idea of locking and unlocking a resource. If the mutex has been locked by thread A, then thread B can’t use the resource until thread A unlocks it. While thread A has the resource locked, thread A owns the resource.
Let’s look at an example. Let’s say I have two threads, thread A and thread B. Thread A uses USART1 to send telemetry data to a GUI application. Thread B uses USART1 to send diagnostic and performance information to a GUI application. As a designer, several interesting things can happen if you allow threads A and B to use USART1 without a Mutex.
First, depending on how the thread priorities are set, you may discover that the application works without any issues. If this happens, it’s by random luck and chance. A slight change to the software could break it! Next, and more likely, you’ll discover that one thread starts to print out its data only to be interrupted by the other thread sending out its data! Since nothing is protecting USART1, the two threads will just stomp all over each other.
When using a Mutex, if thread A has locked USART1, if thread B tries to take the Mutex and finds that it is locked, it will block and not use USART1 until thread A has released it. As you can see, Mutexes help to protect shared resources.
Task Synchronization through Semaphores
Semaphores can be thought of as tokens or flags that are used to synchronize application behavior. They. are used to synchronize behavior between interrupt service routines (ISR) and a thread or one thread to another.
A Semaphore is also sometimes referred to as a Counting Semaphore. You can specify how many tokens the Semaphore will contain. For example, you might create a Semaphore with up to 16 tokens that initialize to 0. When an ISR fires, it gives one token to Semaphore. Thread A might then take or consume a token and perform a behavior specific to the application.
A special type of Semaphore is a Binary Semaphore which can have up to a single token. As you can imagine, the Binary Semaphore has a value of either 0 or 1, hence a binary value. In application design, there is nothing special about Binary Semaphore. They are just a special case of Counting Semaphore. Unfortunately, designers and developers can get themselves in trouble by using Binary Semaphores for mutual exclusion.
Binary Semaphores versus Mutexes
Designers have a bad habit of confusing Binary Semaphores with Mutexes. If you superficially look at a Mutex (lock/unlock), it looks a lot like a Binary Semaphore (0 or 1). In fact, if you look at the FreeRTOS APIs, you’ll discover that they don’t even separate them! They lump Mutexes and Semaphores together, making it look like they are identical!
Note: I’ll take a very strong stance that this is wrong, which has led to a lot of confusion among embedded software engineers and teams that Binary Semaphores and Mutexes are interchangeable.
Besides the confusion, if you dig into the memory usage and performance for Mutexes and Semaphores, for most RTOSes, you’ll find that Mutexes are a bit “heavier”. So if Binary Semaphores are lighter weight, why not use Binary Semaphores for resource protection in place of Mutexes? To answer that question, we look to a special feature of Mutexes called priority inheritance.
The Mutexes Priority Inheritance Mechanism
n 1997, NASA’s Mars Pathfinder rover, suffered from watchdog timeouts
shortly after arriving on the red planet. The underlying cause was a Priority Inversion
caused by a low-priority task blocking access to a higher-priority task from
gaining access to a bus. The developers had properly used a Mutex to control
access to the bus, but they had configured VxWorks such that a Mutex behaved
like a Binary Semaphore. That is, there was no priority inheritance.
Priority Inheritance occurs when a low-priority task’s priority level
is elevated to a higher priority so that it can complete its activity and
release a Mutex that is blocking a higher-priority task. The idea behind Priority
Inheritance is that it minimizes or eliminates priority inversion. For example,
enabling priority inheritance on the Mutex that protected the bus in the
Pathfinder software solved the watchdog reset issue. In addition, they
converted a Mutex that was a Binary Semaphore into a true Mutex with Priority
Binary Semaphores are not the right tool to protect a shared resource. Even though it may be tempting, use a Mutex with Priority Inheritance enabled instead.
Note: You can learn a little more in What really happened on Mars Rover Pathfinder
Semaphore and Mutex APIs
The APIs used to create and manage Semaphores, and Mutexes vary from one RTOS to the next. There is no adopted standard that every RTOS follows. If you examine the APIs from common RTOSes like uCOS, FreeRTOS, ThreadX, and Zephr, you’ll find that they are all different. There is a temptation by the developer to simply adopt the APIs from the selected RTOS and build the entire application around it. While that is one strategy, a better approach is for you to adopt an abstraction layer.
An abstraction layer is a standard interface that the application code can use, using whatever the underlying RTOS API call is. It wraps the selected RTOS API and provides a standard API to the application. While this is extra work, it does give the ability for developers to switch between RTOSes, remove RTOS dependency from their application, and leverage modern DevOps techniques like on-host testing, simulation, and so forth. Again, I recommend you check out the CMSIS-RTOS API v2 for ideas.
Note: Even when you use an abstraction layer, there may be RTOS features you want to use that just don’t make sense to abstract. In these cases, just directly use the API and save yourself the headache.
If you browse the RTOS APIs, you’ll notice a reference to recursive mutexes. A Recursive Mutex is a mutex that can be locked multiple times. Every time the mutex is locked, it must also be unlocked that number of times before it becomes unlocked for another thread.
Recursive mutexes are typically used within recursive functions that need to get access to a resource. In my 20+ years as an embedded software developer, I’ve not encountered a reason or place to use recursive mutexes. Usually, there are better architectural options available. In the embedded space, it’s also a good idea to avoid recursive functions. So, I mention this just as an aside so that you know they exist, although rarely used.
This post has explored everything you need about Semaphores and Mutexes. You’ve seen that these two RTOS tools are very different and serve the designer a specific purpose. Semaphores are for task coordination and synchronization. Mutexes are for shared resource protection. Don’t use Binary Semaphores to protect resources. In addition, the memory and performance improvements are marginal at best and not worth the potential headaches and issues that can arise.
While you know everything you need about Semaphores and Mutexes, you are now ready to investigate the various design patterns and how they are used to create an RTOS application.
- 5 Tips for Developing an RTOS Application Software Architecture
- 5 RTOS Best Practices for Designing RTOS-Based Applications
- 3 Common Challenges Facing RTOS Application Developers
- Beningo RTOS Courses
 – 2019 Embedded Market Study, Slide 49