1 存儲(chǔ)訪問(wèn)一致性問(wèn)題介紹
當(dāng)存儲(chǔ)系統(tǒng)中引入了cache和寫緩沖區(qū)(Write Buffer)時(shí),同一地址單元的數(shù)據(jù)可能在系統(tǒng)中有多個(gè)副本,分別保存在cache、Write Buffer及主存中,如果系統(tǒng)采用了獨(dú)立的數(shù)據(jù)cache和指令cache,同一地址單元的數(shù)據(jù)還可能在數(shù)據(jù)cache和指令cache中 有不同的版本 。
位于不同物理位置的同一地址單元的數(shù)據(jù)可能會(huì)不同 ,使得數(shù)據(jù)讀操作可能得到的不是系統(tǒng)中“最新的數(shù)值”,這樣就帶來(lái)了存儲(chǔ)系統(tǒng)中數(shù)據(jù)的一致性問(wèn)題。
- (1)cache(介于CPU和DDR) 解決CPU速度和內(nèi)存速度的速度差異問(wèn)題(CPU存取數(shù)據(jù)的速度非???,而內(nèi)存比較慢),內(nèi)存中被CPU訪問(wèn)最頻繁的數(shù)據(jù)和指令被復(fù)制到CPU中的緩存,CPU訪問(wèn)數(shù)據(jù)直接去緩存中訪問(wèn)就行了。
- (2)cache一致性 CPU的訪問(wèn)數(shù)據(jù)與DDR的數(shù)據(jù)沒(méi)有同步。
- (3)CPU、Cache以及DDR之間訪問(wèn)關(guān)系 Cache是一個(gè)介于CPU以及DDR(DRAM)之間的一個(gè)高速緩存(一般好像是SRAM),在處理器內(nèi)部,讀寫速度較DDR高,但是低于CPU的速度。假如沒(méi)有Cache,直接訪問(wèn)DDR,CPU的速度遠(yuǎn)遠(yuǎn)高于DDR,那么CPU就需要等待DDR的數(shù)據(jù)到來(lái),才能做其他事情,就會(huì)造成CPU使用效率較低。使用Cache之后,提前將DDR的數(shù)據(jù)緩存到Cache中,如果恰好CPU訪問(wèn)DDR的數(shù)據(jù)在Cache中有,那么CPU拿到數(shù)據(jù)的時(shí)間將更短,處理效率將大大增加。但是同時(shí)也會(huì)造成一致性問(wèn)題,即CPU的訪問(wèn)的數(shù)據(jù)(Cache)與DDR的數(shù)據(jù)沒(méi)有同步,造成執(zhí)行錯(cuò)誤。假設(shè)一種真實(shí)情況,CPU要訪問(wèn)DDR中的一塊數(shù)據(jù),那么這塊數(shù)據(jù)會(huì)放在Cache中,之后DMA控制器直接將外設(shè)的數(shù)據(jù)放在DDR中,更新了剛剛的那一塊CPU要訪問(wèn)的數(shù)據(jù),此時(shí)CPU要獲取數(shù)據(jù)進(jìn)行處理,還是拿著Cache中未更新的數(shù)據(jù)(沒(méi)有立馬反映到DDR中),就會(huì)造成一致性問(wèn)題。
- (4)解決Cache一致性問(wèn)題 將Cache中的數(shù)據(jù)清空,或者將DDR與Cache的數(shù)據(jù)同步。(使用CacheFlush和Cache Invalidate操作,CacheFlush把Cache里的數(shù)據(jù)清空,將Cache內(nèi)容推到DDR中;而Cache Invalidate表示當(dāng)場(chǎng)宣布Cache內(nèi)容無(wú)效,需要從DDR中重新加載數(shù)據(jù),即把數(shù)據(jù)從DDR中拉到Cache中。)
其實(shí)涉及到了緩存,不只是邏輯上的存儲(chǔ),在互聯(lián)網(wǎng),分布式架構(gòu),在現(xiàn)在的大數(shù)據(jù)環(huán)境下,也會(huì)面臨數(shù)據(jù)一致性的問(wèn)題。
怎么保持這個(gè)數(shù)據(jù)的一致性,不要讓彼此讀到了臟數(shù)據(jù),是一個(gè)很重要的事情。
在ARM存儲(chǔ)系統(tǒng)中,數(shù)據(jù)不一致的問(wèn)題則 需要通過(guò)程序設(shè)計(jì)時(shí)遵守一定的規(guī)則來(lái)保證 ,這些規(guī)則說(shuō)明如下。
1.1 地址映射關(guān)系變化造成的數(shù)據(jù)不一致性
當(dāng)系統(tǒng)中使用了MMU時(shí),就建立了虛擬地址到物理地址的映射關(guān)系, 如果查詢cache時(shí)進(jìn)行的相連比較使用的是虛擬地址 ,則當(dāng)系統(tǒng)中虛擬地址到物理地址的映射關(guān)系發(fā)生變化時(shí),可能造成cache中的數(shù)據(jù)和主存中數(shù)據(jù)不一致的情況。
(所以這個(gè)cache好使歸好使,但是插入了中間人,這個(gè)就會(huì)帶來(lái)一些風(fēng)險(xiǎn)與問(wèn)題。)
讀
在虛擬地址到物理地址的映射關(guān)系發(fā)生變化前,如果虛擬地址A1所在的數(shù)據(jù)塊已經(jīng)預(yù)取到cache中,當(dāng)虛擬地址到物理地址的映射關(guān)系發(fā)生變化后,如果虛擬地址A1對(duì)應(yīng)的物理地址發(fā)生了改變,則當(dāng)CPU訪問(wèn)A1時(shí)再使用cache中的數(shù)據(jù)塊將得到錯(cuò)誤的結(jié)果。
這就很嚇人呢,你本來(lái)想看一個(gè)正常的日本動(dòng)作電影,結(jié)果訪問(wèn)到了錯(cuò)誤的地址,運(yùn)氣好點(diǎn)你能訪問(wèn)你有權(quán)限的內(nèi)存,運(yùn)氣不好這個(gè)動(dòng)作電影還帶了愛(ài)情,瑟瑟發(fā)抖。
寫
同樣當(dāng)系統(tǒng)中采用了Write Buffer時(shí),如果CPU寫入Write Buffer的地址是虛擬地址,也會(huì)發(fā)生數(shù)據(jù)不一致的情況。
在虛擬地址到物理地址的映射關(guān)系發(fā)生變化前,如果CPU向虛擬地址為A1的單元執(zhí)行寫操作,該寫操作已經(jīng)將A1以及對(duì)應(yīng)的數(shù)據(jù)寫入到Write Buffer中,當(dāng)虛擬地址到物理地址的映射關(guān)系發(fā)生變化后,如果虛擬地址A1對(duì)應(yīng)的物理地址發(fā)生了改變,當(dāng)Write Buffer將上面被延遲的寫操作寫到主存中時(shí),使用的是變化后的物理地址,從而使寫操作失敗。
這個(gè)歸根結(jié)底,關(guān)鍵點(diǎn)就在于cache里面的數(shù)據(jù)地址是不是最新的,怎么保證是最新的,未被修改的。(閱讀這個(gè)部分的時(shí)候腦子里一定要有SMP架構(gòu),不然要是都是一個(gè)人的,那有啥不一致的呢)
為了避免發(fā)生這種數(shù)據(jù)不一致的情況, 在系統(tǒng)中虛擬地址到物理地址的映射關(guān)系發(fā)生變化前 ,根據(jù)系統(tǒng)的具體情況,執(zhí)行下面操作序列中的一種或幾種:
- 1)如果數(shù)據(jù)cache為write back類型,清空該數(shù)據(jù)的cache;
- 2)使數(shù)據(jù)cache中相應(yīng)的塊無(wú)效;
- 3)使指令cache中相應(yīng)的塊無(wú)效;
- 4)將Write Buffer中被延遲的寫操作全部執(zhí)行;
- 5)有些情況可能還要求相關(guān)的存儲(chǔ)區(qū)域被設(shè)置成非緩沖的。
一、CPU向cache寫入數(shù)據(jù)時(shí)的操作,兩者的區(qū)別
1、Write-through:CPU向cache寫入數(shù)據(jù)時(shí),同時(shí)向memory(后端存儲(chǔ))也寫一份,使cache和memory的數(shù)據(jù)保持一致。
2、Write-back:cpu更新cache時(shí),只是把更新的cache區(qū)標(biāo)記一下,并不同步更新memory(后端存儲(chǔ))。只是在cache區(qū)要被新進(jìn)入的數(shù)據(jù)取代時(shí),才更新memory(后端存儲(chǔ))。
二、兩者相比較優(yōu)勢(shì)
1、Write-through:優(yōu)點(diǎn)是簡(jiǎn)單
2、Write-back:優(yōu)點(diǎn)是CPU執(zhí)行的效率提高
三、兩者相比較劣勢(shì)
1、Write-through:缺點(diǎn)是每次都要訪問(wèn)memory,速度比較慢。
2、Write-back:缺點(diǎn)是實(shí)現(xiàn)起來(lái)技術(shù)比較復(fù)雜。兩者區(qū)別形象比喻:Write-through與Write-back和買賣東西相似,Write-Through就相當(dāng)于你親自去買東西,你買到什么就可以親手拿到;而Write-Back就和中介差不多,你給了中介錢,然后它告訴你說(shuō)你的東西買到了,然后就相信拿到這個(gè)東西了,但是要是出現(xiàn)特殊情況中介跑了,你再去檢查,東西原來(lái)沒(méi)有真正到手。
1.2 指令cache的數(shù)據(jù)不一致性問(wèn)題
由于程序的運(yùn)行而言,指令流的都流過(guò)icache,而指令中涉及到的數(shù)據(jù)流經(jīng)過(guò)dcache。
所以對(duì)于自修改的代碼(Self-Modifying Code)而言,比如我們修改了內(nèi)存p這個(gè)位置的代碼(典型多見(jiàn)于JIT compiler),這個(gè)時(shí)候我們是通過(guò)store的方式去寫的p,所以新的指令會(huì)進(jìn)入dcache。
但是我們接下來(lái)去執(zhí)行p位置的指令的時(shí)候,icache里面可能命中的是修改之前的指令。
相當(dāng)于這個(gè)就是P位置得這個(gè)要執(zhí)行得指令,本質(zhì)上也是數(shù)據(jù),我把這個(gè)指令當(dāng)數(shù)據(jù)得時(shí)候,我去修改以及整理得時(shí)候走的是dcache,但是我操作得時(shí)候去讀得是icache。
當(dāng)系統(tǒng)中采用獨(dú)立的數(shù)據(jù)cache和指令cache時(shí),下面的操作序列可能造成指令不一致的情況:
- 1)讀取地址為A1的指令,從而包含該指令的數(shù)據(jù)塊被預(yù)取到指令cache中。
- 2)與A1在同一個(gè)數(shù)據(jù)塊中的地址為A2的存儲(chǔ)單元的數(shù)據(jù)被修改,這個(gè)數(shù)據(jù)寫操作可能影響數(shù)據(jù)cache、Write Buffer和主存中地址為A2的存儲(chǔ)單元的內(nèi)容,但是不影響指令cache中地址為A2的存儲(chǔ)單元的內(nèi)容。
- 3)如果地址A2存放的是指令,當(dāng)該指令執(zhí)行時(shí),就可能發(fā)生指令不一致的問(wèn)題。如果地址A2所在的塊還在指令cache中,系統(tǒng)將執(zhí)行修改前的指令。如果地址A2所在的塊不在指令cache中,系統(tǒng)將執(zhí)行修改后的指令。
為了避免這種指令不一致情況的發(fā)生,在上面第1)步和第2)步之間插入下面的操作序列:
- 1)對(duì)于使用統(tǒng)一的數(shù)據(jù)cache和指令cache的系統(tǒng),不需要任何操作;
- 2)對(duì)于使用獨(dú)立的數(shù)據(jù)cache和指令cache的系統(tǒng),使指令cache的內(nèi)容無(wú)效;
- 3)對(duì)于使用獨(dú)立的數(shù)據(jù)cache和指令cache的系統(tǒng),如果數(shù)據(jù)cache是write back類型的,清空數(shù)據(jù)cache。
當(dāng)數(shù)據(jù)操作修改了指令時(shí),最好執(zhí)行上述操作序列,保證指令的一致性。下面是上述操作序列的一個(gè)典型應(yīng)用場(chǎng)合。當(dāng)可執(zhí)行文件加載到主存中后,在程序跳轉(zhuǎn)到入口點(diǎn)處開(kāi)始執(zhí)行之前,先執(zhí)行上述的操作序列,以保證下面指令的是新加載的可執(zhí)行代碼,而不是指令中原來(lái)的舊代碼。
所以這個(gè)時(shí)候軟件需要把dcache的東西clean出去,然后讓icache invalidate,這個(gè)開(kāi)銷顯然還是比較大的。但是,比如ARM64的N1處理器,它支持硬件的icache同步,詳見(jiàn)文檔:The Arm Neoverse N1 Platform: Building Blocks for the Next-Gen Cloud-to-Edge Infrastructure SoC
特別注意畫紅色的幾行。軟件維護(hù)的成本實(shí)際很高,還涉及到icache的invalidation向所有核廣播的動(dòng)作。接下來(lái)的一個(gè)問(wèn)題就是多個(gè)核之間的cache同步。下面是一個(gè)簡(jiǎn)化版的處理器,CPU_A和B共享了一個(gè)L3,CPU_C和CPU_D共享了一個(gè)L3。實(shí)際的硬件架構(gòu)由于涉及到NUMA,會(huì)比這個(gè)更加復(fù)雜,但是這個(gè)圖反映層級(jí)關(guān)系是足夠了。
比如CPU_A讀了一個(gè)地址p的變量?CPU_B、C、D又讀,難道B,C,D又必須從RAM里面經(jīng)過(guò)L3,L2,L1再讀一遍嗎?這個(gè)顯然是沒(méi)有必要的,在硬件上,cache的snooping控制單元,可以協(xié)助直接把CPU_A的p地址cache拷貝到CPU_B、C和D的cache。
snooping控制單元,看到這個(gè)單詞想到了什么?是的,咱們的狗狗。嗅探檢測(cè)到改變。
這樣A-B-C-D都得到了相同的p地址的棕色小球。假設(shè)CPU B這個(gè)時(shí)候,把棕色小球?qū)懗杉t色,而其他CPU里面還是棕色,這樣就會(huì)不一致了:
這個(gè)時(shí)候怎么辦?這里面顯然需要一個(gè)協(xié)議,典型的多核cache同步協(xié)議有MESI和MOESI。
MOESI相對(duì)MESI有些細(xì)微的差異,不影響對(duì)全局的理解。下面我們重點(diǎn)看MESI協(xié)議。MESI協(xié)議定義了4種狀態(tài):
- M(Modified): 當(dāng)前cache的內(nèi)容有效,數(shù)據(jù)已被修改而且與內(nèi)存中的數(shù)據(jù)不一致, 數(shù)據(jù)只在當(dāng)前cache里存在 ;類似RAM里面是棕色球,B里面是紅色球 (CACHE與RAM不一致 ),A、C、D都沒(méi)有球。
- E(Exclusive):當(dāng)前cache的內(nèi)容有效,數(shù)據(jù)與內(nèi)存中的數(shù)據(jù)一致, 數(shù)據(jù)只在當(dāng)前cache里存在 ;類似RAM里面是棕色球,B里面是棕色球(RAM和CACHE一致),A、C、D都沒(méi)有球。
- S(Shared):當(dāng)前cache的內(nèi)容有效,數(shù)據(jù)與內(nèi)存中的數(shù)據(jù)一致,數(shù)據(jù)在多個(gè)cache里存在。類似如下圖,在CPU A-B-C里面cache的棕色球都與RAM一致。
- I(Invalid):當(dāng)前cache無(wú)效。前面三幅圖里面cache沒(méi)有球的那些都是屬于這個(gè)情況。然后它有個(gè)狀態(tài)機(jī)
這個(gè)狀態(tài)機(jī)比較難記,死記硬背是記不住的,也沒(méi)必要記,它講的cache原先的狀態(tài),經(jīng)過(guò)一個(gè)硬件在本cache或者其他cache的讀寫操作后,各個(gè)cache的狀態(tài)會(huì)如何變遷。
所以,硬件上不僅僅是監(jiān)控本CPU的cache讀寫行為,還會(huì)監(jiān)控其他CPU的。
只需要記住一點(diǎn):這個(gè)狀態(tài)機(jī)是為了保證多核之間cache的一致性,比如一個(gè)干凈的數(shù)據(jù),可以在多個(gè)CPU的cache share,這個(gè)沒(méi)有一致性問(wèn)題;但是,假設(shè)其中一個(gè)CPU寫過(guò)了,比如A-B-C本來(lái)是這樣:
然后B被寫過(guò)了:
這樣A、C的cache實(shí)際是過(guò)時(shí)的數(shù)據(jù),這是不允許的。 這個(gè)時(shí)候,硬件會(huì)自動(dòng)把A、C的cache invalidate掉 ,不需要軟件的干預(yù),A、C其實(shí)變地相當(dāng)于不命中這個(gè)球了:
- Cache Invalidate 該操作主要為解除內(nèi)存與Cache的綁定關(guān)系。例如操作DMA進(jìn)行數(shù)據(jù)搬移時(shí),如果目標(biāo)內(nèi)存配置為可Cache,那么后續(xù)通過(guò)CPU讀取該內(nèi)存數(shù)據(jù)時(shí)候,若Cache命中,則可能讀取到的數(shù)據(jù)不是DMA搬移后的數(shù)據(jù),那么在進(jìn)行DMA搬移之前,先進(jìn)行Cache Invalidate操作,保證后續(xù)CPU讀取到的數(shù)據(jù)是DMA真正搬移的數(shù)據(jù)。
實(shí)際案例:軟件處理的數(shù)據(jù)異常,與期望結(jié)果不一致,通過(guò)抓取DMA搬移的源數(shù)據(jù),與后續(xù)CPU數(shù)據(jù)進(jìn)行比較,發(fā)現(xiàn)部分?jǐn)?shù)據(jù)相同,部分?jǐn)?shù)據(jù)不一致,后續(xù)確認(rèn)為內(nèi)存地址配置成了可Cache,導(dǎo)致CPU讀取進(jìn)行處理的軟件數(shù)據(jù)異常。
- Cache Flush 該操作為將Cache中的數(shù)據(jù)寫回內(nèi)存。
這個(gè)時(shí)候,你可能會(huì)繼續(xù)問(wèn),如果C要讀這個(gè)球呢?它目前的狀態(tài)在B里面是modified的,而且與RAM不一致,這個(gè)時(shí)候,硬件會(huì)把紅球clean,然后B、C、RAM變地一致,B、C的狀態(tài)都變化為S(Shared):
這一系列的動(dòng)作雖然由硬件完成,但是對(duì)軟件而言不是免費(fèi)的,因?yàn)樗馁M(fèi)了時(shí)間。如果編程的時(shí)候不注意,引起了硬件的大量cache同步行為,則程序的效率可能會(huì)急劇下降。
所以了解知道硬件的行為,寫出來(lái)的代碼才會(huì)更加的效率提升?。?!
都到這里,不得不說(shuō)前輩整的這個(gè)圖真的是非常非常NICE,感激這些前輩的分享。都到這里了,不一起來(lái)學(xué)習(xí)一個(gè)例子的話,就很不合適了。
下面我們寫一個(gè)程序,這個(gè)程序有2個(gè)線程,一個(gè)寫變量,一個(gè)讀變量:
這個(gè)程序里,x和y都是cacheline對(duì)齊的,這個(gè)程序的thread1的寫,會(huì)不停地與thread2的讀,進(jìn)行cache同步。它的執(zhí)行時(shí)間為:
$ time ./a.out
real 0m3.614s
user 0m7.021s
sys 0m0.004s
它在2個(gè)CPU上的userspace共運(yùn)行了7.021秒,累計(jì)這個(gè)程序從開(kāi)始到結(jié)束的對(duì)應(yīng)真實(shí)世界的時(shí)間是3.614秒(就是從命令開(kāi)始到命令結(jié)束的時(shí)間)。如果我們把程序改一句話,把thread2里面的c = x改為c = y,這樣2個(gè)線程在2個(gè)CPU運(yùn)行的時(shí)候,讀寫的是不同的cacheline,就沒(méi)有這個(gè)硬件的cache同步開(kāi)銷了:
它的運(yùn)行時(shí)間:
$ time ./b.out
real 0m1.820s
user 0m3.606s
sys 0m0.008s
現(xiàn)在只需要1.8秒,幾乎減小了一半。感覺(jué)前面那個(gè)a.out,雙核的幫助甚至都不大。如果我們改為單核跑呢?
$ time taskset -c 0 ./a.out
real 0m3.299s
user 0m3.297s
sys 0m0.000s
它單核跑,居然只需要3.299秒跑完,而雙核跑,需要3.614s跑完。單核跑完這個(gè)程序,甚至比雙核還快,有沒(méi)有驚掉下巴??。?!因?yàn)閱魏死锩鏇](méi)有cache同步的開(kāi)銷。下一個(gè)cache同步的重大問(wèn)題,就是設(shè)備與CPU之間。
如果設(shè)備感知不到CPU的cache的話(下圖中的紅色數(shù)據(jù)流向不經(jīng)過(guò)cache),這樣,做DMA前后,CPU就需要進(jìn)行相關(guān)的cacheclean和invalidate的動(dòng)作,軟件的開(kāi)銷會(huì)比較大。
這些軟件的動(dòng)作,若我們?cè)?a href="http://wenjunhu.com/v/tag/538/" target="_blank">Linux編程的時(shí)候,使用的是streaming DMA APIs的話,都會(huì)被類似這樣的API自動(dòng)搞定:
dma_map_single()
dma_unmap_single()
dma_sync_single_for_cpu()
dma_sync_single_for_device()
dma_sync_sg_for_cpu()
dma_sync_sg_for_device()
如果是使用的dma_alloc_coherent() API呢,則設(shè)備和CPU之間的buffer是cache一致的,不需要每次DMA進(jìn)行同步。
對(duì)于不支持硬件cache一致性的設(shè)備而言,很可能dma_alloc_coherent()會(huì)把CPU對(duì)那段DMA buffer的訪問(wèn)設(shè)置為uncachable的。
這些API把底層的硬件差異封裝掉了,如果硬件不支持CPU和設(shè)備的cache同步的話,延時(shí)還是比較大的。
那么,對(duì)于底層硬件而言,更好的實(shí)現(xiàn)方式,應(yīng)該仍然是硬件幫我們來(lái)搞定。比如我們需要修改總線協(xié)議,延伸紅線的觸角:
當(dāng)設(shè)備訪問(wèn)RAM的時(shí)候,可以去snoop CPU的cache:
- 如果做內(nèi)存到外設(shè)的DMA,則直接從CPU的cache取modified的數(shù)據(jù);
- 如果做外設(shè)到內(nèi)存的DMA,則直接把CPU的cache invalidate掉。
這樣,就實(shí)現(xiàn)硬件意義上的cache同步。當(dāng)然,硬件的cache同步,還有一些其他方法,原理上是類似的。
注意,這種同步仍然不是免費(fèi)的,它仍然會(huì)消耗bus cycles的。實(shí)際上,cache的同步開(kāi)銷還與距離相關(guān),可以說(shuō)距離越遠(yuǎn),同步開(kāi)銷越大,比如下圖中A、B的同步開(kāi)銷比A、C小。
對(duì)于一個(gè)NUMA服務(wù)器而言,跨NUMA的cache同步開(kāi)銷顯然是要比NUMA內(nèi)的同步開(kāi)銷大。意識(shí)到CACHE的編程通過(guò)上一節(jié)的代碼,讀者應(yīng)該意識(shí)到了cache的問(wèn)題不處理好,程序的運(yùn)行性能會(huì)急劇下降。
所以意識(shí)到cache的編程,對(duì)程序員是至關(guān)重要的。
從CPU流水線的角度講,任何的內(nèi)存訪問(wèn)延遲都可以簡(jiǎn)化為如下公式:
Average Access Latency = Hit Time + Miss Rate × Miss
Penaltycache miss會(huì)導(dǎo)致CPU的stall狀態(tài),從而影響性能?,F(xiàn)代CPU的微架構(gòu)分了frontend和backend。
- frontend負(fù)責(zé)fetch指令給backend執(zhí)行,
- backend執(zhí)行依賴運(yùn)算能力和Memory子系統(tǒng)(包括cache)延遲。
backend執(zhí)行中訪問(wèn)數(shù)據(jù)導(dǎo)致的cache miss會(huì)導(dǎo)致backend stall,從而降低IPC(instructions per cycle)。
減小cache的miss,實(shí)際上是一個(gè)軟硬件協(xié)同設(shè)計(jì)的任務(wù)。
比如硬件方面,它支持預(yù)取prefetch,通過(guò)分析cache miss的pattern,硬件可以提前預(yù)取數(shù)據(jù),在流水線需要某個(gè)數(shù)據(jù)前,提前先取到cache,從而CPU流水線跑到需要它的時(shí)候,不再miss。
當(dāng)然,硬件不一定有那么聰明,也許它可以學(xué)會(huì)一些簡(jiǎn)單的pattern。但是,對(duì)于復(fù)雜的無(wú)規(guī)律的數(shù)據(jù),則可能需要軟件通過(guò)預(yù)取指令,來(lái)暗示CPU進(jìn)行預(yù)取。
cache這個(gè)學(xué)問(wèn)真的就很大了,比如MESI協(xié)議這些等等。后續(xù)好好整一個(gè)系列,學(xué)習(xí)一下。
1.3 DMA造成的數(shù)據(jù)不一致問(wèn)題
DMA操作直接訪問(wèn)主存,而不會(huì)更新cache和Write Buffer中相應(yīng)的內(nèi)容,這樣就可能造成數(shù)據(jù)的不一致。
如果DMA從主存中讀取的數(shù)據(jù)已經(jīng)包含在cache中,而且cache中對(duì)應(yīng)的數(shù)據(jù)已經(jīng)被更新,這樣DMA讀到的將不是系統(tǒng)中最新的數(shù)據(jù)。同樣DMA寫操作直接更新主存中的數(shù)據(jù),如果該數(shù)據(jù)已經(jīng)包含在cache中,則cache中的數(shù)據(jù)將會(huì)比主存中對(duì)應(yīng)的數(shù)據(jù)“老”,也將造成數(shù)據(jù)的不一致。
為了避免這種數(shù)據(jù)不一致情況的發(fā)生,根據(jù)系統(tǒng)的具體情況,執(zhí)行下面操作序列中的一種或幾種:
- 1)將DMA訪問(wèn)的存儲(chǔ)區(qū)域設(shè)置成非緩沖的,即uncachable及unbufferable;
- 2)將DMA訪問(wèn)的存儲(chǔ)區(qū)域所涉及數(shù)據(jù)cache中的塊設(shè)置成無(wú)效,或者清空數(shù)據(jù)cache;
- 3)清空Write Buffer(執(zhí)行Write Buffer中延遲的所有寫操作);
- 4)在DMA操作期間限制處理器訪問(wèn)DMA所訪問(wèn)的存儲(chǔ)區(qū)域。
1.4 指令預(yù)取和自修改代碼
在ARM中允許指令預(yù)取,在CPU執(zhí)行當(dāng)前指令的同時(shí),可以從存儲(chǔ)器中預(yù)取其后若干條指令,具體預(yù)取多少條指令,不同的ARM實(shí)現(xiàn)中有不同的數(shù)值。
當(dāng)用戶讀取PC寄存器的值時(shí),返回的是當(dāng)前指令下面第2條指令的地址。比如當(dāng)前執(zhí)行的是第N條指令,當(dāng)用戶讀取PC寄存器的值時(shí),返回的是指令N+2的地址。對(duì)于ARM指令來(lái)說(shuō),讀取PC寄存器的值時(shí),返回當(dāng)前指令地址值加8個(gè)字節(jié);對(duì)于Thumb指令來(lái)說(shuō),讀取PC寄存器的值時(shí),返回當(dāng)前指令地址值加4個(gè)字節(jié)。
2 Linux中解決存儲(chǔ)訪問(wèn)一致性問(wèn)題的方法
在Linux中,是用barrier()宏來(lái)解決以上存儲(chǔ)訪問(wèn)一致性問(wèn)題的,barrier()的定義如下所示:
#define barrier() __asm__ __volatile__("": : :"memory")
另外在barrier()的基礎(chǔ)上還衍生出了很多類似的定義,如:
#define mb() __asm__ __volatile__ ("" : : : "memory")
#define rmb() mb()
#define wmb() mb()
#define smp_mb() barrier()
#define smp_rmb() barrier()
#define smp_wmb() barrier()
barrier是內(nèi)存屏障的意思,CPU越過(guò)內(nèi)存屏障后,將刷新自己對(duì)存儲(chǔ)器的緩沖狀態(tài) 。barrier()宏定義這條語(yǔ)句實(shí)際上不生成任何代碼, 但可使gcc在barrier()之后刷新寄存器對(duì)變量的分配。 具體分析如下。
概括起來(lái)說(shuō)barrier()起到兩個(gè)作用:
- 1)告訴編譯器不要優(yōu)化這部分代碼,保持原有的指令執(zhí)行順序;
- 2)告訴CPU執(zhí)行完barrier()之后要進(jìn)行同步操作,更新registers、cache、寫緩存和內(nèi)存中的內(nèi)容,全部重新從內(nèi)存中取數(shù)據(jù)。
評(píng)論
查看更多