Rust語言在嵌入式領域的應用

語言: CN / TW / HK

關注「 Rust程式設計指北 」,一起學習 Rust,給未來投資

Rust語言是二十一世紀的語言新星。Rust被人廣泛承認的一點,就是因為它能執行在多樣的目標上,從桌面和伺服器裝置,到資源有限的嵌入式裝置。
我們可以用適合來評價一門語言和技術。Rust非常適合開發嵌入式應用,它是一種和C相仿的、能應用於嵌入式裝置開發的程式語言。
作業系統都是從裸機裝置開始執行的,Rust語言的這一點也意味著,它能很好地用於編寫作業系統。無論是應用層還是核心本身,Rust都是極富競爭力、值得投入時間的技術選項。

二十一世紀的裸機程式語言

在這個網際網路全面普及、價效比裝置應用更廣的時代,安全和可靠性成為一門語言必須考慮的因素。Rust語言採用移動語義,擁有嚴格的代數型別系統以及生命週期、所有權模型;相比傳統的程式語言,這些模型能在合適的時候釋放所用資源,減少漏洞的出現。此外,通過語義檢查,Rust能在編譯期有效尋找記憶體和執行緒安全問題,降低開發和測試的負擔。
Rust語言的執行效率高、開發效率好、適用範圍廣。作為一門編譯型語言,它直接編譯輸出到彙編程式碼,通常公認裸機的Rust語言效能在C語言級別,擁有較高的執行效率。Rust語言的開發效率很高,文件完善、編譯器提示有幫助,能節省軟體開發所需的時間。它能應用在多個平臺和指令集中,這包括裸機平臺;處理核、作業系統廠家還可以提供自己的編譯目標,無需廠家自己重新開發、提供工具鏈。
Rust語言出彩的地方在於,它向嵌入式平臺引入了大量新的程式設計技術。這包括了閉包、過程巨集等傳統上用於函數語言程式設計的技術,和多型、虛擬函式表等面嚮物件語言的技術。新程式設計技術的引入,擴充了開發者的選擇。即使徹底理解Rust的程式設計概念有一定難度,但這些易用的新技術,讓開發者只需閱讀例項程式碼,便可快速進入開發狀態。這些新技術的引入,是嵌入式平臺從未有過的,Rust能提高開發者的工作效率,降低平臺間遷移的學習時間和成本。

裸機上的過程巨集

傳統用於嵌入式平臺的程式設計,我們加快開發速度使用的巨集,常常基於語法字串的替換和修改。 Rust語言擴充了巨集的概念,提出了基於語法樹的“過程巨集”程式設計方法,讓巨集語法更容易使用、編寫更方便。
“過程巨集”是接收Rust程式碼作為輸入,操作這些程式碼,然後產生另一些程式碼的過程。 它和字串的替換不同,是從語法樹到語法樹的替換。 開發一個過程巨集,可以使用簡單的定義過程,或者有工作量的屬性巨集定義過程。 簡單的定義中,我們編寫程式碼,給出巨集的輸入有哪些,要翻譯到哪些輸出程式碼,這樣就完成了一個巨集的定義。 屬性巨集定義則允許完成語法樹分析、程式碼生成甚至程式碼優化的過程,就需要編寫專門的“屬性巨集庫”,借用Rust編譯器的一部分,完成巨集程式碼的轉化和輸出。
過程巨集是基於語法樹的分析過程,藉助“樹”的結構我們能理解它的一些特點。 因為Rust語法樹的子樹也是Rust程式碼,所以巨集的定義內也可以完成語法分析,這就為程式碼編輯器的提示和補全提供了便利。 一個語法專案不可能同時屬於兩顆不是親子關係的子樹,因為如果屬於兩顆子樹,將和語法樹的樹根產生環,就和語法樹的定義相違背,所以語法專案都是獨立的,巨集內程式碼的解析不會影響外界程式碼的解析。
這樣的獨立性也就是“ 過程 巨集”思想的提出,Rust的過程巨集可以理解為程式碼的“內部展開”,不影響程式碼的上下文。 正因為Rust過程 巨集 產生完整的語法子樹,它的定義不需要額外的界符,因此只需要滿足Rust語法就可以了。
在過程巨集的定義之外,Rust語言提供了大量便於嵌入式開發的標籤。 “align”標籤定義記憶體對齊的方式,“link_section”標籤給定程式碼要連結到的段或區。 這樣,過程巨集可以包裝各種各樣的標籤,Rust語言的使用者可以方便地使用,而不需要深入巨集瞭解程式碼的具體要求。 Rust語言定義的過程巨集可以匯出到包外,給其它的庫使用,這有利於嵌入式Rust生態的搭建和共享。 Rust語言巨集靈活的特性,讓巨集在更多的領域有可用之處,更好地服務嵌入式平臺的開發工作。

