文章目录
调度:
- 相同优先级的任务轮流运行
- 最高优先级的任务先运行
可以得出结论如下:
- a 高优先级的任务在运行,未执行完,更低优先级的任务无法运行
- b 一旦高优先级任务就绪,它会马上运行(假设厨房着火了,会马上去灭火)
- c 如果最高优先级的任务有多个,他们轮流运行
他们都是使用链表进行管理
打开CubeMX,最高优先级56
56个List,
RadeyList[55] --->优先级为55的,处于就绪态的任务
RadeyList[54] --->优先级为54的,处于就绪态的任务
......
RadeyList[N] --->优先级为N的,处于就绪态的任务
......
RadeyList[0]
我们一开始创建了好几个任务,优先级默认都是osPriorityNormal=24
c
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); // 默认任务
/* 创建任务:光 */
// 创建一个静态分配内存的任务
xLightTaskHandle = xTaskCreateStatic(
Led_Test, //LED测试函数,PC13以500ms间隔亮灭一次
"LightTask", //光任务
128, //栈大小,这里提供了栈的大小(长度)
NULL, //无传入的参数
osPriorityNormal, //优先级默认
g_pucStackOfLightTask, // 静态分配的栈,一个buffer,这里只提供了首地址,长度就是栈的大小,最开始栈的类型不对,栈的类型uint32_t
&g_TCBofLightTask // 取址TCB
);
/* 创建任务:色 */
xColorTaskHandle = xTaskCreateStatic(
ColorLED_Test, //LED测试函数,PC13以500ms间隔亮灭一次
"ColorTask", //光任务
128, //栈大小,这里提供了栈的大小(长度)
NULL, //无传入的参数
osPriorityNormal, //优先级默认
g_pucStackOfColorTask, // 静态分配的栈,一个buffer,这里只提供了首地址,长度就是栈的大小
&g_TCBofColorTask // 取址TCB
);
执行完上面的程序之后,会创建三个任务, 放在 RadeyList[24] 这个链表里面~其他链表都是空的
这里是怎么调度的呢?
启动调度器后
执行
c
osStatus_t osKernelStart (void) {
osStatus_t stat;
if (IS_IRQ()) {
stat = osErrorISR;
}
else {
if (KernelState == osKernelReady) {
KernelState = osKernelRunning;
vTaskStartScheduler();
stat = osOK;
} else {
stat = osError;
}
}
return (stat);
}
执行完之后,这里就创建了一个空闲的任务
c
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;
/* The Idle task is created using user provided RAM - obtain the
address of the RAM then create the idle task. */
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
configIDLE_TASK_NAME,
ulIdleTaskStackSize,
( void * ) NULL, /*lint !e961. The cast is not redundant for all compilers. */
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
if( xIdleTaskHandle != NULL )
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
}
#else
{
/* The Idle task is being created using dynamically allocated RAM. */
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
#if ( configUSE_TIMERS == 1 )
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
if( xReturn == pdPASS )
{
/* freertos_tasks_c_additions_init() should only be called if the user
definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
the only macro called by the function. */
#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
{
freertos_tasks_c_additions_init();
}
#endif
/* Interrupts are turned off here, to ensure a tick does not occur
before or during the call to xPortStartScheduler(). The stacks of
the created tasks contain a status word with interrupts switched on
so interrupts will automatically get re-enabled when the first task
starts to run. */
portDISABLE_INTERRUPTS();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent
structure specific to the task that will run first. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) 0U;
/* If configGENERATE_RUN_TIME_STATS is defined then the following
macro must be defined to configure the timer/counter used to generate
the run time counter time base. NOTE: If configGENERATE_RUN_TIME_STATS
is set to 0 and the following line fails to build then ensure you do not
have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your
FreeRTOSConfig.h file. */
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
/* Setting up the timer tick is hardware specific and thus in the
portable interface. */
if( xPortStartScheduler() != pdFALSE )
{
/* Should not reach here as if the scheduler is running the
function will not return. */
}
else
{
/* Should only reach here if a task calls xTaskEndScheduler(). */
}
}
else
{
/* This line will only be reached if the kernel could not be started,
because there was not enough FreeRTOS heap to create the idle task
or the timer task. */
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
meaning xIdleTaskHandle is not used anywhere else. */
( void ) xIdleTaskHandle;
}
函数名:prvIdleTask 空闲任务
现在的链表
这句话是把这个新的任务添加进ReadyList,添加进就绪列表,根据优先级找到链表
还有一个全局变量当前TCB
最开始只有StartDefaultTask这一个任务,那么全局指针pxCurrentTCB就指向这个任务
当我们创建led的任务时,指针指向Led_Test
当创建ColorLED_Test时候,指针就指向了ColorLED_Test
启动调度器的时候,创建了一个空闲任务,但是优先级是0,优先级比较低,pxCurrentTCB保留,还是指向ColorLED_Test
当我们真正启动调度器的时候,就从ColorLED_Test任务开始,先运行最后创建的任务
- =这就是06_create_task_use_params里,后面创建的task3最先开始运行的原因!!!=
在系统里,会初始化一个Tick,Tick中断里面有计数累加,作为时钟基准,并且会调度
cubemx定义了一个时钟,频率是1000,每1ms产生一次中断
Tick中断:
- 计数累加,作为时钟基准
- 并且会发起调度
调度 :从上到下遍历ReadyList,找到24的时候,非空,然后指向下一个任务
- 遍历ReadyList,找到第一个非空的链表,把pxCurrentTCB指向下一个任务,然后启动
运行1ms之后,计数值又累计,发起下一次调度,再次遍历这些链表,从上面找到下面,找到第24个不空,把pxCurrentTCB指向下一个任务,然后启动
下一次调度
上面就是轮流运行这些相同优先级的就绪态任务的,运行依赖于tick中断,依赖于调度机制
在默认任务里,读取遥控器的键值,如果是播放按键,会创建一个播放音乐的任务,优先级是25,如图所示:
这个任务马上处于就绪态,并且是最高优先级,所以会马上运行这个任务
当执行到vTaskDelay的时候,现在是Blocked状态,已经不是Ready状态了,现在就会从25优先级的链表中删除,移动到某一个delay的链表里,有DelayedTaskList1和DelayedTaskList2(这里是防止定时器溢出,才有两个delay链表)
假设存放到DelayedTaskList1里了,下一次Tick中断的时候,现在又要发起一次调度,重新遍历......
取出下一个任务来运行(上次运行的是任务1,下一个任务就是任务2)
下一次中断,在调度如下图所示:
在Tick中断里发起一个比较复杂的调度,同时也判断一下delay链表你的时间有没有到,如果时间到了,就把它放入就绪链表
- 计数值 ++
- 判断DelayedTaskList里的任务是否可恢复,
- 发起调度
假设5号任务是2个Tick,5号任务的Tick已经到了,就需要把这个5号任务从delay链表里移动到第25个优先级的链表中去
然后发起一个调度,再遍历所有链表,发现25非空,就执行5号任务~
在5号任务里又使用了delay,又把这个任务放到DelayedTaskList里
然后遍历整个链表,发现第24项非空,又执行下一个任务(任务1)
假设在任务1里又调用了这个函数(按下了播放键,暂停的功能)
这个任务被暂停了,是不可能通过Tick中断唤醒的,只能Resume(从链表中移除,重新叨叨ReadyList里)
上面全是链表的操作
总结
- 在Tick中断里计数值++
- 判断DelaydeTaskList里的任务是否可以被恢复,如果时间到了,就移动到就绪态链表中
- 发起调度,在找到非空链表中,找到下一个任务运行~