最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

原理和實戰(zhàn)解析Linux中如何正確地使用內(nèi)存屏障(上)

2022-10-28 17:40 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

圈里流傳著一句話“珍愛生命,遠(yuǎn)離屏障”,這足以說明內(nèi)存屏障是一個相當(dāng)晦澀和難以準(zhǔn)確把握的東西。使用過弱的屏障,會導(dǎo)致軟件不穩(wěn)定。使用過強(qiáng)的屏障,會引起性能問題。所以工程上,追求恰到好處、不偏不倚的屏障。本文力求用最淺顯的語言,講清楚內(nèi)存屏障最晦澀的道理,本文也會給出五個工程案例,這些案例皆見于開源的代碼,不涉及任何組織和個人未公開的技術(shù)。

一、引子

我國古代著名程序猿韓愈曾經(jīng)寫下一個名為《春雪》的函數(shù):

新年都未有芳華,二月初驚見草芽。

白雪卻嫌春色晚,故穿庭樹作飛花。

這段代碼講述了一個關(guān)于memory reorder的故事,在計算機(jī)世界里面,冬天和春天并沒有明確的界限,明明已經(jīng)是春天了,但是還飄著冬天的雪。

下面我們看另外一段程序:



我們能確保c = 4嗎?實際上,任何一個角度都確定不了。比如CPU0上面a = 3是“下雪”,flag = 1是“春天”,a=3看似在flag=1之前,實際可能由于memory reorder的原因發(fā)生在flag = 1之后,所以flag即便已經(jīng)等于1,a也不一定等于3。

我們再退一萬步講,哪怕CPU0上面確實確保了春天不下雪,flag=1的時候a 100%就等于了3,那CPU1那邊就萬無一失了嗎?答案也是否定的,因為,在CPU1上面,即便我們的代碼是if(flag==1),接下來才做c=a+1,我們也不能確保a的load一定發(fā)生在flag==1之后。別忘了,CPU1會投機(jī)執(zhí)行,比如碰到if(flag==1)這種條件,CPU可能直接忽略,不管三七二十一,還是可能先執(zhí)行 load a, a+1的動作,然后反過來發(fā)現(xiàn)flag等于1,然后我認(rèn)為我的投機(jī)是成功的;即便投機(jī)失敗,CPU只需要保證load a, a+1的這些指令不retired就好。所以CPU1的load a, a+1完全可能發(fā)生在flag確切地等于1之前,因此即便CPU0保序了,CPU1仍然不能確保c=4。

我們看看CPU1在投機(jī)成功時候的行為邏輯和思想情感:

  1. flag==1嗎?

  2. 不知道??!我現(xiàn)在還沒讀出flag呢!

  3. 管它呢,先假裝flag==1吧,投機(jī)一把,執(zhí)行l(wèi)oad a, 把a(bǔ)+1看看

4.flag==1嗎?哇,它真地等于1,太爽了,load a和a+1已經(jīng)做完了。

如果投機(jī)失敗了呢?

  1. flag==1嗎?

  2. 不知道?。∥椰F(xiàn)在還沒讀出flag呢!

  3. 管它呢,先假裝flag==1吧,投機(jī)一把,執(zhí)行l(wèi)oad a, 把a(bǔ)+1看看

  4. flag==1嗎?Oh,shit,它不等于1,load a, a+1白做了.....

這就是弱序系統(tǒng)的典型特點。請問CPU為什么要這么“混亂”?這正是現(xiàn)代CPU為了保證高效執(zhí)行厲害的地方,但是也引入軟件使用上的復(fù)雜度。這種復(fù)雜度,類似于宋代著名程序媛李清照的函數(shù)《聲聲慢·尋尋覓覓》:“尋尋覓覓,冷冷清清,凄凄慘慘戚戚。乍暖還寒時候,最難將息。三杯兩盞淡酒,怎敵他、晚來風(fēng)急?雁過也,正傷心,卻是舊時相識。滿地黃花堆積,憔悴損,如今有誰堪摘?守著窗兒,獨自怎生得黑。梧桐更兼細(xì)雨,到黃昏、點點滴滴。這次第,怎一個愁字了得!”請問李清照童鞋說的究竟是春天還是秋天還是春天呢?據(jù)說至今也沒有人能夠解密。僅憑“乍暖還寒”一定會覺得是初春,但是你再繼續(xù)看到“雁過也”、“滿地黃花堆積”,這顯然又不是春天的景象。

罷了罷了,這一切都不重要了,重要的是,四季并不分明,四季沒有明確的界限。這是我們要牢記的第一個point!

二、屏障

正是因為四季沒有明確的界限,所以當(dāng)我們希望看到明確的順序的時候,我們希望引入一道屏障。讓冬天跑不到春天,讓春天跑不過去冬天。

典型的ARM64有這么幾種屏障:

a. DMB:Data Memory Barrier

b. DSB:Data Synchronization Barrier

