在实时操作系统里随便写代码都能硬实时吗?
這是轉(zhuǎn)發(fā)宋老師寫(xiě)的文章,我也是剛知道,宋老師跟我一樣也是養(yǎng)娃的人了,國(guó)慶期間,看看文章,看看升升國(guó)旗。
很久沒(méi)有寫(xiě)技術(shù)文章了,做碼農(nóng)難,做養(yǎng)娃的碼農(nóng)更難,趁著娃看動(dòng)畫(huà)片的機(jī)會(huì),受著王菲童鞋《我和我的祖國(guó)》歌唱精神的鼓舞,我要來(lái)說(shuō)幾句。
硬實(shí)時(shí)是什么?
眾所周知,硬實(shí)時(shí)的概念不是越快越好,而是強(qiáng)調(diào)可重復(fù)的(repeatable)、決定性的時(shí)間期限內(nèi)給予響應(yīng)(deterministic response time)。所以它的本質(zhì)點(diǎn)是可預(yù)期,實(shí)時(shí)系統(tǒng)的計(jì)算正確性不僅取決于計(jì)算的邏輯正確性,還取決于產(chǎn)生結(jié)果的時(shí)間。比如,汽車(chē)碰撞后,必須在X時(shí)間內(nèi)彈開(kāi)安全氣囊,你彈開(kāi)晚了,人已經(jīng)掛了。
當(dāng)然最?lèi)毫訄?chǎng)景下的延遲,可能作為一個(gè)時(shí)間參數(shù),來(lái)評(píng)估RTOS本身的性能指標(biāo)。比如你改造了Linux,實(shí)現(xiàn)了中斷或高優(yōu)先級(jí)任務(wù)的100us以內(nèi)的確定性延遲;但是RT-Thread,可能不改造,就可以到達(dá)us以內(nèi)的延遲。那么,你延遲要求只需要200us以內(nèi)的,你選改造后的Linux也沒(méi)有什么毛病,但是如果你要的就是us級(jí),那么對(duì)不起,Linux不是你的菜。
眾所周知,RT-Thread、FreeRTOS、VxWorks這樣的操作系統(tǒng)是硬實(shí)時(shí)的;Linux這樣的操作系統(tǒng)是提供軟實(shí)時(shí)能力的,針對(duì)的 miss 掉截止期限也死不了人的那種應(yīng)用,比如看電影。
那么,這個(gè)時(shí)候我們誕生了一個(gè)疑問(wèn),是不是在RTOS里面隨便寫(xiě)代碼都能滿足硬實(shí)時(shí),而在Linux里面無(wú)論怎么寫(xiě)代碼都滿足不了硬實(shí)時(shí)?我認(rèn)為這2個(gè)問(wèn)題的答案都是否定的。
Linux為什么不硬實(shí)時(shí)?
我們首先看一下,Linux為什么不能提供硬實(shí)時(shí)能力。我們認(rèn)為L(zhǎng)inux主要有如下問(wèn)題(你站在硬實(shí)時(shí)的角度看它是問(wèn)題,你換個(gè)角度看,它就反而是正確的地方)。
1. spinlock是一個(gè)隨處可見(jiàn)被內(nèi)核、驅(qū)動(dòng)使用的API
Linux內(nèi)核和驅(qū)動(dòng)程序員鐘愛(ài)spinlock到了如癡如狂的程度,可以說(shuō)不睡眠、時(shí)間較短的critical section場(chǎng)景,都會(huì)第一時(shí)間想到spinlock。平生不識(shí)自旋鎖,就稱英雄也枉然。自旋鎖的優(yōu)越性在于,在2個(gè)人(這2個(gè)人可能是線程與線程、中斷與線程、中斷與中斷等)競(jìng)爭(zhēng)一個(gè)鎖的時(shí)候,避免失敗的那一方切換上下文context,所以與其上下文切換,不如原地等。但是自旋鎖本身也引起了副作用,它引起了持有鎖的CPU核的搶占調(diào)度的禁止。內(nèi)核自旋鎖的實(shí)現(xiàn),更多的是核間自旋轉(zhuǎn)而核內(nèi)是通過(guò)禁止搶占來(lái)實(shí)現(xiàn)臨界區(qū)保護(hù)的。
假設(shè)T1, T2, T3, T4運(yùn)行在一個(gè)核上面,當(dāng)T1拿到spinlock后,這個(gè)核上的搶占調(diào)度被禁止,如果在T1持有spinlock的時(shí)間內(nèi),T2是一個(gè)高優(yōu)先級(jí)的實(shí)時(shí)任務(wù),盡管T2被喚醒,它也不可能立即打斷T1的執(zhí)行,必須等待T1釋放spinlock。由于T1究竟會(huì)持有 spinlock 多久做xxxx,這個(gè)鬼都不知道,所以T2究竟要等多久,也未可知,這顯然破壞了決定性的時(shí)延。
2. Linux的中斷執(zhí)行時(shí)間可能過(guò)長(zhǎng)且不可嵌套
眾所周知,早期的Linux版本有個(gè)標(biāo)記叫IRQF_DISABLED,標(biāo)記本中斷在執(zhí)行的時(shí)候,其他所有中斷都被禁止進(jìn)入;而后Linux內(nèi)核實(shí)際去掉了這個(gè)申請(qǐng)flags,其實(shí)就是都是IRQF_DISABLED了,總體可認(rèn)為L(zhǎng)inux內(nèi)核不支持中斷的嵌套。
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id);中斷在執(zhí)行的時(shí)候,所有的中斷都進(jìn)不來(lái),這個(gè)設(shè)計(jì)本身簡(jiǎn)化了內(nèi)核,但是對(duì)于硬實(shí)時(shí)的打擊是致命的,前面的中斷不執(zhí)行完成,優(yōu)先級(jí)再高的中斷也得給我等著。
比如中斷1在執(zhí)行的過(guò)程中,來(lái)了中斷2,而中斷2對(duì)應(yīng)的事情是必須要決定性時(shí)延的,由于IRQ1的中斷服務(wù)程序也是碼農(nóng)寫(xiě)的,我們無(wú)法確定這個(gè)中斷服務(wù)程序要執(zhí)行多久。這顯然讓高優(yōu)先級(jí)中斷2的進(jìn)入延遲不再具備可預(yù)期性。
3.軟中斷(softirq)是一個(gè)比進(jìn)程上下文優(yōu)先級(jí)更高的上下文
我們?cè)O(shè)想一個(gè)場(chǎng)景,哪怕Linux解決了問(wèn)題2,就是Linux的中斷變地可嵌套,高優(yōu)先級(jí)的中斷可以打斷低優(yōu)先級(jí)的中斷,并且高優(yōu)先級(jí)的中斷2喚醒了一個(gè)用戶寫(xiě)的實(shí)時(shí)線程。
IRQ2喚醒了實(shí)時(shí)任務(wù)T1,但是T1必須等待IRQ1喚起的軟中斷(也包括使用軟中斷上下文的tasklet等)被執(zhí)行完,T1才能投入執(zhí)行。IRQ1喚起的softirq的代碼是碼農(nóng)寫(xiě)的,這個(gè)碼農(nóng)寫(xiě)多久,鬼都不知道,這顯然破壞了實(shí)時(shí)任務(wù)T1得以調(diào)度執(zhí)行的確定性時(shí)延。
4. 內(nèi)核里面會(huì)屏蔽中斷的API如local_irq_disable、spin_lock_irqsave等
前面筆者已經(jīng)反復(fù)強(qiáng)調(diào)過(guò),在驅(qū)動(dòng)程序里面調(diào)用local_irq_disable()通常都是一個(gè)bug,因?yàn)樗鼰o(wú)法修復(fù)另外一個(gè)核上運(yùn)行的線程、中斷服務(wù)程序與本核線程之間的競(jìng)爭(zhēng)。盡管在單核處理器里面調(diào)用這個(gè)API是通常安全的,但是我們哪怕是在單核編程,都要假裝自己是多核的樣子,這個(gè)是在Linux里面寫(xiě)代碼跨平臺(tái)的最基本常識(shí)。
相信絕大多數(shù)童鞋都不會(huì)傻到寫(xiě)驅(qū)動(dòng)的時(shí)候再去調(diào)local_irq_disable這樣的API。但是spin_lock_irqsave這樣的API在內(nèi)核的使用可以說(shuō)太常見(jiàn)了,它其實(shí)是適用于一個(gè)經(jīng)典的場(chǎng)景,就是中斷服務(wù)程序與線程之間有競(jìng)態(tài)的情況。作為一個(gè)內(nèi)核程序員,相信如下的經(jīng)典用法你已經(jīng)熟悉地不能再熟悉,滿滿地都是套路:
它把T1、T2、T3、T4、IRQ1、IRQ2這6者之間的競(jìng)爭(zhēng)消滅于無(wú)形。T1如果持有了spin_lock_irqsave,本核上的T2、IRQ1顯然進(jìn)不來(lái),CPU1上面的T3、T4、IRQ2想訪問(wèn)T1訪問(wèn)的臨界資源必須spin。IRQ1如果持有了spin_lock, CPU1上面的T3、T4、IRQ2想訪問(wèn)IRQ1訪問(wèn)的臨界資源必須spin。
那么,問(wèn)題又來(lái)了,spin_lock_irqsave既屏蔽了搶占,又屏蔽了中斷,這會(huì)導(dǎo)致中斷和實(shí)時(shí)任務(wù)的確定性時(shí)延造成不可預(yù)期的破壞。因?yàn)閟pin_lock_irqsave 和 spin_lock_irqrestore是碼農(nóng)寫(xiě)的,鬼都不知道它要多久。
當(dāng)然,歷史上,粗獷的大內(nèi)核鎖(Big Kernel Lock,BKL)也是一個(gè)問(wèn)題。由于晶晶姑娘不喜歡內(nèi)核粗獷的一面,BKL在如今的內(nèi)核里面已經(jīng)煙消云散。
在Linux的世界里,這些鎖當(dāng)然都沒(méi)有一個(gè)鎖牛逼,就是RCU,尤其是面對(duì)這個(gè)世界符合阿姆達(dá)爾定律(Amdahl's law)定律的情況下,我們既要保證臨界資源訪問(wèn)的被保護(hù),又要盡一切可能地讓多個(gè)線程同時(shí)狂奔。關(guān)于RCU的細(xì)節(jié),謝神醫(yī)已經(jīng)有多篇文章論述。
Linux的世界大概是這樣的:中斷、軟中斷、線程(包括ksoftirqd線程)。我們都清楚地知道,軟中斷大量陷入的情況下,內(nèi)核會(huì)將后續(xù)的軟中斷投入ksoftirqd內(nèi)核線程執(zhí)行,所以軟中斷還有一個(gè)可能的執(zhí)行時(shí)機(jī)是在內(nèi)核線程里面。
5. Linux用戶空間內(nèi)存的lazy分配機(jī)制與交換swap
對(duì)于喜歡在RTOS寫(xiě)程序的童鞋來(lái)說(shuō),Linux的世界一時(shí)半會(huì)難以理解,但是對(duì)于寫(xiě)Linux的童鞋來(lái)說(shuō),絕大多數(shù)的RTOS簡(jiǎn)直就是在裸奔。
我們都知道,在Linux里面,用戶空間的內(nèi)存都執(zhí)行l(wèi)azy的分配機(jī)制。比如你malloc一個(gè)內(nèi)存
char *p = malloc(1024*1024);這個(gè)時(shí)候Linux忽悠你說(shuō)拿到了內(nèi)存并且p獲得了地址,但是實(shí)際的拿到卻是在你寫(xiě)的時(shí)候,以page fault缺頁(yè)中斷的形式獲得的。比如你寫(xiě)p[0]=1就拿到了第一頁(yè),你寫(xiě)p[4096]就拿到了第2頁(yè)。這個(gè)lazy的分配機(jī)制,也同樣適用于棧、代碼段等。
你是一個(gè)實(shí)時(shí)的線程,你被喚醒得以執(zhí)行,你執(zhí)行的時(shí)候,發(fā)現(xiàn)你訪問(wèn)的臨時(shí)變量還沒(méi)有獲得內(nèi)核,你的代碼段可能還特馬在硬盤(pán)里,請(qǐng)問(wèn)你實(shí)時(shí)個(gè)什么鬼?你執(zhí)行到函數(shù)b的時(shí)候,去訪問(wèn)d[1000],結(jié)果發(fā)現(xiàn)這個(gè)棧的這頁(yè)內(nèi)存還要通過(guò)page fault來(lái)通過(guò)內(nèi)核buddy去申請(qǐng),你的確定性延遲還如何滿足?
main() { … a(); } a() { … b(); } b() { int d[1024]; d[1000]=100; c(); }當(dāng)然,已經(jīng)進(jìn)入內(nèi)存的東西,也由于內(nèi)核的swap機(jī)制,會(huì)與磁盤(pán)進(jìn)行交換。
絕大多數(shù)的RTOS都沒(méi)有這個(gè)“問(wèn)題”,這也恰恰是他們不夠“牛逼”的地方。對(duì)于手機(jī)、電腦這種富應(yīng)用的系統(tǒng)而言,你不能用資源已經(jīng)被確定性分配的思維模式來(lái)思考。
Linux preempt-rt如何解決這些問(wèn)題?
前段時(shí)間,這篇文章刷屏了:《
到今天為止,ARCH_SUPPORTS_RT誰(shuí)他么都不是真:
barry@barryUbuntu:~/develop/linux$ git grep ARCH_SUPPORTS_RT
arch/Kconfig:config ARCH_SUPPORTS_RT
kernel/Kconfig.preempt: depends on EXPERT && ARCH_SUPPORTS_RT
所以,你要真地在mainline見(jiàn)到PREEMPT_RT開(kāi)花結(jié)果,還必須活地更久一點(diǎn)。
當(dāng)提到 preempt-rt 補(bǔ)丁的時(shí)候,我必須強(qiáng)調(diào)一點(diǎn),Linux不是一個(gè)裸奔的操作系統(tǒng)。Linux 的應(yīng)用都是在用戶空間寫(xiě)的一個(gè)個(gè)進(jìn)程、線程。所以相對(duì)于其他 RTOS 可能更加強(qiáng)調(diào)高優(yōu)先級(jí)中斷的確定性時(shí)延(RTOS不太特別強(qiáng)調(diào)機(jī)制與策略分離的概念,整個(gè)東西編譯在一起的話,在中斷里面放策略也未嘗不可),在Linux時(shí)間里,用戶空間高優(yōu)先級(jí)的 RT 線程的確定性調(diào)度時(shí)延就顯得更加critical(因?yàn)長(zhǎng)inux內(nèi)核里面你不能裸奔地把用戶策略的東西放進(jìn)內(nèi)核,內(nèi)核提供的是一些操作接口而已,簡(jiǎn)單來(lái)說(shuō),你要做的事情是一個(gè)應(yīng)用,而應(yīng)用是個(gè)用戶空間的東西)。
風(fēng)在吼,馬在叫,娃兒在咆哮。今天就談到這里,明天接著談。我相信你還有很多的疑惑,比如很多童鞋說(shuō),你剛才提到的Linux的一些硬實(shí)時(shí)的毛病,在 RTOS 里面其實(shí)也都有,我會(huì)給你一個(gè)交代。
掃碼或長(zhǎng)按關(guān)注
回復(fù)「?加群?」進(jìn)入技術(shù)群聊
總結(jié)
以上是生活随笔為你收集整理的在实时操作系统里随便写代码都能硬实时吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 随想,对嵌入式职场建议
- 下一篇: 自动化测试验证码代码常用的四种方式