0% found this document useful (0 votes)
7 views101 pages

Task

Download as pdf or txt
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 101

FreeRTOS

A real time operating system for embedded systems


Introduction to Multitasking in Small
Embedded Systems
• Most embedded real-time applications include a
mix of both hard and soft real-time requirements.
• Soft real-time requirements
– State a time deadline, but breaching (violating) the
deadline would not render the system useless.
• E.g., responding to keystrokes too slowly
• Hard real-time requirements
– State a time deadline, but breaching the deadline
would result in absolute failure of the system.
• E.g., a driver’s airbag would be useless if it responded to
crash event too slowly.

2
FreeRTOS

• FreeRTOS is a real-time kernel/scheduler on


top of which MCU applications can be built to
meet their hard real-time requirements.
– Allows MCU applications be organized as a
collection of independent threads of execution.
– Decides which thread should be executed by
examining the priority assigned to each thread.
• Assume a single core MCU, where only a single thread
can be executing at one time.

3
The simplest case of task priority
assignments
• Assign higher priorities (lower priorities) to
threads that implement hard real-time (soft real-
time) requirements
– As a result, hard real-time threads are always
executed ahead of soft real-time threads.
• But, priority assignment decision are not always
that simple.

• In general, task prioritization can help ensure


an application meet its processing deadline.

4
A note about terminology

• In FreeRTOS, each thread of execution is


called a ‘task’.

5
Why use a real-time kernel
• For a simple system, many well-established
techniques can provide an appropriate solution
without the use of a kernel.
• For a more complex embedded application, a
kernel would be preferable.
• But where the crossover point occurs will always
be subjective.

• Besides ensuring an application meets its


processing deadline, a kernel can bring other
less obvious benefits.

6
Benefits of using real-time kernel 1
• Abstracting away timing information
– Kernel is responsible for execution timing and provides a time-related
API to the application. This allows the application code to be simpler
and the overall code size be smaller.
• Maintainability/Extensibility
– Abstracting away timing details results in fewer interdependencies
between modules and allows sw to evolve in a predictable way.
– Application performance is less susceptible to changes in the underlying
hardware.
• Modularity
– Tasks are independent modules, each of which has a well-defined
purpose.
• Team development
– Tasks have well-defined interfaces, allowing easier development by
teams
7
Benefits of using real-time kernel 2
• Easier testing
– Tasks are independent modules with clean interfaces, they
can be tested in isolation.
• Idle time utilization
– The idle task is created automatically when the kernel is
started. It executes whenever there are no application
tasks to run.
– Be used to measure spare processing capacity, perform
background checks, or simply place the process into a
low-power mode.
• Flexible interrupt handling
– Interrupt handlers can be kept very short by deferring most
of the required processing to handler tasks.
8
Standard FreeRTOS features

• Pre-emptive or co-operative operation


• Very flexible task priority assignment
• Queues
• Binary/Counting / Recursive semaphores
• Mutexes
• Tick/Idle hook functions
• Stack overflow checking
• Trace hook macros
• Interrupt nesting

9
Outline

• Task Management
• Queue Management
• Interrupt Management
• Resource Management
• Memory Management
• Trouble Shooting

10
TASK MANAGEMENT

11
Introduction and scope

• Main topics to be covered


– How FreeRTOS allocates processing time to
each task within an application
– How FreeRTOS chooses which task should
execute at any given time
– How the relative priority of each task affects
system behavior
– The states that a task can exist in.

12
More specific topics

• How to implement tasks


• How to create one or more instances of a task
• How to use the task parameter
• How to change the priority of a task that has
already been created.
• How to delete a task.
• How to implement periodic processing.
• When the idle task will execute and how it can
be used.
13
Task functions

• Tasks are implemented as C functions.


– Special: Its prototype must return void and take a
void pointer parameter as the following
void ATaskFunction (void *pvParameters);

• Each task is a small program in its own right.


– Has an entry point
– Normally runs forever within an infinite loop
– Does not exit

14
ATaskFunction

15
Special features of task function

