實時系統介紹

實時系統介紹

最近準備將之前開發的應用從 TX2 移植到 S32V 上,實驗室同學負責 RTLinux 的移植以及 ROS2 到 RTLinux 的移植,我則負責將軟件從 ROS1 遷移到 ROS2 上,並針對部分應用開發實時版本。這一方面是爲了保證部分應用的執行是確定(determinism)的,另一方面也是爲了提高編寫的自動駕駛應用的質量,更合理地利用開發板硬件資源。因此,這裏就把我最近學習實時應用編程的一些知識整理成文檔以便日後查閱以及探討學習。

本文將從實時計算的定義、實時應用編程的注意事項、ROS2 對實時應用編程的支持進行介紹,最後對本文的主要內容做一個總結。
在這裏插入圖片描述

實時計算的定義

這部分主要會介紹實時系統中的關鍵術語、理解實時系統時的兩個注意事項以及實時系統的分類[1]。

關鍵術語

首先介紹實時系統中的幾個關鍵術語,對於從概念上直觀瞭解實時系統的特點還是有幫助的。

  • Determinism: 給定一個已知輸入,一個確定性系統的輸出永遠都是相同的,而非確定性系統的輸出則可能存在隨機變化。
  • Deadline: 一個特定任務必須在 deadline 規定的有限時間窗口內完成
  • Quality of Service: 描述網絡的整體性能,包括:帶寬、吞吐量、可用性、抖動率、時延、錯誤率等

注意事項

這裏需要注意的有兩點:

  1. 實時系統經常被拿來與低延時(low-latency)系統相關聯,確實很多實施應用同時也是低延時應用,但是實時系統並不是定義爲低延時的,而是確定性調度:系統必須保證在特定的時間完成特定的任務。因此在編寫實時應用時,我們需要對任務的執行延時進行多次測量,然後爲任務設置一個合適的最大可允許延時
  2. 一個實時系統不僅需要底層操作系統是實時的,也需要用戶代碼的執行也是實時的,兩者缺一不可

系統分類

對於實時性的不同要求,可以分爲這麼幾種實時系統:

  1. 硬實時(hard real-time)系統:硬實時系統有着一系列嚴格的 deadline,錯過一個 deadline 就被認爲是系統崩潰。典型例子包括:飛機自動巡航系統、汽車安全氣囊系統
  2. 軟實時(soft real-time)系統:軟實時系統會盡力滿足 deadline,但錯過一個 deadline 不會導致系統崩潰。典型例子是娛樂直播應用,即使網絡延時導致丟幀也不會太大影響
    在這裏插入圖片描述
  3. 嚴格實時(firm real-time)系統:嚴格實時系統將錯過 deadline 的消息/計算視爲 invalid,錯過 deadline 不會導致系統崩潰,但會使收益逐漸降低。典型例子包括:機器人裝備流水線、天氣預報系統
    在這裏插入圖片描述

實時應用編程實踐

在對上節介紹的實時系統有了一個直觀的概念後,本節就稍微介紹下使用 C++ 開發實時應用時的一些注意事項,有些功能點在開發普通應用時可能影響不是很大,但在實時應用開發中就需要特別注意了,尤其是在資源有限的嵌入式設備中更需要注意。本節最後會介紹幾個幫助分析應用實時性能的工具。

內存管理

首先是對系統的實時性能至關重要的內存管理,只要應用中涉及到較大的數據量就會存在內存的分配與釋放,在自動駕駛領域,各種應用對各種傳感器數據的處理顯然需要一個合適的內存管理來提高實時性能。這裏先介紹下爲什麼內存管理對應用的實時性影響很大,主要是因爲 C++ 中對 malloc/new 和 free/delete 的調用可能會產生 page fault [2,3]中斷,性能損耗很大;其次堆空間的分配釋放會造成內存碎片,造成資源浪費。