嵌入式中的模組化程式設計

Rust語言擁有很好的模組化程式設計概念。傳統平臺的Rust語言中,社群總結出了“模組-包-專案”的模型。這個模型也適用於嵌入式平臺,增加協作開發的效率,更好地共享生態。
Rust的模組化程式設計分為模組、包、專案三級。模組是Rust語言可見性 分的最小單位,語言中提供了專門的關鍵字,來區分不同模組的程式碼和可見性,是由Rust語言本身確定的。在Rust語法中,“mod”是定義模組的關鍵字,“pub”是定義可見性的關鍵字。
包是Rust專案的二進位制目標,這個等級是由Rust工具鏈給定的。每個包有版本號、作者和許可協議等元資料,要依賴和使用的庫也要登記到包中,以便共同編譯。庫的特性有點像傳統語言的條件編譯,也是以包為單位規定的,每個包使用的庫可以開啟不同的特性,但庫在同一個包中開啟的特性是相同的。
“專案”這一層並非由Rust語言給定;人們開發軟體時,發現一個解決方案中包含多個二進位制目標是非常好的,總結之後就出現了專案的抽象模型。專案由核心和外圍包組成,或者是功能相近的一組包,它通常由同一個團隊組織和維護,可以在專案上新增擴充套件。專案在習慣上由核心包到功能包,以依賴的形式構成。實踐中,“專案”可以放在同一個工作空間裡,以統一管理和釋出編譯版本。
Rust將模組化程式設計引入到嵌入式開發中,也可以方便地編寫測試和效能檢測程式碼。模組化程式設計能提高Rust嵌入式開發者的工作效率,適應現代化嵌入式軟體的需求。

搭建Rust嵌入式生態

生態是軟體工業不可或缺的一部分。從編譯器到軟體支援,嵌入式Rust目前已經擁有良好的基礎生態。此外,作業系統核心也是嵌入式程式設計的重要部分,嵌入式Rust和核心開發也有較好的相容度。

Rust語言的嵌入式生態

你的架構和指令集

嵌入式Rust的應用支援分為兩個部分:一個是目標處理核的支援,一個是晶片外設的支援。
針對目標處理核,首先我們要編譯Rust到這個指令集架構。Rust語言提供豐富的編譯目標,主流的編譯目標都有很好的支援;此外,如果有自主研發的指令集架構,可以為Rust新增自己的編譯目標。編譯完成後,還需要編寫微架構支援庫和微架構執行時。微架構執行時提供最小的啟動程式碼實現,能搭建一個適合Rust程式碼執行的環境。微架構支援庫簡單包裝彙編程式碼,允許應用程式碼操作暫存器、執行特殊的指令,作為編譯器系統的補充。這之後,Rust對這個指令集架構的程式碼執行支援就完成了。
嵌入式應用定義了各有特點的中斷控制器,有些是指令集架構定義的,有些是晶片設計廠家自己定義的。嵌入式Rust要支援這些中斷控制器,需要在微架構執行時中新增處理和封裝部分,或者作為通用架構的補充,在專用架構的支援庫中新增專有架構的中斷執行時。架構雖然定義了標準,但基地址、中斷數量等配置可能相互不同。這些元資料配置可以放在外設訪問庫的中斷部分,和架構支援庫共同構成中斷控制器的支援。
目標的處理核定義了除錯介面和快閃記憶體燒寫演算法,我們需要在偵錯程式軟體中編寫這些演算法。社群通用的軟體“probe-rs”是很好的偵錯程式實現,可以替代OpenOCD,作為非常好的Rust語言除錯軟體。如果自己的作業系統有軟體除錯介面,可以新增作業系統偵錯程式的載荷,共同完成除錯軟體的部分。只要處理器廠商實現了除錯介面,提供相關的文件,配套的Rust軟體可以儘快完成,方便各種技術的開發者除錯和使用。

嵌入式生態的標準

