FreeRTOS(2)-任务

这是FreeRTOS系列学习文章的第二节,主要是关于FreeRTOS的任务相关的知识点。

0.0 前言

中讲了任务的基础点这里就不再记载了. 但是我们要先知道在FreeRTOS中的任务在代码中是如何描述的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
typedef struct tskTaskControlBlock       /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /**< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /**< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif

#if ( configUSE_CORE_AFFINITY == 1 ) && ( configNUMBER_OF_CORES > 1 )
UBaseType_t uxCoreAffinityMask; /**< Used to link the task to certain cores. UBaseType_t must have greater than or equal to the number of bits as configNUMBER_OF_CORES. */
#endif

ListItem_t xStateListItem; /**< 这个任务的状态(就绪 挂起 阻塞) The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /**< 事件列表中的位置Used to reference a task from an event list. */
UBaseType_t uxPriority; /**< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /**< 任务栈的起始位置Points to the start of the stack. */
#if ( configNUMBER_OF_CORES > 1 )
volatile BaseType_t xTaskRunState; /**< Used to identify the core the task is running on, if the task is running. Otherwise, identifies the task's state - not running or yielding. */
UBaseType_t uxTaskAttributes; /**< Task's attributes - currently used to identify the idle tasks. */
#endif
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /**< Descriptive name given to the task when created. Facilitates debugging only. */

#if ( configUSE_TASK_PREEMPTION_DISABLE == 1 )
BaseType_t xPreemptionDisable; /**< Used to prevent the task from being preempted. */
#endif

#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack; /**< Points to the highest valid address for the stack. */
#endif

#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /**< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif

#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /**< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /**< Stores a number specifically for use by third party trace code. */
#endif

#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /**< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif

#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif

#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif

#if ( configGENERATE_RUN_TIME_STATS == 1 )
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /**< Stores the amount of time the task has spent in the Running state. */
#endif

#if ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 )
configTLS_BLOCK_TYPE xTLSBlock; /**< Memory block used as Thread Local Storage (TLS) Block for the task. */
#endif

#if ( configUSE_TASK_NOTIFICATIONS == 1 )//每一个任务有一组任务通知
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];//任务通知值
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];//任务通知状态
#endif

/* See the comments in FreeRTOS.h with the definition of
* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /**< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif

#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif

#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;

1.0 任务的状态

在该OS中任务主要有一下集中状态:

运行
当任务实际执行时,它被称为处于运行状态。 任务当前正在使用处理器。 如果运行 RTOS 的处理器只有一个内核, 那么在任何给定时间内都只能有一个任务处于运行状态。

准备就绪
准备就绪任务指那些能够执行(它们不处于阻塞或挂起状态), 但目前没有执行的任务, 因为同等或更高优先级的不同任务已经处于运行状态。

阻塞
如果任务当前正在等待时间或外部事件,则该任务被认为处于阻塞状态。 例如,如果一个任务调用vTaskDelay(),它将被阻塞(被置于阻塞状态), 直到延迟结束-一个时间事件。 任务也可以通过阻塞来等待队列、信号量、事件组、通知或信号量 事件。 处于阻塞状态的任务通常有一个”超时”期, 超时后任务将被超时,并被解除阻塞, 即使该任务所等待的事件没有发生。
“阻塞”状态下的任务不使用任何处理时间,不能 被选择进入运行状态。

挂起
与“阻塞”状态下的任务一样, “挂起”状态下的任务不能 被选择进入运行状态,但处于挂起状态的任务 没有超时。 相反,任务只有在分别通过 vTaskSuspend() 和 xTaskResume() API 调用明确命令时 才会进入或退出挂起状态。

2.0 任务的优先级

FreeRTOS使用优先级来安排任务的执行, 优先级数字越低则其优先级越低. 优先级的范围是 0 到 ( configMAX_PRIORITIES - 1 ) 其中的 configMAX_PRIORITIES 在 FreeRTOSConfig.h 中定义。

2.1 相关函数

1
vTaskPrioritySet()

任务的创建

任务支持两种创建方式, 一是静态创建二是动态创建.

在FreeRTOS中,任务的创建可采用两种方法,一种是使用动态创建,另一种是使用静态创建。动态创建时,任务控制块和栈的内存是创建任务时动态分配的,任务删除时,内存可以释放。静态创建时,任务控制块和栈的内存需要事先定义好,是静态的内存,任务删除时,内存不能释放。

具体的函数如下

静态创建函数

1
2
3
4
5
6
7
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )

其实这个函数只是对于函数prvCreateStaticTask的包装, 然后在加上一个prvAddNewTaskToReadyList函数, 这个函数将创建的任务添加到准备队列.

静态创建函数和动态创建函数的主要区别就是, 静态需要用户自己提供stackbuffertaskbuffer也就是任务的栈空间、存储任务的数据结构. 也就是该函数中最后两个参数, 而这些在动态创建任务的函数中都是动态分配的.

所以静态分配的任务,在任务被删除之后是无法删掉对应的内存空间的, 但是动态分配可以.

函数的调用关系:
xTaskCreateStatic()->prvCreateStaticTask()->prvInitialiseNewTask()->vListInitialiseItem()

动态分配函数

任务管理

我们知道任务既具有四种状态, 所以我们学习如何进行状态的转换.

任务删除函数

将一个任务删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
void vTaskDelete( TaskHandle_t xTaskToDelete )//任务删除函数
{
TCB_t * pxTCB;

traceENTER_vTaskDelete( xTaskToDelete );

taskENTER_CRITICAL(); //进入临界区
{
/* If null is passed in here then it is the calling task that is
* being deleted. */
pxTCB = prvGetTCBFromHandle( xTaskToDelete );//获取任务句柄xTaskToDelete所指向的具体的任务, 如果是NULL则代表自己