• FreeRTOS task
– Must not contain a ‘return’ statement
– Must not be allowed to execute past the end of
the function
– If a task is no longer required, it should be
explicitly deleted.
– Be used to create any number of tasks
• Each created task is a separate execution instance with
its own stack, and its own copy of any automatic
variables defined within the task itself.

16
Top level task states

• A task can exist in one of two states: Running


and Not Running
Running state: the processor is
executing its code.

Not Running state: the task is dormant,


its status having been saved ready
for resuming execution the next time

• Scheduler is the only entity that can switch a


task in and out a running state.
17
Creating Tasks

• xTaskCreate() API function


– the most fundamental component in a multitasking
system
• Probably the most complex of all API functions

BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,


const char * const pcName,
const configSTACK_DEPTH_TYPE uxStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );

18
All parameters

• pvTaskCode
– a pointer to the function (just the function name)
that implements the task.
• pcName
– A descriptive name for the task. It is not used by
FreeRTOS, but a debugging aid.
– configMAX_TASK_NAME_LEN: the application
defined constant that defines the maximum length a
task name can task including the NULL terminator.

19
• uxStackDepth
– Each task has its own unique stack that is
allocated by the kernel to the task when the task
is created.
– The value specifies the number of words the task
stack can hold.
• E.g., Cortex-M3 stack is 32 bits wide, if usStackDepth
is passed in as 100, 400 bytes of stack space will be
allocated (100*4 bytes)
– Size of the stack used by the idle task is defined
by configMINIMAL_STACK_SIZE.
• Adjustable w.r.t. applications
20
• pvParameters
– The value assigned to pvParameters will be the
values passed into the task.
• uxPriority
– defines the priority at which the task will execute.
– Priorities can be assigned from 0, which is the
lowest priority, to (configMAX_PRIOIRTIES-1),
which is the highest priority.
– Passing a value above (configMAX_PRIOIRTIES -
1) will result in the priority being capped the
maximum legitimate value.
21
• pxCreatedTask
– pass out a handle to the created task, then be used to
refer the created task in API calls.
• E.g., change the task priority or delete the task
– Be set to NULL if no use for the task handle

• Two possible return values


– pdPASS : task has been created successfully.
– errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
• Task has not been created as there is insufficient heap
memory available for FreeRTOS to allocate enough RAM to
hold the task data structures and stack.

22
Example 1 Creating 2 tasks

• To demonstrate the steps of creating two


tasks then starting the tasks executing.
– 2 Tasks simply print out a string periodically, using
a crude null loop to create the periodic delay.
– Both tasks are created at the same priority and
are identical except for the string they print out.

23
vTask1
void vTask1( void *pvParameters )
{
const char *pcTaskName = "Task 1 is running\r\n";
volatile unsigned long ul;

/* As per most tasks, this task is implemented in an infinite loop. */


for( ;; )
{
/* Print out the name of this task. */
Serial.print( pcTaskName );

/* Delay for a period. */


for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later exercises will replace this crude
loop with a proper delay/sleep function. */
}
}
} 24
vTask2
void vTask2( void *pvParameters )
{
const char *pcTaskName = "Task 2 is running\r\n";
volatile unsigned long ul;

/* As per most tasks, this task is implemented in an infinite loop. */


for( ;; )
{
/* Print out the name of this task. */
Serial.print( pcTaskName );

/* Delay for a period. */


for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later exercises will replace this crude
loop with a proper delay/sleep function. */
}
}
25
}
setup()
void setup( void )
{
Serial.begin(9600);
/* Create one of the two tasks. */
xTaskCreate( vTask1, /* Pointer to the function that implements the task. */
"Task 1", /* Text name for the task. This is to facilitate debugging only. */
2000, /* Stack depth - most small microcontrollers will use much less stack than this.
*/
NULL, /* We are not using the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* We are not using the task handle. */

/* Create the other task in exactly the same way. */


xTaskCreate( vTask2, "Task 2", 2000, NULL, 1, NULL );

/* Start the scheduler so our tasks start executing. */


// vTaskStartScheduler();

}
26
example001 - Serial Monitor

27
Execution pattern of two Example 1 tasks

• Both tasks are rapidly entering and exiting the


Running state.
• Only one task can exist in the Running state at any
one time.
28
Example 2 Using the task parameter

• The two tasks in Example 1 are almost


identical, the only difference between them
being the text string they print out.
– Remove such duplication by creating two
instances of a single task implementation
vTaskFunction.
• Each created instance will execute independently under
the control of the FreeRTOS scheduler.
– The task parameter can then be used to pass into
each task the string that it should print out.

29
Example 2 vTaskFunction
void vTaskFunction( void *pvParameters )
{
char *pcTaskName;
volatile unsigned long ul;

/* The string to print out is passed in via the parameter. Cast this to a
character pointer. */
pcTaskName = ( char * ) pvParameters;

/* As per most tasks, this task is implemented in an infinite loop. */


for( ;; )
{
/* Print out the name of this task. */
Serial.print( pcTaskName );

/* Delay for a period. */


for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ) { }
}
}
30
const char *pcTextForTask1 = "Task 1 is running\r\n";
const char *pcTextForTask2 = "Task 2 is running\r\n";

