↑点击上方蓝色字体,关注“嵌入式软件实战派”获得更多精品干货。
“师兄,不好意思,又打扰你一下。”
此时的我刚解了一个bug,正闭目养神缓解一下疲惫的精神。听到妹子在叫我,忽然又精神百倍了。
“没事,你说。”
“自从你上次给我讲解了RTOS的一些概念原理,我没那么困惑了,我也查阅了很多资料,但是我还有些关于'Task'的疑问……”
“找个时间我再给你详细讲解下吧。”热情的我没等她说完我就答应她了。
于是,我约她在晚上……我当时又恰恰只有晚上比较有空(我真不是故意的)。
顺着上次没讲完的问题点(见《妹子要我教她学RTOS》),这次打算给她讲个透彻。因为我们项目用了FreeRTOS,以下内容也是基于FreeRTOS做讲解。
多任务概念
程序看起来像同时运行的
实际上程序是分时间片运行的
任务调度
在1处Task 1在运行,运行到2处,被系统挂起了;
在第3处,系统切换到Task 2,第4处在运行;
第5处,Task 2被挂起,同时第6处是恢复Task 1运行;
第7处,挂起Task 1,在第8处切换到Task 3运行;
以此类推,第9/10处分别是Task 2和Task 3在运行。
以上提到了个保存Context和恢复Context, 这个是怎么实现的呢?
我从FreeRTOS.org官网找到了个例子(针对AVR的),不同内核的IC实现还不一样,和具体的寄存器组有关,和编译环境有关。另外,这两个函数运行要求越快越好,这个会影响系统任务切换的性能,所以,这俩函数一般都是用汇编实现的。
对于这个例子的代码实现我在这里就不讲解了(我也没给妹子讲解这些细节,对于RTOS入门可以暂时跳过这个内容),有兴趣的可以参考FreeRTOS官网,里面有解释。
任务状态
当任务处于运行状态时,处理器将执行任务的代码。 当任务处于“未运行”状态时,该任务处于休眠状态,其状态已保存,准备好在计划程序下次决定进入“运行”状态时恢复执行。 当任务恢复执行时,它会从上一次离开运行状态之前要执行的指令开始执行。
FreeRTOS.org
但是呢,“幸福总是相似的,而不幸却又各不一样。”Running只有一个,而Not Running却又各不相同。就FreeRTOS来说,Not Running分好几种状态:
以下根据FreeRTOS官网内容解释下:
Running
实际执行任务时,据说它处于Running状态。当前正在使用处理器。如果运行RTOS的处理器只有一个内核,则在任何给定时间只能有一个任务处于“运行”状态。
Ready
Ready任务是那些能够执行的任务(它们不处于Blocked或Suspended状态),但当前却不执行,因为具有相同或更高优先级的其他任务已经处于Running状态。
Blocked
如果任务当前正在等待临时事件或外部事件,则称该任务处于Blocked状态。例如,如果任务调用vTaskDelay(),它将一直阻塞(置于Blocked状态),直到延迟时间到期为止–这是一个时间事件。任务也可以阻塞以等待队列,信号量,事件组,通知或信号量事件。处于Blocked状态的任务通常有一个“超时”时间段,此后该任务将超时并被解除阻止,即使未发生任务正在等待的事件也是如此。
处于Blocked状态的任务不使用任何处理时间,并且不能选择进入Running状态。
Suspended
与处于Blocked状态的任务一样,处于Suspended状态的任务无法选择进入Running状态,但是处于Suspended状态的任务没有超时。相反,只有在分别通过vTaskSuspend()和xTaskResume()API调用明确命令这样做时,任务才进入或退出Suspended状态。
怎么实现几个task轮流执行呢,直接给他们写个死循环行不行。
void task1(void)
{
while(1)
{
// doing something
}
}
void task2(void)
{
while(1)
{
// doing something
}
}
这个问题,我让妹子想了好一会。她犹犹豫豫回答说不行。然后又向我投来期待的眼神。
我不慌不忙地给她说,确实不行,因为优先级高的,根本就没给机会优先级低的任务运行。那么,怎么做呢?
void task1(void)
{
while(1)
{
// doing something
vTaskDelay(10);
}
}
void task2(void)
{
while(1)
{
// doing something
vTaskDelay(10);
}
}
最简单直接的办法,就是给每个Task一个delay,即让其没运行一次循环就睡一会,把机会留给其他任务。但是,这里有点死板,每次都要等10(ms)?能不能有事情就执行,没事情就休息呢?
答案是有的。这要用到信号量或者事件组这些东西了。在此暂不展开讨论,怕扯太远了,妹子会一路问下去。
讲到这里,妹子基本上理解我说的“是”的回答的含义了……但是这上面又提到了很多新概念,如信号量、队列等等。也许我只能再约她在另外一个晚上了(讲解RTOS概念)。
任务优先级
任务间通信
一般情况下,RTOS都会提供一个叫mailbox的东西给两个Task之间通信,可以简单理解为,异地恋双方通过写信交流……(呃,虽然我好想打探妹子有没有男朋友,但是这个比喻我只能生硬地讲到这了。)
引用FreeRTOS官网里面的图来解释:
很明显,FreeRTOS的这个mailbox实际上就是个队列(First In First Out, FIFO),此处我没跟妹子讲解这个结构,而推荐了个文章《图解栈(Stack)与队列(Queue)》给她看。
她问,“不用mailbox,还有没有其他方法,例如直接共享全局变量,如果两个Task传输大量数据,mailbox不是很好吧……”
妹子总是这么好学,我又给她讲解了处理mailbox以外的方式。例如她提的全局变量。我笑着点了点头,但她不是很满意,似乎觉得过于简单了。因为,我们之前讲到过,Context切换时有备份的,但这个全局变量,是没有备份的。假如两个Task都同时更改这全局变量,会发生什么?假如一个Task正在更改变量,另一个Task却正在读取这个变量又会怎样?
我很想给她做以下这个比喻,但是始终没有。
“我跟你在讲解RTOS的知识,有时候需要用笔在纸上画一些图示来说明问题,你也会用笔来把问题指出来。如果只有一支笔,在讨论激烈的时候,我们可能会同时伸手去拿这支笔,然后……”
我很想出这个bug,可项目不能出啊。言归正传,以上这样做是会出问题的,而且问题会变得很复杂,bug也不好找。
怎么办?加锁啊。一个task在用这个变量的时候,另一个task就只能静静地待着。RTOS有锁?锁是啥东西?你姑且认为信号量可以实现这个锁,后续再详细讨论。好了,先给你安利一个文章《我在马路上遇到一个死锁问题》。
空任务
“上善若水。水善利万物而不争,处众人之所恶, 故几于道。” 老子
我又怕装逼装过头了,慢慢地跟她解释这个IDLE Task,它的优先级是最低的,只有其他task不运行的时候,它才运行。
但是,你知不知道,这个IDLE Task在系统运行中,运行时间或次数最多的,正所谓“色即是空,空即是色”……
往期精彩内容推荐>>>
妹子要我教她学RTOS
我在马路上遇到一个死锁问题