I understand that resource sharing is important for ensuring that multiple tasks can safely access and use shared resources, such as CPU time or shared variables. FreeRTOS offers tools like mutexes, semaphores, and queues to manage access to these resources, preventing conflicts and data corruption in real-time embedded systems.
Regarding hardware peripherals on the microcontroller where FreeRTOS runs, resources like Timers, UART, SPI, and I2C
Do you considered them shared resources?
Do you protect them when multiple tasks need to interact with these peripherals concurrently?
You MUST protect any hardware resource that is desired to be used by multiple tasks, as I know of no peripheral that has some multi-threaded operation at the fundamental access level.
Now, if the hardware resource is going to be dedicated to a single task, then it doesn’t need protection. In my experience “Hardware Timers” tend to not need protection, as you tend to allocate a given hardware timer to a particular operation, but if you do make one shared, you will need to have a system to control how it it is shared.
My low-level generic UART, SPI, and I2C assume that they will be shared so include a mutex per physical channel.
I’ve recently came across a concept that’s left me with some questions regarding resource sharing and data protection.
I’ve observed some of example where global variables are used for resource sharing without the use of synchronization tools like mutex or semaphore. I’m trying to understand when it’s necessary to protect resources and when it might not be required.
Could you share your thoughts that might help me clarify when resource protection is essential and when it might be not necessary?
Any practical use case would be helpful for better understanding the difference between the two cases.
When the COMPLETE access to a global variable will be simple enough that the processor will automatically do the operation atomically, then protection might not be needed. For writes, not only does the write need to be atomic, but all reads that use that data.
Sometimes you can make the access become atomic with a critical section (which disables interrupts) to make the protection simpler.
So, reading the value of a word sized memory location that is set by something else would be atomic, and not need protection. If you need to get multiple locations, and they need to be “consistent” with each other, that would not be, and would need some form of protection.
One example is within FreeRTOS. On a 16 bit processor, if the TickCount is only being kept to 16 bits, then you can get the value of the tick counter atomically. But if the TickCount is being kept to 32 bits, then we need a critical section to read the counter, so we can’t get 16 bits from before the increment and 16 bits after it.
Another example would be if you have an array of buffers which have a flag in them to indicate they need servicing. A task could quickly scan the list without needing exclusive access to each one to check that flag. When if finds one that needs servicing, it could then aquire the Mutex for that buffer, recheck that it does need servicing, and then do the operation, knowing it has full control over the buffer.
Another example is setting up e.g. a UART controller for a transfer, which usually requires multiple operations (write to UART HW registers).
If right in the middle of this setup sequence the current task gets preempted by the scheduler and the other now running task also sets up a UART transfer, the previous HW setup is overwritten. After the scheduler switches back to the previously preempted task it continues with the remaining part of the UART HW setup sequence but the HW registers are partially corrupted because they were overwritten by the other task which was meanwhile running.
Hence setting up the UART HW for a transfer and waiting for its completion must be (mutex) protected because the UART HW is exclusively in use during that time.
Alternatively and as already discussed you can design your application that e.g. the UART HW is exclusively handled by 1 UART handler task and other tasks send messages/data requests to this handler task. Then you don’t have concurrent access to the UART HW and won’t need mutex protection for it. If the handler task uses a e.g. FreeRTOS queue or message buffer you’re fine because FreeRTOS queues/message buffers take care about concurrent access by multiple tasks, of course.
The issue is that if the counter is at a value of, say, 0000 FFFF, and the reading task first reads 0000 as the upper byte of the counter, then a task switch occurs, and then the second task (or interrupt) increments the counter to 0001 0000, and then later the first task gets to run again, it will now see 0000 in the bottom word, thinking the counter had a value of 0000 0000, and thus it got an incorrect value.
The “resource” here is the counter value, not processor time, though the sharing of the processor time is one cause of the issue.
For this to happen, the second task/ISR needs a priority at least a high as the first task (ISR are ALWAYS higher in priority to any task). The solution is to do something that keeps the second task from altering the variable in the middle of the read. This is an ideal case for a critical section, instead of a Mutex, as it is much lighter weight, and by temporarily blocking the interrupt for a few cycles, the second task can’t get switched in, as ALL task switches are either cause by an interrupt (perhaps just the tick), or a task calling an API function.
Imagine In a system with multiple tasks that share a UART for communication:
The UART hardware is a critical resource since it can only handle one task at a time.
Consider Task 1 (Temperature Sensor):
Task 1 starts configuring the UART to send temperature data, setting the UART parameters.
And also consider Task 2 (LED Control):
Task 2 starts configuring the UART for LED status updates.
I think Data may become corrupt in the following case:
While Task 1 is configuring the UART for temperature data transmission, it’s interrupted by the scheduler, and Task 2 begins configuring the UART for LED status updates. In this situation, Task 2’s configuration can overwrite the settings that Task 1 was in the process of defining, potentially leading to incorrect or corrupted data transmission for both tasks.
Yes, a resource line the UART, because its usage by a task is not naturally atomic, needs protection.
Note, the SETUP for the uart is very likely not going to be changed, because the UART will almost certainly be physically connected to a line that will have a single defined protocol (baud rate, character length, etc). The protocol might have sequences that negotiate a change in that, but tasks will not be change that just by connecting.
The MESSAGES being sent over the link, may well need protection, so they go out as a whole, and nothing can interfere until that communication is completed.
Right. That’s what I tried to say with setting up the transfer. For instance when using DMA there a usually a few steps needed to setup and start the DMA with the provided buffer.
The basic HW setup for the protocol (baud rate, start/stop bits, parity in case of a UART) is usually done once during init and won’t need protection if this initialization is not done concurrently by application design as it wouldn’t make much sense to do so.
I see that mutex and semaphore are typically used to protect resources, and message queues are commonly used to exchange data between tasks. My confusion arises from observing code where data exchange occurs without the use of a message queue.
I would like to understand when it’s appropriate to skip using a message queue and when it’s necessary to apply one.
The message queue is a SIMPLE way to pass a stream of data between tasks (between a task and an ISR), but there is nothing “magical” about how it works that make it necessary. I would say that ALL uses of a Queue / Stream Buffer could be replaced with other methods.
Sometimes the access pattern just doesn’t match that of a Queue, so you can’t use them. The counter example is one case here. A task wants the current value of some counter to use, not that it want to receive a list of counter values, it want the CURRENT value. While you could “abuse” a queue to provide that (make a length 1 queue that you overwrite the new value into the queue, and peek the queue to see what that value is) that is just adding a lot of overhead to do what could be more simply done.
By treating the block of memory with the data as a resource, and using some form of access exclusion with it, it becomes an effective form of communication. In the case of something like the counter, due to the atomic nature of some of the actions, critical sections can form a very efficient form of access control. In some cases you might want to use a Mutex, and in some cases you can even establish an “ownership” of the blocks of memory, and a task that owns a block has free access to it.
I believe a log file is another example of resource sharing where multiple tasks can concurrently access the same file for reading, writing, or making modifications. However, when multiple tasks attempt to write to the same log file simultaneously without proper synchronization, it can lead to data corruption. Therefore, synchronization mechanisms are essential to protect the file.
Imagine a scenario where two threads are responsible for controlling a DC motor. Does this situation indeed involves the need for thread synchronization?
Having 2 independent tasks trying to control the same thing might cause surprising behavior.
Or is there a master control task controlling those motor control tasks ?
Which I turn leads to the question why 2 motor control tasks are needed at all ?