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

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

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

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

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

實(shí)戰(zhàn)一:運(yùn)行Linux的多核通過(guò)中斷通信


它的一般模式是:CPU0在DDR填入一段數(shù)據(jù),然后通過(guò)store指令寫(xiě)INTR的寄存器向CPU1發(fā)送中斷。

store數(shù)據(jù)

barrier?

store intr寄存器

中間應(yīng)該用什么barrier?我們來(lái)回憶一下三要素:

a. 誰(shuí)和誰(shuí)保序? -> CPU0和CPU1這2個(gè)observer之間看到保序

b. 在哪里保序? -> 只需要CPU1看到CPU0寫(xiě)入DDR和intr寄存器是保序的

c. 朝哪個(gè)方向保序? -> CPU0寫(xiě)入一段數(shù)據(jù),然后寫(xiě)入intr寄存器,只需要在st方向保序。

由此,我們得出結(jié)論,應(yīng)該使用的barrier是:dmb + ish + st,顯然就是smp_wmb。內(nèi)核代碼drivers/irqchip/irq-bcm2836.c也可以證實(shí)這一點(diǎn):



里面的注釋非常清晰,smp_wmb()保證了發(fā)起IPI之前,其他CPU應(yīng)該先觀察到內(nèi)存的數(shù)據(jù)在位。

現(xiàn)在我們把INTR換成gic-v3,就會(huì)變地tricky很多。gic-v3的IPI寄存器并不是映射到內(nèi)存空間的,而是一個(gè)sys寄存器,通過(guò)MSR來(lái)寫(xiě)入。前面我們說(shuō)過(guò)DMB只能搞定load/store之間,搞不定load/store與其他東西之間。

最開(kāi)始的gic-v3驅(qū)動(dòng)的作者其實(shí)也誤用了smp_wmb,造成了該驅(qū)動(dòng)的穩(wěn)定性問(wèn)題。于是Shanker Donthineni童鞋進(jìn)行了一個(gè)修復(fù),這個(gè)修復(fù)的commit如下:



這個(gè)commit解釋了我們不能用dmb搞定memory和sysreg之間的事情,于是這個(gè)patch替換為了更強(qiáng)力的wmb(),那么這個(gè)替換是正確的嗎?

我們還是套一下三要素:

a. 誰(shuí)和誰(shuí)保序? -> CPU0和CPU1保序

b. 在哪里保序? -> 只需要CPU1看到CPU0寫(xiě)入DDR后,再看到它寫(xiě)sysreg

c. 朝哪個(gè)方向保序? -> CPU0寫(xiě)入一段數(shù)據(jù),然后寫(xiě)入sysreg寄存器,只需要在st方向保序。

我們要進(jìn)行保序的是CPU0和CPU1之間,顯然他們屬于inner。于是,我們得出正確的barrier應(yīng)該是:dsb + ish + st,wmb()屬于用力過(guò)猛了,因?yàn)閣mb = dsb(st),保序范圍是full system?;诖?,筆者再次在主線內(nèi)核對(duì)Shanker Donthineni童鞋的“修復(fù)”進(jìn)行了“修復(fù)”,縮小屏障的范圍,提升性能:



實(shí)戰(zhàn)二:寫(xiě)入數(shù)據(jù)到內(nèi)存后,發(fā)起DMA

下面我們把需求變更為,CPU寫(xiě)入一段數(shù)據(jù)后,寫(xiě)Ethernet控制器與CPU之間的doorbell,發(fā)起DMA操作發(fā)包。


我們還是套一下三要素:

a. 誰(shuí)和誰(shuí)保序? -> CPU和EMAC的DMA保序,DMA和CPU顯然不是inner

b. 在哪里保序? -> 只需要EMAC的DMA看到CPU寫(xiě)入發(fā)包數(shù)據(jù)后,再看到它寫(xiě)doorbell

c. 朝哪個(gè)方向保序? -> CPU寫(xiě)入一段數(shù)據(jù),然后寫(xiě)入doorbell,只需要在st方向保序。

于是,我們得出正確的barrier應(yīng)該是:dmb + osh + st,為什么是dmb呢,因?yàn)閐oorbell也是store寫(xiě)的。我們來(lái)看看Yunsheng Lin童鞋的這個(gè)commit,它把用力過(guò)猛的wmb(),替換成了用writel()來(lái)寫(xiě)doorbell:


在ARM64平臺(tái)下,writel內(nèi)嵌了一個(gè)dmb + osh + st,這個(gè)從代碼里面可以看出來(lái):


同樣的邏輯也可能發(fā)生在CPU與其他outer組件之間,比如CPU與ARM64的SMMU:


實(shí)戰(zhàn)三:CPU與MCU通過(guò)共享內(nèi)存和hwspinlock通信

下面我們把場(chǎng)景變更為主CPU和另外一個(gè)cortex-m的MCU通過(guò)一片共享內(nèi)存通信,對(duì)這片共享內(nèi)存的訪問(wèn)透過(guò)硬件里面自帶的hwspinlock(hardware spinlock)來(lái)解決。


我們想象CPU持有了hwspinlock,然后讀取對(duì)方cortex-m給它寫(xiě)入共享內(nèi)存的數(shù)據(jù),并寫(xiě)入一些數(shù)據(jù)到共享內(nèi)存,然后解鎖spinlock,通知cortex-m,這個(gè)時(shí)候cortex-m很快就可以持有鎖。