/* Remove task from the ready/delayed list. */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}

/* Is the task waiting on an event also? */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}

/* Increment the uxTaskNumber also so kernel aware debuggers can
* detect that the task lists need re-generating. This is done before
* portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
* not return. */
uxTaskNumber++;//系统中任务数+1

/* If the task is running (or yielding), we must add it to the
* termination list so that an idle task can delete it when it is
* no longer running. */
if( taskTASK_IS_RUNNING_OR_SCHEDULED_TO_YIELD( pxTCB ) != pdFALSE )//判断当前任务是否在运行, 其实就是判断pxTDB是否等于pxCurrentTCB
{
/* A running task or a task which is scheduled to yield is being
* deleted. This cannot complete when the task is still running
* on a core, as a context switch to another task is required.
* Place the task in the termination list. The idle task will check
* the termination list and free up any memory allocated by the
* scheduler for the TCB and stack of the deleted task. */
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) ); //将任务添加到等待删除的队列, 空闲任务会去删除这些任务

/* Increment the ucTasksDeleted variable so the idle task knows
* there is a task that has been deleted and that it should therefore
* check the xTasksWaitingTermination list. */
++uxDeletedTasksWaitingCleanUp;//等待删除的任务数

/* Call the delete hook before portPRE_TASK_DELETE_HOOK() as
* portPRE_TASK_DELETE_HOOK() does not return in the Win32 port. */
traceTASK_DELETE( pxTCB );

/* The pre-delete hook is primarily for the Windows simulator,
* in which Windows specific clean up operations are performed,
* after which it is not possible to yield away from this task -
* hence xYieldPending is used to latch that a context switch is
* required. */
#if ( configNUMBER_OF_CORES == 1 )
portPRE_TASK_DELETE_HOOK( pxTCB, &( xYieldPendings[ 0 ] ) );
#else
portPRE_TASK_DELETE_HOOK( pxTCB, &( xYieldPendings[ pxTCB->xTaskRunState ] ) );
#endif
}
else//pxTCB指向的任务不是当前任务
{
--uxCurrentNumberOfTasks;
traceTASK_DELETE( pxTCB );

/* Reset the next expected unblock time in case it referred to
* the task that has just been deleted. */
prvResetNextTaskUnblockTime();
}
}

