实时性迷思(1) —— “快是优点么?”
扫描二维码
随时随地手机看文章
【序】
不知道你发现没有,平时我们讨论嵌入式软件开发时总绕不开与实时性( Real Time)相关的话题。相信不少朋友和我一样是通过实时性操作系统( R eal Time Operating System, RTOS)第一次接触到实时性概念的——我记得那还是大学时代、参加机器人竞赛的时候。工作以后自信地以为加深了不少对实时性的本质认识——现在看来其实还未摸到门道。就这样浑浑噩噩一直到毕业后的第八年,因为工作变动的原因,我被迫要在一周内要做一个实时性原理相关的研究报告,也就在那时,我体会到了疯狂练功走火入魔的感觉:走路在思考、吃饭在看资料、头一直发烧一样的微微发热、甚至连睡觉都在梦中推演模型——头发一把一把的掉,幸好有截稿时间,否则真的要秃了。
也就是经过那一次,我突然发现自己之前对实时性的认知可谓徒有其表,甚至从未做对实时性模型本身的定量分析——所幸,那次研究报告如期交付,工作变动也如愿以偿。然而,3年后我发现“我又双叕天真了”——那是有一次,我正跟人讨论嵌入式基本范式,就突然一个瞬间,脑海中原本毫不相关的一些模型猛地被联系到了一起(音效请脑补):
我甚至本能的立即意识到:之前自己在某篇文章中“言之凿凿”的推论过程其实存在巨大漏洞——当然,那本书从未出版过,而且会闲到对我进行深究的人估计也没有几个。
今天,即便我非常确信——在前方至少还有几道数学的深谷阻碍着我触碰“实时性”的圣杯——然而我并不是计算机科学家,现有结论对我来说已经足够 装逼 。回头看来,根据我的经验以及与朋友讨论的结果,大致认为大部分人对实时性的认知过程通常会分以下几个阶段:-
Lv1:“实时性” = “越快越好”,认为用好中断是保证实时性的关键;这类朋友通常最擅长的是裸机下的“前后台系统”;
-
Lv2:“实时性” = RTOS,认为选一个好的RTOS,或者会用RTOS就可以保证实时性;这一阶段的朋友对RTOS充满了好奇,以编写自己的RTOS为“ 终(zhong)极(er)目标”;
-
Lv3:“实时性” = 任务拆分,这一阶段已经能正确的理解实时性窗口的概念,意识到实时性并不意味着越快越好,但也认为“在可能的情况下”“快一点响应事件没啥坏处”;这一阶段的朋友可能已经可以在裸机和RTOS之间自由的反复横跳,无论是裸机下的状态机还是RTOS下的线程都已了如指掌、任务间通信更是游刃有余;
-
Lv4:这一阶段开始思考实时性模型的特点,并逐渐意识到模型本身其实隐含了足以颠覆过往所有关于实时性认知的秘密;到达这一阶段的朋友通常觉得没必要、也没心思继续思考实时性更本质的数学意义——因为此时获得的结论已经足够了应付几乎所有的工程开发了。顺便说一下,我就在这里。
-
Lv5:到了这个阶段,不仅脑洞大开、战斗力惊人、估计打针也没法阻止你抓破脖子了吧——以上只是暴露年龄的玩笑,但肯定可以水几篇SCI论文了……
【击碎 “唯快不破” 的神话】
一个标准的实时性模型:-
基于物理世界客观法则的限制,很多应用在制定需求说明的时候,从某一个事件发生的时刻计算,会规定一个死线(Dead Line),即:一旦事件发生了,如果不在这个死线之前完成整个对事件的处理,就视作失败;
-
这里,从事件发生到死线这段时间长度,习惯上称为实时性窗口。当事件发生时,只有在死线内任意时刻完成了对事件的处理,才能称为实时性得到了满足;
-
容易注意到,处理事件的过程也需要消耗时间——一般称为事件处理时间;
你说“我不管,我不管”, 既然什么时候做都一样为什么不能“尽早做”? “ 你也说了尽早做没啥不好 ”,“ 中断来了,服务程序执行了,我想让它迟点执行也做不到啊? ”
为了回答这个问题,我们不讲大道理,先看一个常见的例子:
-
超级循环里有三个任务A、B和C;
void main(void){ ... while(1) { task_a(); task_b(); task_c(); }}
-
每个任务都使用轮询的方式在等待一个来自芯片外界的事件发生(先不考虑存在中断的情况);
-
当一个任务函数被执行时会检查对应的事件是否已经发生,如果确实已经发生,则执行后续的处理;反之则立即退出任务函数——释放处理器;
-
A、B、C三个事件的实时性窗口分别为10ms, 6ms和4ms;处理三个事件的处理程序分别需要4ms、3ms和0.4ms。
-
需要强调的是,task_a()、task_b()和task_c()三个函数的策略本质上都是一样的——“一旦检测到事件立即处理,绝不迟延”!
基于上述事实,容易发现:假如某一时刻,A、B、C三个函数都处于触发状态(等待处理的状态),而超级循环恰巧进入task_a()执行——这种情况其实比想象中容易发生,比如从task_a()退出到task_c()执行完成期间,事件A触发了;从task_b()退出到task_c()执行完成期间,事件B触发了;在task_c退出()之后恰巧事件C又触发了……此时,任务A会立即响应,消耗4ms的时间来完成事件处理;当从task_a()函数退出时,剩余给task_b()的时间窗口只有2ms(6ms-4ms),而事件B的处理函数需要3ms——显然事件B的实时性是无法得到保证的——当然事件C已经死得透透了……
-
当你使用“越快越好”策略时,你不会有额外的收益,而实际上是走了别人的路,让人无路可走——典型的损人不利己;
-
当你在别人需要的时候,在自己实时性得到保证的前提下,尽可能让出对你没有额外价值的靠前的时间,实际上是一种“利他主义”;
-
当所有的任务都采用这种利他策略时,就变成了“人人为我,我为人人”的合作策略——这种情况下,如果数学证明整个系统一定存在一个方案来满足所有任务的实时性需求,那么利他策略一定能找到这样的解决方案。
作为一个系统开发者,我们显然是需要从全局考虑的,因此完全没有必要从单个实时性任务的自私视角来看问题,因此结论就变得更为直接: 实时性窗口内越靠前的时间价值越高,从总体上来看“单纯”越快越好的策略对实时性是有害的。
既然单纯的“越快越好”不可取,且“实时性窗口内”越靠前的时间越有价值,是否意味着,其实“越靠后越好呢”?
-
每一个事件处理任务都清楚的知道“距离事件发生已经过去了多长时间”;
-
为了做到“卡着上课铃进教室”,不到最后时刻,绝对不执行任务处理。
根据这一算法,我们推演得到以下的尴尬情形:
不妨分析下过程:首先,task_a()执行,在了解到距离自己的最后时刻还有6ms的实时后毅然的决定把宝贵的时间留给他人;于是,CPU来到了下一个任务函数,基于类似的原因,task_b()也摆摆手……最终第一轮三个任务都决定再等一等……
如此谦让(浪费)了3ms以后,任务B终于决定下场——在执行了3ms任务处理后,成功的将随后的任务C逼上了绝路……随着A的沦陷,大型翻车现场成就达成……
从结论上看,另外一个极端“ 越慢越好”也是走不通的 。那么究竟如何才能从模型分析的角度出发得出一个令人信服的、容易理解的、满足所有任务实时性需求的方法呢?关于这一点,我们下次再聊。
【小结】
从系统全局来看,实时性窗口内的时间越靠前越有价值,应该尽可能留给别的更紧急的任务来使用。事件发生时 “越快处理越好”的策略直接占用了它人的“生命线”——当所有的任务都试图“损人不利己”时,那么整个系统没有一个任务是可以保证自己的实时性不被它人破坏的。 从结论上看简单的“越快越好”策略在实时性系统中是不允许的。
免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!