上一文链接:FreeRTOS笔记(四)初识任务
01 - 任务的状态
任务被创建后,它可能正在运行,可能暂停运行,任务有状态之分是由于调度器的存在,调度器需要决定哪些任务可以去运行,于是在FreeRTOS中任务具有4种状态,分别是就绪态、运行态、阻塞态和挂起态,它们之间的转化关系如下:
4个状态的含义如下:
就绪态:已经可以运行,等待调度器的切入
运行态:正在占用CPU运行
阻塞态:等待某个事件的到来,定时或者同步
挂起态:退出调度系统,调度器不可见,只能使用vTaskSuspend()挂起和vTaskResume()唤醒后进入就绪态
虽然官方文档只描述4个状态,但小白认为应该是5种状态,第5种是僵尸态,指在任务被删除后,其TCB控制块扔保留一段时间,等待内核检查和回收资源,在内核没有处理之前,任务其实并没有被完全删除,但是再也不能被调度器调度,这称为僵尸态,在Linux下的进程是存在僵尸态的,而从FreeRTOS的API中就可以看出,FreeRTOS的任务也存在僵尸态。
要使用eTaskGetState()
函数,就要在FreeRTOSConfig.h中配置INCLUDE_eTaskGetState
宏为1
#define INCLUDE_eTaskGetState 1
02 - tick时钟和调度器
调度器本身也是一段程序,任务需要调度器安排执行顺序,那么调度器本身就需要被执行,这个执行由一个称为心跳时钟(tick)的中断触发,tick时钟的频率需要在FreeRTOSConfig.h文件中配置,单位是HZ,比如本例程中配置configTICK_RATE_HZ
为1000,那么中断频时间是1000/1000 = 1ms
,每隔1ms,FreeRTOS就会进入tick中断,触发调度器进行工作。
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
调度器被触发后,会根据事先设定好的调度算法进行工作,FreeRTOS使用的调度算法有优先级抢占式调度和协作式调度:
优先级抢占式调度算法,给每一个任务分配一个优先级,调度器每次都选择优先级最高的任务执行,如果优先级相同,就采用时间片轮流执行,每个任务执行一个时间片后切出,再切入下一个任务。
协作式调度算法,只可能在运行态任务进入阻塞态或是运行态任务显式调用taskYIELD()
主动让出CPU时,才会进行上下文切换。
调度算法的配置也是在FreeRTOSConfig.h中进行。
#define configUSE_PREEMPTION 1 //0-协作调度,1-抢占式调度
03 - 任务状态测试
下面用优先级抢占式调度算法来测试一下任务的各种状态,其中挂起和唤醒的API如下:
项目 | Value |
---|---|
vTaskSuspend() | 挂起一个任务 |
vTaskResume() | 唤醒一个任务 |
测试内容:一共有5个任务,分别是start和ABCD,start优先级最高,其余优先级递增,任务的工作如下:
start:负责创建另外4个任务,输出所有任务的状态,然后删除自己
A:输出所有任务的状态,打开LED
B:输出所有任务的状态,关闭LED,使用vTaskDelay()
阻塞自己
C:输出所有任务的状态使用vTaskDelay()
阻塞自己,然后间隔使用vTaskResume
唤醒D
D:输出所有任务的状态,使用vTaskSuspend()
挂起自己
/* start 任务 */
void start_task(void *pParam)
{
//进入临界区
taskENTER_CRITICAL();
xTaskCreate( (TaskFunction_t)a_task,
(const char*)"a_task",
(uint16_t)A_STACK_SIZE,
(void*)NULL,
(UBaseType_t)A_TASK_PRIO,
(TaskHandle_t*)&aTask_Handler
);
xTaskCreate( (TaskFunction_t)b_task,
(const char*)"b_task",
(uint16_t)B_STACK_SIZE,
(void*)NULL,
(UBaseType_t)B_TASK_PRIO,
(TaskHandle_t*)&bTask_Handler
);
xTaskCreate( (TaskFunction_t)c_task,
(const char*)"c_task",
(uint16_t)C_STACK_SIZE,
(void*)NULL,
(UBaseType_t)C_TASK_PRIO,
(TaskHandle_t*)&cTask_Handler
);
xTaskCreate( (TaskFunction_t)d_task,
(const char*)"d_task",
(uint16_t)D_STACK_SIZE,
(void*)NULL,
(UBaseType_t)D_TASK_PRIO,
(TaskHandle_t*)&dTask_Handler
);
print_state("start",eTaskGetState(StartTask_Handler));
print_state("A",eTaskGetState(aTask_Handler));
print_state("B",eTaskGetState(bTask_Handler));
print_state("C",eTaskGetState(cTask_Handler));
print_state("D",eTaskGetState(dTask_Handler));
printf("\r\n");
//退出临界区
taskEXIT_CRITICAL();
//删除自己
vTaskDelete(NULL);
}
/* A 任务 */
void a_task(void *pParam)
{
for(;;)
{
print_all_state("A");
//打开LED
GPIO_ResetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);
}
}
/* B 任务 */
void b_task(void *pParam)
{
for(;;)
{
//输出
print_all_state("B");
//关闭LED
GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);
//阻塞自己
vTaskDelay(10);
}
}
/* C 任务 */
void c_task(void *pParam)
{
for(;;)
{
//输出
print_all_state("C");
//阻塞自己
vTaskDelay(10);
//唤醒D
vTaskResume(dTask_Handler);
}
}
/* D 任务 */
void d_task(void *pParam)
{
for(;;)
{
//输出
print_all_state("D");
//挂起自己
vTaskSuspend(NULL);
}
}
其中print_all_state()和print_state()是为了方便输出状态,代码如下:
static void print_state(char *msg,eTaskState state)
{
printf("%s-",msg);
switch(state)
{
case eRunning: printf("run");break;
case eReady: printf("ready");break;
case eBlocked: printf("block");break;
case eSuspended:printf("suspend");break;
case eDeleted: printf("delete");break;
default: printf("5");break;
}
printf(" ");
}
static void print_all_state(char *msg)
{
taskENTER_CRITICAL();
printf("%s:",msg);
print_state("start",eTaskGetState(StartTask_Handler));
print_state("A",eTaskGetState(aTask_Handler));
print_state("B",eTaskGetState(bTask_Handler));
print_state("C",eTaskGetState(cTask_Handler));
print_state("D",eTaskGetState(dTask_Handler));
printf("\r\n");
taskEXIT_CRITICAL();
}
运行结果
因为延迟太短,看不到LED的闪烁,但是从串口可以看出,各个任务有序运行,每个状态都出现了,状态之间的转换几乎在一瞬间,运行时序图可以简述如下:
04 - 总结
- 任务有4种(5种)状态,分别为就绪态、运行态、阻塞态、挂起态(僵尸态)
- 调度器程序在每个tick时钟中断里被执行
- FreeRTOS调度器有两种调度算法,抢占式和协作式