#if ( configNUMBER_OF_CORES == 1 )
{
taskEXIT_CRITICAL();

/* If the task is not deleting itself, call prvDeleteTCB from outside of
* critical section. If a task deletes itself, prvDeleteTCB is called
* from prvCheckTasksWaitingTermination which is called from Idle task. */
if( pxTCB != pxCurrentTCB )//不是当前任务,直接删除
{
prvDeleteTCB( pxTCB );
}

/* Force a reschedule if it is the currently running task that has just
* been deleted. */
if( xSchedulerRunning != pdFALSE )//调度器在运行
{
if( pxTCB == pxCurrentTCB )//指向的任务是当前执行的任务, 则切换任务
{
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
#else /* #if ( configNUMBER_OF_CORES == 1 ) */
{
/* If a running task is not deleting itself, call prvDeleteTCB. If a running
* task deletes itself, prvDeleteTCB is called from prvCheckTasksWaitingTermination
* which is called from Idle task. */
if( pxTCB->xTaskRunState == taskTASK_NOT_RUNNING )
{
prvDeleteTCB( pxTCB );
}

/* Force a reschedule if the task that has just been deleted was running. */
if( ( xSchedulerRunning != pdFALSE ) && ( taskTASK_IS_RUNNING( pxTCB ) == pdTRUE ) )
{
if( pxTCB->xTaskRunState == ( BaseType_t ) portGET_CORE_ID() )
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskYieldWithinAPI();
}
else
{
prvYieldCore( pxTCB->xTaskRunState );
}
}

taskEXIT_CRITICAL();
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */

traceRETURN_vTaskDelete();
}

vTaskDelete()函数用于删除一个任务。当一个任务删除另一个任务时,形参为要删除的任务创建时返回的任务句柄。如果是删除自身,则形参为NULL。要想使用该函数,必须在FreeRTOSConfig.h中把INCLUDE_vTaskDelete定义为1,要删除的任务将从所有就绪、阻塞、挂起和事件列表中删除.

任务挂起函数

挂起函数不会禁用中断, 但是无法进行上下文切换, 也就是无法进行任务转换.

通过vTaskSuspend()函数将任何的转换到挂起态. 我们来分析一下这个函数:
这个函数位于task.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
void vTaskSuspend( TaskHandle_t xTaskToSuspend )//哪个任务需要被挂起
{
TCB_t * pxTCB;

#if ( configNUMBER_OF_CORES > 1 )
BaseType_t xTaskRunningOnCore;
#endif

traceENTER_vTaskSuspend( xTaskToSuspend );

taskENTER_CRITICAL();
{
/* If null is passed in here then it is the running task that is
* being suspended. */
pxTCB = prvGetTCBFromHandle( xTaskToSuspend );//简单进行检查, 看xTaskToSuspend是否是NULL

traceTASK_SUSPEND( pxTCB );

#if ( configNUMBER_OF_CORES > 1 )
xTaskRunningOnCore = pxTCB->xTaskRunState;
#endif

/* Remove task from the ready/delayed list and place in the
* suspended list. */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )//将这个任务从就绪或者是阻塞表中删除, 放入挂起列表
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}

/* Is the task waiting on an event also? */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );//删除在任务列表中的任务
}
else
{
mtCOVERAGE_TEST_MARKER();
}

vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );//将任务状态添加到挂起队列

#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
BaseType_t x;

for( x = ( BaseType_t ) 0; x < ( BaseType_t ) configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
{
if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
{
/* The task was blocked to wait for a notification, but is
* now suspended, so no notification was received. */
pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
}
}
}
#endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
}