c. ISB:Instruction Synchronization Barrier

d. LDAR(Load-Acquire)/STLR(Store-Release)

我們隨便打開ARM的手冊,看一個DMB的定義:

The Data Memory Barrier (DMB) prevents the reordering of specified explicit data accesses across the barrier instruction. All explicit data load or store instructions, which are executed by the PE in program order before the DMB, are observed by all Observers within a specified Shareability domain before the data accesses after the DMB in program order.

碼農(nóng)的內(nèi)心是崩潰的,人生已經(jīng)這么悲催了,你為什么還要拿這樣的繞口令來折磨我?什么叫“are observed by all Observers”?

下面我們給大家講述2只狗狗出家門的故事:



上圖的2只狗,首先在一個inner shareable domain里面,比如是自己的家門里面;然后是在一個outer shareable domain里面,比如是小區(qū)的出口;最后在太陽系里面。這2只小狗,出每一道門,都有observer可以看見它,有的observer是inner的(observer1),有的observer是outer的(observer2),有的observer屬于full system,比如天上的嫦娥(observer3)。

現(xiàn)在我們提出如下需求:

a. 黃狗狗出門后白狗狗出門。

b. 黃狗狗和白狗狗出門后,放煙霧消殺。

當(dāng)我們提出這樣的需求的時候,我們看3樣?xùn)|西:

1. 我們首先要看需要保證順序的2個事物的特征

在需求1里面,是2只特征一樣的東西,都是狗狗;在需求2里面,兩個事物之間一個是狗狗,一個是消殺的煙霧,顯然不是同類。

狗狗在硬件和Linux軟件層面上,可以理解為針對內(nèi)存的memory load/store指令;放煙霧,這種不屬于memory的load/store,比如你執(zhí)行的是tlbi、add加法或者寫的是ARM64系統(tǒng)寄存器(MSR指令),則顯然不屬于memory load/store。

這里就涉及到DMB和DSB的一個本質(zhì)區(qū)別,DMB針對的是memory的load/store之間;DSB強(qiáng)調(diào)的是同類或不同類事物的先后完成。

所以對于這個場景,我們正確的屏障是:

load黃狗狗

dmb ??

load白狗狗

dsb ??

MSR 消毒煙霧

第一個是dmb,第2個是dsb。上面dmb和dsb后面都加了兩個“?”,證明這里有情況,什么情況?接著看。

2. 其次我們要看保序的observer在哪里

比如是家門口的小姑娘observer1(ISH,inner shareable)、還是小區(qū)門口的小姑娘observer2(OSH,outer shareable),還是天上的嫦娥呢(SY, Full System)?如果只是observer1看到黃狗狗先出門,白狗狗再出門,延遲顯然更小。在越大的訪問范圍保序,硬件的延遲越大。假設(shè)我們現(xiàn)在的保序需求是:

a. 小區(qū)門口(outer shareable)的observer2先看到黃狗狗出來,再看到白狗狗出來;

b. 家門口(inner shareable)的observer1先看到兩只狗狗出來,再看到放煙霧。

對于這個場景,我們正確的屏障是:

load黃狗狗

dmb OSH?

load白狗狗

dsb ISH?

MSR 消毒煙霧

在DMB后面我們跟的是OSH,在DSB后面我們跟的是ISH,是因為observer的位置不一樣。注意,能用小observer的不用大observer。小區(qū)門口的observer,沒有透視眼+望遠(yuǎn)鏡,是看不到你家門口的狗狗的。



在一個典型的ARM64系統(tǒng)里面,運行Linux的各個CPU在一個inner;而GPU,DMA和CPU則同位于一個outer;當(dāng)然還有可能孤懸海外的一個Cortex-M3的MCU,盡管可以和CPU以某種方式通信,但是不太參與inner以及outer里面的總線interconnect。

3. 最后我們保序的方向是什么

前面我們只關(guān)心狗狗的出門(load),假設(shè)兩只狗狗都是進(jìn)門(store)呢?或者我們現(xiàn)在要求黃狗狗先進(jìn)門,白狗狗再出門呢?這個時候,我們要約束屏障的方向。

比如下面的代碼,約束了observer1(inner)先看到黃狗狗出門,再看到白狗狗出門:

load黃狗狗

dmb ISHLD

load白狗狗

比如下面的代碼,約束了observer2(outer)先看到黃狗狗進(jìn)門,再看到白狗狗進(jìn)門:

store黃狗狗

dmb OSHST

store白狗狗

這里我們看到一個用的是LD,一個用的是ST。我們再來看幾個栗子,它們都是干什么的:

a. A(load); dmb ISHLD; B; C(load/store)

保證Inner內(nèi),A和C的順序,只要A是load,無論C是load還是store;如果B既不是load也不是store,而是別的性質(zhì)的事情,則dmb完全管不到B;

b. A(load); dsb ISHLD; B; C(load/store)

