Linux的原子操作與同步機制

2021-09-02 16:15:19 字數 2904 閱讀 6384

[b]linux的原子操作與同步機制 [/b]

[b]併發問題[/b]

例如c語言語句「count++;」在未經編譯器優化時生成的彙編**為多條機器指令來實現的。

[b]例子:[/b]

假設count變數初始值為0。程序1執行完「mov eax, [count]」後,暫存器eax內儲存了count的值0。此時,

程序2被排程執行,搶占了程序1的cpu的控制權。程序2執行「count++;」的彙編**,將累加後的count值

1寫回到記憶體。然後,程序1再次被排程執行,cpu控制權回到程序1。程序1接著執行,計算count的累加

值仍為1,寫回到記憶體。雖然程序1和程序2執行了兩次「count++;」操作,但是count實際的記憶體值為1,而

不是2!

[b]單處理器原子操作[/b]

解決這個問題的方法是,將「count++;」語句翻譯為單指令操作。

intel x86指令集支援記憶體運算元的inc操作,這樣「count++;」操作可以在一條指令內完成。因為程序的上下文

切換是在總是在一條指令執行完成後,所以不會出現上述的併發問題。對於單處理器來說,一條處理器指令就

是乙個原子操作。

[b]多處理器原子操作[/b]

但是在多處理器的環境下,例如smp架構,這個結論不再成立。我們知道「inc [count]」指令的執行過程分為三步:

1)從記憶體將count的資料讀取到cpu。

2)累加讀取的值。

3)將修改的值寫回count記憶體。

這又回到前面併發問題類似的情況,只不過此時併發的主題不再是程序,而是處理器。

intel x86指令集提供了指令字首lock用於鎖定前端序列匯流排(fsb),保證了指令執行時不會受到其他處理器的干擾。

使用lock指令字首後,處理器間對count記憶體的併發訪問(讀/寫)被禁止,從而保證了指令的原子性。

[b]arm原子操作實現[/b]

在arm的指令集中,不存在指令字首lock,那如何完成原子操作呢?

在armv6之前,swp指令就是通過鎖定匯流排的方式完成原子的資料交換,但是影響系統效能。armv6之後,一般使用ldrex

和strex指令對代替swp指令的功能。

[b]自旋鎖中的原子操作[/b]

1:

lock decb [lock->slock]

jns 3

2:rep nop

cmpb $0, [lock->slock]

jle 2

jmp 1

3:

其中lock->slock欄位初始值為1,執行原子操作decb後值為0。符號位為0,執行jns指令跳轉到3,完成自旋鎖的加鎖。

當再次申請自旋鎖時,執行原子操作decb後lock->slock值為-1。符號位為1,不執行jns指令。進入標籤2,執行一組

nop指令後比較lock->slock是否小於等於0,如果小於等於0回到標籤2進行迴圈(自旋)。否則跳轉到標籤1重新申請

自旋鎖,直到申請成功。

自旋鎖釋放時會將lock->slock設定為1,這樣保證了其他程序可以獲得自旋鎖。

[b]訊號量中的原子操作[/b]

訊號量的申請操作由函式down實現

lock   decl [sem->count]

js 2

1:<********** another section **********>

2:lea [sem->count], eax

call __down_failed

jmp 1

訊號量的sem->count一般初始化為乙個正整數,申請訊號量時執行原子操作decl,將sem->count減1。如果該值減為負數

(符號位為1)則跳轉到另乙個段內的標籤2,否則申請訊號量成功。

標籤2被編譯到另乙個段內,進入標籤2後,執行lea指令取出sem->count的位址,放到eax暫存器作為引數,然後呼叫函式

__down_failed表示訊號量申請失敗,程序加入等待佇列。最後跳回標籤1結束訊號量申請。

訊號量的釋放操作由函式up實現。

lock   incl sem->count

jle 2

1:<********** another section **********>

2:lea [sem->count], eax

call __up_wakeup

jmp 1

釋放訊號量時執行原子操作incl將sem->count加1,如果該值小於等於0,則說明等待佇列有阻塞的程序需要喚醒,跳轉到標籤2,

否則訊號量釋放成功。

標籤2被編譯到另乙個段內,進入標籤2後,執行lea指令取出sem->count的位址,放到eax暫存器作為引數,然後呼叫函式__up_wakeup

喚醒等待佇列的程序。最後跳回標籤1結束訊號量釋放。

[b]例子:[/b]

**1(低優先順序)(執行緒1)

**1down

**1down處理**

**1up

**2(高優先順序)(執行緒2)

**2down

**2down處理**

**2up

[b]分析:[/b]

**1down成功就執行**1down處理**,(假如**2剛執行到**2down,因沒有得到訊號量,所以被掛起了),**1處理完

**1down處理**,到**1up時會看有沒有其它執行緒等待訊號量(執行緒2等待著),所以系統恢復執行緒2進行執行,執行

**2down處理**,再執行到**2up,發現沒有就退出直到執行緒2不再執行,系統恢復執行緒1進行執行,執行到**1up時被

系統掛起時下一條**,再執行**1其他**直到再次被掛起。

原文參考:[url]

linux核心同步機制之原子操作(基於ARM)

linux 使用 乙個變數atomic t來表示原子變數。typedef struct atomic t 可以看到裡面是乙個int 型別的變數。這裡只分析乙個atomic add函式 static inline void atomic add int i,atomic t v 如下,可以看到linu...

linux同步機制

一.併發控制 1 自旋鎖 得不到資源,會原地打轉,直到獲得資源為止 定義自旋鎖 spinlock t spin 初始化自旋鎖 spin lock init lock 獲得自旋鎖 spin lock lock 獲得自旋鎖,如果能立即獲得,則馬上返回,否則自旋在那裡,直到該自旋鎖的保持者釋放 spin ...

linux同步機制

原子操作 原子操作是由編譯器來保證的,保證乙個執行緒對資料的操作不會被其他執行緒打斷。當執行緒正在對乙個變數操作而這個操作過程不想被其他執行緒打斷時,可以用原子操作,原子操作結構體 atomic t 原子操作缺點 會阻塞優先順序很高的執行緒。自旋鎖 當乙個執行緒在讀寫乙個共享資源時,加上自旋鎖,其他...