void setup( void )


{
Serial.begin(9600);
xTaskCreate( vTaskFunction, /* Pointer to the function that implements the task. */
"Task 1", /* Text name for the task. This is to facilitate debugging only. */
2000, /* Stack depth - most small microcontrollers will use much less stack than this. */
(void*)pcTextForTask1, /* Pass the text to be printed in as the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* We are not using the task handle. */

xTaskCreate( vTaskFunction, "Task 2", 2000, (void*)pcTextForTask2, 1, NULL );

/* Start the scheduler so our tasks start executing. */


// vTaskStartScheduler();

for( ;; );

}
31
example002 - Serial Monitor

32
1.5 Task priorities

• uxPriority parameter of xTaskCreate() assigns


an initial priority to the task being created.
– It can be changed after the scheduler has been
started by using vTaskPrioritySet() API function

• configMAX_PRIORITIES in FreeRTOSConfig.h
– Maximum number of priorities
– Higher this value, more RAM consumed
Range: [0(low), configMAX-PRIORITIES-1(high)]
– Any number of tasks can share the same priority

33
• To select the next task to run, the scheduler itself
must execute at the end of each time slice.
– Use a periodic interrupt called the tick (interrupt).
– Effectively set the length of time slice by the tick
interrupt frequency -- configTICK_RATE_HZ in
FreeRTOSConfig.h
• configTICK_RATE_HZ
– If it is 100(Hz), the time slice will be 10 ms.
– API always calls specify time in tick interrupts (ticks)
• portTICK_PERIOD_MS
– Convert time delays from milliseconds into the
number of tick interrupts.

34
• When kernel itself is running, the arrows in the
above figure show the sequence of execution
from task interrupt, then from interrupt back to a
next task.
35
Example 3. Experimenting with priorities

• Scheduler always ensures that the highest


priority task is able to run is the task selected
to enter the Running state.
– In Example 1 and 2, two tasks have been created
at the same priority, so both entered and exited
the Running state in turn.
– In this example, the second task is set at priority
2.

36
Example 3. Experimenting with priorities

• In setup() function, change


xTaskCreate( vTaskFunction, "Task 2", 200,
(void*)pcTextForTask2, 1, NULL );
to
xTaskCreate( vTaskFunction, "Task 2", 200,
(void*)pcTextForTask2, 2, NULL );

37
example003 - Serial Monitor

38
• The scheduler always selects the highest priority
task that is able to run.
– Task 2 has a higher priority than Task 1; so Task 2 is
the only task to ever enter the Running state.
– Task 1 is to be ‘starved’ of processing time of Task 2.

39
‘Continuous processing’ task

• So far, the created tasks always have work to


perform and have never had to wait for
anything
– Always able to enter the Running state.
• This type of task has limited usefulness as
they can only be created at the very lowest
priority.
– If they run at any other priority, they will prevent
tasks of lower priority ever running at all.
• Solution: Event-driven tasks
40
Expanding the ‘Not Running’ state

• An event-driven task
– has work to perform only after the occurrence of the event
that triggers it
– Is not able enter the Running state before that event has
occurred.
• The scheduler selects the highest priority task that is
able to run.
– High priority tasks not being able to run means that the
scheduler cannot select them, and
– Must select a lower priority task that is able to run.
• Using event-driven tasks means that
– tasks can be created at different priorities without the
highest priority tasks starving all the lower priority tasks.

41
Full task state machine

42
Blocked state

• Tasks enter this state to wait for two types of events


– Temporal (time-related) events: the event being either a
delay expiring, or an absolute time being reached.
• A task enter the Blocked state to wait for 10ms to pass
– Synchronization events: where the events originate from
another task or interrupt
• A task enter the Blocked state to wait for data to arrive on a
queue.
– Can block on a synchronization event with a timeout,
effectively block on both types of event simultaneously.
• A task waits for a maximum of 10ms for data to arrive on a
queue. It leaves the Blocked state if either data arrives within
10ms or 10ms pass with no data arriving.

43
Suspended state

• Tasks in this state are not available to the


scheduler.
– The only way into this state is through a call to
the vTaskSuspend() API function
– The only way out this state is through a call to the
vTaskResume() or vTaskResumeFromISR() API
functions

44
Ready state

• Tasks that are in the ‘Not Running’ state but


are not Blocked or Suspended are said to be
in the Ready state.
– They are able to run, and therefore ‘ready’ to run,
but are not currently in the Running state.

45
Full Task State machine

46
Example004 - Using the Block state to
create a delay
• All tasks in the previous examples have been
periodic
– They have delayed for a period and printed out
their string before delay once more, and so on.
– Delayed generated using a null loop
• the task effectively polled an incrementing loop counter
until it reached a fixed value.

47
• Disadvantages to any form of polling
– While executing the null loop, the task remains in
the Ready state, ‘starving’ the other task of any
processing time.
– During polling, the task does not really have any
work to do, but it still uses maximum processing
time and so wastes processor cycles.
• This example corrects this behavior by
– replacing the polling null loop with a call to
vTaskDelay() API function.
– setting INCLUDE_vTaskDelay to 1 in
FreeRTOSConfig.h

48
vTaskDelay() API function

• Place the calling task into the Blocked state


for a fixed number of tick interrupts.
– The Blocked state task does not use any
processing time, so processing time is consumed
only when there is work to be done.
void vTaskDelay( const TickType_t xTicksToDelay );
xTicksToDelay: the number of ticks that the calling task should remain in
the Blocked state before being transitioned back into the Ready state.
E.g, if a task called vTaskDelay(100) while the tick count was 10,000, it
enters the Blocked state immediately and remains there until the tick
count is 10,100.
49
Example 004

• From example 003, in


void vTaskFunction(void *pvParameters)
Change a NULL loop
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ) { }
To
vTaskDelay(250 / portTICK_PERIOD_MS);
// a period of 250ms is being specified.