#if ( configNUMBER_OF_CORES == 1 )//只有一个内核
{
taskEXIT_CRITICAL();
//非当前任务
if( xSchedulerRunning != pdFALSE )//如果调度器正在运行的话
{
/* Reset the next expected unblock time in case it referred to the
* task that is now in the Suspended state. */
taskENTER_CRITICAL();
{
prvResetNextTaskUnblockTime();
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}

if( pxTCB == pxCurrentTCB )//pxTCB指向当前任务
{
if( xSchedulerRunning != pdFALSE )//调度器正在执行
{
/* The current task has just been suspended. */
configASSERT( uxSchedulerSuspended == 0 );//uxSchedulerSuspended==0表示调度器是否被挂起
portYIELD_WITHIN_API();//进行任务切换
}
else//如果没有调度器没有在执行
{
/*调度器没有运行, 但是pxCurrentTCB指向的任务正好被挂起了, 所以我们必须要更改pxCurrentTCB*/
if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks )//这里是使用通过将挂起任务队列中结点数量和当前系统中的任务数比较, 如果相同则表示所有的任务都被挂起了
{
/* No other tasks are ready, so set pxCurrentTCB back to
* NULL so when the next task is created pxCurrentTCB will
* be set to point to it no matter what its relative priority
* is. */
pxCurrentTCB = NULL;
}
else
{
vTaskSwitchContext();//由于不是所有的任务都是挂起状态, 所以转换pxCurrentTCB, 切换上下文
}
}
}
else//pxTCB指向的不是当前任务
{
mtCOVERAGE_TEST_MARKER();
}
}
#else /* #if ( configNUMBER_OF_CORES == 1 ) 不止一个内核时*/
{
if( xSchedulerRunning != pdFALSE )
{
/* Reset the next expected unblock time in case it referred to the
* task that is now in the Suspended state. */
prvResetNextTaskUnblockTime();
}
else
{
mtCOVERAGE_TEST_MARKER();
}

if( taskTASK_IS_RUNNING( pxTCB ) == pdTRUE )
{
if( xSchedulerRunning != pdFALSE )
{
if( xTaskRunningOnCore == ( BaseType_t ) portGET_CORE_ID() )
{
/* The current task has just been suspended. */
configASSERT( uxSchedulerSuspended == 0 );
vTaskYieldWithinAPI();
}
else
{
prvYieldCore( xTaskRunningOnCore );
}
}
else
{
/* This code path is not possible because only Idle tasks are
* assigned a core before the scheduler is started ( i.e.
* taskTASK_IS_RUNNING is only true for idle tasks before
* the scheduler is started ) and idle tasks cannot be
* suspended. */
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}

taskEXIT_CRITICAL();
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */

traceRETURN_vTaskSuspend();
}

注意任务可以调用vTaskSuspend()函数来挂起自身,但是在挂起自身时会进行一次任务上下文切换,需要挂起自身时就将xTaskToSuspend设置为NULL传递进来即可。无论任务是什么状态都可以被挂起,只要调用了vTaskSuspend()函数就会挂起成功,不论是挂起其他任务还是挂起任务自身。

函数vTaskSuspendAll()这个函数是挂起所有的任务. 其原理就是直接将调度器锁上. 与之相反的函数是xTaskResumeAll(), 调用了多少次vTaskSuspendAll()就需要调用多少次xTaskResumeAll()来恢复.

vTaskSuspendAll()函数的源码:

1
2
3
4
1 void vTaskSuspendAll( void )
2 {
3 ++uxSchedulerSuspended;(1)
4}

uxSchedulerSuspended用于记录调度器是否被挂起,该变量默认初始值为pdFALSE,表明调度器是未被挂起的,每调用一次vTaskSuspendAll()函数就将变量加1,用于记录调用了多少次vTaskSuspendAll()函数。

任务恢复函数

上面说到的函数xTaskResumeAll()也是一种恢复函数, 只是这个函数是和vTaskSuspendAll()对应的, 和函数vTaskSuspendAll()对应的是vTaskResume()

这个函数也位于task.c 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void vTaskResume( TaskHandle_t xTaskToResume )
{
TCB_t * const pxTCB = xTaskToResume;

traceENTER_vTaskResume( xTaskToResume );

/* It does not make sense to resume the calling task. 恢复调用任务是没有意义的, 意思是xTaskToResume不要是自己*/
configASSERT( xTaskToResume );

#if ( configNUMBER_OF_CORES == 1 )//一核心

/* The parameter cannot be NULL as it is impossible to resume the
* currently executing task. */
if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )//恢复调用任务是没有意义的, 意思是xTaskToResume不要是自己和NULL
#else

/* The parameter cannot be NULL as it is impossible to resume the
* currently executing task. It is also impossible to resume a task
* that is actively running on another core but it is not safe
* to check their run state here. Therefore, we get into a critical
* section and check if the task is actually suspended or not. */
if( pxTCB != NULL )
#endif
{
taskENTER_CRITICAL();//进入临界区
{
if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )//pxTCB是挂起状态
{
traceTASK_RESUME( pxTCB );

/* The ready list can be accessed even if the scheduler is
* suspended because this is inside a critical section. */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );//从挂起表中移除
prvAddTaskToReadyList( pxTCB );//添加到就绪表

/* This yield may not cause the task just resumed to run,
* but will leave the lists in the correct state for the
* next yield. */
taskYIELD_ANY_CORE_IF_USING_PREEMPTION( pxTCB );//如果当前任务的优先级小于pxTCB指向的优先级则进行上下文切换(任务切换)
}
else//pxTCB不是挂起状态
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();//退出临界区
}
else
{
mtCOVERAGE_TEST_MARKER();
}

