FreeRTOS学习笔记3-任务的几种状态及转换
如果将所有任务都设置在相同的优先级,那么在系统跑起来之后,所有任务将进行轮盘式的调度。如果任务仅仅具有不同的优先级而没有经过其他处理时。高优先级的任务将一直重复运行,将低优先级的任务“饿死(starved))”。
事件驱动:
为了使我们的任务切实有用,我们需要通过某种方式来进行事件驱动。一个事件驱动任务只会在事件发生后触发工作(处理),而在事件没有发生时是不能进入运行态的。调度器总是选择所有能够进入运行态的任务中具有最高优先级的任务。一个高优先级但不能够运行的任务意味着不会被调度器选中,而代之以另一个优先级虽然更低但能够运行的任务。因此,采用事件驱动任务的意义就在于任务可以被创建在许多不同的优先级上,并且最高优先级任务不会把所有的低优先级任务饿死。
任务有两种大的运行状态:运行态和非运行态。非运行态有较多类型,以下一一解释:
1 阻塞态 blocked:
任务可以进入阻塞态以等待以下两种不同类型的事件:
1. 定时(时间相关)事件——这类事件可以是延迟到期或是绝对时间到点。比如说某个任务可以进入阻塞态以延迟10ms。
2. 同步事件——源于其它任务或中断的事件。比如说,某个任务可以进入阻塞态以等待队列中有数据到来。同步事件囊括了所有板级范围内的事件类型。
任务可以在进入阻塞态以等待同步事件时指定一个等待超时时间,这样可以有效地实现阻塞状态下同时等待两种类型的事件。
我们可以利用阻塞态进行相应的延时操作提高系统的效率,原来使用for循环或者while循环进行延时,处理器进行的都是很多无用的操作。使用阻塞态进行延时时,处理器可以处理其他任务。从而提高了系统的效率。调用vTaskDelay() API 函数来代替空循环即可。
void vTaskDelay( portTickType xTicksToDelay );
xTicksToDelay 延迟多少个心跳周期。调用该延迟函数的任务将进入阻塞态,经延迟指定的心跳周期数后,再转移到就绪态。
举个例子,当某个任务调用vTaskDelay( 100 )时,心跳计数值为10,000,则该任务将保持在阻塞态,直到心跳计数计到10,100。
常数 portTICK_RATE_MS 可以用来将以毫秒为单位的时间值转换为以心跳周期为单位的时间值。
2 挂起状态 (suspended))
。让一个任务进入挂起状态的唯一办法就是调用vTaskSuspend() API 函数;而把一个挂起状态的任务唤醒的唯一途径就是调用vTaskResume() 或vTaskResumeFromISR() API 函数。大部分应用程序都不需要进入挂起状态。不在详细赘述。
3 就绪状态 (ready)
任务处于非运行状态,但既没有阻塞也没有挂起,则这个任务处于就绪(ready,准备或就绪)状态。处于就绪态的任务能够被运行,但是没有运行。当前状态的任务已经具有了所有可执行的条件,只等待处理器将他进行调度。
其他延时函数:
vTaskDelayUntil() API 函数的参数就是用来指定任务离开阻塞态进入就绪态那一刻的精确心跳计数值。API 函数vTaskDelayUntil()可以用于实现一个固定执行周期的需求(当你需要让你的任务以固定频率周期性执行的时候)。由于调用此函数的任务解除阻塞的时间是绝对时刻,比起相对于调用时刻的相对时间更精确(即比调用vTaskDelay()可以实现更精确的周期性)。