Although two tasks are being created at different


priorities, both will now run.
50
Example 4 - Serial Monitor

51
• Each time the tasks leave the Blocked state they
execute for a fraction of a tick period before re-
entering the Blocked state.
– Most of the time no application tasks are able to run and,
so, no tasks can be selected to enter the Running state.
– The idle task will run to ensure there is always at least one
task that is able to run. 52
Bold lines indicate the state transitions
performed by the tasks in Example 4

53
vTaskDelayUntil() API Function

• Parameters to vTaskDelayUntil()
– specify the exact tick count value at which the
calling task should be moved from the Blocked
state into the Ready state.
– Be used when a fixed execution period is
required.
• The time at which the calling task is unblocked is
absolute, rather than relative to when the function was
called (as vTaskDelay())
void vTaskDelayUntil( TickType_t
*pxPreviousWakeTime, const TickType_t
xTimeIncrement );
54
vTaskDelayUntil() prototype

• pxPreviousWakeTime
– Assume that vTaskDelayUtil() is being used to
implement a task that executes periodically and
with a fixed frequency.
– Holds the time at which the task left the Blocked
state.
– Be used as a reference point to compute the time
at which the task next leaves the Blocked state.
– The variable pointed by pxPreviousWakeTime is
updated automatically, not be modified by
application code, other than when the variable is
first initialized.
55
vTaskDelayUntil() prototype

