日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【转】自旋锁及其衍生锁

發布時間:2025/3/13 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【转】自旋锁及其衍生锁 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文網址: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年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的【转】自旋锁及其衍生锁的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。