FreeRTOS之任务调度机制
不同于前后台轮询系统,FreeRTOS正是使用任务调度机制来选择某个任务运行。那么到底如何选择任务,就需要根据任务的优先级的状态来决定。
文章目录
1 【优先级与任务状态】
1.1 [优先级]
- 高优先级任务可以抢占低优先级任务优先执行
- 高优先级的任务不停止,低优先级的任务永远无法执行
- 同等优先级的任务,轮流执行:时间片轮转
1.2 [任务状态]
- 运行态 Running
- 就绪态 Ready
- 阻塞态 Blocked,等待某事件(时间,信号,标志位等)被动阻塞
- 暂停态 Suspend,主动暂停
2 【任务链表】
根据优先级与状态可将任务发放到多个任务链表当中。通过任务链表帮助调度器对任务进行管理。
-
就绪态
- 任务在就绪状态下,每个优先级都有一个对应的链表,最多有configMAX_PRIORITIES个
static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
-
阻塞态
- 当任务因为延时或等待事件的发生时会处于阻塞状态,延时任务链表里的任务通过延时的先后顺序由小到大排列。
static List_t * volatile pxDelayedTaskList;
static List_t * volatile pxOverflowDelayedTaskList;
-
暂停态
- 当任务被挂起时,任务将会放在暂停列表里
static List_t xSuspendedTaskList;
-
pending态(Note)
- 当任务从暂停态或阻塞态被恢复时,如果调度器此时已经被挂起,则任务会先放进
xPendingReadyList
链表,等到调度器恢复时(xTaskResumeAll)再将这些xPendingReadyList里的任务放进就绪链表。 xPendingReadyList
- 此链表的作用是什么?为何不直接将恢复的任务放进就绪链表?(因为进入此链表的前提是调度器被挂起,保持当前任务的运行状态,无法执行抢占操作(调度器不工作就无法将就绪任务放入就绪链表)。这时恢复的任务就需要先存放在此链表)
- 当任务从暂停态或阻塞态被恢复时,如果调度器此时已经被挂起,则任务会先放进
3 【如何管理?】
怎么取出要运行的任务?
- 找到最高优先级的运行态、就绪态任务,运行它。
- 如果任务的优先级都相同,轮流执行:排队,链表前面的先运行,运行1个tick后将当前任务重新排到链表尾部。
3.1 [如何找到最高优先级的运行态任务]
以最简单的为例,在创建任务的时候,先为TCB与栈申请空间,然后使用prvAddNewTaskToReadyList(pxNewTCB)
添加到就绪链表中,实际上在此函数中调用的proAddTaskToReadyList(pxNewTCB)
插入对应优先级的链表尾部。(链表在RTOS是非常重要的,任务切换,内存管理都使用链表实现的)。在调度器选择任务的时候会从pxReadyTaskLists[优先级]从大到小进行挑选,并会选择最大优先级链表的第一个就绪任务执行。
3.2 [pxReadyTaskLists就绪任务优先级数组]
pxReadyTaskLists[]
指针数组的大小是可配置的,每个元素放置一个优先级就绪链表的首地址,因为Task有多个优先级,选择任务运行的时候往往需要考虑任务状态以及优先级,根据不同优先级,将就绪任务放在不同优先级的链表中方便调度器进行选择。但pxDelayedTaskList
与xPendingReadyList
大小都为1,因为阻塞态不需要根据优先级处理。
eg:
/* 创建三个任务 */
xTaskCreate(vTask1, "Task_1", 1000, NUll, 0, NUll);
xTaskCreate(vTask1, "Task_2", 1000, NUll, 0, NUll);
xTaskCreate(vTask1, "Task_3", 1000, NUll, 2, NUll);
---------------------------------------------------
/* 创建三个任务会根据优先级将任务地址链接到对应优先级的链表末尾,vTask3的优先级高,它对应的链表就与vTask1、vTask2不同。
* 并根据创建顺序可以看到,相同优先级的任务,vTask2创建的晚,则它在链表末端。当vTask1执行完成之后,又放回到链表末端 */
pxReadyTaskLists[4]
pxReadyTaskLists[3]
pxReadyTaskLists[2] ---> vTask3 /* 任务调度器从pxReadyTaskLists[高优先级]挑选就会先挑选vtask3执行 */
pxReadyTaskLists[1]
pxReadyTaskLists[0] ---> vTask1 ---> vTask2 ---> Idle Task(优先级为0 的空闲任务)
pxDelayedTaskList
pxPendingReadyList
4 【谁进行调度?】
TICK中断,调度系统的核心,一个任务执行一个Tick时长,在Tick中断函数中会进行任务切换(调整任务所在的任务链表)。
eg:每次中断会检查有哪些延时任务到期,将其从延时任务列表里移除并加入到就绪列表里。如果就绪任务的优先级都相同,如果开启时间片轮询,就会每个tick执行一个任务,轮询执行。
Tick中断做了什么:
- 找到最高优先级的就绪任务
- 保存当前任务的信息,并将其插入就绪任务链表末端
- 恢复新的Task
5 【通过链表理解调度机制】
- 可抢占:高优先级的任务先运行
- 时间片轮转:同优先级的任务轮流执行
- 空闲任务礼让:如果其他优先级为0 的任务,空闲任务主动放弃一次运行机会
5.1 [空闲任务礼让]
/*
* -----------------------------------------------------------
* The Idle task.
* ----------------------------------------------------------
*
* The portTASK_FUNCTION() macro is used to allow port/compiler specific
* language extensions. The equivalent prototype for this function is:
*
* void prvIdleTask( void *pvParameters );
*
*/
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
( void ) pvParameters;
portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );
for( ; ; ) {
prvCheckTasksWaitingTermination();
#if ( configUSE_PREEMPTION == 0 ) {
taskYIELD();
}
#endif /* configUSE_PREEMPTION */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) {
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 ) {
/* 如果0优先级的就绪列表长度大于1,那么主动执行一次让步,使其他任务都执行完毕之后空闲任务再接着执行 */
taskYIELD();
} else {
mtCOVERAGE_TEST_MARKER();
}
}
#endif
#if ( configUSE_IDLE_HOOK == 1 ) {
extern void vApplicationIdleHook( void );
vApplicationIdleHook();
}
#endif /* configUSE_IDLE_HOOK */
#if ( configUSE_TICKLESS_IDLE != 0 ) {
TickType_t xExpectedIdleTime;
xExpectedIdleTime = prvGetExpectedIdleTime();
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) {
vTaskSuspendAll();
{
configASSERT( xNextTaskUnblockTime >= xTickCount );
xExpectedIdleTime = prvGetExpectedIdleTime();
configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) {
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();
} else {
mtCOVERAGE_TEST_MARKER();
}
}
( void ) xTaskResumeAll();
} else {
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICKLESS_IDLE */
}
}
待完善,欢迎大家指正