traceRETURN_vTaskResume();
}

这个函数和vTaskSuspendAll()那两个函数不同的是:
无论任务在挂起时调用过多少次vTaskSuspend()函数,只需要调用一次vTaskResume()函数即可将任务恢复运行,当然,无论调用多少次vTaskResume()函数,也只在任务为挂起态时才进行恢复。

前面提到了xTaskResumeAll()函数, 下面是其源码:

1

任务延时函数

我们知道任何形式的轮询有一下几个缺点,其中最重要的是它的效率低下。在轮询期间,该任务实际上没有任何工作要做,但它仍然使用最长的处理时间,因此浪费了处理器的资源。使用任务延时函数可以解决这个问题,这是因为FreeRTOS中的延时函数实际上是将这个任务状态转换为阻塞,直到设置的时间到达。所以这使得在“延时”的时候是可以通过调度程序来做其他事情的。

这个延时函数就是普通的延时,而绝对延时保证的是任务重复执行的时间间隔。

在FreeRTOS中两个延时函数,分别是:
vTaskDelay()相对延时函数和vTaskDelayUntil()绝对延时函数。前者的时间精度要小于后者。

相对延时函数vTaskDelay()

请注意,vTaskDelay() API 函数是仅当 FreeRTOSConfig.h 中的 INCLUDE_vTaskDelay 设置为 1 时才可用。

vTaskDelay() 将调用任务置于阻塞状态,以获得固定数量的滴答中断。任务在处于阻塞状态时不使用任何处理时间,因此任务仅在实际工作时使用处理时间。

函数原型:

1
void vTaskDelay( TickType_txTicksToDelay );

参数:

调用任务在转换回就绪状态之前将保持在阻塞状态的滴答中断数。例如,如果一个名为 vTaskDelay(100) 的任务在滴答计数为 10,000 时立即进入阻塞状态,并保持阻塞状态,直到滴答计数达到 10,100。可以使用宏 pdMS_TO_TICKS() 将以毫秒为单位的时间转换为以 int 为单位的时间。例如,调用 vTaskDelay(pdMS_TO_TICKS(100)) 将导致调用任务保持阻塞状态 100 毫秒。

下面是一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void vTaskFunction( void *pvParameters ) 
{
char *pcTaskName;
const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 );

/* 要打印的字符串通过参数传入。将此转换为字符指针。 */
pcTaskName = ( char * ) pvParameters;

/* 对于大多数任务,都是在一个无限循环中实现的。 */
for( ;; )
{
/* 打印出此任务的名称。 */
vPrintString( pcTaskName );

/* 延迟一段时间。 这次调用 vTaskDelay() 会将任务置于阻塞状态,直到延迟时间结束。
参数需要 'ticks' 中指定的时间,并使用 pdMS_TO_TICKS() 宏(声明 xDelay250ms
常量)将 250 毫秒转换为滴答的等效时间。*/
vTaskDelay( xDelay250ms );
}
}

绝对延时函数 vTaskDelayUntil()

绝对延时常用于较精确的周期运行任务,比如希望某个任务以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的,而不是相对的。

