Resource Sharing in RTOS-based Designs
This article is written by Jean J. Labrosse, RTOS Expert.
Even though the RTOS makes each task think it has the CPU all to itself, tasks must often share resources such as variables, tables, data structures and I/O devices in order to accomplish their work. Because of the possibility of pre-emption, a task must ensure it has exclusive access to shared resources.
An RTOS typically provides many services to ensure mutual exclusion: enabling/disabling interrupts, preventing the scheduler from switching tasks (a.k.a. locking/unlocking the scheduler), semaphores and mutual exclusion semaphores. The actual mechanism used depends on the situation.
If the application is accessing simple variables and can do so in just a few CPU clock cycles, then it is probably best to disable interrupts, access the variables and re-enable interrupts.
If accessing the resource will require thousands of CPU cycles, then it is best to use mutual exclusion semaphores (a.k.a.. mutexes) because they avoid unbounded priority inversions, an issue that can occur when using (regular) semaphores as they were defined by Edsger Dijkstra in the early 1960s.
The illustration on code snippet below shows how to use the mutex mechanism.
One word of caution, the mutex must be created (i.e. initialized) before it can be used. All RTOSs have a mechanism to do this so you should consult the documentation of the RTOS you are using or plan to use.
Deadlocks occur when a task needs access to a resource owned by another task. Deadlocks can be avoided if all tasks agree to access all needed resources in the same order, and of course, release those in the reverse order.
The code snippet shown below accesses multiple resources correctly. Note that once you have both mutexes, you can technically access resource A or B in any order.
Also, an RTOS that provides a timeout mechanism can avoid deadlocks. Specifically, a timeout prevents a task from waiting forever for a resource to be released.
However, returning from a ‘Get()’ because of a timeout means the task doesn’t have access to the resource, and your code must deal with the situation accordingly; i.e. don’t access the resource because you didn’t get exclusive access to it!
Resource contention can be avoided by creating server tasks. A server task always owns the resource and client tasks request services from the server.
For example, as shown below, a task (the server) is responsible for keeping track of the current time of day (as well as day, month, year, day of week, etc.). A client could request, through APIs provided by the developer of the server task, that the server:
- returns the current date/time in an ASCII string (Clk_GetDateTimeASCII())
- change the time (Clk_SetTime())
- change the date (Clk_SetDate())
The APIs would simply queue the requests and the server would service the requests in sequence. In this example, the clock task always has exclusive access to the clock variables and thus doesn’t need to share these with other tasks or ISRs.
This scheme avoids many issues related to shared resources.
Want to learn more?
Check out the on-demand webinars Benefits of multitasking your embedded application with an RTOS and Tips and hints for better debugging your RTOS-based application .