Execution Context/ Scheduling
The processors registers, the task's dedicated stack area, status registers, program counters and stack pointers determine the context of a task. Storing the context of a task for later use and loading the context of another task is called task switch. This concept enables Multitasking on a single CPU core.
Each task has its own dedicated stack area. On task switch, it is therefore not required to store the stack itself, but the processor registers at the moment of suspension. If the FPU is not used by the task, the dedicated registers are not required to be stored.
The scheduler assigns work to resources that complete the work. In the following, different real-time operating System (RTOS) scheduler will be discussed.
Cooperative Scheduling
In cooperatively designed Multitasking concepts a context switch is performed when a task suspends itself (see picture below). The command to do this in FreeRTOS is taskYIELD().
In poorly designed systems a Task can consume all CPU time for itself (e.g. extensive calculations, busy waiting, etc.) which causes a System Freeze. This is why most systems are designed pre-emptive.
To use cooperative multitasking in FreeRTOS you need to set configUSE_PREEMPTION to 0 in the FreeRTOSConfig.h file.
Pre-emptive Scheduling
The RTOS Kernel can temporarily interrupt a task intending to resume later. Meanwhile, the RTOS Kernel can give the processor capacity to another Task (see picture below). This method is called context switching and the System that carries out the Interrupt is called scheduler.
The time a process is allowed to use the CPU core is called "time slice" and is critical to balance system performance vs process responsiveness.
Concepts of pre-emptive schedulers can be:
- Rate-monotonic scheduling
- Round-robin scheduling
- Fixed priority pre-emptive scheduling, an implementation of pre-emptive time slicing
- Fixed-Priority Scheduling with Deferred pre-emption
- Fixed-Priority non-pre-emptive scheduling
- Critical section pre-emptive scheduling
- Static time scheduling
The difficulty here is that the task can be interrupted at any given moment. This is why thread safe functions are mostly required.
Pre-emptive Scheduling: To use cooperative multitasking in FreeRTOS you need to set configUSE_PREEMPTION to 1 in the FreeRTOSConfig.h file.
Round Robin Scheduling
A Round Robin (RR) Scheduling Algorithm gives each task a time slot to process its job. When the time slot has reached its end, the processing gets interrupted and the next task has its time slot to process. In FreeRTOS this concept is called Time-Slicing and can be altered defining configUSE_TIME_SLICING. If disabled (set to 0) tasks of equal priority will not switch between one another until one has finished its job.
Fixed Priority Scheduling
A Fixed-priority Scheduling Algorithm ensures that the task with the highest priority is processed first. FreeRTOS uses this concept by default.
FreeRTOS Example
The graphic on the right shows how different Tasks can get processor resources by the scheduler.
- At (1) task 1 is executing.
- At (2) the kernel suspends (swaps out) task 1 …
- … and at (3) resumes task 2.
- While task 2 is executing (4), it locks a processor peripheral for its own exclusive access.
- At (5) the kernel suspends task 2 …
- … and at (6) resumes task 3.
- Task 3 tries to access the same processor peripheral, finding it locked, task 3 cannot continue so suspends itself at (7).
- At (8) the kernel resumes task 1.
- Etc.
- The next time task 2 is executing (9) it finishes with the processor peripheral and unlocks it.
- The next time task 3 is executing (10) it finds it can now access the processor peripheral and this time executes until suspended by the kernel.
ARM Cortex M System Tick
In pre-emptive scheduling, the scheduler is called on system tick (systick) interrupt event (). Since interrupt priorities on Cortex M architectures can be quite confusing and counterintuitive this chapter is an abstract of that topic.
The first thing that can be confusing is that the highest interrupt priority is the lowest number (-14). The higher the number, the lower the interrupt priority. The ARM Cortex M4 provides up to 32 unique Priority values.
On the contrary to that, tasks with a high-priority number are scheduled first, in FreeRTOS.
The syscall interrupt priority is configured in FreeRTOSConfig.h at configMAX_SYSCALL_INTERRUPT_PRIORITY. By default, this is the lowest priority (the highest value). This means all other interrupts will have priority over the systick interrupt. This has to be considered when other interrupts are very frequently called — they might suppress the systick interrupt. The priority structure needs to be kept in mind!