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