GEX thesis source code, full text, references

ch.freertos.tex 8.2KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. \chapter{FreeRTOS} \label{sec:freertos}
  2. FreeRTOS is a free, open-source real-time operating system kernel targeted at embedded systems; it has been ported to many different microcontroller architectures~\cite{freertos-ports-list} and it is the de-facto industry standard. The kernel is compact and designed to be easy to understand; it is written in C, with the exception of architecture-specific routines which may use assembly. A complete overview of its \gls{API} is available in the FreeRTOS reference manual~\cite{freertos-rm} and its guide book~\cite{freertos-book}.
  3. FreeRTOS provides a task scheduler, forming the central part of the system, and implements queues, semaphores, and mutexes for message passing and the synchronization of concurrent tasks. Those features are summarily called \textit{synchronization objects}, or simply \textit{objects}.
  4. The system is used in GEX for its synchronization objects that allow us to safely pass messages between interrupts and working threads, without deadlocks or race conditions; the particular usage of FreeRTOS features will be explained in \cref{sec:rtos_in_gex}. A built-in stack overflow protection (\cref{sec:stackprot}) helps us optimize task memory allocation\footnote{The stack monitor reports how much stack space was really used, so we can expand or shrink it as needed to make the application work reliably, without wasting memory that would never be used.}, and the heap allocator provided by FreeRTOS enables thread-safe dynamic allocation with a shared heap.
  5. \section{Basic FreeRTOS Concepts and Functions}
  6. \subsection{Tasks}
  7. Threads in FreeRTOS are called \textit{tasks}. Each task is assigned a memory area to use as its stack space, and a holding structure with its name, saved \textit{context}, and other metadata used by the kernel. A task context includes the program counter, stack pointer and other register values. Task switching is done by saving and restoring this context by manipulating the values on the stack before leaving an \gls{ISR}. The FreeRTOS website provides an example with the AVR port~\cite{freertos-task-switching} demonstrating how its internal functionality is implemented, including the context switch.
  8. At start-up, the firmware initializes the kernel, registers tasks to run, and starts the scheduler. From this point onward the scheduler is in control and runs the tasks using a Round Robin scheduling scheme, always giving a task one tick of run time (usually 1\,ms) before interrupting it. Which task should run is determined primarily by their priority numbers, but there are other factors, explained below.
  9. \subsubsection{Task Run States}
  10. Tasks can be in one of four states: Suspended, Ready, Blocked, and Running. The Suspended state does not normally occur in a task's life cycle, it is entered and left using \gls{API} calls from the application. A task is in the Ready state when it can run, but is currently paused because a higher priority task is running. It enters the Running state when the scheduler switches to it. A Running task can wait for a synchronization object (e.g., a mutex) to be available; at this point it enters a Blocked state and the scheduler runs the next Ready task. When no tasks can run, the Idle Task takes control; it can either enter a sleep state to save power, or wait in a loop until another task is available. The Idle task is always either Ready or Running and has the lowest priority of all tasks.
  11. \subsubsection{Task Switching and Interrupts}
  12. Task switching occurs periodically in a timer interrupt, usually every 1\,ms; in \armcm chips this is typically the SysTick interrupt, generated by a core timer that is specifically designed fro this purpose.
  13. After one tick of run time, the Running task is paused and becomes Ready, or continues to run if no higher-priority task is available. If a higher-priority task waits for an object and this is made available in an \gls{ISR}, the running lower-priority task is paused and the waiting task resumes immediately. FreeRTOS defines interrupt-friendly variants of some of the \gls{API} functions intended for this purpose; however, only a subset of the \gls{API} is available in an \gls{ISR}, for example, it is not possible to use the delay function or wait for an object with a timeout, as the SysTick interrupt, incrementing the tick counter, has the lowest priority and could not run. This is by design, intended to prevent unexpected context switching in application interrupts.
  14. FreeRTOS uses a \textit{priority inheritance} mechanism to prevent situations where a high-priority task waits for an object held by a lower-priority task (called \textit{priority inversion}). The blocking task's priority is temporarily raised to the level of the blocked high-priority task so it can finish earlier and release the held object. Its priority is then degraded back to the original value. When the lower-priority task itself is blocked, the same process can be repeated.
  15. \subsection{Synchronization Objects}
  16. FreeRTOS provides binary and counting semaphores, mutexes, and queues, which will now be briefly explained; a more in-depth description can be found in the guide book~\cite{freertos-book}.
  17. \begin{itemize}
  18. \item \textbf{Binary semaphores} are used for task notifications, e.g., when a task waits for a semaphore to be set by an \gls{ISR}. This makes the task Ready and if it has a higher priority than the task previously running, it is immediately resumed to process the event.
  19. \item \textbf{Counting semaphores} represent available resources in a resource pool, a set of software or hardware resources used by tasks. The pool is accompanied by a counting semaphore on which tasks wait for a resource to become available, and then subtract the semaphore value. After a resource is no longer needed by the task, the semaphore is incremented again and another task can use it.
  20. \item \textbf{Mutexes} (locks) are similar to semaphores, but they must be taken and released in the same task. We use them to
  21. guard exclusive access to a resource, typically a hardware peripheral or a shared memory area. When a mutex is taken, any other tasks trying to take it too enter become Blocked. A Blocked task waiting for a mutex is resumed once this becomes available, at which point the task becomes its owner and is resumed.
  22. \item \textbf{Queues} are used for passing messages between tasks, or from interrupts to tasks. Both the sending and receiving of queue messages can block the task until the operation becomes possible. A queue handing task is often simply a loop which tries to read from the queue with an infinite timeout and processes the received data once the reading succeeds.
  23. \end{itemize}
  24. It must be noted that synchronization objects like mutexes and semaphores can help combat concurrent access only when used consistently and correctly. A locked mutex cannot guard against a rogue task accessing the protected resource without checking.
  25. \section{Stack Overflow Protection} \label{sec:stackprot}
  26. Each task in FreeRTOS is assigned a block of \gls{RAM} to use as its stack when it runs. This is where the stack pointer is restored to in the context switch. The stack pointer could move outside the designated area if the allocated space is insufficient; without countermeasures, this would mean that we are overwriting bytes in some unrelated memory structure, perhaps another task's stack memory.
  27. A stack overflow protection can be enabled by a flag in the FreeRTOS configuration file. This function works in two ways: the more obvious is a simple check that the stack pointer remains in the designated area; however, as the check may be performed only in the scheduler interrupt, it is possible that the stack pointer exceeds the bounds only temporarily and returns back before the check can run. A more advanced solution, used by FreeRTOS, fills the stack memory with a known filler value before starting the task; the last few bytes are then tested to match this value. Not only can we detect a stack overflow more reliably, this feature also makes it possible to estimate the peak stack usage by counting the remaining filler bytes. We cannot distinguish between the original values and the same data stored on the stack by the program, but the possibility of this happening is sufficiently low and this method proves remarkably successful at detecting misconfigured stack sizes.