漫談C變量——天然原子性是怎麼回事?

【寫在前面的話】

20世紀初葉,人們曾經一度認爲原子是物質的最小組成單位,原子不可再分。雖然很快人們就發現這是一個謬誤——原子不僅可以再分,由質子、中字、電子組成,事實上這些微觀粒子仍然是可以繼續分割的——但計算機科學借用了“原子不可再分”的說法,提出了操作原子性(Atomicity)的概念,即:

對一個由多個步驟構成的操作來說,如果當步驟執行時不能夠被打斷,我們就說該操作具有原子性——是一個原子操作;反之該操作不具備原子性,不是原子操作。

簡單來說,所謂原子操作就是當這個操作執行的時候,其他任務沒法打斷它,就好像原子無法再分一樣——如果存在任務有能力將當前的操作從中間一刀切開,我們就說這個操作不是原子操作。很容易想到,當多個任務共享統一資源時,對資源訪問操作的原子性是非常值得討論的。

【從理解天然原子性開始】


通常我們所說的一個內核是8位、16位還是32位並不是指地址總線的寬度,而是ALU操作數的位寬,習慣上又稱之爲字長。對8位機來說,ALU一次可以進行8位運算,當我們針對一個32位的數據進行操作時就要拆成4次。對32位機來說,ALU一次就可以完成32位的運算。比較二者的區別,除了操作次數不同以外還隱含着原子性的信息,即對8位機來說,操作32位數據要分4個步驟來完成,這期間如果發生中斷/異常,操作是會被打斷的,因此不具備原子性;對32位機來說,由於ALU一次運算的過程是不可打斷的,因而針對32位數據的運算天然具有原子性,我們稱之爲天然原子性。

ALU對相同字長數據的處理具有天然原子性。也就是說,16位機對16位數據的處理具有天然原子性;64位機對64位數據的處理具有天然原子性。實際應用中,天然原子性的體現還要受到數據讀寫對齊方式的限制:爲了提升效率,總線往往規定,數據的讀寫地址需要對齊到數據的寬度,比如對16位數據的讀寫,其目標地址必須對齊到雙字節;對32位數據的讀寫,其地址必須對齊到四字節。當且僅當數據的地址對齊到數據的寬度時,天然原子性才能得到表達。

32位機爲例,對於1所示的情形,由於32位數據的地址並未對齊到字,內核會將針對該數據的讀寫拆解成四個步驟:1)針對變量的第一個字節(8位)發起一個8位的讀取;2)針對變量的第二和第三個字節發起一個16位的讀取(它們正好對齊到了半字);3)針對變量的最後一個字節發起一個8位的讀取;4)最後將三次獲得的結果合併成一個完整的32位數據——針對該變量的操作由多個步驟構成,且中途有可能被打斷,因而不具備原子性。

基於同樣的原因,很多編譯器爲了提高內核的訪問效率,在默認情況下,對結構體的變量採取了同樣的策略——每個成員的地址都各自對齊到了與自己類型相同大小的位置上(如2所示)——具體內容可以通過閱讀《漫談C變量——對齊》系列文章來了解,這裏就不再贅述。

實際上,很多處理器的的ALU不光對相同字長數據的訪問具有天然原子性,對小於這一字長的數據類型也具有天然原子性。比如Cortex-M的ALU不光對32位的整型變量的訪問具有原子性,對16位甚至是8位變量的訪問也具有原子性。事實上,這一特性對很多8位機、16位機和64位機都一樣適用。基於這一特性,我們基本上可以放心的認爲uint8_t和int8_t在幾乎所有8位及其以上的系統中都是具有天然原子性的——原因很簡單,不光因爲8bit的寬度小於等於ALU的寬度,還因爲,8bit的對齊方式是字節、在所有支持單字節訪問的Memory系統中,基本上可以認爲是永遠對齊的。這就是爲什麼布爾類型(bool)幾乎在所有的平臺中都“可以”被定義爲8bit(uint8_t)。

那麼,是不是一個變量擁有了“天然原子性”,當多個任務都要對其發起訪問時,我們就可以高枕無憂了呢?答案是否定的。關於這一點,我們將在下篇文章中把每一種情況都列舉出來——詳細的爲您分析和展開。


1.嵌入式工程師心目中的微內核架構~

2.do{}while(0)只執行一次無意義?你可能真的沒理解!

3.BUG 終結者,現場抓獲!|顛覆認知

4.MATLAB 只是冰山一角!海外資深程序員聊被卡脖子……

5.“STM32CubeMonitor” 拍了拍你

6.如何把C++的源代碼改寫成C代碼?

免責聲明:本文系網絡轉載,版權歸原作者所有。如涉及作品版權問題,請與我們聯繫,我們將根據您提供的版權證明材料確認版權並支付稿酬或者刪除內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章