linux讀寫自旋鎖

來源:互聯網
上載者:User

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/yunsongice/archive/2010/05/18/5605264.aspx

讀/寫自旋鎖同樣是在保護SMP體系下的共用資料結構而引入的,它的引入是為了增加核心的並發能力。只要核心控制路徑沒有對資料結構進行修改,讀/寫自旋鎖就允許多個核心控制路徑同時讀同一資料結構。如果一個核心控制路徑想對這個結構進行寫操作,那麼它必須首先擷取讀/寫鎖的寫鎖,寫鎖授權獨佔訪問這個資源。這樣設計的目的,即允許對資料結構並發讀可以提高系統效能。
每個讀/寫自旋鎖都是一個rwlock_t結構:
typedef struct {
    raw_rwlock_t raw_lock;
#if defined(CONFIG_PREEMPT) && defined(CONFIG_SMP)
    unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
    unsigned int magic, owner_cpu;
    void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lockdep_map dep_map;
#endif
} rwlock_t;
typedef struct {
    volatile unsigned int lock;
} raw_rwlock_t;
其lock欄位是一個32位的欄位,分為兩個不同的部分:
(1)24位計數器,表示對受保護的資料結構並發地進行讀操作的核心控制路徑的數目。這個計數器的二進位補碼存放在這個欄位的0~23位。
(2)“未鎖”標誌欄位,當沒有核心控制路徑在讀或寫時設定該位,否則清0。這個“未鎖”標誌存放在lock欄位的第24位。
注意,如果自旋鎖為空白(設定了“未鎖”標誌且無讀者),那麼lock欄位的值為0x01000000;如果寫者已經獲得自旋鎖(“未鎖”標誌清0且無讀者),那麼lock欄位的值為0x00000000;如果一個、兩個或多個進程因為讀擷取了自旋鎖,那麼,lock欄位的值為Ox00ffffff,Ox00fffffe等(“未鎖”標誌清0表示寫鎖定,不允許寫該資料結構的進程,讀者個數的二進位補碼在0~23位上;如果全為0,則表示有一個寫進程在操作此資料結構)。與spinlock_t結構一樣,rwlock_t結構也包括break_lock欄位。
rwlock_init宏把讀/寫自旋鎖的lock欄位初始化為0x01000000(“未鎖”),把break_lock初始化為0,演算法類似spin_lock_init。進程獲得讀寫自旋鎖的方式不僅有是否設定了核心搶佔選項而不同外,還跟讀或寫的操作相關。前者的演算法跟自旋鎖機制幾乎一樣,下面我們就重點討論後者:

1 為讀擷取和釋放一個鎖

read_lock宏,作用於讀/寫自旋鎖的地址*lock,與上一篇博文所描述的spin_lock宏非常相似。如果編譯核心時選擇了核心搶佔選項,read_lock宏執行與spin_lock()非常相似的操作,只有一點不同:該宏執行_raw_read_trylock( )函數以在第2步有效地擷取讀/寫自旋鎖。
void __lockfunc _read_lock(rwlock_t *lock)
{
    preempt_disable();
    rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_);
    LOCK_CONTENDED(lock, _raw_read_trylock, _raw_read_lock);
}
在沒有定義調試自旋鎖操作時rwlock_acquire_read為空白函數,我們不去管它。所以_read_lock的實務函數是_raw_read_trylock:
# define _raw_read_trylock(rwlock)    __raw_read_trylock(&(rwlock)->raw_lock)
static inline int __raw_read_trylock(raw_rwlock_t *lock)
{
    atomic_t *count = (atomic_t *)lock;
    atomic_dec(count);
    if (atomic_read(count) >= 0)
        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
        ret

read_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)
{
    preempt_disable();
    rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_);
    LOCK_CONTENDED(lock, _raw_write_trylock, _raw_write_lock);
}

_raw_write_trylock()函數描述如下:
    int _raw_write_trylock(rwlock_t *lock)
    {
        atomic_t *count = (atomic_t *)lock->lock;
        if (atomic_sub_and_test(0x01000000, count))
            return 1;
        atomic_add(0x01000000, count);
        return 0;
    }

static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
{
    unsigned char c;

    __asm__ __volatile__(
        LOCK "subl %2,%0; sete %1"
        :"=m" (v->counter), "=qm" (c)
        :"ir" (i), "m" (v->counter) : "memory");
    return c;
}
函數_raw_write_trylock()調用tomic_sub_and_test(0x01000000, count)從讀/寫自旋鎖lock->lock的值中減去0x01000000,從而清除未上鎖標誌(看見沒有?正好是第24位)。如果減操作產生0值(沒有讀者),則擷取鎖並返回1;否則,函數原子地在自旋鎖的值上加0x01000000,以取消減操作。

釋放寫鎖同樣非常簡單,因為write_unlock宏只需使用組合語言指令:
lock; addl $0x01000000,rwlp
把lock欄位中的“未鎖”標識置位,然後再調用preempt_enable()。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.