• xTimeIncrement
– Assume that vTaskDelayUtil() is being used to
implement a task that executes periodically and
with a fixed frequency – set by xTimeIncrement.
– Be specified in ‘ticks’. The constant
portTICK_PERIOD_MS can be used to convert
ms to ticks.

56
Example 5 Converting the example tasks
to use vTaskDelayUntil()
• Two tasks created in Example 4 are periodic
tasks.
• vTaskDelay() does not ensure that the
frequency at which they run is fixed,
– as the time at which the tasks leave the Blocked
state is relative to when they call vTaskDelay().

57
• In void vTaskFunction(void *pvParameters)
Change
vTaskDelay(250 / portTICK_RATE_MS);
// a period of 250ms is being specified.
To
vTaskDelayUntil( &xLastWakeTime, (250 /
portTICK_PERIOD_MS));
/*xLastWakeTime is initialized with the current tick count
before entering the infinite loop. This is the only time it is
written to explicitly. */
xLastWakeTime = xTaskGetTickCount();
/*It is then updated within vTaskDelayUntil();
automatically */
58
Example 5 - Serial Monitor

59
Example 6 Combining blocking and non-
blocking tasks
• Two tasks are created at priority 1.
– Always be either the Ready or the Running state
as never making any API function calls.
– Tasks of this nature are called continuous
processing tasks they always have work to do.
• A Third task is created at priority 2.
– Periodically prints out a string by using
vTaskDelayUntil() to place itself into the Blocked
state between each print iteration.

60
void vContinuousProcessingTask(void * pvParameters) {
char *pcTaskName;
pcTaskName = (char *) pvParameters;
for (;;) { Serial.print(pcTaskName);
delay(100);
}}

void vPeriodicTask(void * pvParameters) {


TickType_t xLastWakeTime;
char *pcTaskName;
pcTaskName = (char *) pvParameters;
xLastWakeTime = xTaskGetTickCount();
for (;;){ Serial.print(pcTaskName);
vTaskDelayUntil(&xLastWakeTime,
(500/portTICK_PERIOD_MS));
}}
61
Example 6 - Serial Monitor

62
63
Idle Task and the Idle task hook
• 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.
• 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.
• The idle task is responsible for freeing
memory allocated by the RTOS to tasks that have
since been deleted. It is therefore important in
applications that make use of
the vTaskDelete() function to ensure the idle task
is not starved of processing time. 64
• The Idle task is immediately swapped out to
allow Task 2 to execute at the instant Task 2
leaves the Blocked state.
– Task 2 pre-empts the idle task automatically.

65
Idle Task Hook Functions

• Add application specific functionality directly into


the idle task by the use of an idle hook
– A function called automatically by the idle task once
per iteration of the idle task loop
• Common uses for the Idle task hook
– Executing low priority, background, or continuous
processing
– Measuring the amount of spare processing capacity
– Placing the processor into a low power mode,
providing an automatic method of saving power
whenever no application processing to be performed.
65
Limitations on the implementation of Idle
Task Hook Functions
• Rules idle task hook functions must adhere to
– Must never attempt to block or suspend.
– If the application uses vTaskDelete(), the Idle task
hook must always return to its caller within a
reasonable time period.
• Idle task is responsible for cleaning up kernel resources after
a task has been deleted.
• If the idle task remains permanently in the Idle hook function,
this clean-up cannot occur.
• Idle task hook functions have the name and
prototype as
void vApplicationIdleHook(void);
66
Example 7 Defining an idle task hook
function
• Set configUSE_IDLE_HOOK to 1
• Add following function
unsigned long ulIdleCycleCount = 0UL;
/* must be called this name, take no parameters and return void. */
void vApplicationIdleHook (void) {
ulIdleCycleCount++;
}
• In vTaskFunction(), change
Serial.print(pcTaskName) To
Serial.print(pcTaskName); Serial.println(ulIdleCycleCount);
67
Example 7 - Serial Monitor

68
Change the priority of a task

