FreeRTOS——利用阻塞态实现延迟
调度器总是选择具有最高优先级的可运行任务来执行。
一个事件驱动任务只会在事件发生后触发工作(处理),而在事件没有发生时是不能进入运行态的。
调度器总是选择所有 能够进入运行态的任务中具有最高优先级的任务。
一个高优先级但不能够运行的任务意味着不会被调度器选中,
而代之以另一个优先级虽然更低但能够运行的任务。
因此,采用事件驱动任务的意义就在于任务可以被创建在许多不同的优先级上,
并且最高优先级任务不会把所有的低优先级任务饿死。
如果一个任务正在等待某个事件,则称这个任务处于”阻塞态(blocked)”。
阻塞态是非运行态的一个子状态。
任务可以进入阻塞态以等待以下两种不同类型的事件:
1. 定时(时间相关)事件——这类事件可以是延迟到期或是绝对时间到点。比如说某个任务可以进入阻塞态以延迟 10ms。
2. 同步事件——源于其它任务或中断的事件。比如说,某个任务可以进入阻塞态以等待队列中有数据到来。同步事件囊括了所有板级范围内的事件类型。
挂起状态
“挂起(suspended)”也是非运行状态的子状态。处于挂起状态的任务对调度器而言是不可见的。
让一个任务进入挂起状态的唯一办法就是调用 vTaskSuspend() API 函数;
而 把 一 个 挂 起 状 态 的 任 务 唤 醒 的 唯 一 途 径 就 是 调 用 vTaskResume()
或vTaskResumeFromISR() API 函数。大多数应用程序中都不会用到挂起状态。
void vTaskDelay( portTickType xTicksToDelay );
图 4 中红色的线段表时内核本身在运行。黑色箭头表示任务到中断,中断再到另一个任务的执行顺序。
空闲任务是在调度器启动时自动创建的,以保证至少有一个任务可运行(至少有一个任务处于就绪态)。
改变了两个任务的实现方式,并没有改变其功能。对比 图 9 与 图 4 可以清晰地看到(图9)中以更有效的方式实现了任务的功能。
图 4 展现的是当任务采用空循环进行延迟时的执行流程——结果就是任务总是可运行并占用了大量的机器周期。
从图 9 中的执行流程中可以看到,任务在整个延迟周期内都处于阻塞态,只在完成实际工作的时候才占用处理器时间。
在图 9 所示的情形中,任务离开阻塞态后,仅仅执行了一个心跳周期的一个片段,然后又再次进入阻塞态。所以大多数时间都没有一个应用任务可运行(即没有应用任务处于就绪态),因此没有应用任务可以被选择进入运行态。这种情况下,空闲任务得以执行。空间任务可以获得的执行时间量,是系统处理能力裕量的一个度量指标。
状态转移过程
vTaskDelayUntil()类似于 vTaskDelay()。函数 vTaskDelay()的参数用来指定任务在调用 vTaskDelay()到切出阻塞态整个过程包含多少个心跳周期。任务保持在阻塞态的时间量由 vTaskDelay()的入口参数指定,但任务离开阻塞态的时刻实际上是相对于 vTaskDelay()被调用那一刻的。vTaskDelayUntil()的参数就是用来指定任务离开阻塞态进入就绪态那一刻的精确心跳计数值。API 函数 vTaskDelayUntil()可以用于实现一个固定执行周期的需求(当你需要让你的任务以固定频率周期性执行的时候)。由于调用此函数的任务解除阻塞的时间是绝对时刻,比起相对于调用时刻的相对时间更精确(即比调用 vTaskDelay()可以实现更精确的周期性)。
vTaskDelayUntil函数原型:
void vTaskDelayUntil( portTickType * pxPreviousWakeTime, portTickType xTimeIncrement );
欢迎加入作者的小圈子
扫描下方左边二维码加入QQ交流群,扫描下方右边二维码关注个人微信公众号并,获取更多隐藏干货,QQ交流群:859800032 微信公众号:Crystal软件学堂
作者:Liu_Jing bilibili视频教程地址:https://space.bilibili.com/5782182 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在转载文章页面给出原文连接。 如果你觉得文章对你有所帮助,烦请点个推荐,你的支持是我更文的动力。 文中若有错误,请您务必指出,感谢给予我建议并让我提高的你。 |