1.预备知识:
1.1空闲任务:
1.1.1空闲任务的创建:
空闲任务在开启调度器函数vTaskStartScheduler()中创建,空闲任务的函数将会做:
- 检查自杀的任务,做清理工作
- 如果user配置了开启hook函数,将会调用hook函数,用户可以在hook函数里面添加相应功能
1.2两个Delay:
- vTaskDelay:指定的是阻塞的时间
- vTaskDelayUntil:指定的是任务执行的间隔、周期
freertos有一个使用定时器每隔若干时间会产生一次中断(cubemx里的宏TICK_RATE_Hz=中断频率,1ms),类似于人的心跳,每一次中断之间的时间间隔被称为Tick,vTaskDelay(n)的n指的就是delay n个Tick。
2.任务的管理与调度
2.1任务调度特性:
a. 高优先级的任务未执行完,低优先级的任务无法运行
b. 一旦高优先级任务就绪,马上运行
c. 最高优先级的任务有多个,它们轮流运行
2.2任务调度本质:
使用链表来管理:
理论
任务的最高优先级=56:
跳转到定义,宏=数组成员是链表的数组,共56个:
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ] = {0};
PRIVILEGED_DATA static List_t xDelayedTaskList1 = {0};
PRIVILEGED_DATA static List_t xDelayedTaskList2 = {0};
每个链表存放一个优先级的任务:(以3个任务优先级均=24为例)
在代码中体现为:
BaseType_t xTaskCreate():
- 当创建任务的时候,任务将被添加到就绪中:
- 判断添加进哪个就绪列表以及当前任务优先级是否更高,pxCurrentTCB指向优先级最高的列表:
BaseType_t xTaskCreate()
{
/*...*/
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 Macro has been consolidated. */
{
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxNewTCB );
/*将新任务添加进ReadyList*/
vAddTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
/*...*/
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
// 其他代码
}
}
void vTaskStartScheduler( void ):
在启动任务调度器函数里面会创建空闲任务,优先级=0,最低,此时pxCurrentTCB指向当前创建完毕的最高优先级任务(task3)。
此为优先级最高的任务执行。
int main( void )
{
prvSetupHardware();
/* 初始化rtos任务*/
MX_Freertos_Init();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
/*启动任务调度器*/
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
/* Add the idle task at the lowest priority. */
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
staticTask_t *pxIdleTaskTCBBuffer = NULL;
StackType_t *pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
/* 创建空闲任务*/
xIdleTaskHandle = xTaskCreateStatic(
prvIdleTask,
configIDLE_TASK_NAME,
ulIdleTaskStackSize,
( void * ) NULL, /*lint !e961. The cast is not redundant as per the MISRA exception. */
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer ); parameter is not used. */
}
#endif
if( xIdleTaskHandle != NULL )
{
/* ... */
}
}
task3释放任务调度权后,执行同优先级的任务,时间片轮转:
每一次Tick中断进行一次任务调度,从上往下遍历ReadyList,找到第一个非空的链表,然后依次取出其中任务来运行(pxCurrentTCB指向下一个任务)。
此时若创建一个新任务task5,且优先级=25,由 一旦高优先级任务就绪,马上运行的原则和任务创建完即为就绪态,pcCurrentTCB立刻指向task5,开始运行。
在task5内部若存在延时,则运行到延时时,task5进入blocked状态,此时task5被从ReadyList中删除,移到其他链表里(某一个DelayTaskList):
Delay过程中,将触发Tick调度:
- cnt++ 累加计数
- 判断DelayTaskList里的任务是否可恢复回ReadyTask。(比一般Tick多做的)
- 检查是否有新的任务需要运行,触发PendSV,并执行上下文切换。
PendSV:
- 保存当前任务寄存器、堆栈指针等
- 寄存器PC值会被恢复为即将运行的任务的任务函数,B开始运行
由于刚才Task5已经从就绪列表里面删除,此时将从就序列表(优先级=24)的进行,又由于刚刚Task1,3均已经运行过,此时从Task2运行。(每个就绪列表里有一个index记录谁运行过了)。
Delay结束后,恢复task5进入就绪列表。一样发起Tick调度:
- 从上往下遍历ReadyList,找到第一个非空的链表,然后依次取出其中任务来运行(pxCurrentTCB指向下一个任务)
若在下次运行1时暂停task5:
则task5将被从就绪列表中移除,放入挂起链表中:
PRIVILEGED_DATA static List_t xSuspendedTaskList = {0};
只有再次调用:task5才会被恢复回就绪列表。
vTaskResume(xSoundTaskHandle);
3.总结:
调度的触发情况:
时间片轮转中:Tick中断触发一次
任务运行:任务运行完毕,释放任务调度权/被高优先级任务打断。