• void vTaskPrioritySet( TaskHandle_t xTask,


UBaseType_t uxNewPriority ); Be used to
change the priority of any task after the
scheduler has been started.
– Available if INCLUDE_vTaskPrioritySet is set 1.
• Two parameters
– xTask: Handle of the task whose priority is being
modified. A task can change its own priority by
passing NULL in place of a valid task handle.
– uxNewPriority: the priority to be set.
69
Get the Priority of a Task

• UBaseType_t uxTaskPriorityGet( const


TaskHandle_t xTask );
Be used to query the priority of a task
– Available if INCLUDE_vTaskPriorityGet is set 1
– pxTask: Handle of the task whose priority is being
modified. A task can query its own priority by
passing NULL in place of a valid task handle.
– Returned value: the priority currently assigned to
the task being queried

70
Example 8 Changing task priorities

• Demonstrate the scheduler always selects the


highest Ready state task to run
– by using the vTaskPrioritySet() API function to change
the priority of two tasks relative to each other.
• Two tasks are created at two different
priorities.
– Neither task makes any API function calls that
cause it to enter the Blocked state,
• So both are in either Ready or Running state.
• So the task with highest priority will always be the task
selected by the scheduler to be in Running state

71
Expected Behavior of Example 8

1. Task 1 is created with the highest priority to be


guaranteed to run first. Task 1 prints out a
couple of strings before raising the priority of
Task 2 to above its own priority.
2. Task2 starts to run as it has the highest relative
priority.
3. Task 2 prints out a message before setting its
own priority back to below that of Task 1.
4. Task 1 is once again the highest priority task,
so it starts to run and forcing Task 2 back into
the Ready state.
72
Example 8 code

• Declare a global variable to hold the handle


of Task 2.
xTaskHandle xTask2Handle;
• In setup() function, create two tasks
xTaskCreate(vTask1, “Task 1”, 200,
NULL, 2, NULL);
xTaskCreate(vTask2, “Task 2”, 200,
NULL, 1, &xTask2Handle);

73
• Change vTask1 by initialization
unsigned portBASE_TYPE uxPriority;
uxPriority = uxTaskPriorityGet(NULL);
• And adding to the infinite loop
vTaskPrioritySet(xTask2Handle,
(uxPriority+1));

• Change vTask2 by initialization


unsigned portBASE_TYPE uxPriority;
uxPriority = uxTaskPriorityGet(NULL);
• And adding to the infinite loop
vTaskPrioritySet(NULL, (uxPriority-2));
74
Example 8 - Serial Monitor

75
Task execution sequence

76
Deleting a task

• Deleted tasks no longer exist and cannot enter


the Running state again.
• Idle task is responsible to automatically free
memory allocated by kernel to tasks that have
been deleted.
– Remember if applications use vTaskDelete(), do not
completely starve the idle task of all processing time.
• Note: any memory or other resource that the
application task allocates itself must by be freed
explicitly by the application code.
77
vTaskDelete() API function

• Function prototype
void vTaskDelete( TaskHandle_t xTask );

– xTask: Handle of the task that is to be deleted. A


task can delete itself by passing NULL in place
of valid task handle.
• Available only when INCLUDE_vTaskDelete
set 1

78
Example 9

void setup( void )