要想使用该函数,必须在FreeRTOSConfig.h中把INCLUDE_vTaskDelayUntil定义为1。

vTaskDelayUntil()与vTaskDelay()一样,都用来实现任务的周期性延时,但vTaskDelay()的延时是相对的,是不确定的,它的延时是等vTaskDelay()调用完毕后开始计算的,并且vTaskDelay()延时的时间到了之后,如果有高优先级的任务或者中断正在执行,被延时阻塞的任务并不会马上解除阻塞,所以每次执行任务的周期并不完全确定。而vTaskDelayUntil()延时是绝对的,适用于周期性执行的任务。当*pxPreviousWakeTime+xTimeIncrement时间到达后,vTaskDelayUntil()函数立刻返回,如果任务是最高优先级的,那么任务会立刻解除阻塞,所以说vTaskDelayUntil()函数的延时是绝对性的。

所以这两个函数最大的区别在于前者是从调用这个函数就开始计时的,但是后者是直接确定了任务重复执行的时间间隔。
具体可以看这个文件:https://zhuanlan.zhihu.com/p/475762149

函数原型:

1
void vTaskDelayUntil( TickType_t* pxPreviousWakeTime, TickType_t xTimeIncrement );

参数:
pxPreviousWakeTime:

此参数的命名是基于 vTaskDelayUntil() 用于实现定期执行且具有固定频率的任务。 在这种情况下,pxPreviousWakeTime 保持任务最后一次离开阻塞状态的时间(被 ‘唤醒’)。 此时间用作参考点,用于计算任务下次离开阻塞状态的时间。pxPreviousWakeTime 指向的变量在 vTaskDelayUntil()函数中自动更新;它通常不会被应用程序代码修改,但必须在第一次使用之前初始化为当前的滴答计数。 清单 25 演示了如何执行初始化。

xTimeIncrement :

此参数的命名也是基于 vTaskDelayUntil() 用于实现定期执行且具有固定频率的任务,频率由 xTimeIncrement 值设置。xTimeIncrement 在 ‘ticks’ 中指定。 pdMS_TO_TICKS()宏可用于将毫秒指定的时间转换为刻度中指定的时间。

任务通知

FreeRTOS从V8.2.0版本开始提供任务通知功能,每个任务都有一个32位的通知值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组,也可以替代长度为1的队列(可以保存一个32位整数或指针值)。相对于以前使用FreeRTOS内核通信的资源时必须创建队列、二进制信号量、计数信号量或事件组的情况,使用任务通知显然更灵活。

默认情况下,RTOS 任务通知功能为启用状态,并且可以 通过将 FreeRTOSConfig.h 中的 configUSE_TASK_NOTIFICATIONS 设置为 0 将其从构建中排除(每个数组索引的每个任务节省 8 字节)。

FreeRTOS提供以下几种对于通知的处理:

  • 发送通知给任务,如果有通知未读,则不覆盖通知值。
  • 发送通知给任务,直接覆盖通知值。
  • 发送通知给任务,设置通知值的一个或者多个位,可以当作事件组来使用。
  • 发送通知给任务,递增通知值,可以当作计数信号量使用。

通过对以上任务通知方式的合理使用,可以在一定场合下替代FreeRTOS的信号量、队列、事件组等。

任务通知的优缺点

根据FreeRTOS官方文档指出, 使用任务通知比通过信号量等ICP通信方式解除阻塞的任务速度要快45%,并且更加省RAM内存空间(使用GCC编译器,-o2优化级别),任务通知的使用无须创建队列。

更加少的RAM空间, 使得任务通知有以下缺点:

  • 只能有一个任务接收通知消息,因为必须指定接收通知的任务。
  • 只有等待通知的任务可以被阻塞,发送通知的任务在任何情况下都不会因为发送失败而进入阻塞态。

任务通知的运作机制

这个机制其实在TCB是包含了的:

1
2
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];

这些成员其实就是任务通知机制的数据结构.

相关函数


FreeRTOS(2)-任务
https://ysc2.github.io/ysc2.github.io/2024/01/07/FreeRTOS-2-任务/
作者
Ysc
发布于
2024年1月7日
许可协议