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.