我們還是套一下三要素:

a. 誰(shuí)和誰(shuí)保序? -> CPU和Cortex-M保序

b. 在哪里保序? -> CPU讀寫(xiě)共享內(nèi)存后,寫(xiě)入hwspinlock寄存器解鎖,需要cortex-m看到同樣的順序

c. 朝哪個(gè)方向保序? -> CPU讀寫(xiě)數(shù)據(jù),然后釋放hwspinlock,我們要保證,CPU的寫(xiě)入對(duì)cortex-m可見(jiàn);我們同時(shí)要保證,CPU放鎖前的共享內(nèi)存讀已經(jīng)完成,如果我們不能保證解鎖之前CPU的讀已經(jīng)完成,cortex-m很可能馬上寫(xiě)入新數(shù)據(jù),然后CPU讀到新的數(shù)據(jù)。所以這個(gè)保序是雙向的。


里面用的是mb(),這是一個(gè)dsb+full system+ld+st,讀代碼的注釋也是一種享受。

實(shí)戰(zhàn)四:S MMU與CPU通過(guò)一個(gè)queue通信

現(xiàn)在我們把場(chǎng)景切換為,SMMU與CPU之間,通過(guò)一片放入共享內(nèi)存的queue來(lái)通信,比如SMMU要通知CPU一些什么event,它會(huì)把event放入queue,放完了SMMU會(huì)更新另外一個(gè)pointer內(nèi)存,表示queue增長(zhǎng)到哪里了。


然后CPU通過(guò)這樣的邏輯來(lái)工作


這是一種典型的控制依賴(lài),而控制依賴(lài)并不能被硬件自動(dòng)保序,CPU完全可以在if(pointer滿(mǎn)足什么條件)滿(mǎn)足之前,投機(jī)load了queue的內(nèi)容,從而load到了錯(cuò)誤的queue內(nèi)容。

我們還是套一下三要素:

a.誰(shuí)和誰(shuí)保序? -> CPU和SMMU保序

b.在哪里保序? -> 要保證CPU先讀取SMMU的pointer后,再讀取SMMU寫(xiě)入的queue;

c.朝哪個(gè)方向保序? -> CPU讀pointer,再讀queue內(nèi)容,在load方向保序

于是,我們得出正確的barrier應(yīng)該是:dmb + osh + ld,我們來(lái)看看wangzhou童鞋的這個(gè)修復(fù):


ARM64平臺(tái)的readl()也內(nèi)嵌了dmb + osh + ld屏障。顯然這個(gè)修復(fù)的價(jià)值是非常大的,這是一個(gè)由弱變強(qiáng)的過(guò)程。前面我們說(shuō)過(guò),由強(qiáng)變?nèi)跏切阅軉?wèn)題,而由弱變強(qiáng)則往往修復(fù)的是穩(wěn)定性問(wèn)題。也就是這種用錯(cuò)了弱barrier的場(chǎng)景,往往bug非常難再現(xiàn),需要很長(zhǎng)時(shí)間的測(cè)試才再現(xiàn)一次。

實(shí)戰(zhàn)五:修改頁(yè)表PTE后刷新tlb

現(xiàn)在我們的故事演變成了,CPU0修改了頁(yè)表PTE,然后通知其他所有CPU,PTE應(yīng)該被更新,其他CPU需要刷新TLB。


它的一般流程是CPU調(diào)用set_pte_at()修改了內(nèi)存里面的PTE,然后進(jìn)行tlbi等動(dòng)作。這里就變地非常復(fù)雜了:


我們看看barrier1,它在屏障store和tlbi之間,由于二者一個(gè)是狗狗,一個(gè)是消殺煙霧,顯然不能是dmb,只能是dsb;我們需要CPU1看到set_pte_at的動(dòng)作先于tlbi的動(dòng)作,所以這個(gè)屏障的范圍應(yīng)該是ISH;由于屏障需要保障的是set_pte_at的store,而不是load,所以方向是st,由此我們得出第一個(gè)barrier應(yīng)該是:dsb + ish + st。

詳細(xì)的流程我們可以參考下如下代碼:


barrier2用的是dsb(ish),它保證了inner內(nèi)的CPU都先看到了tlbi的完成;barrier3用的isb(),它保證了CPU fetch到PTE修正之后的指令。

結(jié)語(yǔ)

本文對(duì)Linux內(nèi)核的內(nèi)存屏障的原理和用法進(jìn)行一些分析和實(shí)戰(zhàn),它并未覆蓋內(nèi)存屏障的全部知識(shí),但是應(yīng)該可應(yīng)付工程里面90%以上的迷惘和困惑。


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






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

分享到微博請(qǐng)遵守國(guó)家法律
瑞丽市| 临泽县| 天等县| 利辛县| 顺昌县| 巴塘县| 沙湾县| 平武县| 罗城| 安顺市| 莲花县| 萍乡市| 郯城县| 靖边县| 冀州市| 达拉特旗| 普格县| 九龙坡区| 麻江县| 光山县| 南丹县| 台江县| 巫山县| 乌兰察布市| 田阳县| 丹巴县| 伊川县| 台前县| 遂川县| 娄底市| 五指山市| 富民县| 奉贤区| 灌南县| 西丰县| 诸城市| 遂昌县| 右玉县| 牡丹江市| 南澳县| 大丰市|