- 嵌入式系統Linux內核開發實戰指南(ARM平臺)
- 王洪輝編著
- 273字
- 2018-12-27 18:21:40
第6章 ARM處理器存儲訪問一致性問題
6.1 存儲訪問一致性問題介紹
當存儲系統中引入了cache和寫緩沖區(Write Buffer)時,同一地址單元的數據可能在系統中有多個副本,分別保存在cache、Write Buffer及主存中,如果系統采用了獨立的數據cache和指令cache,同一地址單元的數據還可能在數據cache和指令cache中有不同的版本。位于不同物理位置的同一地址單元的數據可能會不同,使得數據讀操作可能得到的不是系統中“最新的數值”,這樣就帶來了存儲系統中數據的一致性問題。在ARM存儲系統中,數據不一致的問題則需要通過程序設計時遵守一定的規則來保證,這些規則說明如下。
6.1.1 地址映射關系變化造成的數據不一致性
當系統中使用了MMU時,就建立了虛擬地址到物理地址的映射關系,如果查詢cache時進行的相連比較使用的是虛擬地址,則當系統中虛擬地址到物理地址的映射關系發生變化時,可能造成cache中的數據和主存中數據不一致的情況。
在虛擬地址到物理地址的映射關系發生變化前,如果虛擬地址A1所在的數據塊已經預取到cache中,當虛擬地址到物理地址的映射關系發生變化后,如果虛擬地址A1對應的物理地址發生了改變,則當CPU訪問A1時再使用cache中的數據塊將得到錯誤的結果。
同樣當系統中采用了Write Buffer時,如果CPU寫入Write Buffer的地址是虛擬地址,也會發生數據不一致的情況。在虛擬地址到物理地址的映射關系發生變化前,如果CPU向虛擬地址為A1的單元執行寫操作,該寫操作已經將A1以及對應的數據寫入到Write Buffer中,當虛擬地址到物理地址的映射關系發生變化后,如果虛擬地址A1對應的物理地址發生了改變,當Write Buffer將上面被延遲的寫操作寫到主存中時,使用的是變化后的物理地址,從而使寫操作失敗。
為了避免發生這種數據不一致的情況,在系統中虛擬地址到物理地址的映射關系發生變化前,根據系統的具體情況,執行下面操作序列中的一種或幾種:
1)如果數據cache為write back類型,清空該數據的cache;
2)使數據cache中相應的塊無效;
3)使指令cache中相應的塊無效;
4)將Write Buffer中被延遲的寫操作全部執行;
5)有些情況可能還要求相關的存儲區域被設置成非緩沖的。
6.1.2 指令cache的數據不一致性問題
當系統中采用獨立的數據cache和指令cache時,下面的操作序列可能造成指令不一致的情況:
1)讀取地址為A1的指令,從而包含該指令的數據塊被預取到指令cache中。
2)與A1在同一個數據塊中的地址為A2的存儲單元的數據被修改,這個數據寫操作可能影響數據cache、Write Buffer和主存中地址為A2的存儲單元的內容,但是不影響指令cache中地址為A2的存儲單元的內容。
3)如果地址A2存放的是指令,當該指令執行時,就可能發生指令不一致的問題。如果地址A2所在的塊還在指令cache中,系統將執行修改前的指令。如果地址A2所在的塊不在指令cache中,系統將執行修改后的指令。
為了避免這種指令不一致情況的發生,在上面第1)步和第2)步之間插入下面的操作序列:
1)對于使用統一的數據cache和指令cache的系統,不需要任何操作;
2)對于使用獨立的數據cache和指令cache的系統,使指令cache的內容無效;
3)對于使用獨立的數據cache和指令cache的系統,如果數據cache是write back類型的,清空數據cache。
當數據操作修改了指令時,最好執行上述操作序列,保證指令的一致性。下面是上述操作序列的一個典型應用場合。當可執行文件加載到主存中后,在程序跳轉到入口點處開始執行之前,先執行上述的操作序列,以保證下面指令的是新加載的可執行代碼,而不是指令中原來的舊代碼。
6.1.3 DMA造成的數據不一致問題
DMA操作直接訪問主存,而不會更新cache和Write Buffer中相應的內容,這樣就可能造成數據的不一致。
如果DMA從主存中讀取的數據已經包含在cache中,而且cache中對應的數據已經被更新,這樣DMA讀到的將不是系統中最新的數據。同樣DMA寫操作直接更新主存中的數據,如果該數據已經包含在cache中,則cache中的數據將會比主存中對應的數據“老”,也將造成數據的不一致。
為了避免這種數據不一致情況的發生,根據系統的具體情況,執行下面操作序列中的一種或幾種:
1)將DMA訪問的存儲區域設置成非緩沖的,即uncachable及unbufferable;
2)將DMA訪問的存儲區域所涉及數據cache中的塊設置成無效,或者清空數據cache;
3)清空Write Buffer(執行Write Buffer中延遲的所有寫操作);
4)在DMA操作期間限制處理器訪問DMA所訪問的存儲區域。
6.1.4 指令預取和自修改代碼
在ARM中允許指令預取,在CPU執行當前指令的同時,可以從存儲器中預取其后若干條指令,具體預取多少條指令,不同的ARM實現中有不同的數值。
當用戶讀取PC寄存器的值時,返回的是當前指令下面第2條指令的地址。比如當前執行的是第N條指令,當用戶讀取PC寄存器的值時,返回的是指令N+2的地址。對于ARM指令來說,讀取PC寄存器的值時,返回當前指令地址值加8個字節;對于Thumb指令來說,讀取PC寄存器的值時,返回當前指令地址值加4個字節。
6.2 Linux中解決存儲訪問一致性問題的方法
在Linux中,是用barrier()宏來解決以上存儲訪問一致性問題的,barrier()的定義如下所示:
#define barrier() __asm__ __volatile__("": : :"memory")
另外在barrier()的基礎上還衍生出了很多類似的定義,如:
#define mb() __asm__ __volatile__ ("" : : : "memory") #define rmb() mb() #define wmb() mb() #define smp_mb() barrier() #define smp_rmb() barrier() #define smp_wmb() barrier()
barrier是內存屏障的意思,CPU越過內存屏障后,將刷新自己對存儲器的緩沖狀態。barrier()宏定義這條語句實際上不生成任何代碼,但可使gcc在barrier()之后刷新寄存器對變量的分配。具體分析如下。

概括起來說barrier()起到兩個作用:
1)告訴編譯器不要優化這部分代碼,保持原有的指令執行順序;
2)告訴CPU執行完barrier()之后要進行同步操作,更新registers、cache、寫緩存和內存中的內容,全部重新從內存中取數據。