3.2. Architecture Overview
FreeRTOS is a relatively small application. The minimum core of FreeRTOS is only three source (.c
) files and a handful of header files, totalling just under 9000 lines of code, including comments and blank lines. A typical binary code image is less than 10KB.
FreeRTOS's code breaks down into three main areas: tasks, communication, and hardware interfacing.
- Tasks: Almost half of FreeRTOS's core code deals with the central concern in many operating systems: tasks. A task is a user-defined C function with a given priority.
tasks.c
andtask.h
do all the heavy lifting for creating, scheduling, and maintaining tasks. - Communication: Tasks are good, but tasks that can communicate with each other are even better! Which brings us to the second FreeRTOS job: communication. About 40% of FreeRTOS's core code deals with communication.
queue.c
andqueue.h
handle FreeRTOS communication. Tasks and interrupts use queues to send data to each other and to signal the use of critical resources using semaphores and mutexes. - The Hardware Whisperer: The approximately 9000 lines of code that make up the base of FreeRTOS are hardware-independent; the same code runs whether FreeRTOS is running on the humble 8051 or the newest, shiniest ARM core. About 6% of FreeRTOS's core code acts a shim between the hardware-independent FreeRTOS core and the hardware-dependent code. We'll discuss the hardware-dependent code in the next section.
Hardware Considerations
The hardware-independent FreeRTOS layer sits on top of a hardware-dependent layer. This hardware-dependent layer knows how to talk to whatever chip architecture you choose. Figure 3.1 shows FreeRTOS's layers.
Illegal HTML tag removed : _ FreeRTOS_files/freertos-figures-layers.png)
FreeRTOS ships with all the hardware-independent as well as hardware-dependent code you'll need to get a system up and running. It supports many compilers (CodeWarrior, GCC, IAR, etc.) as well as many processor architectures (ARM7, ARM Cortex-M3, various PICs, Silicon Labs 8051, x86, etc.). See the FreeRTOS website for a list of supported architectures and compilers.
FreeRTOS is highly configurable by design. FreeRTOS can be built as a single CPU, bare-bones RTOS, supporting only a few tasks, or it can be built as a highly functional multicore beast with TCP/IP, a file system, and USB.
Configuration options are selected in FreeRTOSConfig.h
by setting various #defines
. Clock speed, heap size, mutexes, and API subsets are all configurable in this file, along with many other options. Here are a few examples that set the maximum number of task priority levels, the CPU frequency, the system tick frequency, the minimal stack size and the total heap size:
#define configMAX_PRIORITIES ( ( unsigned portBASE_TYPE ) 5 ) #define configCPU_CLOCK_HZ ( 12000000UL ) #define configTICK_RATE_HZ ( ( portTickType ) 1000 ) #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 100 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 4 * 1024 ) )
Hardware-dependent code lives in separate files for each compiler toolchain and CPU architecture. For example, if you're working with the IAR compiler on an ARM Cortex-M3 chip, the hardware-dependent code lives in the FreeRTOS/Source/portable/IAR/ARM_CM3/
directory. portmacro.h
declares all of the hardware-specific functions, while port.c
and portasm.s
contain all of the actual hardware-dependent code. The hardware-independent header file portable.h
#include
's the correct portmacro.h
file at compile time. FreeRTOS calls the hardware-specific functions using #define
'd functions declared in portmacro.h
.
Let's look at an example of how FreeRTOS calls a hardware-dependent function. The hardware-independent file tasks.c
frequently needs to enter a critical section of code to prevent preemption. Entering a critical section happens differently on different architectures, and the hardware-independent tasks.c
does not want to have to understand the hardware-dependent details. So tasks.c
calls the global macro portENTER_CRITICAL()
, glad to be ignorant of how it actually works. Assuming we're using the IAR compiler on an ARM Cortex-M3 chip, FreeRTOS is built with the file FreeRTOS/Source/portable/IAR/ARM_CM3/portmacro.h
which defines portENTER_CRITICAL()
like this:
#define portENTER_CRITICAL() vPortEnterCritical()
vPortEnterCritical()
is actually defined in FreeRTOS/Source/portable/IAR/ARM_CM3/port.c
. The port.c
file is hardware-dependent, and contains code that understands the IAR compiler and the Cortex-M3 chip. vPortEnterCritical()
enters the critical section using this hardware-specific knowledge and returns to the hardware-independent tasks.c
.
The portmacro.h
file also defines an architecture's basic data types. Data types for basic integer variables, pointers, and the system timer tick data type are defined like this for the IAR compiler on ARM Cortex-M3 chips:
#define portBASE_TYPE long // Basic integer variable type #define portSTACK_TYPE unsigned long // Pointers to memory locations typedef unsigned portLONG portTickType; // The system timer tick type
This method of using data types and functions through thin layers of #defines
may seem a bit complicated, but it allows FreeRTOS to be recompiled for a completely different system architecture by changing only the hardware-dependent files. And if you want to run FreeRTOS on an architecture it doesn't currently support, you only have to implement the hardware-dependent functionality which is much smaller than the hardware-independent part of FreeRTOS.
As we've seen, FreeRTOS implements hardware-dependent functionality with C preprocessor #define
macros. FreeRTOS also uses #define
for plenty of hardware-independent code. For non-embedded applications this frequent use of #define
is a cardinal sin, but in many smaller embedded systems the overhead for calling a function is not worth the advantages that "real" functions offer.