保證Inner內(nèi),A和C的順序,只要A是load,無論C是load還是store;無論B是什么事情,inner都先到干完了A,再干B(注意這里是dsb啊,親)。

c. A(store); dmb ISHLD; B; C(store)

A,B,C三個東西完全亂序,因為dmb約束不了性質(zhì)不同的B,“LD”約束不了A和C的store順序。

d. A(store); dmb ISHST; B; C(store)

ST約束了A和C 2個東西在inner這里看起來是順序的,因為dmb約束不了B,所以B和A、C之間亂序。

注意上述4個屏障,由于都是ISH,故都不能保證observer2和observer3的順序,在observer2和3眼里,上述所有屏障,A、B、C都是亂序的。

另外,如果無論什么方向,我們都要保序,我們可以去掉LD和ST,這樣的保序方向是any-any。

到這里我們要牢記3個point:誰和誰保序;在哪里保序;朝哪個方向保序。

由此,我們可以清楚地看到DMB和DSB的區(qū)別,一個是保序內(nèi)存load,store;一個是保序內(nèi)存load,store + 其他指令。ISB的性質(zhì)會有很大的不同,ISB主要用于刷新處理器中的pipeline,因此可確保在 ISB 指令完成后,才從內(nèi)存系統(tǒng)中fetch位于該指令后的其他指令。比如你更新了代碼段的PTE,需要重新取指。而LDAR(Load-Acquire)/STLR(Store-Release)則是比較新的one-way barrier。如下圖,LDAR之前的LDR、STR可以跑到LDAR之后,但是不能跑到STLR之后;STLR之后的LDR,STR可以跑到STLR之前,但是不能跑到LDAR之前。所以STLR堵住了前面的往后面跑,LDAR堵住了后面的往前面跑。下面夾在LDAR和STLR之間的LDR,STR由于兩邊都是單向車道,而且都與它的行進(jìn)方向相反,所以它夾在死胡同里,哪里也去不了。



注意,LDAR和STLR與前面的dmb, dsb有本質(zhì)的不同,它本身是要跟地址的。比如現(xiàn)在家里有3只狗狗:



假設(shè)我們現(xiàn)在的要求是黃狗狗一定要在紅尾哈巴狗之后出門,而白狗狗什么時候出我們都不在乎,則代碼邏輯為:

ldr 白狗狗

ldar 紅尾哈巴狗

ldr 黃狗狗

黃狗狗被紅尾哈巴狗的ldar擋住了,而白狗狗沒有被任何東西擋住,它可以:

  1. 第一個出門

  2. 紅尾哈巴狗出門后,黃狗狗出門前出門

  3. 最后一個出門。

三、API

在Linux內(nèi)核,有4組經(jīng)典API:

SMP屏障



此屏障主要用于運行Linux的多個核之間對內(nèi)存訪問的保序,所以它主要是dmb,它是ish,通過ld、st來區(qū)分保序的方向。

DMA屏障



此屏障主要用于運行Linux的多個核與DMA引擎之間的保序,所以它主要是dmb,它是osh,通過ld、st來區(qū)分保序的方向。

屏障



非常嚴(yán)格的完成屏障,mb()保證了前面的指令的完成,前面的指令不必是load,store,比如可以是TLBI。dsb(ld)、dsb(st)則要弱一點,分別保證前面的load,store執(zhí)行完了才執(zhí)行后面的指令。

load_acquire/store_release

邏輯通常是一種成對的__smp_load_acquire()、__smp_store_release()邏輯,特別適合2個或者多個CPU之間的鏈?zhǔn)奖P?。在ARM64里面用的是stlr,ldar實現(xiàn)如下:




比如,下面的代碼邏輯,保證了CPU0、CPU1、CPU2這3個CPU在鏈條上保序訪問:




中間循環(huán)了一個鏈條邏輯,從而保證了這三個CPU中間內(nèi)存訪問的一些保序:




下面我們進(jìn)入五個工程實戰(zhàn),“熟讀唐詩三百首,不會吟詩也會吟”,最后我們會形成針對內(nèi)存屏障正確用法的語感,而全然忘記語法。

篇幅有限,下文繼續(xù)講解

原文作者:內(nèi)核工匠



原理和實戰(zhàn)解析Linux中如何正確地使用內(nèi)存屏障(上)的評論 (共 條)

分享到微博請遵守國家法律
博白县| 巴中市| 斗六市| 故城县| 高淳县| 雷波县| 克山县| 石门县| 盈江县| 新竹县| 通榆县| 白沙| 松原市| 广灵县| 金塔县| 门头沟区| 彰化市| 普陀区| 林口县| 德兴市| 鄄城县| 南宫市| 佛山市| 北宁市| 新龙县| 上饶市| 新密市| 观塘区| 富宁县| 徐汇区| 兴隆县| 阳新县| 时尚| 竹溪县| 和顺县| 方山县| 鹿邑县| 安多县| 延吉市| 博乐市| 永州市|