Tick中断
在默认调度算法下,对于同优先级的任务,它们轮流执行,轮流的时间基准由tick中断决定,由定时器产生固定间隔的中断。修改FreeRTOSConfig.h文件中的:
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
可以修改中断产生的时间间隔。1000Hz表示间隔为1ms。实验结果如下图所示:
任务状态与转换
我们可以简单把任务状态分为运行和非运行,其中非运行可以分为
- 阻塞状态(blocked)
- 暂停状态(suspended)
- 就绪状态(ready)
完整的状态转换图如下所示
由上图可知,进入运行状态,必须先处于就绪状态。
阻塞状态与暂停状态的区别在于,进入阻塞状态后需要等待某件事件发生才能退出,而进入暂停状态后需要由其他任务调用vTaskResume()函数才能退出。
在FreeRTOS中只能通过调用以下函数进入暂停状态:
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
参数xTaskToSuspend表示要暂停的任务,如果是NULL,则表示暂停自己。 退出必须通过其他任务调用vTaskResume函数或者中断调用xtaskResumeFromISR函数。并且由官方源码可知,退出暂停状态的函数参数不能为NULL。
任务状态实验
创建三个优先级相同的任务进行实验,部分代码如下:
void vmainTask1Function( void * param)
{
UBaseType_t flag = 0;
TickType_t t = 0;
TickType_t start = xTaskGetTickCount();
while(1) {
t = xTaskGetTickCount();
task1Run = 1;
task2Run = 0;
task3Run = 0;
printf("1");
if(!flag && (t > start + 10)) {
flag = 1;
vTaskSuspend(xTask3Handle);
}
else if(t > start + 20) {
vTaskResume(xTask3Handle);
}
}
}
void vmainTask2Function( void * param)
{
while(1) {
task1Run = 0;
task2Run = 1;
task3Run = 0;
printf("2");
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void vmainTask3Function( void * param)
{
int num = (int)(param);
while(1) {
task1Run = 0;
task2Run = 0;
task3Run = 1;
printf("%d", num);
}
}
实验现象如下图所示:
空闲任务及其钩子函数
空闲任务由任务调度器vTaskStartScheduler函数创建,优先级为0,任务状态只有就绪状态和运行状态(由空闲任务的作用决定),如果处于阻塞状态或者暂停状态,程序将会崩溃。创建空闲任务的官方源码如下所示:
xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
configIDLE_TASK_NAME,
ulIdleTaskStackSize,
( void * ) NULL, /*lint !e961. The cast is not redundant for all compilers. */
portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
portPRIVILEGE_BIT表示优先级,其值为0。
空闲任务的作用之一释放被删除任务的内存(参考文章:FreeRTOS学习笔记-------任务创建与删除),如果希望空闲任务实现此功能,其必须得到执行。
空闲任务每执行一次,就会执行一次钩子函数,钩子函数的作用包括:
- 执行一些后台的、需要连续执行的函数
- 测量系统的空闲时间
- 让系统进入省电模式
根据官方源码可知,如果使用钩子函数(在使用的文件中实现vApplicationIdleHook函数),需要在FreeRTOSConfig.h中配置configUSE_IDLE_HOOK。
#if ( configUSE_IDLE_HOOK == 1 )
{
extern void vApplicationIdleHook( void );
/* Call the user defined function from within the idle task. This
* allows the application designer to add background functionality
* without the overhead of a separate task.
* NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
* CALL A FUNCTION THAT MIGHT BLOCK. */
vApplicationIdleHook();
}
#endif /* configUSE_IDLE_HOOK */
任务调度算法
调度算法就是确定哪个处于就绪状态的任务可以切换成运行状态。调度算法的行为主要体现在两方面:高优先级的任务先运行、同优先级的就绪态任务如何被选中。调度算法要确保同优先级的就绪态任务,能"轮流"运行,策略是"轮转调度"(Round Robin Scheduling)。轮转调度并不保证任务的运行时间是公平分配的,我们还可以细化时间的分配方法。
在FreeRTOS.h和FreeRTOSConfig.h中,官方源码如下:
#ifndef configUSE_PREEMPTION
#error Missing definition: configUSE_PREEMPTION must be defined in FreeRTOSConfig.h as either 1 or 0. See the Configuration section of the FreeRTOS API documentation for details.
#endif
............
#ifndef configIDLE_SHOULD_YIELD
#define configIDLE_SHOULD_YIELD 1
#endif
............
#ifndef configUSE_TIME_SLICING
#define configUSE_TIME_SLICING 1
#endif
#define configUSE_PREEMPTION 1
由官方源码可知,默认调度算法是高优先级的任务可以先执行(抢占,configUSE_PREEMPTION),同优先级的任务轮流执行(时间片轮转,configUSE_TIME_SLICING),空闲任务让步于用户任务(configIDLE_SHOULD_YIELD)。
对于空闲任务让步于用户任务的官方源码如下:
#if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
{
/* When using preemption tasks of equal priority will be
* timesliced. If a task that is sharing the idle priority is ready
* to run then the idle task should yield before the end of the
* timeslice.
*
* A critical region is not required here as we are just reading from
* the list, and an occasional incorrect value will not matter. If
* the ready list at the idle priority contains more than one task
* then a task other than the idle task is ready to execute. */
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 )
{
taskYIELD();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */
configIDLE_SHOULD_YIELD分别为1和0的实验现象如下:
若修改调度算法,主要通过修改FreeRTOSConfig.h中的configUSE_PREEMPTION、configUSE_TIME_SLICING、configIDLE_SHOULD_YIELD选项,0表示不允许,1表示允许。
有关具体的任务调度分析文章:FreeRTOS学习笔记------任务调度机制-CSDN博客