前言
受到2022年“谷歌使用Rust重寫Android系統(tǒng)且所有Rust代碼的內(nèi)存安全漏洞為零” [1] 的啟發(fā),最近筆者懷著濃厚的興趣也順應(yīng)Rust 的潮流,嘗試著將一款C語言開發(fā)的基礎(chǔ)軟件轉(zhuǎn)化為 Rust 語言。本文的主要目的是通過記錄此次轉(zhuǎn)化過程中遇到的比較常見且有意思的問題以及解決此問題的方法與大家一起做相關(guān)的技術(shù)交流和討論。
問題描述
在項目轉(zhuǎn)化過程中我遇到了一個與 CAS (Compare and Swap) [2] 操作實現(xiàn)相關(guān)的問題,在計算機(jī)科學(xué)中CAS 是多線程/協(xié)程中用于實現(xiàn)同步的原子指令。該軟件針對不同的芯片平臺,通過在C語言中根據(jù)芯片平臺的類別進(jìn)行宏定義并嵌入相應(yīng)的匯編代碼來實現(xiàn)CAS操作。我知道不同芯片平臺對應(yīng)的 CAS 操作的匯編代碼是不一樣的 [3],例如:
x86-64 (Intel/AMD) 需要類似如下匯編代碼塊:
lockcmpxchgq[destination],rdx
ARM 需要類似如下匯編代碼塊:
ldrexr1,[destination] cmpr1,r2 strexeqr1,r2,[destination]
PowerPC 需要類似如下匯編代碼塊:
lwarxr0,0,destination cmpwr0,r1 bneretry;branchifnotequal stwcx.r2,0,destination bneretry;branchifstorefailed
然而如下面的代碼片段所示,即使該軟件使用相同的Intel x86芯片平臺,但是在不同的操作系統(tǒng)平臺上其實現(xiàn)的匯編指令也有可能是不一樣的。
C頭文件中 cas_operation.h 的部分代碼如下:
#ifdefined(__i386)||defined(__x86_64__)||defined(__sparcv9)||defined(__sparcv8plus) typedefunsignedintslock_t; #else typedefunsignedcharslock_t; #endif externslock_tmy_atomic_cas(volatileslock_t*lock,slock_twith,slock_tcmp); #defineTAS(a)(my_atomic_cas((a),1,0)!=0) #endif
對應(yīng)實現(xiàn)的x86匯編文件 cas_operation.s 的部分代碼如下:
my_atomic_cas: #ifdefined(__amd64) movl%edx,%eax lock cmpxchgl%esi,(%rdi) #else movl4(%esp),%edx movl8(%esp),%ecx movl12(%esp),%eax lock cmpxchgl%ecx,(%edx) #endif Ret
眾所周知雖然Rust也有宏定義的包 Macros,但是目前也與C語言的有不小的差別。因此,在做轉(zhuǎn)化的過程中如何做到芯片平臺和操作系統(tǒng)級別的代碼兼容則是我遇到的最大挑戰(zhàn)。
解決方案
想到兩個解決方案:
使用asm! 宏去處理不同芯片平臺的匯編代碼
使用 Rust代碼對特定的操作進(jìn)行針對性的實現(xiàn)
第一種方案比較簡單,只需要在代碼中使用std::asm 包,然后使用 asm! 宏(類似 println! 宏)去包裹不同平臺的匯編代碼即可,這也是最直接最容易想到的解決方案,而且無需考慮具體的匯編操作實現(xiàn)的指令和代碼。但是這方法雜糅了很多的不同平臺的匯編代碼,同時需要Rust做很多額外的平臺相關(guān)的邏輯控制,對這些控制邏輯部分代碼的維護(hù)也是一個持久且復(fù)雜的工作。比如對新的平臺指令 RSIC-V 的支持也要納入其中。
第二種方案則需要考慮具體的操作邏輯,然后通過Rust代碼去實現(xiàn)與匯編指令相同的邏輯,雖然有較大的工作量,但是這種方案可以消除由于芯片和系統(tǒng)平臺不同帶來的各種匯編代碼實現(xiàn)的差異。關(guān)于第一種方案的實現(xiàn)讀者可以參照文檔 Inline assembly [4] 中去做。針對 CAS 操作的第二種方案的實現(xiàn)則是本文主要提出的一種解決方案,而本文以類似Rust u32類型的 CAS 操作為例子實現(xiàn)其代碼,在 my_compare_and_swap.rs 中會有如下代碼段實現(xiàn):
usestd::{AtomicU32,Ordering}; pubtypeuint32=libc::c_uint; pubstructmy_atomic_uint32{ pubvalue:uint32, } implmy_atomic_uint32{ #[inline] pubfncompare_and_swap(&self,expected:uint32,newval:uint32)->bool{ letatomic_ptr=selfas*constmy_atomic_uint32as*constAtomicU32; letatomic=unsafe{&*(atomic_ptr)}; atomic.compare_and_swap(expected,newval,Ordering::SeqCst)==expected } } pubfnmy_compare_and_swap_u32_impl( mutptr:*mutpg_atomic_uint32, mutexpected:*mutuint32, mutnewval:uint32, )->bool{ letatomic=&*ptr; atomic.compare_and_swap(*expected,newval) }
下面我來解釋一下上面的代碼。由于是從 C 轉(zhuǎn)到 Rust,因此我使用了 Rust 的 libc 包來自定義 uint32類型。然后通過自定義struct my_atomic_uint32 來對uint32進(jìn)行CAS原子操作的包裹,同時對于此 struct實現(xiàn)其 inline 的compare_and_swap 操作函數(shù)。在該函數(shù)的實現(xiàn)中最關(guān)鍵的是將my_atomic_uint32的實體轉(zhuǎn)化為一個AtomicU32的常量(注意需要在 Rust 代碼文件開頭使用 std::{AtomicU32, Ordering} [5]),然后通過調(diào)用 AtomicU32 的compare_and_swap 來最終實現(xiàn) uint32 的 CAS 操作。另外對于Ordering::SeqCst內(nèi)存順序 [6] 的選擇也是比較考究的一個話題,這里我使用 SeqCst實際上是一個在保證正確的情況下不太考慮效率優(yōu)化問題的選項。代碼的最后my_compare_and_swap_u32_impl 則是對外使用的 u32 的 CAS 操作(事實上該軟件主要也是需要實現(xiàn) uint32 的 CAS 操作)。
結(jié)論
在本例中由于剛好有對應(yīng)AtomicU32的CAS 實現(xiàn),而且軟件中整個原子同步的代碼部分都是使用uint32進(jìn)行的比較交換操作,因此我選擇第二種方案則是最佳選擇。由此可知上述的兩種解決方案其實是各有利弊的,我必須結(jié)合實際的應(yīng)用場景才能去做決定。那么這里有一個問題,如果需要對許多數(shù)據(jù)類型(比如uint32, int32, uint64, int64, float, float32, float64……)進(jìn)行比較交換操作,又該做何種選擇呢?這也許是仁者見仁智者見智的。
關(guān)于作者
張懷龍曾就職于阿爾卡特朗訊,百度,IBM等企業(yè)從事云計算研發(fā)相關(guān)的工作。目前就職于 Intel 中國,擔(dān)任云原生開發(fā)工程師并致力于云原生、服務(wù)網(wǎng)格等技術(shù)領(lǐng)域研究實踐,也是Istio 的maintainer的開發(fā)者。曾多次在 KubeCon、ServiceMeshCon、IstioCon、GOTC 和 InfoQ/QCon 等大會上發(fā)表演講。
審核編輯:黃飛
-
ARM
+關(guān)注
關(guān)注
134文章
9306瀏覽量
374987 -
Android
+關(guān)注
關(guān)注
12文章
3963瀏覽量
129543 -
C語言
+關(guān)注
關(guān)注
180文章
7630瀏覽量
140302 -
匯編代碼
+關(guān)注
關(guān)注
0文章
24瀏覽量
7621 -
Rust
+關(guān)注
關(guān)注
1文章
233瀏覽量
6956
原文標(biāo)題:一次Rust重寫基礎(chǔ)軟件的實踐(一)
文章出處:【微信號:Rust語言中文社區(qū),微信公眾號:Rust語言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Rust GUI實踐之Rust-Qt模塊
Rust的 match 語句用法
Rust的多線程編程概念和使用方法
如何編寫高性能的Rust代碼

在Rust代碼中加載靜態(tài)庫時,出現(xiàn)錯誤 ` rust-lld: error: undefined symbol: malloc `怎么解決?
微軟開發(fā)基于Rust的新編程語言,將很快開源
Chromium正式開始支持Rust
Cloudflare用Rust重寫Nginx C模塊,構(gòu)建沒有Nginx的未來
如何在同步的Rust方法中調(diào)用異步代碼呢?
Rust重寫的LSP:KCL IDE 插件的功能介紹與設(shè)計解析

Windows 11初嘗Rust,36000行內(nèi)核代碼已重寫!

rust語言基礎(chǔ)學(xué)習(xí): rust中的錯誤處理
Rust的內(nèi)部工作原理

一次Rust重寫基礎(chǔ)軟件的實踐
[鴻蒙]OpenHarmony4.0的Rust開發(fā)
![[鴻蒙]OpenHarmony4.0的<b class='flag-5'>Rust</b>開發(fā)](https://file1.elecfans.com/web2/M00/C1/DB/wKgaomXbKX-AAe6rAADEW5Pyw8c913.png)
評論