針對這一問題,解決方法還是有的,比如直接鎖定內存 mlockall、使用內存池進行內存管理、自定義實時內存分配器(Two-Level Segregate Fit)等等。由於之前沒有解決此類問題的需求,我沒還有仔細瞭解過這些解決方案,這裏就不展開了,包括後面的注意事項也都是隻會大體給出解決思路,具體遇到問題了可以自行深入瞭解。

異常

這裏先提一點,在操作系統內核代碼一般是不用 C++ 編寫的[4],其中一個原因就是異常。

The whole C++ exception handling thing is fundamentally broken. It’s especially broken for kernels.
----Linus Torvalds

對於實時系統來說,對異常可以說是又愛又恨,恨的是編譯器爲了支持異常會產生多餘的代碼,如果一個頻繁調用的函數可能會拋出異常的話,整個應用可能會增加大量的代碼,這在資源有限的嵌入式系統中是 problematic 的。而且如果一個異常實際被拋出的話,異常處理會將很多對象壓棧,導致棧資源緊張。但是異常的好處在於其可以幫助應用處理各種邊界條件,在難以通過用戶交互解決異常的嵌入式環境中,這點尤爲重要[4]。不過 stackoverflow 上也有人針對這一問題有所討論[5],一般認爲目前編譯器對異常的支持已經足夠到對處理時間的影響很小,問題主要在於代碼膨脹。代碼大小的影響我還沒有用過工具實際測試過,具體影響是多少我也沒有太多概念,最關鍵的是目前我還沒有遇到代碼大小影響實時性能的瓶頸。。。

不過解決方法也是很簡單粗暴了,就是好好設計異常,儘量不要在 innner-loop 內拋異常,同時靈活使用 nothrow。

虛表與指針

C++ 的運行時多態是由虛表支持的,虛表又是由虛函數指針構成的,使用 C++ 編寫面向對象應用時,不可避免地會用到虛表,但在實時應用編程中使用虛表需要特別注意它的效率問題。在閱讀到相關文獻前,我也大概知道虛表存在一定的效率損失,但我還以爲只是因爲多了個指針解引用,後來才知道多個指針解引用真正的問題在什麼地方。使用虛表的真正問題在於虛表與對象的存儲位置不同,當對象調用虛函數的時候,需要跳轉到虛表找到對應的虛函數,再根據每個對象的狀態調用虛函數。由於代碼指令、數據、虛表的存儲位置各不相同,cache 不能保證能夠同時持有所有這些數據,也就是說使用虛表可能會使得 cache locality(緩存局部性)較差,這纔是實時應用中使用虛表的關鍵問題,而不僅僅是多個指針解引用這麼簡單(當然實際可能比我瞭解還要複雜)。如果使用數據成員使用指針的話問題是類似的,因爲指針的存儲位置與指針指向的數據的存儲位置也不同,同樣也會有緩存局部性的問題。

但是不使用虛函數是不可能的,這輩子都不可能的,只能靠。。。好吧,解決方法就是僅針對必要的接口設計使用虛函數了(感覺需要對架構設計有一定了解才能駕馭住啊),同時也儘量使用普通聚合,少用指針成員[4](這當然都是具體問題具體分析了,包括之前以及之後要介紹的注意事項,都是沒有那麼絕對的)。

多線程

多線程也是開發計算密集型應用很難繞過去的一個問題,在開發實時應用中自然也有需要注意的地方。多線程同步的問題算是比較通用的問題了,不管開發什麼應用都需要注意。對於實時應用來說,多線程還有個嚴重問題是優先級反轉,意思是說低優先級線程佔有了互斥鎖,由於實時操作的搶佔調度特性,另一個申請該鎖的高優先級線程會搶佔低優先級線程,這就導致死鎖了。這就是多線程在實時應用中新出現的問題,不過這個問題在實時操作系統中也會出現,因此解決方法也可以拿來參考,比如合理設置優先級啊、優先級繼承啊等等。還有一點需要注意的是,在創建線程時儘量不要用 fork,因爲它也可能會產生 page fault 中斷。

其他

