核心同步機制之自旋鎖 讀 寫鎖

2021-06-22 02:15:45 字數 3707 閱讀 9497

自旋鎖(spin lock)是用來在多處理器環境中工作的一種特殊的鎖。如果核心控制路徑發現自旋鎖「開著」,就獲取鎖並繼續自己的執行。相反,如果核心控制路徑發現由執行在另乙個cpu上的核心控制路徑「鎖著」,就在一直迴圈等待,反覆執行一條緊湊的迴圈指令,直到鎖被釋放。

自旋鎖與互斥鎖有點類似,只是自旋鎖不會引起呼叫者睡眠,如果自旋鎖已經被別的執行單元保持,呼叫者就一直迴圈在那裡看是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名。

由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。訊號量和讀寫訊號量適合於保持時間較長的情況,它們會導致呼叫者睡眠,因此只能在程序上下文使用(_trylock的變種能夠在中斷上下文使用),而自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用。

如果被保護的共享資源只在程序上下文訪問,使用訊號量保護該共享資源非常合適,如果對共享資源的訪問時間非常短,自旋鎖也可以。但是如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理控制代碼和軟中斷),就必須使用自旋鎖。

自旋鎖保持期間是搶占失效的,而訊號量和讀寫訊號量保持期間是可以被搶占的。自旋鎖只有在核心可搶占或smp的情況下才真正需要,在單cpu且不可搶占的核心下,自旋鎖的所有操作都是空操作。

一般來說,由自旋鎖所保護的每個臨界區都是禁止核心搶占的。在單處理器系統上,這種鎖本身並不起鎖的作用,自旋鎖原語僅僅是禁止或啟用核心搶占。請注意,在自旋鎖忙等期間,核心搶占還是有效的,因此,等待自旋鎖釋放的程序有可能被更高優先順序的程序替代。

自旋鎖巨集

spin_lock_init     把自旋鎖置為1

spin_lock          迴圈,直到自旋鎖變為1(未鎖),然後,把自旋鎖置為0(鎖上)

spin_unlock        把自旋鎖置為1

spin_unlock_wait() 等待,直到自旋鎖變為1

spin_is_locked()   如果自旋鎖被置為1(未鎖),返回0,否則,返回1

spin_try_lock()    把自旋鎖置為0(鎖上),如果原來鎖的值是1,則返回1,否則,返回0

所有這些巨集都是基於原子操作的

typedef struct  spinlock_t;
在linux中,每個自旋鎖都用spinlock_t結構表示:

lock 該欄位表示自旋鎖的狀態,值為1表示未加鎖狀態,而任何負數和零都表示加鎖狀態

break_lock 表示程序正在忙等自旋鎖

#ifndef config_preempt

#define spin_lock_string \

"\n1:\t" \

"lock ; decb %0\n\t" \

"jns 3f\n" \

"2:\t" \

"rep;nop\n\t" \

"cmpb $0,%0\n\t" \

"jle 2b\n\t" \

"jmp 1b\n" \

"3:\n\t"

static inline void _raw_spin_lock(spinlock_t *lock)

#endif

__asm__ __volatile__(

spin_lock_string

:"=m" (lock->slock) : : "memory");

}#else /* config_preempt: */

#define build_lock_ops(op, locktype) \

void __lockfunc _##op##_lock(locktype##_t *lock) \

\}

build_lock_ops(spin, spinlock);

分析spin_lock的實現

非搶占式核心

1、首先呼叫preempt_disable()禁止核心搶占,實際什麼也不執行。

2、執行spin_lock_string彙編指令

decb遞減自旋鎖的值,該指令是原子的,因為它帶有lock位元組字首。

檢查符號標誌,如果它被清零,說明自旋鎖被設定為1,因此從標籤3(字尾f表示標籤是向前的)繼續執行,否則

在標籤2(字尾b表示標籤是向後的)執行緊湊迴圈直到自旋鎖出現正值。然後從標籤2開始重新執行。

搶占式核心

1、首先呼叫preempt_disable()禁止核心搶占

2、呼叫函式_raw_spin_trylock(),它對自旋鎖slock欄位執行原子性的測試和設定操作。執行下面的指令:

static inline int _raw_spin_trylock(spinlock_t *lock)

首先初始化oldval為0,然後使用xchgb指令交換oldvar與lock->slock的內容,然後判斷如果oldval>0 返回1,否則返回0

如果返回1,說明加鎖成功,就直接退出迴圈。否則呼叫preempt_enable()開啟核心搶占,設定break_lock忙等標記。呼叫cpu_relax()迴圈等待。

static inline void rep_nop(void)

#define cpu_relax() rep_nop()

xchg exchange

rep  repeat while ecx not zero

讀/寫自旋鎖的引入是為了增加核心的併發能力。只要沒有核心控制路徑對資料進行修改,讀/寫自旋鎖就允許多個核心控制路徑同時讀乙個資料結構。如果乙個核心控制路徑想對這個結構進行寫操作,那麼它必須首先獲取讀/寫鎖的寫鎖,寫鎖授權獨佔訪問這個資源。

typedef struct  rwlock_t;
每個讀/寫自旋鎖都是乙個rwlock_t結構,其中lock欄位是乙個32位的字段,分為兩個不同的部分:

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

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

如果讀寫鎖為空,那麼lock欄位的值為0x01000000;如果寫者已經獲得自旋鎖,那麼lock字段值為0x00000000;

如果乙個、兩個或多個程序因為讀獲取了自旋鎖,那麼,lock欄位的值為0x00ffffff,0x00fffffe。

rwlock_t結構中的break_lock與spinlock_t中的字段一樣。

linux中讀寫鎖使用的巨集

rwlock_init    初始化lock欄位為0x01000000

write_lock     加寫鎖

read_lock      加讀鎖

write_unlock   解寫鎖

read_unlock    解讀鎖

write_trylock  嘗試獲取寫鎖,獲取不到返回,非阻塞

read_trylock   嘗試獲取讀鎖,獲取不到返回,非阻塞

write_can_lock 檢查是否可以加寫鎖

read_can_lock  檢查是否可以加讀鎖

由於讀寫自旋鎖和自旋鎖比較類似,就不再分析具體實現了。 

linux核心同步機制之自旋鎖

定義 最多只能被乙個可執行執行緒持有。如果乙個執行執行緒試圖獲得乙個被爭用的自旋鎖,那麼該執行緒就會一直進行忙迴圈 旋轉 等待鎖重新可用。自旋鎖有 加鎖 和 解鎖 兩種狀態。加鎖 一直在尋求 解鎖 解鎖 馬上會尋求 加鎖 並原地打轉,所以加鎖位置的 進入臨界區執行,直到解鎖。注意 1.占用臨界區的時...

Linux 同步機制 讀寫鎖

讀寫鎖也叫 shared exclusive 鎖,也是一種同步機制。讀寫鎖有三種狀態 讀模式下加鎖,寫模式下加鎖,不加鎖。有如下的使用約定 讀模式共享,寫模式獨佔,適合讀頻率遠大於寫頻率的場景。這些api位於 pthread.h 下。initialize read write lock rwlock...

同步機制 「讀寫鎖「的實現

同步機制 讀寫鎖 pj.courtois,f.heymans,and d.l.parnas mble research laboratory brussels,belgium 多程序同時訪問臨界區域 critical section 的問題可以看成兩類程序 讀者 readers 和寫者 writer...