搶佔式核心與非搶佔式核心中的自旋鎖(spinlock)的差別
阿新 • • 發佈:2022-03-09
spin_lock()
在Linux2.6中,spin_lock()巨集有兩種實現方式,一種是具有核心搶佔的spin_lock(),一種是非搶佔式核心中的spin_lock(),下面先看下自旋鎖的資料結構,在Linux中,每個自旋鎖都用spinlock_t結構表示,如下:
typedef struct { /** * 該欄位表示自旋鎖的狀態,值為1表示未加鎖,任何負數和0都表示加鎖 */ volatile unsigned int slock; #ifdef CONFIG_PREEMPT /** * 表示程序正在忙等待自旋鎖。 * 只有核心支援SMP和核心搶佔時才使用本標誌。*/ unsigned int break_lock; #endif } spinlock_t;
spin_lock()定義如下:
#define spin_lock(lock) _spin_lock(lock)
具有核心搶佔的_spin_lock巨集
/** * 通過BUILD_LOCK_OPS(spin, spinlock);定義了_spin_lock,進而實現了spin_lock * 這是在具有核心搶佔時,spin_lock的實現。 */ #define BUILD_LOCK_OPS(op, locktype) \ void__lockfunc _##op##_lock(locktype##_t *lock) \ { \ /** * preempt_disable禁用核心搶佔。 * 必須在測試spinlock的值前,先禁止搶佔,原因很簡單: * 下面的迴圈中有可能會成功獲得自旋鎖,如果獲得鎖之後被搶佔了,將造成死鎖 */ preempt_disable(); \for (;;) { \ /** * 呼叫_raw_spin_trylock,它對自旋鎖的slock欄位進行原子性的測試和設定。 * 本質上它執行以下程式碼: * movb $0,%al * xchgb %al, slp->slock * xchgb原子性的交換al和slp->slock記憶體單元的內容。如果原值>0,就返回1,否則返回0 * 換句話說,如果原來的鎖是開著的,就關掉它,它返回成功標誌。如果原來就是鎖著的,再次設定鎖標誌,並返回0。 */ if (likely(_raw_##op##_trylock(lock))) \ /** * 如果舊值是正的,表示鎖是開啟的,巨集結束,已經獲得自旋鎖了。 * 注意:返回後,本函式的一個負作用就是禁用搶佔了。配對使用unlock時再開啟搶佔。 * 請想一下禁用搶佔的必要性。 */ break; \ /** * 否則,無法獲得自旋鎖,就迴圈一直到其他CPU釋放自旋鎖。 * 在迴圈前,暫時開啟preempt_enable。也就是說,在等待自旋鎖的中間,程序是可能被搶佔的。 */ preempt_enable(); \ /** * break_lock表示有其他程序在等待鎖。 * 擁有鎖的程序可以判斷這個標誌,如果程序把持鎖的時間太長,可以提前釋放鎖。 */ if (!(lock)->break_lock) \ (lock)->break_lock = 1; \ /** * 執行等待迴圈,cpu_relax簡化成一條pause指令,對應rep;nop,即空操作 * 為什麼要加入cpu_relax,是有原因的,表面上看,可以用一段死迴圈的彙編來代替這個迴圈 * 但是實際上是不能那樣的的,那樣會鎖住匯流排,unlock想設定值都不能了。 * cpu_relax就是要讓CPU休息一下,把匯流排暫時讓出來。 */ while (!op##_can_lock(lock) && (lock)->break_lock) \ cpu_relax(); \ /** * 上面的死迴圈lock的值已經變化了。那麼關搶佔後,再次呼叫_raw_spin_trylock * 真正的獲得鎖還是在_raw_spin_trylock中。 */ preempt_disable(); \ } \ }
_raw_spin_trylock如下:
static inline int _raw_spin_trylock(spinlock_t *lock) { char oldval; __asm__ __volatile__( "xchgb %b0,%1" :"=q" (oldval), "=m" (lock->slock) :"0" (0) : "memory"); return oldval > 0; }
非搶佔式核心中的_spin_lock()
void __lockfunc _spin_lock(spinlock_t *lock) { preempt_disable(); //一直關閉可搶佔 , _raw_spin_lock(lock); }
_raw_spin_lock(lock)如下:
/** * 對自旋鎖的slock欄位執行原子性的測試和設定操作。 */ static inline void _raw_spin_lock(spinlock_t *lock) { #ifdef CONFIG_DEBUG_SPINLOCK if (unlikely(lock->magic != SPINLOCK_MAGIC)) { printk("eip: %p\n", __builtin_return_address(0)); BUG(); } #endif __asm__ __volatile__( spin_lock_string :"=m" (lock->slock) : : "memory"); } /* spin_lock_string如下: */ #define spin_lock_string \ "\n1:\t" \ /** * %0對應上面的lock->slock * decb遞減自旋鎖的值。它有lock字首,因此是原子的。 */ "lock ; decb %0\n\t" \ /** * 如果結果為0(不是負數),說明鎖是開啟的,跳到3f處繼續執行。 */ "jns 3f\n" \ /** * 否則,結果為負,說明鎖是關閉的。就執行死迴圈,等待它的值變化。 */ "2:\t" \ "rep;nop\n\t" \ /** * 比較lock值,直到它變化,才跳到開頭,試圖再次獲得鎖。 * 否則,繼續死迴圈等lock值變化。 ..自選期間不開啟可搶佔 */ "cmpb $0,%0\n\t" \ "jle 2b\n\t" \ "jmp 1b\n" \ "3:\n\t"
arch_spin_lock