其他還有很多問題是在編寫實時應用的過程中需要注意的,比如全局變量和靜態數據的併發問題和緩存局部性問題、使用模板時的代碼膨脹問題、設備 I/O 的延遲問題等等,這裏就不先不一一介紹了,日後有機會再來整理。這裏稍微總結一下,編寫實時應用是有很多注意事項的,甚至有些是違背自己平時的編程習慣的,但沒有辦法,要想編寫出真正穩定可靠的實時應用,這些注意事項都是需要我們瞭解的,不僅僅是用戶代碼本身對實時性能的影響,編譯器對用戶代碼的擴展都是需要我們關注的,道阻且長,行則將至。

性能測試分析工具

在介紹完這麼多注意事項後,是該瞭解一下各種性能測試分析工具來幫助我們分析實際編寫的應用到底有沒有滿足一定的實時性要求了。

  • cyclictest:用於抖動(jitter)分析
  • size:代碼大小分析
  • perf:linux 硬件事件計數,軟件性能分析
  • gprof:函數耗時統計

大家在實際使用時可以酌情參考。

ROS2 與實時應用編程

這部分來談一談 ROS2 對實時應用編程的支持,畢竟目前從 ROS1 遷移到 ROS2 的最大動力就是它的實時性能了。

實時架構設計

在這裏插入圖片描述

值得一提的是,ROS1 也是可以編寫實時應用的(注意底層都是實時操作系統),不過從圖中可以看到,ROS1 並沒有從通信中間件進行支持,還是直接用的 TCP/UDP。而 ROS2 依託於通信中間件 DDS 提供的可靠性和分段傳輸對上層實時應用進行了支持,其實 ROS2 大部分的實時特性都是由 DDS 支持的,ROS2 只是做了一個封裝的工作,而且目前 ROS2 支持的 DDS 中,只有 Connext 的實時支持最完整,還是期待下 ROS2 之後的實時應用支持中能有更多其他的開源 DDS 吧。再提一點,雖然 ROS 可以用 python 甚至 java 編程,但實時應用只能用 C/C++ 編寫。

實時應用支持

上節提到,ROS2 對實時應用的支持大多是由 DDS 提供的,其實還有一部分是由其他第三方庫支持的,ROS2 同樣是做了一個封裝的工作,當然 ROS2 自己也是提供了不少支持的,不過我瞭解的還不夠多。這節就簡單列一下 ROS2 對實時應用的支持吧。

  • rttest:實時性能測試工具,可以測量並繪製抖動、延時及 missed deadline
  • TLSF:實時動態內存分配器,可以避免上文提到的 page fault
  • SROS:安全通信,由 Connext 支持
  • QoS:服務質量保證,由 DDS 提供,ROS2 的應用默認都會使用 QoS,因爲 Node 構造時會有一個默認的 QoS profile。

總結

以上就是本文的所有內容,只能算作一個對實時應用編程的簡單介紹,日後應該還會接觸到很多實時應用編程的知識,有機會的話會針對各個 topic 專門開篇博文詳細介紹。

這裏對本文做個簡短的總結:

  1. 實時系統的關鍵在於 確定性:確定時間完成確定任務,確定輸入產生確定輸出
  2. 實時系統需要從上層軟件到下層操作系統甚至底層硬件的實時支持
  3. 編寫實時應用不僅需要了解用戶代碼本身的實時性能,還要了解編譯器對用戶代碼的擴展可能會對實時性能產生哪些影響
  4. 編寫的實時應用需要通過各種工具進行一系列的實時性能分析才能真正確保最終的實時性

參考資料

  1. http://design.ros2.org/articles/realtime_background.html
  2. https://yq.aliyun.com/articles/55820
  3. https://stackoverflow.com/questions/5684365/what-causes-page-faults
  4. https://www.embedded.com/design/programming-languages-and-tools/4429790/How-to-make-C--more-real-time-friendly
  5. https://stackoverflow.com/questions/5257190/are-exceptions-still-undesirable-in-realtime-environment
  6. https://stackoverflow.com/questions/17874946/is-there-any-disadvantage-to-declare-a-variable-global
  7. http://wiki.c2.com/?GlobalVariablesAreBad
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章