I’m learning FreeRTOS and trying to get a clearer picture of when it makes sense to use a mutex. Let’s say I have two periodic tasks running at the same priority. Task1 runs every 500 ms, and Task2 runs every 1000 ms. So every second they both wake up at the same time. I believe If both of them want to use the same hardware resource (for example, a UART or I2C peripheral), then it makes sense to use a mutex to protect that resource so only one task can access it at a time.
But here’s where I get a bit confused. What if the ‘resource’ is just the CPU itself? For example, imagine two tasks that simply toggle two different LEDs, one at 500 ms and the other at 1000 ms. They don’t actually share any hardware except the CPU time.
Would I still need a mutex in this case to protect the CPU?
I think you’re still need a mutex. You may find no problems if your tasks are finishing their operation with shared resources (UART or IIC) within a single tick. But if both tasks will be ready to run and currently running task will not finish shared resource access in time then next tick of system timer will switch the tasks and will abruptly interrupt one task operation and will switch-in other task which start using UART or I2C assuming they are clear.
So to protect yourself from undefined hardware behavior you should use a mutex, may be for each kind of hardware separately if they may be used separately.
No - that’s what FreeRTOS (the scheduler) does for you
But be careful and check the low level access to the 2 LEDs.
They might be controlled by the same GPIO controller of your MCU and that’s a HW resource that probably need mutex protection when accessing it.
In single core FreeRTOS, tasks can become Ready for execution at the same time (capitalization to indicate state) however only one single task will be executing (aka Running) at a given moment.
Yes. But nobody should miss a task switch possibility except special scheduler setup when task switching is not occurring until current task become blocked.
By default, a task can be preempted by even same priority task when next Tick occurs.
If hardware access of one task should be uninterrupted, for example, to complete I2C transaction with a slave, other task which want to use I2C too should block itself unilt first task completes it’s hardware interaction.
I see two possible approaches:
To use a mutex for hardware access. This way the scheduler will not switch mutex owner task for same priority muted task. And mutex owner will be able to complete it’s hardware transaction uninterrupted.
To configure the scheduler to not switch the tasks until they are finished (self-blocked). This way the task currently executing and accessing the hardware will not be preempted by other tasks but there may be a problems for holding higher priority tasks from an execution.
No. The CPU itself is not “a resource to be protected.”
FreeRTOS in essence is nothing but a task scheduler, that means that each task over time makes progress during its computation completly unaware of it being interrupted, preempted or suspended (unless of course the task voluntarily suspends itself waiting on something).
A mutex is sort of an agent that tasks employ to deliberately control access to something. That something is typically a sequence of instructions that access a peripheral, as you have correctly pointed out. That agent interacts with the OS to ensure that only the task holding the mutex will execute its portion of the code needing to access the peripheral “uninterrupted” (of course only uniterrupted with respect to the other tasks participating in the scheme).
Note that this agent only affects the tasks that decide to share it - for a good reason as all other tasks still want to participate in concurrency.
If for whatever reason there is code that positively absolutely must run “globally” uninterrupted, you can use either xTask<Suspend|Resume>All() (to prevent all other tasks from being scheduled) or the critical section (to prevent all ISRs up to MAX_SYCALL from executing, which as a corollary stops the scheduler).
Be aware, though, that both these mechanisms have very very few legitimate use cases in all embedded design. Many people tend to abuse them for cases where normally a mutex would perfectly suffice. In effect, you disable the OS when using them.
It’s the scheduler’s job to select tasks that are ready for execution. In this example priority probably doesn’t matter much because there will be much more idle time than execution time.
Hi, Mike. This sentence reminds me typical Arduino-style of system design. When only normal operation is assumed. But in the reality something may go wrong. Say, I2C transaction may hang in a middle due to a noise on a bus or slave failure. USART may fall into an error which needs to be cleared. Etc. So the programmer should ether ensure somehow that even if transaction get wrong, get broken, it will complete in less than system tick time. Not 500ms, not 1000ms but one system tick interval. Even with that, correct operation is ensured only if there are none tasks with higher priority than these two. Else way higher priority task may “eat” some time of current tick leaving a few time for lower priority task before next tick and next scheduler call.
By default the scheduler will switchover the tasks even they are not blocked itself yet.
See: “Example 4.1Creating tasks”, “Figure 4.3” The actual execution pattern of the two
in FreeRTOS Kernel Book.
It is obviously that if the task will not finish it’s cycle action within a single tick it will be switched-out to let second task of same priority to execute effectively providing simultaneous execution of several tasks on single CPU, single hardware.
If programmer can’t ensure the hardware access will be completed before current tick interval is ended, also if there are the tasks with higher priority, a mutex or similar mechanism is required to protect hardware transaction from interruption by other task which wants the same peripheral.
I gave a simple solution to the question that OP gave. In the real world nobody will notice if an LED is blinking at 501ms or even 510ms.
If you care more about timing you can add more code and use vTaskDelayUntil to get better timing. If you care even more than you setup a hardware interrupt, but that is a waste of resources for blinking an LED.
Also in general you do want code to be as interruptable as possible, otherwise how can a high priority task preempt a lower one?
And if the transmissions are not a single byte in one direction a care must be taken with mutexes.
Even with simple LED blinking. Some peripheral architectures may suffer from read-modify-write phenomenon while accessing GPIO hardware registers. And if your LEDs are not just LEDs but reversive contactors contrlol coils…
There is a proverb: “Do well. Bad will happen by itself.”
What for do you blinking by LEDs? Just to blink? Or may be you’ll go further and will design and build more complex and serious systems? So even a LEDs should blink perfectly. Else way it is very bad, to have a RTOS but blinking bad.
Nikolay, you are mixing up a two things. Your elaborations concern exclusive uninterrupted access, whereas muteces address the issue of interleaved shared access.
Again, there are very few valid use cases in which ANY code (whether in interrupt or task mode does not matter) needs to execute a sequence of instructions without any interference with other code. For these rare use cases, there are ways to prevent interrupts and/or task switches from occurring.
The far more prevalent use case is to serialize access from several strands of execution (ie tasks or interrupts) from accessing the same resource (eg a UART) interleaved. A UART, for example, does normally not care if a sequence of reading from its register set gets interrupted by, say, a QSPI interrupt handler, as long as the peripheral itself is not affected by the interrupt AND the execution of that interrupt is short enough to prevent buffer overruns.
I’m mixing-up to exactly the same extent the mutexes are applicable to.
Even Read-Modify-Write sequence may be interrupted then continued later if an interruption doesn’t accesses the register, a subject of R-M-W sequence.
So a mutex may be used to protect R-M-W sequence from other tasks if there are none interrupts that can access the same resource.
About the rest part of your post above you’re absolutely right.
As for initial TS question. I’d recommend to use a mutex even if there are only two tasks. If @RTOS1 decides that the mutex has too big overhead, for simple projects of one programmer I will recommend Adam Dunkels Protothreads instead of full feature RTOS.
This is correct, but we need to be precise about the wording: In this use case it is not “the CPU” that needs to be protected from interleaved access but a variable.
To amend your above statement, let me add that mutual exclusion that involves interrupts requires “asymmetric serialization,” eg a task that must ensure an uninterrupted code sequence against an ISR needs to inhibit interrupt execution during that time. That can typically be accomplished on one of several levels (eg on the processor level, the interrupt controller level and the device interrupt level), allowing different levels of control over remaining concurrency. The “reverse direction,” ie ensuring that an isr executes its shared-resource-accessing code uninterrupted against a task or a lower priority interrupt, is implied.
I am not quite sure which aspect of the use case that the TO sketched requires a mutex. Could you draft some pseudo code that outlines where you would want to serialize task access? The code that @mike919192 outlined is a sufficient solution to the TOs requirements, and like Mike, I do not see a need for mutual exclusion here.
An example: two tasks of equal priority communicate over common UART with some extrrnal device. Each transaction is a request and a replay. Each task performs one transaction then sleep util next interval.
Normal case example:
Both tasks get ready to run simultaneously when next cycle time begun (delay timeout is over).
One task get running, the other kept ready to run. Which one depends on which task get blocked first previously.
Running task executes a transaction and get UART replay before system timer tick is over. Running task finishing it’s operation and get blocked for a large time till next wake-up.
As soon as one task get blocked, other task which was ready to run get running.
Second task which started to run after the first got blocked, starts it’s own transaction.
Second task received UART replay successfully and get blocked for long time till it’s next scheduled wake-up.
This scenario will work good if both tasks will finish within single system tick.
Bad case example. The speed of UART was decreased to let UART be transferred over radio channel. Or, external device spent more time to replay because of its internal calibration procedure:
Both tasks get ready to run simultaneously when next cycle time begun (delay timeout is over).
One task get running, the other kept ready to run. Which one depends on which task get blocked first previously.
Running task start executing a transaction. Sends a request and waits for a replay. Yet none replay received but current system tick interval is passes. A scheduler is called.
Scheduler looks for tasks in eRunning state and finds second task which is ready to run. To share CPU time, the scheduler switching out first task and switching in second task. Remember, first task sent a request but didn’t get a replay yet.
Second task sends it’s own request. And waits for a replay. Bingo! There is a byte in receiving buffer of a UART! I got a replay! Actually, second task gets a replay for a request of first task. And left a replay for it’s own request unread.
Due to momentary transaction completion second task gets sleeping till next wake-up time. Remember, an answer for a request of second task will be left in UART receiving buffer.
As soon as second task get blocked, the scheduler switches in first task which transaction was interrupted when first task was awaiting a replay.
First task get running where it checks for UART replay. Bingo! There is a replay in UART Rx! And first task accepts a replay for a request of second task.
After “successful” transaction, first task gets asleep until it’s next wake-up time.
In an example above, only transaction time of an exchange with external device is increased. But the consecutives are negative and may be hard to debug - UART received data are swapped between two tasks even when both tasks completed their cycles normally.
In case of mutex introduction, negative scenario gets corrected:
UART mutex is free. Both tasks get ready to run simultaneously when next cycle time begun (delay timeout is over).
One task get running, the other kept ready to run. Which one depends on which task get blocked first previously.
First running task tries to get a mutex. Successful. Now it is an owner of a mutex.
Running task start executing a transaction. Sends a request and waits for a replay. Yet none replay received but current system tick interval is over and the scheduler is called.
Scheduler looks for tasks in eRunning state and finds second task which is ready to run. To share CPU time, the scheduler switching out first task and switching in second task. Remember, first task sent a request but didn’t get a replay yet. But is UART mutex owner.
Second task gets running and trying to get a mutex. Failure! Second task gets blocked because of awaiting a mutex.
Scheduler is called to take a control for some eReady taks. And finds first task which is waiting for a replay for it’s transaction.
First task get running where it checks for UART replay. The UART access from other task was blocked by mutex, so first task transaction continues and completes normally even if there were several ticks of system time passed. Due to mutex block, second task didn’t get any CPU time until UART mutex will got released.
After successful with no quotes transaction, first task is releasing a mutex and gets asleep till next wake-up time.
Upon a release of UART mutex second task gets unblocked and is switching in. Now second task is an owner of a mutex. And starts and completes it’s own UART transaction. Then it releasing a mutex and get sleep for next wake-up.
So, an interaction with shared hardware may work in some cases but is fragile and may lead to difficult debugging. A mutexes are right instrument to protect a series of hardware accesses from other task intervention.
Your first sentence is already off the topic of the original question. OP stated that the two tasks do not share the same resource. There is no concurrent access to protect.
You’re right. It seems to me I’m more a “writer” than a “reader”. It is true the OP didn’t asked for shared hardware but for shared CPU. And yes, my posts are completely irrelevant to original question.
As I know, the CPU share is provided by tick timer and a scheduler and they are already performing all necessary control.
Now it become clear to me why Harmut answer was “No”.
I’m sorry for bothering this thread. Despite some experience in embedded, I’m just starting with an RTOS and I used the thread to formalize my own thoughts about a mutexes. Thank you for your attention.
Also, it needs to be repeated (we had this many many times) that the UART example you sketched out exposes a few design flaws that are nevertheless educational:
In your code, several tasks attempt to access an inherently serialized device concurrently, ie unpredictably. This in itself is rather error prone and sort of undermines the idea of a serial device. Some argue in favor of mutex controlled access (but see below), but this is first of all highly protocol dependent and secondly still subject to subtle timeout and race conditions. The preferrable approach is to have a single task serve the UART and feed that task with asynchronous requests from client tasks (for example via a message pump).
In your design, the UART Rx side is being polled. There is a wide consensus that this should be avoided because the polling wastes CPU cycles. The preferred way is to let an ISR queue the incoming characters and let a medium to high priority task buffer them for the client tasks to pick them up as needed without the risk of buffer overruns. Of course there are protocol dependencies and subleties here as well, but the isr driven design is widely agreed upon to be superior to polling.
You man want to query the forum for UART, there have literally been dozens to hundreds of in depth discussions about the pros and cons of different system designs concerning interacting with serial devices.
how do we actually decide which things need protection and which don’t? From what I understand so far, when there are multiple tasks they share the CPU time, so doesn’t that make CPU time itself a shared resource?
And it seems the same applies to shared variables if two tasks are using the same variable, It should be protected or not ?
But I also notice that peripherals like UART, SPI, I²C are always considered shared resources, because if multiple tasks access them at the same time, operations can get corrupted and lead to undefined behaviour
So overall, what I really want to understand is: how do we decide which resources in RTOS actually need protection, and which ones don’t?”
The basic answer is that if the operation will misbehave if you switch from one task working on the resource to another, then you need some protection. Remember that neither the task being interrupted or the task interrupting knows this happened right here.
Operations on “shared variables” if naturally atomic, don’t need protection, but if not atomic, do.
One example is to think of two tasks trying to increment a shared variable, both tasks read the variable to a register, increment the variable, then write it back. If the first task to do this gets switched from between the read and the write, then when it get control back after the other task did the same, it will write back a value that ignored the affect of the second task.
The “CPU” doesn’t need protection, as each task keeps its own copy of the CPU state, so other tasks won’t affect it.
Generally, anything else “shared’“ should be protected, unless it is defined to be self-protecting, so a shared Queue handles that sharing, but a shared normal variable doesn’t. The other exception are things that naturally are atomic (but that does need knowledge of your platform as to what will be atomic).