起初嵌入式開發者會為每個晶片都編寫一次程式碼。 隨著生態的發展,大家認識到,需要提供一個基本的抽象,大家都圍繞著抽象去編寫,就能省下為大量外設反覆編碼的時間。 embedded-hal就是這樣的標準,它是Rust語言的嵌入式外設抽象,支援大量的片內和片外外設,包括感測器等,很好地擴充了嵌入式的生態。
embedded-hal是統一的Rust語言標準,它是針對外設功能本身的抽象,是抽象的集合,具體實現由實現庫去完成。 它的擴充套件性很好,比如“SPI-GPIO擴充套件器”外設輸入SPI介面抽象,輸出GPIO的抽象,很多模組都是抽象到抽象的過程,就可以方便的聯、銜接和巢狀,整合更多的專案; 這就非常容易為新的晶片編寫支援庫。
市場上海量的晶片都支援embedded-hal標準。 K210、GD32V和BL602系列的晶片都提供很好的embedded-hal實現庫。 要編寫embedded-hal標準的支援庫,只需要機器生成外設庫,然後編寫中間層庫,就能完成對此標準的原廠支援。

Rust與作業系統核心

作業系統也是嵌入式應用。常見的作業系統如按是否包含虛擬記憶體區分,有不含虛擬記憶體的實時系統,和包含虛擬記憶體傳統作業系統。基於微架構的支援庫和執行時庫,作業系統核心可以很方便地編寫。
社群中提供了大量成熟的作業系統執行時。如rCore系列作業系統是第一個基於RISC-V架構的完整Rust作業系統,尤其適合教學使用。RTIC框架是中斷驅動的非同步實時系統,完全針對應用使用Rust的巨集語法生成,擁有極高的效率。Tock系統是針對微處理器的安全實時系統,已經用於手錶、智慧路標和加密狗等產品。
針對作業系統和應用程式開發,Rust是適合編寫硬體驅動的語言。如果使用有產權的程式碼,可以以混合連結的形式,與Rust程式碼聯合編譯為二進位制使用。系統模組、外掛和動態連結庫等等都能受益於Rust語言記憶體安全的特性,適合現在對安全敏感的開發需求。
物聯網系統要求嵌入式的作業系統能夠連上網路。Rust嵌入式社群也在探索射頻連線的技術標準,包括藍芽、WiFi等硬體標準。smoltcp是社群提供的非常好的TCP協議棧實現,它可以代替lwip,在嵌入式系統領域高效、安全地完成網路傳輸。搭配緩衝區和協議庫,物聯網作業系統就可以連上網了。

RustSBI:新型作業系統引導軟體

我們在開發作業系統核心時,有的核心直接執行在裸機上,有的還依託於一個執行環境。在RISC-V上,“SBI”就是這樣的執行環境。它除了引導啟動核心,還將常駐後臺,提供作業系統需要的實用功能。
RISC-V標準中,“SBI”意味著“作業系統二進位制介面”,執行在其上的作業系統會通過環境呼叫“ecall”指令,陷入到二進位制介面的實現中,由其呼叫具體硬體的實現功能。這種實現被稱作“SBI實現”,社群常用的實現有開源的OpenSBI。RustSBI是鵬城實驗室“rCore程式碼之夏-2020”活動提出的SBI實現,它是全新的作業系統引導軟體。

實現與模組組成

RustSBI由幾個功能模組組成。硬體環境介面實現了RISC-V SBI v0.2版本的介面,能執行支援此版本的作業系統。硬體執行時則是SBI實現執行在裸機環境的必要模組,它將由硬體啟動,開始執行所有的RustSBI模組。SBI的初始化完成後,將進入引導啟動模組,這裡將發揮SBI標準“引導啟動”的功能,最終啟動作業系統核心。另外,相容性模組能完成硬體到硬體間的支援,能模擬舊版硬體不存在的指令、暫存器,進一步延長作業系統的生命週期。
去年12月,RustSBI的0.1版本在深圳的Rust中國社群2020年年會上釋出。使用目前最新的0.1.1版本,RustSBI已經支援大量SBI標準提出的功能,支援大量自定義的擴充套件功能;完全使用安全的Rust語言編寫,提高開發效率。開發Rust語言的作業系統核心,可以統一編譯工具鏈。另外,RustSBI已經被RISC-V組織收錄入RISC-V SBI標準,它的實現編號為4。
RustSBI是一個庫,它以庫的形式設計的初衷是,便於平臺開發者“積木”式地引入庫的模組,為自己的硬體目標開發SBI支援。雖然RustSBI提供了QEMU、K210平臺的參考實現,但應用開發者不應當將自己的目標也加入參考實現中,而是在自己的倉庫裡引用RustSBI的模組,可以選擇參考這些實現的內容,最終完成完全可控的開發過程。這兩個平臺的使用範圍較廣,參考實現也會長期維護,以發現RustSBI本身可能的少量問題,並及時修補完善。
為什麼用Rust開發RustSBI呢?我們認為,相比使用C語言,嵌入式Rust的生態圈在協調發展階段,它容易支援新硬體,Rust語言較強的編譯約束也提高了硬體程式碼的安全性。

