核心同步
核心同步解決併發帶來的問題,多個執行緒對同一資料進行修改,資料會出現不一致的情況,同步用於保護共享資料等資源。
有兩種形式的併發:
同時進行式併發,在不同cpu上執行的程序同時訪問共享資料
二次進入式併發,某程序讀寫一段資料時,中斷觸發,在中斷處理函式中再次修改之前程序讀寫的內容
訪問共享資料的那部分**被稱為臨界區。
原子操作
不可打斷的操作為原子操作,一條彙編指令不可被中斷,其為原子操作。在核心**中,我們可以看到類似atomic64_add這樣的函式,使用它們完成加減運算,而不是簡單地使用」+」、」-「運算子。
x86_64架構下,訪問乙個對齊的long型是原子操作:
volatile unsigned longvalue_;
value_=191987987;
以上賦值語句是原子的,即使多執行緒同時訪問以上value_亦不需要加鎖,所有執行緒要麼看到舊值,要麼看到新值。
但value_++;這條自增語句不是原子的,它需要讀記憶體、改值、寫記憶體三條指令:
4004ca: 48 8b 45 f8 mov -0x8(%rbp),%rax4004ce:
4883 c0 01 add $0x1,%rax
4004d2:
4889
45 f8 mov %rax,-0x8(%rbp)
gcc等編譯器,會針對這種操作,提供內建的原子方法,如上面的value_++可以修改為:
__sync_fetch_and_add(&value_, 1);
對應__sync_fetch_and_add的彙編如下:
4004bc: 48 8d 45 f8 lea -0x8(%rbp),%rax4004c0: f0
4883
0001 lock addq $0x1,(%rax)
以上lock字首用於鎖定匯流排,保證後面一條指令對記憶體的獨佔訪問。gcc提供了一組原子方法,更多可以參看gcc手冊。
根據需要保護的資料的粒度、等待鎖時程序是否可休眠等不同應用場景,鎖有很多種類,下面我們來看核心**中幾種常用的鎖。
原子鎖
像以上介紹的atomic64_add就是乙個原子鎖,其用於保護乙個整型值,在核心**中由一條彙編語句實現:
static __inline__ void atomic64_add(long i, atomic64_t *v)
以上**中,同樣用到lock進行記憶體保護。
自旋鎖
在我們編寫應用程式的時候,常使用c庫中的pthread_mutex_lock對臨界區進行加鎖,pthread_mutex_lock底層使用futex系統呼叫實現。若鎖變數mutex已被其他執行緒占用,則後續申請鎖的程序將進入休眠,當mutex被釋放時,後續的程序被喚醒。
不同於pthread_mutex_lock獲取不到鎖的程序將進入休眠,使用自旋鎖(spin lock)的程序,若鎖已被其他程序占用,則一直占用cpu,重複檢查鎖的狀態,直到該鎖可用為止。
自旋鎖是為多處理器的使用而設計的,對於執行可搶占核心的單處理器,其行為類似於多處理器。因而,自旋鎖對多處理器和使用可搶占核心的單處理器都適用,均可用於臨界區保護。
但自旋鎖對使用不可搶占核心的單處理器沒有意義,因為當cpu處於自旋狀態時,它做不了任何有用的工作,非搶占式單處理器系統上通過禁止中斷實現臨界區的保護,自旋鎖被實現為空操作。
在同乙個cpu上,自旋鎖不可遞迴獲取。
下面是自旋鎖的乙個具體使用例子:
syscall_define1(close, unsigned int, fd)
以上是close系統呼叫的實現**(擷取了自旋鎖相關的部分)。可以看到操作檔案結構、檔案描述符前,先呼叫spin_lock獲取當前檔案對應的files_struct結構中的file_lock,之後修改臨界區,完成清除標誌位、把檔案描述符fd放入未使用列表等工作,最後呼叫spin_unlock釋放file_lock自旋鎖。
讀寫自旋鎖
對於讀操作而言,其實並不需要加鎖,因而我們可以對讀和寫區別對待:
使用讀寫自旋鎖,在讀得多,寫得少的場景下,有很大的效率提公升。
核心中讀寫自旋鎖的型別為rwlock_t,相關的操作函式有read_lock、write_lock等。
訊號量
訊號量(semaphore),類似於c庫中的pthread_mutex_lock。程序1申請的訊號量若被程序2占用,則程序1進入休眠狀態,這時允許程序排程,程序1被切換後,cpu可以進行其他工作。
核心中訊號量用semaphore結構表示,獲取訊號量的函式為down(),釋放訊號量的函式為up()。
使用自旋鎖時程序一直占用cpu,而使用訊號量時程序可休眠,但程序休眠時發生切換將帶來一定cpu開銷。根據以上兩種鎖的特點,自旋鎖與訊號量適用於不同場景:
requirement recommended locklow overhead locking spin lock is preferred
short lock hold
time
spin lock is preferred
long lock hold
time
semaphore is preferred
need to lock from interrupt contex spin lock is required
need to
sleep
while holding lock semaphore is required
讀寫訊號量
與自旋鎖分讀寫自旋鎖類似,訊號量也分讀寫訊號量。讀寫訊號量由rw_semaphore表示,相關的操作函式有down_read/up_read、down_write/up_write。
下面來看程序獲取訊號量,進入休眠,喚醒並獲取訊號量的具體實現過程:
down_read呼叫__down_read,在__down_read函式中,呼叫set_task_state設定程序狀態,將獲取讀訊號量的請求加入請求佇列中,在獲取不到鎖的情況下,呼叫schedule進行程序切換
up_read呼叫__up_read,__up_read函式呼叫rwsem_wake,該函式呼叫__rwsem_do_wake,__rwsem_do_wake函式中,獲取請求佇列中的下乙個請求,呼叫wake_up_process函式喚醒發起下乙個請求的程序,wake_up_process呼叫try_to_wake_up,try_to_wake_up呼叫activate_task,activate_task呼叫enqueue_task,將程序加入可執行佇列
bkl
大核心鎖(big kernel lock, bkl),是乙個全域性可見的鎖,它的出現是為了解決smp出現後的併發問題。
獲取bkl之後,核心態被上鎖,同一時刻只能有乙個cpu能執行核心**,無法發揮多處理器的威力,bkl正逐漸地被其他更細粒度的鎖替代。
reference: chapter 9 and chapter 10, linux kernel development.3rd.edition
核心自旋鎖與ARM同步原語
在閱讀linux核心 時,毫無疑問會遇到spin lock,下面談談我對於spin lock的arm原始碼分析。首先看一下spinlock t的結構 arm include asm spinlock types.h typedef struct tickets arch spinlock t inc...
Linux核心同步方法 互斥鎖
互斥體 互斥 指的是任何可以睡眠的強制互斥鎖,比如計數是1的訊號量。也就是說,互斥體是一種互斥訊號。互斥在核心中對應資料結構互斥,其行為和使用計數為1的訊號量類似,因為是直接呼叫的訊號量的操作介面,實現更高效,而且使用限制更強。也就是乙個簡化版的訊號量,因為不需要管理任何使用計數。define mu...
併發同步與鎖
1.乙個生產者和乙個消費者。存在資源競爭情況 1 生產者和消費者同時訪問fruit store的庫存量,及同時修改庫存量時,存在資源競爭。細分這種資源競爭的情況,至少會有如下兩種原因 1 當時fruit store庫存為0時,還未成功執行this.curfruitnum 消費者發現fuilt sto...