【转】自旋锁及其衍生锁
原文網址:http://blog.chinaunix.net/uid-26126915-id-3032644.html
自旋鎖
自旋鎖(spinlock)是用在多個CPU系統中的鎖機制,當一個CPU正訪問自旋鎖保護的臨界區時,臨界區將被鎖上,其他需要訪問此臨界區的CPU只能忙等待,直到前面的CPU已訪問完臨界區,將臨界區開鎖。自旋鎖上鎖后讓等待線程進行忙等待而不是睡眠阻塞,而信號量是讓等待線程睡眠阻塞。自旋鎖的忙等待浪費了處理器的時間,但時間通常很短,在1毫秒以下。
? ?
自旋鎖用于多個CPU系統中,在單處理器系統中,自旋鎖不起鎖的作用,只是禁止或啟用內核搶占。在自旋鎖忙等待期間,內核搶占機制還是有效的,等待自旋鎖釋放的線程可能被更高優先級的線程搶占CPU。
? ?
自旋鎖基于共享變量。一個線程通過給共享變量設置一個值來獲取鎖,其他等待線程查詢共享變量是否為0來確定鎖現是否可用,然后在忙等待的循環中"自旋"直到鎖可用為止。
? ?
通用自旋鎖
自旋鎖的狀態值為1表示解鎖狀態,說明有1個資源可用;0或負值表示加鎖狀態,0說明可用資源數為0。Linux內核為通用自旋鎖提供了API函數初始化、測試和設置自旋鎖。API函數功能說明如下。
??????????spin_lock_init(lock)?初始化自旋鎖,將自旋鎖設置為1,表示有一個資源可用。
??????????spin_is_locked(lock)?如果自旋鎖被置為1(未鎖),返回0,否則返回1。
??????????spin_unlock_wait(lock)?等待直到自旋鎖解鎖(為1),返回0;否則返回1。
??????????spin_trylock(lock)?嘗試鎖上自旋鎖(置0),如果原來鎖的值為1,返回1,否則返回0。
??????????spin_lock(lock)?循環等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。
??????????spin_unlock(lock)?將自旋鎖解鎖(置為1)。
??????????spin_lock_irqsave(lock, flags)?循環等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。關中斷,將狀態寄存器值存入flags。
??????????spin_unlock_irqrestore(lock, flags)?將自旋鎖解鎖(置為1)。開中斷,將狀態寄存器值從flags存入狀態寄存器。
??????????spin_lock_irq(lock)?循環等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。關中斷。
??????????spin_unlock_irq(lock)?將自旋鎖解鎖(置為1)。開中斷。
??????????spin_unlock_bh(lock)?將自旋鎖解鎖(置為1)。開啟底半部的執行。
??????????spin_lock_bh(lock)?循環等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。阻止軟中斷的底半部的執行。
? ?
自旋鎖用結構spinlock_t描述,在include/linux/spinlock.h中有類型?spinlock_t定義,列出如下:
typedef struct {
????raw_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK /*引入另一個自旋鎖*/
????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; /*映射lock實例到lock-class對象
#endif
} spinlock_t;
? ?
由于自旋鎖的性能嚴重地影響著操作系統的性能,Linux內核提供了Lock-class和Lockdep跟蹤自旋鎖的使用對象和鎖的狀態,并可從/proc文件系統查詢自旋鎖的狀態信息。自旋鎖的調試通過配置項CONFIG_DEBUG_*項打開。
? ?
對于對稱多處理器系統(SMP),slock為一個int數據類型,對于單個處理器系統,slock定義為空。SMP的slock定義列出如下(在include/linux/spinlock_types.h):
typedef struct {
????volatile unsigned int slock;
} raw_spinlock_t;
? ?
自旋鎖的實現機制類型
spin_lock_init
函數spin_lock_init將自旋鎖狀態值設置為1,表示未鎖狀態。其列出如下(在include/linux/spinlock.h中):
# define spin_lock_init(lock)????????????????????/
????do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)
? ?
宏__SPIN_LOCK_UNLOCKED列出如下(在include/linux/spinlock_types.h中):
# define __SPIN_LOCK_UNLOCKED(lockname) /
????(spinlock_t)????{????.raw_lock = __RAW_SPIN_LOCK_UNLOCKED,????/
????????????????SPIN_DEP_MAP_INIT(lockname) }
?
#define __RAW_SPIN_LOCK_UNLOCKED????{ 1 }
? ?
spin_lock_irqsave
函數spin_lock_irqsave等待直到自旋鎖解鎖,即自旋鎖值為1,它還關閉本地處理器上的中斷。其列出如下(在include/linux/spinlock.h中):
#define spin_lock_irqsave(lock, flags)????flags = _spin_lock_irqsave(lock)
? ?
函數spin_lock_irqsave分析如下(在kernel/spinlock.c中):
unsigned long __lockfunc _spin_lock_irqsave(spinlock_t *lock)
{
????unsigned long flags;
?
????local_irq_save(flags); //將狀態寄存器的值寫入flags保存
????preempt_disable(); //關閉內核搶占,內核搶占鎖加1
????spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
?
#ifdef CONFIG_LOCKDEP
????LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
#else
????_raw_spin_lock_flags(lock, &flags);
#endif
????return flags;
}
? ?
spin_unlock_irqrestore
? ?
宏定義spin_unlock_irqrestore是解鎖,開中斷,并把flags值存入到狀態寄存器中,這個宏定義分析如下:
#define spin_unlock_irqrestore(lock, flags)????_spin_unlock_irqrestore(lock, flags)函數_spin_unlock_irqrestore列出如下(在kernel/spinlock.c中):
? ?
void __lockfunc _spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
????spin_release(&lock->dep_map, 1, _RET_IP_);
????_raw_spin_unlock(lock); //解鎖
????local_irq_restore(flags); //開中斷,將flag的值存入狀態寄存器
????preempt_enable(); //開啟內核搶占
}
?
# define _raw_spin_unlock(lock)????????__raw_spin_unlock(&(lock)->raw_lock)
? ?
函數__raw_spin_unlock將自旋鎖狀態值加1,表示有1個資源可用,從而釋放自旋鎖,其列出如下(在include/asm-x86/spinlock.h中):
static __always_inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
????asm volatile(UNLOCK_LOCK_PREFIX "incw %0" // lock->slock= lock->slock +1
???????? : "+m" (lock->slock)
???????? :
???????? : "memory", "cc");
}
? ?
讀/寫自旋鎖
"讀/寫自旋鎖"用來解決讀者/寫者問題。如果有多個線程(進程、中斷處理程序、底半部例程)以只讀的方式訪問一個臨界區數據,讀/寫自旋鎖允許多個線程同時讀取數據。如果一個線程需要對臨界區數據進行寫操作,它必須獲取寫鎖,只有在沒有讀者或寫者進行操作時,寫者才獨占臨界區數據進行寫操作。讀操作時需要獲取讀鎖,寫操作時需要獲取寫鎖。
? ?
Linux內核為讀/寫自旋鎖提供了操作API函數初始化、測試和設置自旋鎖。API函數功能說明如下:
??????????rwlock_init(lock)?初始化自旋鎖值為0x01000000(未鎖)。
??????????read_lock(lock)?加讀者鎖,即將讀者計數加1。
??????????read_lock_irqsave(lock, flags)?加讀者鎖,即將讀者計數加1。并且關中斷,存儲狀態標識到flags中。
??????????read_lock_irq(lock)?加讀者鎖,即將讀者計數加1。并且關中斷。
??????????read_unlock(lock)?解讀者鎖,即將讀者計數減1。
??????????read_unlock_irqrestore(lock, flags)?解讀者鎖,即將讀者計數減1。并且開中斷,將狀態標識從flags讀到狀態寄存器中。
??????????read_unlock_irq(lock)?解讀者鎖,即將讀者計數減1。并且開中斷。
??????????write_lock(lock)?加寫者鎖,即將寫者鎖置0。
??????????write_lock_irqrestore(lock, flags)?加寫者鎖,即將寫者鎖置0。并且關中斷,存儲狀態標識到flags中。
??????????write_lock_irq(lock)?加寫者鎖,即將寫者鎖置0。并且關中斷。
??????????write_unlock(lock)?解寫者鎖,即將寫者鎖置1。
??????????write_unlock_irqrestore(lock, flags)?解寫者鎖,即將寫者鎖置1。并且開中斷,將狀態標識從flags讀到狀態寄存器中。
??????????write_unlock_irq(lock)?解寫者鎖,即將寫者鎖置1。并且開中斷。
? ?
用戶使用讀/寫自旋鎖,應先自旋鎖的狀態值初始化為鎖初始化為RW_LOCK_BIAS,即0x01000000,表示為未鎖狀態。
? ?
讀/寫自旋鎖用結構rwlock_t描述,它的主要成員為鎖狀態值變量lock,結構rwlock_t列出如下(在include/linux/spinlock_types.h中):
typedef struct {
????raw_rwlock_t raw_lock;
……
} rwlock_t;
?
typedef struct {
????unsigned int lock;
} raw_rwlock_t;
? ?
在結構raw_rwlock_t中,讀/寫自旋鎖狀態變量lock為32位,它分為2個部分,0~23位是一個24位計數器,表示對臨界數據區進行并發讀操作的線程數,線程數以補碼形式存入計數器;第24位為表示"未鎖"的狀態位,在沒有線程讀或寫臨界區時,設置為1,否則,設置為0。
? ?
如果自旋鎖設置了"未鎖"狀態且無讀者,那么lock值為0x01000000;如果寫者已獲得自旋鎖且無讀者,則未鎖狀態位清0,lock值為0x00000000。如果有一個、2個或多個線程獲取鎖對臨界數據區進行讀操作,則lock值為0x00ffffff、0x00fffffe等(第24位清0表示未鎖,第0~23位為讀者個數的補碼)。
? ?
下面說明讀/寫自旋鎖API函數的實現:
rwlock_init
函數rwlock_init將讀/寫自旋鎖狀態值設為0x01000000,其列出如下(在include/linux/spinlock.h中):
? ?
# define rwlock_init(lock)????????????????????/
????do { *(lock) = RW_LOCK_UNLOCKED; } while (0)
?
#define RW_LOCK_UNLOCKED????__RW_LOCK_UNLOCKED(old_style_rw_init)
#define __RW_LOCK_UNLOCKED(lockname) /
????(rwlock_t)????{????.raw_lock = __RAW_RW_LOCK_UNLOCKED,????/
????????????????RW_DEP_MAP_INIT(lockname) }
?
#define __RAW_RW_LOCK_UNLOCKED????????{ RW_LOCK_BIAS }
#define RW_LOCK_BIAS????????0x01000000
? ?
read_lock和read_unlock
函數read_lock用于加讀者鎖,函數read_unlock用于解讀者鎖,兩函數需要配對使用。下面分別進行說明:
? ?
函數read_lock
讀/寫自旋鎖lock空閑值為0x01000000,當有一個讀者進行讀操作時,它加讀者鎖,執行運算lock=lock-1,lock值為0x00ffffff;當接著有第二個讀者進行讀操作時,可以進行并發的讀,再執行運算lock=lock-1,lock值為0x00fffffe;依此類推,可支持多個讀者同時讀操作。
? ?
如果在讀操作正進行(如:有2個讀者正進行操作,lock值為0x00fffffe)時,有一個寫者請求寫操作時,寫操作必須等待讀者全部完成操作,每個讀者完成操作時,執行運算lock=lock+1,當2個讀者的操作完成后,lock值為0x01000000,表示寫鎖空閑,可以進行寫操作或并發的讀操作。
? ?
如果一個寫操作正進行時,執行運算lock=lock-0x01000000,lock值為0x00000000,表示寫者鎖已加鎖,另一個寫者無法對臨界區數據進行訪問。此時,如果有一個讀者進行讀操作請求時,執行運算lock=lock-1,結果為負數,則狀態寄存器符號位置為1,加讀者鎖失敗,將lock還原(lock=lock+1),讀者循環等待,直到寫操作完成(即lock值為0x01000000)時。
? ?
寫操作完成時,lock值為0x01000000,表示寫鎖空閑,可以進行寫操作或并發的讀操作。這時,正等待的讀者執行運算lock=lock-1,結果為0x00ffffff,則狀態寄存器符號位置為0,跳出加讀者鎖的等待循環,加鎖成功,讀者進行讀操作。
? ?
函數read_lock關閉內核搶占,加讀者鎖,即將讀者數增加1,其列出如下(在include/linux/spinlock.h中):
? ?
#define read_lock(lock)????????????_read_lock(lock)
? ?
函數_read_lock列出如下(在kernel/spinlock.c中):
? ?
void __lockfunc _read_lock(rwlock_t *lock)
{
????preempt_disable(); //關閉內核搶占
????rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_); /*用于自旋鎖調試*/
/*下面語句相當于_raw_read_lock(lock)*/
????LOCK_CONTENDED(lock, _raw_read_trylock, _raw_read_lock);
}
# define _raw_read_lock(rwlock)????????__raw_read_lock(&(rwlock)->raw_lock)
? ?
函數__raw_read_lock增加讀鎖,即鎖狀態值rw減1,由于讀者計數以補碼形式存放在鎖狀態值中,因此,減1表示讀者計數增加1。其列出如下(在include/asm-x86/spinglock.h中):
? ?
static inline void __raw_read_lock(raw_rwlock_t *rw)
{
????asm volatile(LOCK_PREFIX " subl $1,(%0)/n/t" //*rw=*rw-1
???????? "jns 1f/n" //如果符號位為0,跳轉到1
???????? "call __read_lock_failed/n/t"
???????? "1:/n"
???????? ::LOCK_PTR_REG (rw) : "memory");
}
? ?
函數__read_lock_failed進行加讀者鎖失敗后的循環等待操作。加讀者鎖失敗,說明有一個寫者正在寫操作,因此,鎖狀態值為*rw=0x00000000,函數__raw_read_lock在執行*rw=*rw-1后,rw值為0xffffffff,即傳入函數__read_lock_failed的rw值為0xffffffff。
? ?
函數__read_lock_failed執行*rw=*rw+1后,鎖狀態值為*rw=0x00000000,然后,進入循環等待狀態,直到,寫者完成寫操作后將鎖狀態值*rw置為0x01000000。這時,函數__read_lock_failed才跳出循環等待狀態,加讀者鎖成功。
? ?
函數__read_lock_failed列出如下(在include/asm-x86/lib/rwlock_64.h中):
? ?
/* rdi指向rwlock_t */
ENTRY(__read_lock_failed)
????CFI_STARTPROC //即:#define CFI_STARTPROC .cfi_startproc
????LOCK_PREFIX
????incl (%rdi) // *rw=*rw+1,值為0x00000000
1:????rep //循環等待*rw值被寫者修改為0x01000000
????nop
????cmpl $1,(%rdi) // *rw-1
????js 1b //如果符號位為1,表明*rw值還為0x00000000,跳轉到1進行循環等待
????LOCK_PREFIX
/*?運行到這里,說明寫者操作完成,*rw值為0x01000000 */
????decl (%rdi) //執行加讀者鎖操作*rw=*rw-1
????js __read_lock_failed//如果符號位為1,表明*rw值為0x00000000,跳轉到函數開頭進行循環等待
????ret
????CFI_ENDPROC //即:#define CFI_ENDPROC .cfi_endproc
END(__read_lock_failed)
? ?
由于匯編語言程序無法產生幀信息,由用戶手動添加指示語句。上述代碼中,指示語句.cfi_startproc用于調試時的調用幀信息處理,在每個函數的開始處使用,它在.eh_frame中生成一個條目,初始化一些內部數據結構,并發出構架依賴的初始CFI(Call Frame Information)指令。在函數結束處使用.cfi_endproc關閉該功能。
? ?
函數read_unlock
函數read_unlock開讀者鎖,即將鎖狀態值減1,由于讀者計數以補碼形式存放在鎖狀態值中,因此,加1表示讀者計數減1。其列出如下
? ?
# define read_unlock(lock) /
do {__raw_read_unlock(&(lock)->raw_lock); __release(lock); } while (0)
# define __release(x)????__context__(x,-1)
static inline void __raw_read_unlock(raw_rwlock_t *rw)
{ /* rw->lock= rw->lock +1*/
????asm volatile(LOCK_PREFIX "incl %0" :"+m" (rw->lock) : : "memory");
}
? ?
write_lock和write_unlock
函數write_lock和write_unlock分別加寫者鎖和解寫者鎖,分別說明如下:
? ?
函數write_lock
只有在沒有讀者或寫者對臨界區數據進行操作時,加寫者鎖才會成功,即:只有鎖狀態值lock值為0x01000000時,寫者加鎖才能成功,執行運行lock=lock-0x01000000運算。
? ?
當有讀者或寫者操作臨界區數據時,lock值為比0x01000000小的正數,如果值為0x00000000表示有一個寫者正在寫操作,如果值為0x00ffffff,表示有1個讀者在進行讀操作,如果值為0x00fffffe,表示有2個讀者在進行讀操作,依此類推。此時,寫者只能循環等待,直到lock值為0x01000000。
? ?
函數write_lock關閉內核搶占,加寫者鎖,其列出如下(在include/linux/spinlock.h中):
? ?
#define write_lock(lock)????????_write_lock(lock)
? ?
函數_write_lock列出如下(在kernel/spinlock.c中):
? ?
void __lockfunc _write_lock(rwlock_t *lock)
{
????preempt_disable(); /*關閉內核搶占*/
????rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_); /*用于自旋鎖調試*/
/*下面語句相當于_raw_write_lock(lock)*/
????LOCK_CONTENDED(lock, _raw_write_trylock, _raw_write_lock);
}
?
# define _raw_write_lock(rwlock)????__raw_write_lock(&(rwlock)->raw_lock)
?
static inline void __raw_write_lock(raw_rwlock_t *rw)
{
????asm volatile(LOCK_PREFIX " subl %1,(%0)/n/t" /* RW_LOCK_BIAS-rw*/
/*?如果沒有讀者或寫者,*rw為0x01000000,RW_LOCK_BIAS-rw為0 */
???????? "jz 1f/n" /*值為0,跳轉到1*/
???????? "call __write_lock_failed/n/t" /*加寫者鎖失敗*/
???????? "1:/n"
/* RW_LOCK_BIAS定義為0x01000000*/
???????? ::LOCK_PTR_REG (rw), "i" (RW_LOCK_BIAS) : "memory");
}
? ?
運行函數__write_lock_failed時,說明加寫者鎖失敗。如果加寫者鎖失敗,說明有讀者或寫者正在訪問臨界區數據,*rw值為一個小于0x01000000的正數。此時,函數__write_lock_failed循環等待直到,讀者或寫者完成操作,鎖變為空閑,即*rw值為0x01000000。
? ?
函數__write_lock_failed列出如下(在include/asm-x86/lib/rwlock_64.h中):
? ?
/* rdi:????pointer to rwlock_t */
ENTRY(__write_lock_failed)
????CFI_STARTPROC /*用于調試時將調用幀信息寫入
????LOCK_PREFIX
????addl $RW_LOCK_BIAS,(%rdi) // *rw=*rw+$RW_LOCK_BIAS,還原為嘗試加鎖前的狀態值
1:????rep
????nop
????cmpl $RW_LOCK_BIAS,(%rdi) //比較結果?= *rw-$RW_LOCK_BIAS
????jne 1b //比較結果不為0,說明有寫者或讀者在訪問臨界區,跳轉到1進行循環等待
????LOCK_PREFIX //鎖內存管理器,確保原子操作
/*運行到這里,說明鎖空閑,*rw值為0x010000,執行加寫者鎖操作*/
????subl $RW_LOCK_BIAS,(%rdi) //*rw=*rw-RW_LOCK_BIAS
????jnz __write_lock_failed /*如果*rw不為0,說明加寫者鎖失敗,跳轉到函數頭循環等待*/
????ret
????CFI_ENDPROC
END(__write_lock_failed)
? ?
? ?
函數write_unlock
函數write_unlock在寫者操作完后解寫者鎖,讀/寫自旋鎖變為空閑,鎖狀態值lock變為: 0x00000000+0x01000000。以后,讀者或寫者可以訪問臨界區數據了。
? ?
函數write_unlock列出如下:
? ?
# define write_unlock(lock) /
do {__raw_write_unlock(&(lock)->raw_lock); __release(lock); } while (0)
? ?
函數_write_unlock列出如下(在kernel/spinlock.c中):
? ?
void __lockfunc _write_unlock(rwlock_t *lock)
{
????rwlock_release(&lock->dep_map, 1, _RET_IP_);
????_raw_write_unlock(lock);
????preempt_enable(); /*打開內核搶占*/
}
?
# define _raw_write_unlock(rwlock)????__raw_write_unlock(&(rwlock)->raw_lock)
? ?
函數__raw_write_unlock開寫者鎖,即將鎖狀態值加上RW_LOCK_BIAS,其列出如下(在include/asm-x86/spinlock.h中):
? ?
static inline void __raw_write_unlock(raw_rwlock_t *rw)
{
????asm volatile(LOCK_PREFIX "addl %1, %0" /* RW_LOCK_BIAS+rw*/
???????? : "+m" (rw->lock) : "i" (RW_LOCK_BIAS) : "memory");
}
? ?
順序鎖
當使用讀/寫鎖時,讀者必須等待寫者完成時才能讀,寫者必須等待讀者完成時才能寫,兩者的優先權是平等的。順序鎖是對讀/寫鎖的優化,它允許讀寫同時進行,提高了并發性,讀寫操作同時進行的概率較小時,其性能很好。順序鎖對讀/寫鎖進行了下面的改進:
? ?
寫者不會阻塞讀者,即寫操作時,讀者仍可以進行讀操作。
寫者不需要等待所有讀者完成讀操作后才進行寫操作。
寫者與寫者之間互斥,即如果有寫者在寫操作時,其他寫者必須自旋等待。
如果在讀者進行讀操作期間,有寫者進行寫操作,那么讀者必須重新讀取數據,確保讀取正確的數據。
要求臨界區的共享資源不含指針,因為如果寫者使指針失效,讀者訪問該指針,將導致崩潰。
順序鎖實際上由一個自旋鎖和一個順序計數器組成,有的應用已包括自旋鎖,只需要一個順序計數器配合就可以實現順序鎖。針對這兩種情況,Linux內核給順序鎖提供了兩套API函數。一套API函數為*seq*,完整地實現了順序鎖;另一套API函數為*seqcount*,只包含了順序計數器,需要與用戶的自旋鎖配套實現順序鎖。順序鎖API函數的功能說明如下:
??????????seqlock_init(x)?初始化順序鎖,將順序計數器置0。
??????????write_seqlock(seqlock_t *sl)?加順序鎖,將順序號加1。寫者獲取順序鎖s1訪問臨界區,它使用了函數spin_lock。
??????????write_sequnlock(seqlock_t *sl)?解順序鎖,使用了函數spin_unlock,順序號加1。
??????????write_tryseqlock(seqlock_t *sl)?功能上等同于spin_trylock,順序號加1。
??????????read_seqbegin(const seqlock_t *sl)?返回順序鎖s1的當前順序號,讀者沒有開鎖和釋放鎖的開銷。
??????????read_seqretry(const seqlock_t *sl, unsigned start)?檢查讀操作期間是否有寫者訪問了共享資源,如果是,讀者就需要重新進行讀操作,否則,讀者成功完成了讀操作。
??????????seqcount_init(x)?初始化順序號。
??????????read_seqcount_begin(const seqcount_t *s)?讀者在讀操作前用此函數獲取當前的順序號。
??????????read_seqcount_retry(const seqcount_t *s, unsigned start)?讀者在訪問完后調用此函數檢查在讀期間是否有寫者訪問臨界區。如果有,讀者需要重新進行讀操作,否則,完成讀操作。
??????????write_seqcount_begin(seqcount_t *s)?寫者在訪問臨界區前調用此函數將順序號加1,以便讀者檢查是否在讀期間有寫者訪問過。
??????????write_seqcount_end(seqcount_t *s)?寫者寫完成后調用此函數將順序號加1,以便讀者能檢查出是否在讀期間有寫者訪問過。
? ?
用戶使用順序鎖時,寫操作加鎖方法與自旋鎖一樣,但讀操作需要使用循環查詢,使用順序鎖的讀操作樣例列出如下(在kernel/time.c中):
u64 get_jiffies_64(void)
{
????unsigned long seq;
????u64 ret;
?
????do {
????????seq = read_seqbegin(&xtime_lock); //獲取當前的順序號
????????ret = jiffies_64; //讀取臨界區數據
/*檢查seq值與當前順序號是否相等,若不等,說明有寫者開始工作,函數read_seqretry返回1,繼續循環*/
????} while (read_seqretry(&xtime_lock, seq));
????return ret;
}
? ?
在非SMP系統上,自旋鎖消失,但寫者還必須遞增順序變量,因為中斷例程可能改變數據的狀態。
? ?
下面分析順序鎖的數據結構及API函數:
? ?
(1)順序鎖結構seqlock_t
順序鎖用結構seqlock_t描述,它包括順序計數器sequence和自旋鎖lock。結構seqlock_t列出如下(在include/linux/seqlock.h中):
? ?
typedef struct {
????unsigned sequence;
????spinlock_t lock;
} seqlock_t;
? ?
在結構seqlock_t中,順序計數器sequence存放順序號,每個讀者在讀數據前后兩次讀順序計數器,并檢查兩次讀到的順序號是否相同。如果不相同,說明新的寫者已經開始寫并增加了順序計數器,表明剛讀到的數據無效。
? ?
寫者通過調用函數write_seqlock獲取順序鎖,將順序號加1,調用函數write_sequnlock釋放順序鎖,再將順序號加1。這樣,寫者正在寫操作時,順序號為奇數,寫完臨界區數據后,順序號為偶數。
? ?
讀者應以循環查詢方法讀取臨界區數據,讀者執行的臨界區代碼的方法列出如下:
? ?
do {
???? seq = read_seqbegin(&foo); //返回當前的順序號
????... //臨界區數據操作
} while (read_seqretry(&foo, seq));在上述代碼中,讀者在讀臨界區數據之前,先調用函數read_seqbegin獲致當前的順序號,如果順序號seq為奇數,說明寫者正寫臨界區數據,或者seq值與順序號當前值不等,表明讀者正讀時,寫者開始寫,函數read_seqretry返回1,讀者繼續循環等待寫者完成。
? ?
(2)順序鎖初始化函數seqlock_init
函數seqlock_init初始化順序鎖,順序鎖實際上由一個自旋鎖和一個順序計數器組成。其列出如下:
? ?
#define seqlock_init(x)????????????????????/
????do {????????????????????????/
????????(x)->sequence = 0;????????????/
????????spin_lock_init(&(x)->lock);????????/
????} while (0)
? ?
(3)寫者加鎖函數write_seqlock
函數write_seqlock加順序鎖。方法是:它先加自旋鎖,然后將順序號加1,此時,順序號值為奇數。此函數不需要關閉內核搶占,因為自旋鎖加鎖時已關閉了內核搶占。其列出如下:
? ?
static inline void write_seqlock(seqlock_t *sl)
{
????spin_lock(&sl->lock);
????++sl->sequence;
????smp_wmb();
}
? ?
(4)寫者解鎖函數write_sequnlock
函數write_sequnlock表示寫者解順序鎖,它將順序號加1,然后解開自旋鎖。此時,順序號應為偶數。其列出如下(在include/linux/seqlock.h中):
? ?
static inline void write_sequnlock(seqlock_t *sl)
{
????smp_wmb(); //加上SMP寫內存屏障
????sl->sequence++; //順序號加1
????spin_unlock(&sl->lock); //解開自旋鎖
}
? ?
(5)讀操作開始時讀順序號函數read_seqbegin
函數read_seqbegin讀取順序號,如果順序號為奇數,說明寫者正在寫操作,處理器執行空操作,進行循環等待,否則,函數返回讀取的順序號值。其列出如下:
? ?
static __always_inline unsigned read_seqbegin(const seqlock_t *sl)
{
????unsigned ret;
?
repeat:
????ret = sl->sequence;
????smp_rmb(); //加上SMP讀內存屏障
????if (unlikely(ret & 1)) { //如果ret & 1為true,表示順序號為奇數,寫者正在寫操作
????????cpu_relax(); //空操作
????????goto repeat;
????}
?
????return ret;
}
? ?
(6)讀操作完成時順序號檢查函數read_seqretry
函數read_seqretry用于讀操作完成后檢測讀的數據是否有效。如果讀操作完成后的順序號與讀操作開始前的順序號不一致,函數返回1,說明有寫者更改了臨界區數據,因此,調用者必須重新讀臨界者數據。
? ?
函數read_seqretry列出如下:
? ?
static __always_inline int read_seqretry(const seqlock_t *sl, unsigned start)
{
????smp_rmb();//加上SMP讀內存屏障
?
????return (sl->sequence != start); //順序鎖的順序號值與讀操作開始時的順序號值start不一致
}
? ?
大內核鎖
Linux內核因歷史原因還保留著大內核鎖(Big Kernel Lock,BKL),它在內核中的用途越來越小。大內核鎖用于同步整個內核,鎖的保持時間較長,嚴重地影響延遲,不提倡使用。
? ?
大內核鎖本質上是自旋鎖,它由一個自旋鎖和一個鎖深度變量組成。自旋鎖不能遞歸獲得鎖的,否則導致死鎖。大內核鎖進行了改進,它可以遞歸獲得鎖,還實現了搶占。整個內核只有一個大內核鎖,因為內核只有一個,用于保護整個內核。
? ?
鎖深度變量定義列出如下(在include/linux/sched.h):
? ?
struct task_struct {
????……
????int lock_depth;????????/* BKL鎖深度*/
……
}
? ?
自旋鎖定義列出如下(在lib/kernel_lock.c中):
? ?
static __cacheline_aligned_in_smp DEFINE_SPINLOCK(kernel_flag);
大內核鎖的API函數為lock_kernel(void)和unlock_kernel(void),函數lock_kernel為獲取大內核鎖,可以遞歸調用而不導致死鎖;函數unlock_kernel釋放大內核鎖。它們的用法與一般自旋鎖類似。
?
轉載于:https://www.cnblogs.com/wi100sh/p/4997518.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的【转】自旋锁及其衍生锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 字节 字符 utf-8 unicode
- 下一篇: BZOJ 2326: [HNOI2011