{
Serial.begin(9600);
/* Create the first task at priority 1. This time the task parameter is
not used and is set to NULL. The task handle is also not used so likewise
is also set to NULL. */
xTaskCreate( vTask1, "Task 1", 200, NULL, 1, NULL );
/* The task is created at priority 1 ^. */

/* Start the scheduler so our tasks start executing. */


// vTaskStartScheduler();

for( ;; );

79
void vTask1( void *pvParameters )
{
const TickType_t xDelay100ms = 100 / portTICK_PERIOD_MS;

for( ;; )
{
/* Print out the name of this task. */
Serial.print( "Task1 is running\r\n" );

/* Create task 2 at a higher priority. Again the task parameter is not


used so is set to NULL - BUT this time we want to obtain a handle to the
task so pass in the address of the xTask2Handle variable. */
xTaskCreate( vTask2, "Task 2", 200, NULL, 2, &xTask2Handle );
/* The task handle is the last parameter ^^^^^^^^^^^^^ */

/* Task2 has/had the higher priority, so for Task1 to reach here Task2
must have already executed and deleted itself. Delay for 100
milliseconds. */
vTaskDelay( xDelay100ms );
}
}
80
void vTask2( void *pvParameters )
{
/* Task2 does nothing but delete itself. To do this it
could call vTaskDelete() using a NULL parameter, but
instead and purely for demonstration purposes it
instead calls vTaskDelete() with its own task handle. */
Serial.print( "Task2 is running and about to delete
itself\r\n" );
vTaskDelete( xTask2Handle );
}
Example 9 - Serial Monitor

81
Example 9 Deleting tasks (Behavior)
1. Task 1 is created by setup() with priority 1. When it runs,
it creates Task 2 at priority 2. Task 2 as the highest priority
task starts to execute immediately.
2. Task 2 does nothing but delete itself by passing NULL or its
own task handle.
3. When Task 2 has been deleted, Task 1 is again the highest
priority task, so continues executing – at which point it calls
vTaskDelay() to block for a short period.
4. The idle task executes while Task 1 is in the blocked state
and frees the memory that was allocated to the now deleted
Task 2.
5. When Task 1 leaves the blocked state it again becomes the
highest priority Ready state task and preempts the Idle task.
Then, start from Step1 again.
82
Execution sequence

83
Scheduling Algorithms - Summary

• So far, examples illustrate how and when


FreeRTOS selects which task should be in
the Running state.
– Each task is assigned a priority.
– Each task can exist in one of several states.
– Only one task can exist in the Running state at any
one time.
– The scheduler always selects the highest priority
Ready state task to enter the Running state.

84
Selecting the Scheduling Algorithm
The scheduling algorithm is the software routine that decides which Ready state task to
transition into the Running state. All the examples so far have used the same scheduling
algorithm, but the algorithm can be changed using
the configUSE_PREEMPTION and configUSE_TIME_SLICING configuration constants. Both
constants are defined in FreeRTOSConfig.h.

Scheduling configUSE_PREE configUSE_TIME_


Prioritized
Algorithm MPTION SLICING
Preemptive With
Yes 1 1
Time Slicing
Preemptive
Without Time Yes 1 0
Slicing
Co-Operative No 0 Any
Fixed priority Pre-emptive scheduling
• Fixed priority: Scheduling algorithms described as 'Fixed Priority' do not
change the priority assigned to the tasks being scheduled, but also do not
prevent the tasks themselves from changing their own priority or that of
other tasks.

• Pre-emptive: Preemptive scheduling algorithms will immediately


'preempt' the Running state task if a task that has a priority higher than
the Running state task enters the Ready state.

• Time Slicing: Time slicing is used to share processing time between tasks
of equal priority, even when the tasks do not explicitly yield or enter
the Blocked state. Scheduling algorithms described as using Time
Slicing select a new task to enter the Running state at the end of each
time slice if there are other Ready state tasks that have the same priority
as the Running task. A time slice is equal to the time between two RTOS
tick interrupts.
85
Tasks in the Blocked state
• Tasks can wait in the Blocked state for an event
and are automatically moved back to the Ready
state when the event occurs.
• Temporal events
– Occur at a particular time, e.g. a block time expires.
– Generally be used to implement periodic or timeout
behavior.
• Synchronization events
– Occur when a task or ISR sends info to a queue or to
one of the many types of semaphore.
– Generally be used to signal asynchronous activity,
such as data arriving at a peripheral.
86
Execution pattern highlighting task prioritization and preemption in a hypothetical
application in which each task has been assigned a unique priority

• Event for Task 1 occur at : t10


• Task2 is released at : t1, t6, t9
• Events for Task 3 occur at: t3, t5, between t9 and t12
87
• Idle task
– The idle task is running at the lowest priority, so
gets pre-empted every time a higher priority
task enters the Ready state
• at times t3, t5, and t9.

88
• Task 3
– An event-driven task
• Execute with a low priority, but above the Idle task priority.
– It spends most of its time in the Blocked state waiting
for the event of interest, transitioning from Blocked to
Ready state each time the event occurs.
• All FreeRTOS inter-task communication mechanisms (task
notifications, queues, semaphores, etc.) can be used to signal
events and unblock tasks in this way.
– Event occur at t3, t5, and also between t9 and t12.
• The events occurring at t3 and t5 are processed immediately
as it is the highest priority task that is able to run.
• The event occurring somewhere between t9 and t12 is not
processed as until t12 because, until then, the higher priority
tasks Task 1 and Task 2 are still executing. It is only at time
t12 that both Task 1 and Task 2 are in the Blocked state,
89
making Task 3 the highest priority Ready state task.
• Task 2
– A periodic task that executes at a priority above
Task 3, but below Task1. The period interval
means Task 2 wants to execute at t1, t6 and t9.
• At t6, Task 3 is in Running state, but task 2 has the
higher relative priority so preempts Task 3 and start to
run immediately.
• At t7, Task 2 completes its processing and reenters the
Blocked state, at which point Task 3 can re-enter the
Running state to complete its processing.
• At t8, Task 3 blocks itself.

90
• Task 1
– Also an event-driven task.
– Execute with the highest priority of all, so can preempt any
other task in the system.
– The only Task 1 event shown occurs at t10, at which time
Task 1 pre-empts Task 2.
– Only after Task 1 has re-entered the Blocked at t11, Task 2
can complete its processing.

91
Execution pattern highlighting task
prioritization and time slicing in a hypothetical
application in which two tasks run at the same
priority
• The Idle Task and Task 2
The Idle task and Task 2 are both continuous processing tasks, and both have a priority
of 0 (the lowest possible priority). The scheduler only allocates processing time to the
priority 0 tasks when there are no higher priority tasks that are able to run, and shares
the time that is allocated to the priority 0 tasks by time slicing. A new time slice starts
on each tick interrupt, which occurs at times t1, t2, t3, t4, t5, t8, t9, t10 and t11.
The Idle task and Task 2 enter the Running state in turn, which can result in both tasks
being in the Running state for part of the same time slice, as happens between time t5
and time t8.
• Task 1
The priority of Task 1 is higher than the Idle priority. Task 1 is an event driven task
that spends most of its time in the Blocked state waiting for its event of interest,
transitioning from the Blocked state to the Ready state each time the event occurs.
The event of interest occurs at time t6. At t6 Task 1 becomes the highest priority
task that is able to run, and therefore Task 1 preempts the Idle task part way
through a time slice. Processing of the event completes at time t7, at which point
Task 1 re-enters the Blocked state.
The configIDLE_SHOULD_YIELD compile time configuration constant can be used to
change how the Idle task is scheduled:
•If configIDLE_SHOULD_YIELD is set to 0 then the Idle task remains in
the Running state for the entirety of its time slice, unless it is preempted by a higher
priority task.
•If configIDLE_SHOULD_YIELD is set to 1 then the Idle task yields (voluntarily gives up
whatever remains of its allocated time slice) on each iteration of its loop if there are other
Idle priority tasks in the Ready state.
Prioritized Preemptive Scheduling without Time Slicing

If time slicing is not used, then the scheduler only selects a new task to enter
the Running state when either:
• A higher priority task enters the Ready state.
• The task in the Running state enters the Blocked or Suspended state.
There are fewer task context switches when time slicing is not used than when time
slicing is used. Therefore, turning time slicing off results in a reduction in the scheduler's
processing overhead. However, turning time slicing off can also result in tasks of equal
priority receiving greatly different amounts of processing time, a scenario
demonstrated by Figure above. For this reason, running the scheduler without time
slicing is considered an advanced technique that should only be used by experienced
users.
Co-operative scheduling
• When using the cooperative scheduler, a context
switch occur only when
– the Running state task enters the Blocked state
or
– the Running state task explicitly calls
taskYIELD() – a Kernel Control API function.
• Tasks will never be pre-empted and tasks of equal
priority will not automatically share processing
time (time slicing cannot be used).
– Results in a less responsive system.

94
Co-operative scheduling
Figure below demonstrates the behavior of the cooperative scheduler.
The horizontal dashed lines in the figure show when a task is in the Ready state.

You might also like