Home | Projects | Notes > Real-Time Operating Systems (RTOS) > Tasks
In a computing world, an application comprises of different tasks.
For example, a "Temperature monitoring application" can be implemented by using the following tasks:
Task 1 - Sensor data gathering
Task 2 - Updating display
Task 3 - User input processing
...
A task is essentially a schedulable piece of code, which implements a specific functionality of an application.
Continuous task - Runs continuously on the CPU.
Periodic task - Runs on the CPU with a fixed execution period.
Aperiodic task - Runs on the CPU when a certain event occurs. (e.g., Keypad interfacing)
Create a task
This step is creating a task in memory, which involves allocating memory for the Task Control Block (TCB).
xxxxxxxxxx
81/* Task creation */
2BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,
3 const char *const pcName,
4 unsigned short usStackDepth,
5 void *pvParameters,
6 UBaseType_t uxPriority,
7 TaskHandle_t *pxCreatedTask
8 );
Implement the task (i.e., Create task handler)
This is a piece of code that will run on the CPU.
xxxxxxxxxx
201/* Task implementation = task handler = task function */
2void vATaskFunction(void *pvParameters)
3{
4 /* Variables can be declared just like any regular functions. Each instance of a task created
5 using this function will have its own copy of the 'iVariableExample' variable.
6 This would not be true if the variable was declared static - in which case only one copy of
7 the variable would exist and this copy would be shared among all the instances of the task. */
8 int iVariableExample = 0;
9
10 /* A task will normally be implemented as an infinite loop. */
11 for (;;)
12 {
13 /* Task application code here */
14 }
15
16 /* Should the task implementation ever break out of the above loop then the task must be
17 deleted BEFORE reaching the end of this function. The argument NULL passed to the vTaskDelete()
18 function indicates that the task to be deleted is the calling task (i.e., current task) */
19 vTaskDelete(NULL);
20}
iVariableExample
is a local variable, which will be created in the stack space of this task. If this task handler is used by multiple tasks, theniVariableExample
will be created in each task's own stack.If
iVariableExample
were declared as astatic
variable, it would've been a global (shared) variable among those tasks that use this task handler.
A task handler is generally implemented as an infinite loop.
A task handler should never return without deleting the associated task.
The follwoing API creates a new FreeRTOS task dynamically (i.e., using dynamic memory allocation), and adds the newly created task (TCP) to Ready List or Ready Queue of the kernel.
It can be used in your application to create a new FreeRTOS task with a couple of parameters.
xxxxxxxxxx
121BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, // Address of the associated task handler.
2 const char *const pcName, // A descriptive name to identify this task
3 configSTACK_DEPTH_TYPE usStackDepth, // Amount of stack memory allocated to this
4 // task (memory in WORDS not in bytes).
5 void *pvParameters, // Pointer to the data which needs to be passed
6 // to the task handler once it gets scheduled.
7 UBaseType_t uxPriority, // Task priority.
8 TaskHandle_t *pxCreatedTask // Used to save the task handle (an address
9 // of the task created). Using task handle
10 // you can suspend, block, delete or whatever
11 // you want to do with the task.
12 );
In the program's stack memory, each task's stack space will be setup according to the
usStackDepth
. Note that this is not in bytes but in WORDs (push
orpop
unit).
If the stack is 16-bit wide and
usStackDepth
is 100, then 200 bytes will be allocated use as the task's stack. Also, the basic unit of push/pop operation will be 16-bit.If the stack is 32-bit wide and
usStackDepth
is 100, then 400 bytes will be allocated use as the task's stack. Also, the basic unit of push/pop operation will be 32-bit.e.g., STM32F407 microcontroller
[!] Note: Word is a maximum size of a data which can be accessed (load/store) by the processor in a single clock cycle using a single instruction. Processor design also offers native support (register width, bus width) to load/store word-sized data. Word size could be 8bit/16bit/32bit or more, depending upon the processor design.
For ARM Cortex-M based microcontrollers, word size is 32-bit.
[!] Note:
BaseType_t
is defined to be the most efficient, natural type for the architecture. e.g., On a 32-bit architectureBaseType_t
will be defined to be a 32-bit type. See https://freertos.org/FreeRTOS-Coding-Standard-and-Style-Guide.html.In earlier versions of FreeRTOS,
xxxxxxxxxx
11UBaseType_t usStackDepth /* for 8bit MCU, 255 was the max possible value for usStackDepth */
But it turned out to be too restrictive. So, in later versions,
configSTACK_DEPTH_TYPE
was introduced in place ofUBaseType_t
.xxxxxxxxxx
11configSTACK_DEPTH_TYPE usStackDepth
xxxxxxxxxx
21/* FreeRTOS.h */
2If
uint16
is too restrictive for your application, you can redefine it to a larger data type inFreeRTOSConfig.h
Return value of xTaskCreate()
:
pdPASS
- If the task was created successfully.
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
- Otherwise.
The only possibility of the task creation failure is due to memory allocation failure (i.e., insufficient memory).
FreeRTOS API Reference - https://www.freertos.org/a00106.html
A priority of a task comes into play when there are 2 or more tasks in the system.
Priority value helps the scheduler to decide which task should run first on the processor.
Priority value
Lower the priority value, lower the task priority (urgency).
In FreeRTOS each task can be assigned a priority value from 0 to configMAX_PRIORITIES
- 1, where configMAX_PRIORITIES
may be defined in the FreeRTOS.h
.
You must decide configMAX_PRIORITIES
as per your application requirements.
Using too many task priority values could lead to onverconsumption of RAM.
If many tasks are allowed to execute with varying task priorities, it may decrease the system's overall performance.
Limit configMAX_PRIORITIES
to 5, unless you have a valid reason to increase it.
Write an application that creates 2 tasks, Task1 and Task2, of equal priorities to print the message "hello world from TaskX".
xxxxxxxxxx
751/* main.c */
2
3...
4/* Private includes ----------------------------------------------------------*/
5/* USER CODE BEGIN Includes */
6
7
8/* USER CODE END Includes */
9
10...
11
12/* Private function prototypes -----------------------------------------------*/
13...
14/* USER CODE BEGIN PFP */
15static void task1_handler(void *parameters);
16static void task2_handler(void *parameters);
17/* USER CODE END PFP */
18
19...
20
21/**
22 * @brief The application entry point.
23 * @retval int
24 */
25int main(void)
26{
27 /* USER CODE BEGIN 1 */
28 TaskHandle_t task1_handle;
29 TaskHandle_t task2_handle;
30
31 BaseType_t status; // stores return value of xTaskCreate()
32 /* USER CODE END 1 */
33
34 ...
35
36 /* USER CODE BEGIN 2 */
37
38 // create task 1
39 status = xTaskCreate(task1_handler,
40 "Task-1",
41 200,
42 "Hello world from Task-1",
43 2,
44 &task1_handle);
45 configASSERT(status == pdPASS); // macro defined in 'FreeRTOSConfig.h'
46
47 // create task 1
48 status = xTaskCreate(task2_handler,
49 "Task-2",
50 200,
51 "Hello world from Task-2",
52 2,
53 &task2_handle);
54 configASSERT(status == pdPASS); // macro defined in 'FreeRTOSConfig.h'
55
56 /* USER CODE END 2 */
57
58 ...
59} // end main
60
61...
62
63/* USER CODE BEGIN 4 */
64
65static void task1_handler(void *parameters)
66{
67
68}
69
70static void task2_handler(void *parameters)
71{
72
73}
74
75/* USER CODE END 4 */
configASSERT(x)
is a macro defined inFrreeRTOSConfig.h
:xxxxxxxxxx
11It will trap the code when undesired flow is detected which is helpful for debugging.
xTaskCreate()
is defined intask.c
which manages task management of the real-time kernel.It allocates space for the stack, allocates space for TCB, store the stack location in the TCB, and so on. If, during any of these processes, memory allocation fails (i.e., heap memory allocation failure),
xTaskCreate()
will returnerrCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
.If previous steps were successful, it then adds the its TCB to the Ready list (
prvAddNewTaskToReadyList( pxNewTCB )
). Ready list is a list maintained by the kernel along with Blocked list, Suspended list, etc. This function will lead you tolist.c
which is a source file manages lists of the kernel.When writing code for task handlers, be aware that they each have limited stack space and declaring a large-sized local array may cause stack overflow. (
static
variables will not be stored in stack, but in RAM.Is stack not part of RAM?)
If you have your tasks created (i.e., created tasks have been inserted into the Ready list of the kernel), now it's the time to invoke the scheduler, schedule and dispatch those tasks according to the defined scheduling policy.
Memory usage
When xTaskCreate()
is called to create a task following things happen behind the scenes:
Dynamic memory allocation for TCB and the TCB variables (e.g., pxTopOfStack
) initialization
Dynamic memory allocation for the task's private stack (Since it is a dynamic allocation, this stack will be created in the HEAP space). This stack memory will be tracked using PSP register of the ARM Cortex-M processor.
pxTopOfStack
of TCB is set to point to the stack created in (2).
TCB will be added to the Ready List.
If the task creation fails due to insufficient memory in heap, xTaskCreate()
will return an error code.
Remember, things that are dynamically allocated using malloc()
reside on heap section of RAM. If you need to take a look at or modify the heap size, check the following configuration in the FreeRTOSConfig.h
file.
xxxxxxxxxx
11
When modifying the heap size, check the MCU reference manual for the valid range of heap size.
Also, heap is nothing but an array defined in Project/ThirdParty/FreeRTOS/portable/MemMang/heap_x.c
.
xxxxxxxxxx
111/* heap_4.c */
2
3/* Allocate the memory for the heap. */
4
5
6/* The application writer has already defined the array used for the RTOS
7* heap - probably so it can be placed in a special segment or address. */
8 extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
9
10 PRIVILEGED_DATA static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
11/* configAPPLICATION_ALLOCATED_HEAP */
malloc()
is allocates memory from this arrayucHeap[]
!
The Idle task is created automatically when the RTOS scheduler is started to ensure there is always at least one task that is able to run. (Idle task is created by the kernel.)
It is the scheduler's first job to create an Idle task.
It is created at the lowest possible priority to ensure it does not use any CPU time if there are higher priority application tasks in the ready state.
Idle task is responsible for freeing the memory (e.g., TCB, stack) allocated by the RTOS to the tasks that have been completed or deleted. prvCheckTasksWaitingTermination()
function of the idle task is responsible for it. (Note that it is NOT the non-Idle task itself that cleans up the memory it was allocated. See Deleting a Task for more information.)
You can also give an application a hook function (or callback function) such as vApplicationIdleHook()
in the Idle task to send the CPU to low power mode when there are no useful tasks are executing.
xxxxxxxxxx
401/* task.c */
2...
3// Idle task
4static portTASK_FUNCTION( prvIdleTask, pvParameters )
5{
6 /* Stop warnings. */
7 ( void ) pvParameters;
8
9 /** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE
10 * SCHEDULER IS STARTED. **/
11
12 /* In case a task that has a secure context deletes itself, in which case
13 * the idle task is responsible for deleting the task's secure context, if
14 * any. */
15 portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );
16
17 for( ; ; )
18 {
19 /* See if any tasks have deleted themselves - if so then the idle task
20 * is responsible for freeing the deleted task's TCB and stack. */
21 prvCheckTasksWaitingTermination();
22
23 ...
24
25
26 {
27 extern void vApplicationIdleHook( void );
28
29 /* Call the user defined function from within the idle task. This
30 * allows the application designer to add background functionality
31 * without the overhead of a separate task.
32 * NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
33 * CALL A FUNCTION THAT MIGHT BLOCK. */
34 vApplicationIdleHook();
35 }
36 /* configUSE_IDLE_HOOK */
37 ...
38 }
39 ...
40}
L21: Memory cleanup
L27: Application callback (e.g., Send the CPU to sleep, etc.)
Timer_svc
)Also called as "Timer Daemon Task" and it deals with software timers.
Timer Services task gets created by the scheduler if configUSE_TIMERS = 1
in FreeRTOSConfig.h
.
The RTOS uses this daemon just to manage FreeRTOS software timers and nothing else.
If you don't use software timers in your FreeRTOS application you don't need to use this Timer Services task. If this is the case, configure configUSE_TIMERS = 0
in FreeRTOSConfig.h
.
All software timer callback functions execute in the context of the Timer Services task.
If you want to create a task with a stack of size 512 bytes, how many bytes of memory do you need to dynamically allocate for this task?
What is the first member element of the TCB Structure ?
In FreeRTOS, a task can be created statically as well. If you create a task statically, both TCB and stack of the task will reside in the global (a.k.a. .data
section) space of RAM, which is outside the stack and heap space.
Suppose there is a non zero initialized static variable is declared in the task function, where exactly memory for that static variable is allocated?
xxxxxxxxxx
51/* task function */
2void task_function(void *p)
3{
4 static int i=10;
5}
.data
section)
Suppose there is a non static local variable declared in the task function, where exactly the memory for the non static variable is allocated during the execution of task function?
xxxxxxxxxx
41void task_function(void *p)
2{
3 int i ; /* non static variable */
4}
Nayak, K. (2022). Mastering RTOS: Hands on FreeRTOS and STM32Fx with Debugging [Video file]. Retrieved from https://www.udemy.com/course/mastering-rtos-hands-on-with-freertos-arduino-and-stm32fx/