讀寫自旋鎖

2021-06-16 11:31:57 字數 2953 閱讀 1014

讀/寫自旋鎖同樣是在保護smp體系下的共享資料結構而引入的,它的引入是為了增加核心的併發能力。只要核心控制路徑沒有對資料結構進行修改,讀/寫自旋鎖就允許多個核心控制路徑同時讀同一資料結構。如果乙個核心控制路徑想對這個結構進行寫操作,那麼它必須首先獲取讀/寫鎖的寫鎖,寫鎖授權獨佔訪問這個資源。這樣設計的目的,即允許對資料結構併發讀可以提高系統效能。

下圖顯示有兩個受讀/寫鎖保護的臨界區(ci和c2)。核心控制路徑r0和r1正在同時讀取c1中的資料結構,而w0正等待獲取寫鎖。核心控制路徑w1正對c2中的資料結構進行寫操作,而r2和w2分別等待獲取讀鎖和寫鎖。

每個讀/寫自旋鎖都是乙個rwlock_t結構:

typedef struct rwlock_t;

typedef struct raw_rwlock_t;

其lock欄位是乙個32位的字段,分為兩個不同的部分:

1、24位計數器,表示對受保護的資料結構併發地進行讀操作的核心控制路徑的數目。這個計數器的二進位制補碼存放在這個欄位的0~23位。

2、「未鎖」標誌字段,當沒有核心控制路徑在讀或寫時設定該位,否則清0。這個「未鎖」標誌存放在lock欄位的第24位。

注意,如果自旋鎖為空(設定了「未鎖」標誌且無讀者),那麼lock欄位的值為0x0100 0000;如果寫者已經獲得自旋鎖(「未鎖」標誌清0且無讀者),那麼lock欄位的值為0x00000000;如果乙個、兩個或多個程序因為讀獲取了自旋鎖,那麼,lock欄位的值為ox00ff ffff,ox00fffffe等(「未鎖」標誌清0表示寫鎖定,不允許寫該資料結構的程序,讀者個數的二進位制補碼在0~23位上;如果全為0,則表示有乙個寫程序在操作此資料結構)。與spinlock_t結構一樣,rwlock_t結構也包括break_lock欄位。

break_lock==》是幹啥的???

rwlock_init巨集把讀/寫自旋鎖的lock欄位初始化為0x01000000(「未鎖」),把break_lock初始化為0,演算法類似spin_lock_init。程序獲得讀寫自旋鎖的方式不僅有是否設定了核心搶占選項而不同外,還跟讀或寫的操作相關。前者的演算法跟自旋鎖機制幾乎一樣,下面我們就重點討論後者:

1 為讀獲取和釋放乙個鎖

return 1;

atomic_inc(count);

return 0;

}讀/寫鎖計數器lock欄位是通過原子操作來訪問的。注意,儘管如此,但整個函式對計數器的操作並不是原子性的,利用原子操作主要目的是禁止核心搶占。例如,在用if語句完成對計數器值的測試之後並返回1之前,計數器的值可能發生變化。不過,函式能夠正常工作:實際上,只有在遞減之前計數器的值不為0或負數的情況下,函式才返回1,因為計數器等於0x01000000表示沒有任何程序占用鎖,等於ox00ffffff表示有乙個讀者,等於0x00000000表示有乙個寫者(因為只可能有乙個寫者)。

如果編譯核心時沒有選擇核心搶占選項,read_lock巨集產生下面的組合語言**:

movl $rwlp->lock,%eax

lock; subl $1,(%eax)

jns 1f

call _ _read_lock_failed

1:這裡,__read_lock_failed()是下列組合語言函式:

_ _read_lock_failed:

lock; incl (%eax)

1:  pause

cmpl $1,(%eax)

js 1b

lock; decl (%eax)

js _ _read_lock_failed

retread_lock巨集原子地把自旋鎖的值減1,由此增加讀者的個數。如果遞減操作產生乙個非負值,就獲得自旋鎖;否則就算作失敗。我們看到lock欄位的值由ox00ffffff到0x00000000要減多少次才可能出現負值,所以幾乎很難出現呼叫__read_lock_failed()函式的情況。該函式原子地增加lock欄位以取消由read_lock巨集執行的遞減操作,然後迴圈,直到lock欄位變為正數(大於或等於0)。接下來,__read_lock_failed()又試圖獲取自旋鎖(正好在cmpl指令之後,另乙個核心控制路徑可能為寫獲取自旋鎖)。

釋放讀自旋鎖是相當簡單的,因為read_unlock巨集只需要使用組合語言指令簡單地增加lock欄位的計數器:

lock; incl rwlp->lock

以減少讀者的計數,然後呼叫preempt_enable()重新啟用核心搶占。

2 為寫獲取或釋放乙個鎖

write_lock巨集實現的方式與spin_lock()和read_lock()相似。例如,如果支援核心搶占,則該函式禁用核心搶占並通過呼叫_raw_write_trylock()立即獲得鎖。如果該函式返回0,說明鎖已經被占用,因此,該巨集像前面博文描述的那樣重新啟用核心搶占並開始忙等待迴圈。

#define write_lock(lock)        _write_lock(lock)

void __lockfunc _write_lock(rwlock_t *lock)

_raw_write_trylock()函式描述如下:

int _raw_write_trylock(rwlock_t *lock)

static __inline__ int atomic_sub_and_test(int i, atomic_t *v)

函式_raw_write_trylock()呼叫atomic_sub_and_test(0x01000000, count)從讀/寫自旋鎖lock->lock的值中減去0x01000000,從而清除未上鎖標誌(看見沒有?正好是第24位)。如果減操作產生0值(沒有讀者),則獲取鎖並返回1;否則,函式原子地在自旋鎖的值上加0x01000000,以取消減操作。

釋放寫鎖同樣非常簡單,因為write_unlock巨集只需使用組合語言指令:

lock; addl $0x01000000,rwlp

把lock欄位中的「未鎖」標識置位,然後再呼叫preempt_enable()。

讀寫鎖與自旋鎖

一 讀寫鎖 1 特點 讀寫鎖比mutex有更高的適用性,可以多個執行緒同時占用讀模式的讀寫鎖,但是只能乙個執行緒占用寫模式的讀寫鎖。1 當讀寫鎖是寫加鎖狀態時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的執行緒都會被阻塞 2 當讀寫鎖在讀加鎖狀態時,所有試圖以讀模式對它進行加鎖的執行緒都可以得到訪問權...

互斥鎖 自旋鎖 讀寫鎖 條件變數

互斥鎖 同一時刻只能有乙個執行緒進入臨界區,乙個執行緒獲取鎖如果失敗,則該執行緒進入睡眠狀態,同一執行緒多次加鎖會造成死鎖。使用場景 1.持鎖時間長 2臨界區競爭非常激烈 3 單核處理器 自旋鎖 不會造成執行緒進入睡眠狀態,執行緒會不斷檢測鎖是否已經釋放,減少了執行緒從睡眠到喚醒的核心開銷。使用場景...

Linux 互斥鎖 遞迴鎖 自旋鎖 讀寫鎖

在多執行緒中,我們經常會要用到鎖,那麼,鎖是什麼,我們為什麼要用到鎖?回到問題的本質,我們在什麼場景下會用到鎖?鎖是針對程式中的臨界資源,也就是公共資源的,當我們有兩個或多個執行緒同時對乙個臨界資源操作的時候,為了保證共享資料操作的完整性,我們要為這些公共資源加鎖。在linux中常見的鎖主要有互斥鎖...