目录
默认FreeRTOS调度策略(单核)
默认情况下,FreeRTOS使用固定优先级抢占式调度策略,并对同等优先级的任务进行循环时间切片:
- "固定优先级"意味着调度程序不会一直更改任务的优先级,但是由于优先级继承,它可能会暂时提高任务的优先级。
- "抢占式"意味着调度程序始终运行能够运行的最高优先级的 RTOS 任务,而不管任务何时能够运行。例如,如果中断服务例程 (ISR) 更改了能够运行的最高优先级任务,则计划程序将停止当前运行的较低优先级任务并启动优先级较高的任务 - 即使这发生在某个时间片内。在这种情况下,优先级较低的任务被称为已被优先级较高的任务"抢占"。
- "轮循机制"是指共享优先级的任务轮流进入"正在运行"状态。
- "时间切片"意味着调度程序将在每个系统中断时优先级相等的任务之间切换 - 系统中断之间的时间是一个时间片。
FreeRTOS调度策略的实现
list是是FreeRTOS核心的数据结构,它在list.c文件中实现为一个双向循环的链表。而FreeRTOS的抢占式的调度策略就是基于此实现的。
在task.c中定义了一个pxReadyTasksLists[ configMAX_PRIORITIES ] 链表数组,其中
configMAX_PRIORITIES 是定义在FreeRTOSConfig.h中的配置参数表示task的最大优先级。所以有几个优先级就有几个ReadyList,这是一一对应的。如下图:
任务创建
而当我们创建task时就会在创建时,把创建的task插入对应优先级的pxReadyTasksLists中,如下图:
同时创建任务时也会使pxCurrentTCB指针指向优先级最高的TCB,如下图:
任务调度的4种情景:
1.第一次启动任务调度器
任务创建完成之后,启动任务调度器,调度器就会使能SVC中断,如下图:(至于为什么要进入SVC中断,因为在只有在特权级模式下,程序才以访问一些内核的寄存器,和特殊功能寄存器,且我们希望在task正常运行时芯片工作在线程模式,而要启动第一个task,需要访问这些寄存器,具体的可以参考这篇文件章:【RTX操作系统教程】第9章 任务运行在特权级或非特权级模式_硬汉Eric2013_新浪博客 (sina.com.cn))
之后会跳转到SVC中断,而在SVC中会初始化一些通用寄存器,psp指针,打开BASEPRI中断屏蔽寄存器,并切换到线程模式跳转到第一个task的函数入口,之后退出中断就会执行第一个task了,如下图:
2.任务主动触发调度
当任务执行一些系统提供的API函数时,就可能触发任务调度,例如执行vTaskDelay函数,执行接收或发送队列函数且设置了阻塞时间,执行信号量和锁相关的函数导致阻塞等等。
例如:调用xQueueReceive队列接收函数时,如果队列为空且设置了阻塞时间,则会把当前的task从pxReadyTasksLists插入到delay链表中,然后调用portYIELD_WITHIN_API 使能PendSV中断,然后在PendSV中断里保存当前任务的上下文,并切换到下一个优先级task
3.SystemTick时钟触发调度
整个os的时间依赖于SystemTick,而SystemTick时间来源于内核硬件的滴答定时器。当某个task运行时,SystemTick时间到了会进入SystemTick的中断,在此中断里会调用xTaskIncrementTick,先判断是否有任务的阻塞时间到了,如果有然后把任务从delayList中放入pxReadyTasksLists中,同时判断新加入的task优先级是否大于当前正在运行的优先级,如果是就置一个pdTRUE的标志位。
如果设置了时间片轮转的宏configUSE_TIME_SLICING,就会再判断当前task所在的pxReadyTasksLists中是否还有其他同优先级的任务,有的话就置标志位相当于一个时间片调度一次同优先级的任务。
BaseType_t xTaskIncrementTick( void ) { TCB_t * pxTCB; TickType_t xItemValue; BaseType_t xSwitchRequired = pdFALSE; /* Called by the portable layer each time a tick interrupt occurs. * Increments the tick then checks to see if the new tick value will cause any * tasks to be unblocked. */ traceTASK_INCREMENT_TICK( xTickCount ); if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) { /* Minor optimisation. The tick count cannot change in this * block. */ const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1; /* Increment the RTOS tick, switching the delayed and overflowed * delayed lists if it wraps to 0. */ xTickCount = xConstTickCount; //判断tick是溢出,是则交换delaylist if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */ { taskSWITCH_DELAYED_LISTS(); } else { mtCOVERAGE_TEST_MARKER(); } /* See if this tick has made a timeout expire. Tasks are stored in * the queue in the order of their wake time - meaning once one task * has been found whose block time has not expired there is no need to * look any further down the list. */ if( xConstTickCount >= xNextTaskUnblockTime ) { for( ; ; ) { //判断延时链表是否为空,为空就是没有要解阻塞的任务,当前任务就一直运行 if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) { /* The delayed list is empty. Set xNextTaskUnblockTime * to the maximum possible value so it is extremely * unlikely that the * if( xTickCount >= xNextTaskUnblockTime ) test will pass * next time through. */ xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ break; } else { /* The delayed list is not empty, get the value of the * item at the head of the delayed list. This is the time * at which the task at the head of the delayed list must * be removed from the Blocked state. */ //找出阻塞优先级最高的task,查看其阻塞时间是否到了 pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */ xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) ); if( xConstTickCount < xItemValue ) { /* It is not time to unblock this item yet, but the * item value is the time at which the task at the head * of the blocked list must be removed from the Blocked * state - so record the item value in * xNextTaskUnblockTime. */ xNextTaskUnblockTime = xItemValue; break; /*lint !e9011 Code structure here is deedmed easier to understand with multiple breaks. */ } else { mtCOVERAGE_TEST_MARKER(); } //时间到了从阻塞链表中移除 /* It is time to remove the item from the Blocked state. */ ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); /* Is the task waiting on an event also? If so remove * it from the event list. */ if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) { ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); } else { mtCOVERAGE_TEST_MARKER(); } //插入reday链表中 /* Place the unblocked task into the appropriate ready * list. */ prvAddTaskToReadyList( pxTCB ); /* A task being unblocked cannot cause an immediate * context switch if preemption is turned off. */ //如果支持抢占式调度 #if ( configUSE_PREEMPTION == 1 ) { /* Preemption is on, but a context switch should * only be performed if the unblocked task has a * priority that is equal to or higher than the * currently executing task. */ //判断插入的task优先级是否大于当前的task if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { xSwitchRequired = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_PREEMPTION */ } } } /* Tasks of equal priority to the currently running task will share * processing time (time slice) if preemption is on, and the application * writer has not explicitly turned time slicing off. */ //是否支持时间片调度 #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) { //同优先级任务不止一个 if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 ) { xSwitchRequired = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */ #if ( configUSE_TICK_HOOK == 1 ) { /* Guard against the tick hook being called when the pended tick * count is being unwound (when the scheduler is being unlocked). */ if( xPendedTicks == ( TickType_t ) 0 ) { vApplicationTickHook(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_TICK_HOOK */ #if ( configUSE_PREEMPTION == 1 ) { //是否有挂起请求 if( xYieldPending != pdFALSE ) { xSwitchRequired = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_PREEMPTION */ } else { ++xPendedTicks; /* The tick hook gets called at regular intervals, even if the * scheduler is locked. */ #if ( configUSE_TICK_HOOK == 1 ) { vApplicationTickHook(); } #endif } return xSwitchRequired; }
最后会判断返回的标志位,来确定是否进行调度,如果是pdTRUE则会启动pendSV中断在此中断里切换上下文调度到新的task。
4.因为中断而引起的任务调度
中断里调用系统API导致更高优先级的task唤醒,如果不显示的调用调度的函数,则唤醒的任务不会在中断退出后立马调度,它会等到下一个SystemTick中断来时才切换task,这样会造成中断的响应延时,在某些实时性要求高的地方是不能满足需求的。所以可以在中断里直接在条件满足时,先切换task,这样在中断退出时就会直接返回到高优先级的task了。见如下例子:
void ISR(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE, tmpHPT=pdFALSE;
xQueueSendFromISR(rx_handle, &buffer, tmpHPT);
xHigherPriorityTaskWoken = xHigherPriorityTaskWoken || tmpHPT;
/* If lHigherPriorityTaskWoken is now equal to pdTRUE, then a context
switch should be performed before the interrupt exists. That ensures the
unblocked (higher priority) task is returned to immediately. */
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}
(如有描述不对烦请指正谢谢!)