天天看點

并發與競态——自旋鎖

Release log:

2021-05-07 五: 完成初版

2021-05-10 一: 添加自旋鎖和信号量的對比

原文位址

Linux 核心中自旋鎖的使用

在 Linux 核心中,要使用自旋鎖需要包含的檔案

<linux/spinlock.h>

,鎖的資料類型為

spinlock_t

自旋鎖的聲明以及初始化

void spin_lock_init(spinlock_t *lock);
           

自旋鎖的擷取以及釋放

void spin_lock(spinlock_t *lock);
void spin_unlock(spinlock_t *lock);
           

注意: 所有自旋鎖等待在本質上是不可中斷的

使用自旋鎖的核心規則

  1. 任何擁有自旋鎖的代碼都必須是原子的。它不能休眠,事實上,它不能因為任何原因放棄處理器,除了服務中斷以外(某些情況下此時也不能放棄處理器)
  • 核心搶占的情況由自旋鎖代碼本身處理。任何時候,隻要核心代碼擁有自旋鎖,在相關處理器上的搶占就會被禁止
  • 在擁有鎖的時候避免休眠有時候很難做到,當我們編寫需要自旋鎖下執行的代碼時,必須注意每一個所調用的函數
  • 擁有鎖的時間越短越好

自旋鎖函數

鎖定一個自旋鎖的函數

void spin_lock(spinlock_t *lock);
/* 這是一個宏,獲得自旋鎖之前禁止中斷(隻在本地處理器上),而先前的中斷狀态儲存在 flags 中 */
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
/* 在獲得鎖之前禁止軟體中斷,但是會讓硬體中斷保持打開 */
void spin_lock_bh(spinlock_t *lock);
           

如果我們有一個自旋鎖,它可以被運作在(硬體或軟體)中斷上下文中的代碼獲得,則必須使用某個禁止中斷的 spin_lock 形式

釋放自旋鎖的方法需要嚴格對應

void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
           

另外,還有如下非阻塞的自旋鎖操作

int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
           

讀取者/寫入者自旋鎖

讀取者/寫入者鎖的類型為 rwlock_t,在

<linux/spinlock.h>

中定義

自旋鎖的初始化

void rwlock_init(rwlock_t *lock);
           

讀取者對應的函數(這裡沒有 read_trylock 可用)

void read_lock(rwlock_t *lock);
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
void read_lock_irq(rwlock_t *lock);
void read_lock_bh(rwlock_t *lock);

void read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);
           

寫入者對應的函數

void write_lock(rwlock_t *lock);
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
void write_trylock(rwlock_t *lock);

void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);
           

和 rwsem 類似,讀取者/寫入者鎖可能造成讀取者饑餓

對比信号量與自旋鎖

自旋鎖可以在不能休眠的代碼中使用,比如中斷處理例程。自旋鎖如果被其他人獲得,則代碼進入忙循環并重複檢查這個鎖,直到該鎖可用為止。信号量是一種睡眠鎖,如果一個任務試圖獲得一個已被持有的信号量時,信号量會将其推入等待隊列,然後讓其睡眠。信号量不能在中斷上下文中被排程

自旋鎖會關閉搶占,而信号量不會

在正确使用的情況下,自旋鎖通常可以提供比信号量更高的性能

需求                        建議的加鎖方式
低開銷加鎖                  優先使用自旋鎖
短期鎖定                    優先使用自旋鎖
中斷上下文中加鎖            使用自旋鎖

長期加鎖                    優先使用信号量
持有鎖是需要睡眠、排程      使用信号量
           

繼續閱讀