報道:Linux內核鎖的那點事

2023-07-04 14:05:50 來源:嵌入式藝術

Linux設備驅動中,我們必須要解決的一個問題是:多個進程對共享資源的并發訪問,并發的訪問會導致競態。

1、并發和競態

并發(Concurrency):指的是多個執行單元同時、并行的被執行。


(相關資料圖)

競態(RaceConditions):并發執行的單元對共享資源的訪問,容易導致競態。

共享資源:硬件資源和軟件上的全局變量、靜態變量等。

解決競態的途徑是:保證對共享資源的互斥訪問。

互斥訪問:一個執行單元在訪問共享資源的時候,其他執行單元被禁止訪問。

臨界區(Critical Sections):訪問共享資源的代碼區域成為臨界區。臨界區需要以某種互斥機制加以保護。

常見的互斥機制包括:中斷屏蔽,原子操作,自旋鎖,信號量,互斥體等。

2、競態發生的場合

image-20230511140139520

多對稱處理器(SMP)的多個CPU之間

多個CPU使用共同的系統總線,可以訪問共同的外設和存儲器。在SMP的情況下,多核(CPU0、CPU1)的競態可能發生于:

CPU0的進程和CPU1的進程之間CPU0的進程和CPU1的中斷之間CPU0的中斷和CPU1的中斷之間單CPU內,該進程與搶占它的進程之間

在單CPU內,多個進程并發執行,當一個進程執行的時間片耗盡,也有可能被另一個高優先級進程打斷,會發生競態。

中斷(軟中斷、硬中斷、Tasklet、底半部)與進程之間

當一個進程正在執行,一個外部/內部中斷(軟中斷、硬中斷、Tasklet等)將其打斷,會導致競態發生。

3、編譯亂序和執行亂序

除了并發訪問導致的競態外,還需要了解編譯器和處理器的一些特點所引發的一些問題。

3.1 編譯亂序

現代的高性能編譯器在目標代碼優化上都具有亂序優化的能力,編譯器為了盡量 提高Cache命中率以及CPU的Load/Store單元的工作效率,可以對訪存的指令進行亂序,減少邏輯上不必要的訪存。

因此,在打開編譯器優化后,生成的匯編碼并沒有嚴格按照代碼的邏輯順序執行,這是正常的。

為了解決編譯亂序的問題,可以加入barrier()編譯屏障

該屏障可以阻擋編譯器的優化。設置屏障的前后,可以保證執行的語句不亂。

加入barrier()編譯屏障,即可保證正確的執行順序。

例子:

#define barrier() __asm__ __volatile__("": : :"memory")int main(int argc,char *argv[]){ int a = 0,b,c,d[4096],e; e = d[4095];    barrier(); b = a; c = a; return 0;}

3.2 執行亂序

編譯亂序是編譯器的行為,而執行亂序就是處理器運行時的行為。

高級的CPU往往會根據自身的緩存特性,將訪存指令重新排序執行!這樣就導致了多個順序的指令,后發的指令仍有可能先執行完畢。

這種執行亂序,在多個CPU之間,以及單個CPU內部,都是非常常見的。

3.2.1 多CPU之間

處理器為了解決多核之間,一個CPU的行為對另一個CPU可見的情況,ARM處理器引入了內存屏障指令:

DMB(數據內存屏障),保證在該指令前的所有指令,內存訪問完成,再去訪問該指令之后的訪存動作DSB(數據同步屏障),保證在該指令前的所有訪存指令執行完畢(訪存,緩存,跳轉預測,TLB維護等)完成ISB(指令同步屏障),Flush流水線,保證所有在ISB之后執行的指令都是從緩存或者內存中獲得。

3.2.2 單CPU內部

在單CPU中,我們常遇到訪問外設寄存器時,某些外設寄存器就對讀寫順序有很高的要求,為了避免執行亂序的發生,這時候就需要CPU的一些內存屏障指令了。

CPU內部,為了解決這種問題,CPU提供了一些內存屏障指令:

可以參考Documentation/memory-devices.txtDocumentation/io_ordering.txt

讀寫屏障:mb()讀屏障:rmb()寫屏障:wmb()寄存器讀屏障__iormb()__寄存器寫屏障__iowmb()__
#define writeb_relaxed(v,c) __raw_writeb(v,c)#define writew_relaxed(v,c) __raw_writew((__force u16) cpu_to_le16(v),c)#define writel_relaxed(v,c) __raw_writel((__force u32) cpu_to_le32(v),c)#define readb(c)  ({ u8  __v = readb_relaxed(c); __iormb(); __v; })#define readw(c)  ({ u16 __v = readw_relaxed(c); __iormb(); __v; })#define readl(c)  ({ u32 __v = readl_relaxed(c); __iormb(); __v; })#define writeb(v,c)  ({ __iowmb(); writeb_relaxed(v,c); })#define writew(v,c)  ({ __iowmb(); writew_relaxed(v,c); })#define writel(v,c)  ({ __iowmb(); writel_relaxed(v,c); })

writelwritel_relaxed的區別就在于有無屏障。

4、總結

由上文可知,為了解決

并發導致的競態問題高性能的編譯器編譯亂序問題高性能的CPU帶來的執行亂序問題

CPUARM處理器提供的內存屏障指令等,這也是內核鎖存在的意義。

標簽:

上一篇:Linux內核中斷屏蔽的實現-世界熱推薦
下一篇:最后一頁