——任务状态和调度器运行过程)
上一节创建了两个LED任务并通过vTaskStartScheduler()启动了FreeRTOS任务调度器。程序运行后两个LED可以按照不同的周期闪烁。但是单片机只有一个CPU内核两个任务是怎样运行的为什么一个任务延时的时候另一个任务还可以继续执行这一节学习FreeRTOS任务的基本状态以及任务调度器的运行过程。一、什么是任务调度器任务创建完成后任务并不会立即自动运行还需要调用vTaskStartScheduler();启动FreeRTOS任务调度器。任务调度器的作用是判断当前有哪些任务可以运行并从中选择一个任务交给CPU执行。例如上一节创建了两个任务xTaskCreate(task1, task1, 128, NULL, 1, NULL); xTaskCreate(task2, task2, 128, NULL, 1, NULL);当调度器启动后task1和task2进入FreeRTOS的任务调度过程。调度器会根据任务的优先级和当前状态决定CPU接下来执行哪个任务。二、FreeRTOS任务的四种基本状态FreeRTOS中的任务主要有四种基本状态运行态就绪态阻塞态挂起态。任务在运行过程中会根据程序执行情况在这些状态之间进行转换。1. 运行态运行态表示任务当前正在占用CPU执行。对于单核STM32F407来说同一时刻只能有一个任务处于运行态。例如CPU正在执行GPIO_ResetBits(GPIOE, GPIO_Pin_3);此时执行这条语句的任务就是运行态。虽然程序中创建了多个任务但是单核CPU不能在同一时刻真正执行多个任务只能通过快速切换形成多个任务同时运行的效果。2. 就绪态就绪态表示任务已经具备运行条件但是当前没有获得CPU的使用权。例如task1和task2都可以运行但CPU当前正在执行task1那么task2就可能处于就绪态。处于就绪态的任务并没有停止它只是在等待调度器将CPU分配给它。当当前运行任务退出运行态后调度器会从就绪任务中重新选择一个任务运行。3. 阻塞态阻塞态表示任务正在等待某个条件在条件满足之前暂时不参与CPU竞争。在上一节的LED任务中使用了vTaskDelay(pdMS_TO_TICKS(300));执行这条语句以后当前任务会进入阻塞态。在300ms延时时间结束之前这个任务不会继续运行也不会占用CPU。这时调度器就可以安排其他已经就绪的任务运行。除了延时以外任务等待队列、信号量、事件或者任务通知时也可能进入阻塞态。4. 挂起态挂起态表示任务被人为暂停运行。任务可以通过下面的函数进入挂起状态vTaskSuspend();被挂起的任务不会继续参与调度即使等待很长时间也不会自动恢复。必须调用vTaskResume();才能使任务重新进入就绪态。挂起和恢复任务将在后面的文章中单独学习这一节先理解挂起态的基本含义。三、LED任务的状态变化继续观察上一节的任务1void task1(void *pvParameters) { while(1) { vTaskDelay(pdMS_TO_TICKS(300)); GPIO_ResetBits(GPIOE, GPIO_Pin_3); vTaskDelay(pdMS_TO_TICKS(300)); GPIO_SetBits(GPIOE, GPIO_Pin_3); } }当调度器选择task1运行后任务首先执行vTaskDelay(pdMS_TO_TICKS(300));此时task1由运行态进入阻塞态等待300ms。因为task1已经进入阻塞态不能继续占用CPU所以调度器会寻找其他处于就绪态的任务。如果task2处于就绪态CPU就会开始执行task2。300ms到达后task1的阻塞条件满足它会由阻塞态进入就绪态。需要注意的是进入就绪态并不代表任务一定会立即运行而是表示它已经具备运行条件正在等待调度器分配CPU。当调度器再次选择task1时它才会从上次停止的位置继续向下执行GPIO_ResetBits(GPIOE, GPIO_Pin_3);然后再次调用vTaskDelay()进入阻塞态。因此任务执行vTaskDelay()以后并不是CPU停在那里等待而是任务主动让出CPU。四、两个LED任务的调度过程上一节的两个任务优先级相同都设置为1。程序启动后一个可能的运行过程如下首先调度器选择任务1运行任务1调用vTaskDelay()进入阻塞态调度器切换到任务2运行任务2调用vTaskDelay()也进入阻塞态当两个LED任务都处于阻塞态时CPU会运行FreeRTOS的空闲任务任务1的延时时间到达后任务1进入就绪态调度器再次选择任务1运行任务2的延时时间到达后任务2也会重新进入就绪态。任务就在运行态、就绪态和阻塞态之间不断切换从而实现两个LED按照不同的周期闪烁。五、为什么任务函数要使用while(1)FreeRTOS任务函数通常会写成void task1(void *pvParameters) { while(1) { /* 任务执行内容 */ } }这是因为任务一般需要持续运行而不是只执行一次。如果不使用while(1)任务函数执行到结尾以后直接返回就会破坏FreeRTOS任务的正常运行过程。因此FreeRTOS任务通常有两种结束方式一种是在while(1)中一直循环运行另一种是在任务内部调用vTaskDelete(NULL);主动删除当前任务。不能像普通C语言函数一样直接执行到函数结尾后返回。六、多任务并不等于真正同时运行STM32F407采用Cortex-M4内核属于单核处理器。所以在任意一个时刻只能有一个任务真正占用CPU运行。FreeRTOS通过任务切换使CPU快速地在多个任务之间切换。由于切换速度很快从外部观察时就像多个任务同时执行一样。这种运行方式称为并发运行。如果处理器具有多个CPU内核并且多个任务分别运行在不同内核上才属于真正意义上的并行运行。对于我们当前使用的STM32F407可以理解为任务1执行一段时间任务1阻塞或者被切换任务2继续执行调度器不断重复这个过程。七、总结这一节学习了FreeRTOS任务的四种基本状态运行态表示任务正在占用CPU就绪态表示任务已经可以运行正在等待CPU阻塞态表示任务正在等待延时或者某个事件挂起态表示任务被人为暂停。在LED点灯程序中任务执行vTaskDelay()后会进入阻塞态并主动让出CPU。调度器随后选择其他处于就绪态的任务运行。因此FreeRTOS多任务程序并不是让CPU停在延时函数中等待而是利用任务状态切换提高CPU的使用效率。下一节继续学习vTaskDelay()和vTaskDelayUntil()的区别以及怎样让任务按照更加准确的固定周期运行。