硬體到硬體的相容性

RISC-V是快速更迭的指令集規範。我們為新版RISC-V硬體編寫軟體,會遇到與舊版硬體不相容的情況。硬體和硬體之間的相容性,也能通過軟體完成——這是RustSBI提供的功能與亮點之一。
RustSBI實現的硬體相容性,是靠捕獲指令異常完成的。例如,K210平臺實現的是1.9.1版本的RISC-V特權級標準,它規定了舊版的頁表重新整理指令;而目前最新的1.11版標準,規定的是新版的重新整理指令。為新標準編寫的作業系統核心,使用新版重新整理指令,會因為K210硬體無法找到新版指令,丟擲非法指令異常。這個非法指令異常被RustSBI捕獲,它解析後,發現是新版的頁表重新整理指令,便直接在硬體上執行舊版的指令,完成指令的頁表重新整理功能。
這種硬體相容性,目前能支援新增的指令和暫存器。一切情況下,指令、暫存器在仍然存在,但新版中修改了它們的功能和意義。只靠RustSBI軟體本身,就不足以提供相容性支援了。如果RISC-V晶片實現提供特定的相容性外設,比如這個外設能攔截特定CSR暫存器的訪問指令,就可以在功能修改的暫存器訪問時,產生一個可供軟體捕獲的中斷。這樣的外設設計之後,使用RustSBI軟體,將能支援功能修改的指令和暫存器,將進一步提升作業系統核心的硬體相容性。
相容舊硬體,也是相容未來新硬體的過程。未來的RISC-V標準快速發展,將與目前的硬體標準產生一定的差異;在硬體不變的前提下,未來軟體能對當前的硬體相容,就能延長軟體的生命週期。或許,我們未來升級RISC-V上的作業系統,只需要更換硬體中的RustSBI韌體,就能完美相容最新標準的作業系統了。升級原有系統的硬體也非常容易,替換RustSBI韌體就能達到升級效果。
另外,硬體相容性也意味著實現硬體上缺少的指令集。當這些指令集執行時,就會陷入到軟體中,由RustSBI軟體模擬這些指令,最終返回,這個過程應用軟體不會有感知。當然,這種軟體模擬過程可以滿足正確性,效率不如新版的硬體,但臨時執行一個新版的軟體、體驗新版的指令集還是足夠的。當模擬指令的過程多到影響效能時,也就是硬體該升級的時候了。

RustSBI與嵌入式Rust生態

在RustSBI的實現中,多次使用“embedded-hal”的實現完成編寫過程。“embedded-hal”是Rust嵌入式的外設規範,它對大量廠家的外設提供了軟體支援。只要廠家的硬體支援“embedded-hal”,只需要編寫部分抽象介面程式碼,RustSBI支援就可以快速地開發完成。
硬體處理核和SoC系統的開發也受益於設計好的RustSBI軟體架構。“RustSBI很快速地實現了模擬環境的雙核測試,”華中科技大學的一位社群貢獻者說,“這能為處理核提供豐富的測試環境,在開發高效能RISC-V處理核中非常重要。”
無論硬體和軟體,我們都樂於看到各個應用領域積極互動,嵌入式Rust生態的發展過程得到加快。“embedded-hal”本是裸機外設的標準,RustSBI將這個標準運用在引導軟體上,能加速裸機外設的開發和建設,也能更快適配SBI標準到平臺上。
借這個專案,我們很高興能參與嵌入式領域Rust語言的建設,希望這些微小的技術更新和迭代,最終能回饋到未來物聯網行業更輕便、更安全的開發體驗中去。(本文作者:洛佳)

免責宣告:本文素材來源網路,版權歸原作者所有。如涉及作品版權問題,請與我聯絡刪除。( 來源: IT創客學院

推薦閱讀

覺得不錯,點個贊吧

掃碼關注「 Rust程式設計指北