1. 程式人生 > >11.Linux核心設計與實現 P160---順序鎖總結 (轉)

11.Linux核心設計與實現 P160---順序鎖總結 (轉)

當使用讀/寫自旋鎖時,核心控制路徑發出的執行read_lock或write_lock操作的請求具有相同的優先權:讀者必須等待,直到寫操作完成。同樣地,寫者也必須等待,直到讀操作完成。

Linux 2.6中引入了順序鎖(seqlock),它與讀/寫自旋鎖非常相似,只是它為寫者賦予了較高的優先順序:事實上,即使在讀者正在讀的時候也允許寫者繼續執行。這種策略的好處是寫者永遠不會等待讀(除非另外一個寫者正在寫),缺點是有些時候讀者不得不反覆讀多次相同的資料直到它獲得有效的結果。

每個順序鎖都是包括兩個欄位的seqlock_t結構:
typedef struct {
    unsigned sequence;
    spinlock_t lock;

} seqlock_t;

一個型別為spinlock_t的lock欄位和一個整型的sequence欄位,第二個欄位sequence是一個順序計數器。

每個讀者都必須在讀資料前後兩次讀順序計數器,並檢查兩次讀到的值是否相同,如果不相同,說明新的寫者已經開始寫並增加了順序計數器,因此暗示讀者剛讀到的資料是無效的。

通過把SEQLOCK_UNLOCKED賦給變數seqlock_t或執行seqlock_init巨集,把seqlock_t變數初始化為“未上鎖”,並把sequence設為0:
#define __SEQLOCK_UNLOCKED(lockname) /
         { 0, __SPIN_LOCK_UNLOCKED(lockname) }


#define SEQLOCK_UNLOCKED /
         __SEQLOCK_UNLOCKED(old_style_seqlock_init)

# define __SPIN_LOCK_UNLOCKED(lockname) /
    (spinlock_t)    {    .raw_lock = __RAW_SPIN_LOCK_UNLOCKED,    /
                SPIN_DEP_MAP_INIT(lockname) }
#define __RAW_SPIN_LOCK_UNLOCKED    { 1 }

寫者通過呼叫write_seqlock()和write_sequnlock()獲取和釋放順序鎖。write_seqlock()函式獲取seqlock_t資料結構中的自旋鎖,然後使順序計數器sequence加1;write_sequnlock()函式再次增加順序計數器sequence,然後釋放自旋鎖。這樣可以保證寫者在整個寫的過程中,計數器sequence的值是奇數,並且當沒有寫者在改變資料的時候,計數器的值是偶數。讀者程序執行下面的臨界區程式碼:


    unsigned int seq;
    do {
        seq = read_seqbegin(&seqlock);
        /* ... CRITICAL REGION ... */
    } while (read_seqretry(&seqlock, seq));

read_seqbegin()返回順序鎖的當前順序號;如果區域性變數seq的值是奇數(寫者在read_seqbegin()函式被呼叫後,正更新資料結構),或seq的值與順序鎖的順序計數器的當前值不匹配(當讀者正執行臨界區程式碼時,寫者開始工作),read_seqretry()就返回1:
static __always_inline int read_seqretry(const seqlock_t *sl, unsigned iv)
{
    smp_rmb();
    return (iv & 1) | (sl->sequence ^ iv);
}


注意在順序鎖機制裡,讀者可能反覆讀多次相同的資料直到它獲得有效的結果(read_seqretry返回0)。另外,當讀者進入臨界區時,不必禁用核心搶佔;另一方面,由寫者獲取自旋鎖,所以它進入臨界區時自動禁用核心搶佔。

並不是每一種資源都可以使用順序鎖來保護。一般來說,必須在滿足下述條件時才能使用順序鎖:

(1)被保護的資料結構不包括被寫者修改和被讀者間接引用 的指標(否則,寫者可能在讀者的眼皮子底下就修改指標)。
(2)讀者的臨界區程式碼沒有副作用(否則,多個讀者的操作會與單獨的讀操作有不同的結果)。

此外,讀者的臨界區程式碼應該簡短,而且寫者應該不常獲取順序鎖,否則,反覆的讀訪問會引起嚴重的開銷。在Linux 2.6中,使用順序鎖主要是保護一些與系統時間處理相關的資料結構。