前言
回顾下之前的章节:我们在一个简单的定时器OS基础上实现了cortex-M系列架构的兼容,并基于单片机的基本资源实现了很多实例。
从这个章节开始,我们把FreeRTOS移植进来,同时还考虑兼容性。
上一节我们介绍了移植FreeRTOS的方法,并基于此实现一个最基本的例子:串口定时打印数据。
这一节我们分析下FreeRTOS的任务管理。
开发板是GD32的开发板。
关键字:FreeRTOS,STM32,GD32,任务管理
关于FreeRTOS的官方文档,里面实际上也讲的很详细,参见:FreeRTOS官方文档[1]
任务
如果非要给任务下个定义的话,在嵌入式系统中,应该叫做具象业务的具象表达(独家定义,如有雷同,对方是假货)。
比如:采样是具象业务,对应的具象表达就是sample()。
任务需要占用一定的资源,比如内存,CPU。
不同任务都想要执行,那么存在资源的冲突问题。
任务需要解决资源和资源冲突的问题。
写过单片机程序的同学应该都写过裸机程序,官方叫法叫前后台系统。
具体做法就是:
- 定义一个时基,比如:10ms。
- 给不同的任务定义不同的运行周期。
- main函数中一个while(1),时基计数,到了某个任务的运行周期就运行某个任务。
- 如有中断,优先运行中断。
这种做法的缺点也很明显,就是一大锅饭,大家一起等着,到点了才能领饭,管你是不是老弱病残孕。
在FreeRTOS中,任务被赋予了优先级,高优先级的任务可优先运行;当然,它占用ram资源多一些。
这个跟软件优化有点像,要么是用时间换空间,要么是空间换时间。
FreeRTOS的任务已经具备了进程的特性,类似于Linux中没有线程的进程。
任务状态机
创建任务
代码语言:javascript复制void ATaskFunction( void *pvParameters )
{
for( ;; )
{
/* The code to implement the task functionality will go here. */
}
/* 如果要删除任务,调用vTaskDelete函数 */
vTaskDelete( NULL );
}
任务由xTaskCreate函数创建(详细可查看手册的3.4 Creating Tasks
):
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
uint16_t usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );
- 参数
- pvTaskCode:任务函数指针:
typedef void (*TaskFunction_t)( void * )
。 - pcName:任务名称字符串,该字符串的最大长度由FreeRTOSConfig.h中的宏configMAX_TASK_NAME_LEN指定。
- usStackDepth:任务堆栈大小,单位是堆栈位宽的数量。在32位宽度的堆栈下,usStackDepth定义为100,则实际使用100*4字节堆栈存储空间。
- pvParameters:任务的指针参数,任务创建时,作为一个参数传递给任务。
- uxPriority:任务的优先级,最大值由
configMAX_PRIORITIES
指定。 - pvCreatedTask:当前任务指针,可以通过它来修改任务优先级或者删除任务;如果不用,置为NULL。
- pvTaskCode:任务函数指针:
- 返回值
- pdPASS:成功。
- pdFAIL:失败,堆栈或者ram不够。
删除任务
函数比较简单。
代码语言:javascript复制void vTaskDelete( TaskHandle_t pxTaskToDelete );
- 参数
- pxTaskToDelete:任务指针,如果为NULL,表示删除当前任务。
注:使用本函数需要在FreeRTOSConfig.h
中设置INCLUDE_vTaskDelete
宏为1
其他任务相关函数
vTaskStartScheduler 任务调度主函数
代码语言:javascript复制void vTaskStartScheduler( void );
vTaskSuspend 挂起指定任务
代码语言:javascript复制void vTaskSuspend( TaskHandle_t xTaskToSuspend );
注:使用本函数需要在FreeRTOSConfig.h
中设置INCLUDE_vTaskSuspend
宏为1
vTaskSuspendAll 挂起所有任务
代码语言:javascript复制void vTaskSuspendAll( void );
vTaskResume 让挂起任务重新就绪
代码语言:javascript复制void vTaskResume( TaskHandle_t xTaskToResume );
注:使用本函数需要在FreeRTOSConfig.h
中设置INCLUDE_vTaskSuspend
宏为1
xTaskResumeFromISR 让挂起任务重新就绪(中断)
代码语言:javascript复制BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );
注:使用本函数需要在FreeRTOSConfig.h
中设置INCLUDE_vTaskSuspend
和INCLUDE_xTaskResumeFromISR
宏为1
xTaskResumeAll 恢复所有任务
代码语言:javascript复制BaseType_t xTaskResumeAll( void );
uxTaskPriorityGet 任务优先级获取
代码语言:javascript复制UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask );
注:使用本函数需要在FreeRTOSConfig.h
中设置INCLUDE_uxTaskPriorityGet
宏为1
vTaskPrioritySet 任务优先级设置
代码语言:javascript复制void vTaskPrioritySet( TaskHandle_txTask, UBaseType_tuxNewPriority );
注:使用本函数需要在FreeRTOSConfig.h
中设置INCLUDE_vTaskPrioritySet
宏为1
空闲任务
3.8 The Idle Task and the Idle Task Hook There must always be at least one task that can enter the Running state1. To ensure this is the case, an Idle task is automatically created by the scheduler when vTaskStartScheduler() is called. The idle task has the lowest possible priority (priority zero), to ensure it never prevents a higher priority application task from entering the Running state—although there is nothing to prevent application designers creating tasks at, and therefore sharing, the idle task priority, if desired. Running at the lowest priority ensures the Idle task is transitioned out of the Running state as soon as a higher priority task enters the Ready state. Note: If an application uses the vTaskDelete() API function then it is essential that the Idle task is not starved of processing time. This is because the Idle task is responsible for cleaning up kernel resources after a task has been deleted.
总结一下:
- 为了保证系统正常运行,必须有一个空闲任务。
- 空闲任务在vTaskStartScheduler被调用时由系统自动创建。
- 空闲任务为最低优先级0,保证其他任务正常运行。
- 当其他任务调用vTaskDelete时,空闲任务负责处理资源回收。
空闲任务的堆栈大小由configMINIMAL_STACK_SIZE
指定。
空闲任务Hook
Hook一般翻译为钩子,在软件设计中,通常是一个函数指针。
空闲任务Hook的函数原型如下:
代码语言:javascript复制void vApplicationIdleHook( void );
如果需要使用它,需要在FreeRTOSConfig.h
中设置configUSE_IDLE_HOOK
宏的值为1。
运行时,它将在每一个空闲任务周期被调用一次。
由于空闲任务钩子的特殊性,vApplicationIdleHook中不可以调用可能引起空闲任务阻塞的API函数(比如vTaskDelay())。
Common uses for the Idle task hook include: Executing low priority, background, or continuous processing functionality. Measuring the amount of spare processing capacity. (The idle task will run only when all higher priority application tasks have no work to perform; so measuring the amount of processing time allocated to the idle task provides a clear indication of how much processing time is spare.) Placing the processor into a low power mode, providing an easy and automatic method of saving power whenever there is no application processing to be performed (although the power saving that can be achieved using this method is less than can be achieved by using the tick-less idle mode described in Chapter 10, Low Power Support).
通常,使用这个空闲钩子函数来测试性能或者设置CPU进入低功耗模式。
任务堆栈溢出
当系统运行异常时,首先应该要想到是否任务堆栈溢出
比如进入HardFault_Handler查不到错误信息
或者
正常的数据被修改
FreeRTOS可以通过配置configCHECK_FOR_STACK_OVERFLOW
为1或2来检查是否溢出。
configCHECK_FOR_STACK_OVERFLOW = 2
相对configCHECK_FOR_STACK_OVERFLOW = 1
而言,会在创建任务时把堆栈初始化为0xA5:
#define tskSTACK_FILL_BYTE ( 0xa5U )
建议调试时,设置configCHECK_FOR_STACK_OVERFLOW为2。
当然,当堆栈溢出时,可能已经造成了内存错误从而无法检查,所以如果系统进入了HardFault_Handler却查不到错误信息,要想到可能是堆栈出了问题。
测试
我们测试2个任务,进行任务运行,优先级打印,任务暂停,任务恢复,任务删除。
任务优先级max设置为5
vTask1:设置优先级为3
- 运行并打印当前运行优先级
vTask2:设置优先级为2,task2_count=10->0
- task2_count = 6时,修改task1优先级为4
- task2_count = 4时,暂停task1
- task2_count = 2时,恢复task1
- task2_count = 0时,删除task1
- 最后:删除task2
正常测试
主代码:
代码语言:javascript复制TaskHandle_t task1 = NULL;
TaskHandle_t task2 = NULL;
int task2_count = 10;
void vTask1(void *pvParameters)
{
while(1)
{
#ifdef STM32
printf("[STM32] hello, this is freertos!rn");
#endif
#ifdef GD32
printf("[ GD32] vTask1 running, priority = %ld!rn", uxTaskPriorityGet(NULL));
#endif
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vTask2(void *pvParameters)
{
while(task2_count)
{
task2_count--;
printf("[ GD32] vTask2 running of %d!rn", task2_count);
if (task2_count == 6)
{
printf("[ GD32] vTask1 priority %ld -> 4rn", uxTaskPriorityGet(task1));
vTaskPrioritySet(task1, 4);
}
if (task2_count == 4)
{
printf("[ GD32] vTask1 Suspendrn");
vTaskSuspend(task1);
}
if (task2_count == 2)
{
printf("[ GD32] vTask1 Resumern");
vTaskResume(task1);
}
if (task2_count == 0)
{
vTaskDelete(task1);
printf("[ GD32] vTask1 Deletern");
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
printf("[ GD32] vTask2 Deletern");
vTaskDelete(NULL);
}
代码语言:javascript复制 xTaskCreate(vTask1, "Task1", 1024, NULL, 3, &task1);
xTaskCreate(vTask2, "Task2", 1024, NULL, 2, &task2);
运行结果:
测试结果ok
代码语言:javascript复制[ GD32] vTask1 running, priority = 3!
[ GD32] vTask2 running of 9!
[ GD32] vTask1 running, priority = 3!
[ GD32] vTask1 running, priority = 3!
[ GD32] vTask2 running of 8!
[ GD32] vTask1 running, priority = 3!
[ GD32] vTask1 running, priority = 3!
[ GD32] vTask2 running of 7!
[ GD32] vTask1 running, priority = 3!
[ GD32] vTask1 running, priority = 3!
[ GD32] vTask2 running of 6!
[ GD32] vTask1 priority 3 -> 4
[ GD32] vTask1 running, priority = 4!
[ GD32] vTask1 running, priority = 4!
[ GD32] vTask2 running of 5!
[ GD32] vTask1 running, priority = 4!
[ GD32] vTask1 running, priority = 4!
[ GD32] vTask2 running of 4!
[ GD32] vTask1 Suspend
[ GD32] vTask2 running of 3!
[ GD32] vTask2 running of 2!
[ GD32] vTask1 Resume
[ GD32] vTask1 running, priority = 4!
[ GD32] vTask1 running, priority = 4!
[ GD32] vTask1 running, priority = 4!
[ GD32] vTask2 running of 1!
[ GD32] vTask1 running, priority = 4!
[ GD32] vTask1 running, priority = 4!
[ GD32] vTask2 running of 0!
[ GD32] vTask1 Delete
[ GD32] vTask2 Delete
异常测试 - 堆栈溢出
代码:
代码语言:javascript复制 xTaskCreate(vTask1, "Task1", 50, NULL, 3, &task1);
xTaskCreate(vTask2, "Task2", 50, NULL, 2, &task2);
增加溢出检查配置:
代码语言:javascript复制#define configCHECK_FOR_STACK_OVERFLOW 2
增加溢出检查Hook:
代码语言:javascript复制void vApplicationStackOverflowHook( TaskHandle_t xTask, char * pcTaskName )
{
printf("StackOverflow: %srn", pcTaskName);
}
运行结果:
代码语言:javascript复制[ GD32] vTask1 running, priority = 3!
StackOverflow: Task1
[ GD32] vTask2 running of 9!
StackOverflow: Task2
[ GD32] vTask1 running, priority = 3!
StackOverflow: Task1
[ GD32] vTask1 running, priority = 3!
StackOverflow: Task1
[ GD32] vTask2 running of 8!
StackOverflow: Task2
[ GD32] vTask1 running, priority = 3!
StackOverflow: Task1
[ GD32] vTask1 running, priority = 3!
StackOverflow: Task1
[ GD32] vTask2 running of 7!
StackOverflow: Task2
[ GD32] vTask1 running, priority = 3!
StackOverflow: Task1
[ GD32] vTask1 running, priority = 3!
StackOverflow: Task1
[ GD32] vTask2 running of 6!
[ GD32] vTask1 priority 3 -> 4
StackOverflow: Task2
[ GD32] vTask1 running, priority = 4!
StackOverflow: €
[ GD32] vTask1 running, priority = 4!
StackOverflow: €
[ GD32] vTask2 running of 5!
StackOverflow: Task2
[ GD32] vTask1 running, priority = 7!
StackOverflow: €
[ GD32] vTask1 running, priority = 7!
StackOverflow: €
[ GD32] vTask2 running of 4!
[ GD32] vTask1 Suspend
StackOverflow: Task2
[ GD32] vTask2 running of 3!
StackOverflow: Task2
[ GD32] vTask2 running of 2!
[ GD32] vTask1 Resume
StackOverflow: Task2
[ GD32] vTask1 running, priority = 7!
StackOverflow: €
StackOverflow: Task2
[ GD32] vTask1 running, priority = 7!
StackOverflow: €
[ GD32] vTask1 running, priority = 7!
StackOverflow: €
[ GD32] vTask2 running of 1!
StackOverflow: Task2
[ GD32] vTask1 running, priority = 7!
StackOverflow: €
[ GD32] vTask1 running, priority = 7!
StackOverflow: €
[ GD32] vTask2 running of 0!
[ GD32] vTask1 Delete
StackOverflow: Task2
[ GD32] vTask2 Delete
StackOverflow: Task2
参考资料
[1]FreeRTOS官方文档: https://www.freertos.org/Documentation/RTOS_book.html