自旋锁使用场景和实现分析(转载)
自旋鎖
最近看到的一篇文章,覺(jué)得寫的很清晰,通過(guò)場(chǎng)景應(yīng)用解答了我對(duì)自旋鎖使用的一些疑問(wèn),推薦給大家。
引入問(wèn)題:
(1)如果cpu0持有鎖,cpu1一直不釋放鎖怎么辦?
(2)什么場(chǎng)景下必須要用自旋鎖,而不能用互斥量?
(3)互斥量或者自旋鎖,他們會(huì)被多個(gè)進(jìn)程使用,那么它屬于進(jìn)程的一部分?
內(nèi)核當(dāng)發(fā)生訪問(wèn)資源沖突的時(shí)候,可以有兩種鎖的解決方案選擇:
- 一個(gè)是原地等待
- 一個(gè)是掛起當(dāng)前進(jìn)程,調(diào)度其他進(jìn)程執(zhí)行(睡眠)
Spinlock 是內(nèi)核中提供的一種比較常見的鎖機(jī)制,自旋鎖是“原地等待”的方式解決資源沖突的,即,一個(gè)線程獲取了一個(gè)自旋鎖后,另外一個(gè)線程期望獲取該自旋鎖,獲取不到,只能夠原地“打轉(zhuǎn)”(忙等待)。由于自旋鎖的這個(gè)忙等待的特性,注定了它使用場(chǎng)景上的限制 ——?自旋鎖不應(yīng)該被長(zhǎng)時(shí)間的持有(消耗 CPU 資源)。
自旋鎖的使用
在linux kernel的實(shí)現(xiàn)中,經(jīng)常會(huì)遇到這樣的場(chǎng)景:共享數(shù)據(jù)被中斷上下文和進(jìn)程上下文訪問(wèn),該如何保護(hù)呢?如果只有進(jìn)程上下文的訪問(wèn),那么可以考慮使用semaphore或者mutex的鎖機(jī)制,但是現(xiàn)在中斷上下文也參和進(jìn)來(lái),那些可以導(dǎo)致睡眠的lock就不能使用了,這時(shí)候,可以考慮使用spin lock。
這里為什么把中斷上下文標(biāo)紅加粗呢?因?yàn)樵谥袛嗌舷挛?#xff0c;是不允許睡眠的(原因詳見文章《Linux 中斷之中斷處理淺析》中的第四章),所以,這里需要的是一個(gè)不會(huì)導(dǎo)致睡眠的鎖——spinlock。
換言之,中斷上下文要用鎖,首選 spinlock。
使用自旋鎖,有兩種方式定義一個(gè)鎖:
動(dòng)態(tài)的:
spinlock_t lock;
spin_lock_init (&lock);
靜態(tài)的:
DEFINE_SPINLOCK(lock);自旋鎖的死鎖和解決
自旋鎖不可遞歸,自己等待自己已經(jīng)獲取的鎖,會(huì)導(dǎo)致死鎖。
自旋鎖可以在中斷上下文中使用,但是試想一個(gè)場(chǎng)景:一個(gè)線程獲取了一個(gè)鎖,但是被中斷處理程序打斷,中斷處理程序也獲取了這個(gè)鎖(但是之前已經(jīng)被鎖住了,無(wú)法獲取到,只能自旋),中斷無(wú)法退出,導(dǎo)致線程中后面釋放鎖的代碼無(wú)法被執(zhí)行,導(dǎo)致死鎖。(如果確認(rèn)中斷中不會(huì)訪問(wèn)和線程中同一個(gè)鎖,其實(shí)無(wú)所謂)
一、考慮下面的場(chǎng)景(內(nèi)核搶占場(chǎng)景):
(1)進(jìn)程A在某個(gè)系統(tǒng)調(diào)用過(guò)程中訪問(wèn)了共享資源 R
(2)進(jìn)程B在某個(gè)系統(tǒng)調(diào)用過(guò)程中也訪問(wèn)了共享資源 R
會(huì)不會(huì)造成沖突呢?假設(shè)在A訪問(wèn)共享資源R的過(guò)程中發(fā)生了中斷,中斷喚醒了沉睡中的,優(yōu)先級(jí)更高的B,在中斷返回現(xiàn)場(chǎng)的時(shí)候,發(fā)生進(jìn)程切換,B啟動(dòng)執(zhí)行,并通過(guò)系統(tǒng)調(diào)用訪問(wèn)了R,如果沒(méi)有鎖保護(hù),則會(huì)出現(xiàn)兩個(gè)thread進(jìn)入臨界區(qū),導(dǎo)致程序執(zhí)行不正確。OK,我們加上spin lock看看如何:A在進(jìn)入臨界區(qū)之前獲取了spin lock,同樣的,在A訪問(wèn)共享資源R的過(guò)程中發(fā)生了中斷,中斷喚醒了沉睡中的,優(yōu)先級(jí)更高的B,B在訪問(wèn)臨界區(qū)之前仍然會(huì)試圖獲取spin lock,這時(shí)候由于A進(jìn)程持有spin lock而導(dǎo)致B進(jìn)程進(jìn)入了永久的spin……怎么破?linux的kernel很簡(jiǎn)單,在A進(jìn)程獲取spin lock的時(shí)候,禁止本CPU上的搶占(上面的永久spin的場(chǎng)合僅僅在本CPU的進(jìn)程搶占本CPU的當(dāng)前進(jìn)程這樣的場(chǎng)景中發(fā)生)。如果A和B運(yùn)行在不同的CPU上,那么情況會(huì)簡(jiǎn)單一些:A進(jìn)程雖然持有spin lock而導(dǎo)致B進(jìn)程進(jìn)入spin狀態(tài),不過(guò)由于運(yùn)行在不同的CPU上,A進(jìn)程會(huì)持續(xù)執(zhí)行并會(huì)很快釋放spin lock,解除B進(jìn)程的spin狀態(tài)
二、再考慮下面的場(chǎng)景(中斷上下文場(chǎng)景):
(1)運(yùn)行在CPU0上的進(jìn)程A在某個(gè)系統(tǒng)調(diào)用過(guò)程中訪問(wèn)了共享資源 R
(2)運(yùn)行在CPU1上的進(jìn)程B在某個(gè)系統(tǒng)調(diào)用過(guò)程中也訪問(wèn)了共享資源 R
(3)外設(shè)P的中斷handler中也會(huì)訪問(wèn)共享資源 R
在這樣的場(chǎng)景下,使用spin lock可以保護(hù)訪問(wèn)共享資源R的臨界區(qū)嗎?我們假設(shè)CPU0上的進(jìn)程A持有spin lock進(jìn)入臨界區(qū),這時(shí)候,外設(shè)P發(fā)生了中斷事件,并且調(diào)度到了CPU1上執(zhí)行,看起來(lái)沒(méi)有什么問(wèn)題,執(zhí)行在CPU1上的handler會(huì)稍微等待一會(huì)CPU0上的進(jìn)程A,等它立刻臨界區(qū)就會(huì)釋放spin lock的,但是,如果外設(shè)P的中斷事件被調(diào)度到了CPU0上執(zhí)行會(huì)怎么樣?CPU0上的進(jìn)程A在持有spin lock的狀態(tài)下被中斷上下文搶占,而搶占它的CPU0上的handler在進(jìn)入臨界區(qū)之前仍然會(huì)試圖獲取spin lock,悲劇發(fā)生了,CPU0上的P外設(shè)的中斷handler永遠(yuǎn)的進(jìn)入spin狀態(tài),這時(shí)候,CPU1上的進(jìn)程B也不可避免在試圖持有spin lock的時(shí)候失敗而導(dǎo)致進(jìn)入spin狀態(tài)。為了解決這樣的問(wèn)題,linux kernel采用了這樣的辦法:如果涉及到中斷上下文的訪問(wèn),spin lock需要和禁止本 CPU 上的中斷聯(lián)合使用。
三、再考慮下面的場(chǎng)景(底半部場(chǎng)景)
linux kernel中提供了豐富的bottom half的機(jī)制,雖然同屬中斷上下文,不過(guò)還是稍有不同。我們可以把上面的場(chǎng)景簡(jiǎn)單修改一下:外設(shè)P不是中斷handler中訪問(wèn)共享資源R,而是在的bottom half中訪問(wèn)。使用spin lock+禁止本地中斷當(dāng)然是可以達(dá)到保護(hù)共享資源的效果,但是使用牛刀來(lái)殺雞似乎有點(diǎn)小題大做,這時(shí)候disable bottom half就OK了
四、中斷上下文之間的競(jìng)爭(zhēng)
同一種中斷handler之間在uni core和multi core上都不會(huì)并行執(zhí)行,這是linux kernel的特性。
如果不同中斷handler需要使用spin lock保護(hù)共享資源,對(duì)于新的內(nèi)核(不區(qū)分fast handler和slow handler),所有handler都是關(guān)閉中斷的,因此使用spin lock不需要關(guān)閉中斷的配合。
bottom half又分成softirq和tasklet,同一種softirq會(huì)在不同的CPU上并發(fā)執(zhí)行,因此如果某個(gè)驅(qū)動(dòng)中的softirq的handler中會(huì)訪問(wèn)某個(gè)全局變量,對(duì)該全局變量是需要使用spin lock保護(hù)的,不用配合disable CPU中斷或者bottom half。
tasklet更簡(jiǎn)單,因?yàn)橥环Ntasklet不會(huì)多個(gè)CPU上并發(fā)。
自旋鎖的實(shí)現(xiàn)
1、文件整理
和體系結(jié)構(gòu)無(wú)關(guān)的代碼如下:
(1)?include/linux/spinlock_types.h
這個(gè)頭文件定義了通用spin lock的基本的數(shù)據(jù)結(jié)構(gòu)(例如spinlock_t)和如何初始化的接口(DEFINE_SPINLOCK)。這里的“通用”是指不論SMP還是UP都通用的那些定義。
(2)include/linux/spinlock_types_up.h
這個(gè)頭文件不應(yīng)該直接include,在include/linux/spinlock_types.h文件會(huì)根據(jù)系統(tǒng)的配置(是否SMP)include相關(guān)的頭文件,如果UP則會(huì)include該頭文件。這個(gè)頭文定義UP系統(tǒng)中和spin lock的基本的數(shù)據(jù)結(jié)構(gòu)和如何初始化的接口。當(dāng)然,對(duì)于non-debug版本而言,大部分struct都是empty的。
(3)include/linux/spinlock.h
這個(gè)頭文件定義了通用spin lock的接口函數(shù)聲明,例如spin_lock、spin_unlock等,使用spin lock模塊接口API的驅(qū)動(dòng)模塊或者其他內(nèi)核模塊都需要include這個(gè)頭文件。
(4)include/linux/spinlock_up.h
這個(gè)頭文件不應(yīng)該直接include,在include/linux/spinlock.h文件會(huì)根據(jù)系統(tǒng)的配置(是否SMP)include相關(guān)的頭文件。這個(gè)頭文件是debug版本的spin lock需要的。
(5)include/linux/spinlock_api_up.h
同上,只不過(guò)這個(gè)頭文件是non-debug版本的spin lock需要的
(6)linux/spinlock_api_smp.h
SMP上的spin lock模塊的接口聲明
(7)kernel/locking/spinlock.c
SMP上的spin lock實(shí)現(xiàn)。
對(duì)UP和SMP上spin lock頭文件進(jìn)行整理:
| UP需要的頭文件 | SMP需要的頭文件 |
| linux/spinlock_type_up.h:? | asm/spinlock_types.h? |
2、數(shù)據(jù)結(jié)構(gòu)
首先定義一個(gè) spinlock_t 的數(shù)據(jù)類型,其本質(zhì)上是一個(gè)整數(shù)值(對(duì)該數(shù)值的操作需要保證原子性),該數(shù)值表示spin lock是否可用。初始化的時(shí)候被設(shè)定為1。當(dāng)thread想要持有鎖的時(shí)候調(diào)用spin_lock函數(shù),該函數(shù)將spin lock那個(gè)整數(shù)值減去1,然后進(jìn)行判斷,如果等于0,表示可以獲取spin lock,如果是負(fù)數(shù),則說(shuō)明其他thread的持有該鎖,本thread需要spin。
內(nèi)核中的spinlock_t的數(shù)據(jù)類型定義如下:
typedef struct spinlock {
struct raw_spinlock rlock;
} spinlock_t;
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
} raw_spinlock_t;
通用(適用于各種arch)的spin lock使用spinlock_t這樣的type name,各種arch定義自己的struct raw_spinlock。聽起來(lái)不錯(cuò)的主意和命名方式,直到linux realtime tree(PREEMPT_RT)提出對(duì)spinlock的挑戰(zhàn)。real time linux是一個(gè)試圖將linux kernel增加硬實(shí)時(shí)性能的一個(gè)分支(你知道的,linux kernel mainline只是支持soft realtime),多年來(lái),很多來(lái)自realtime branch的特性被merge到了mainline上,例如:高精度timer、中斷線程化等等。realtime tree希望可以對(duì)現(xiàn)存的spinlock進(jìn)行分類:一種是在realtime kernel中可以睡眠的spinlock,另外一種就是在任何情況下都不可以睡眠的spinlock。分類很清楚但是如何起名字?起名字絕對(duì)是個(gè)技術(shù)活,起得好了事半功倍,可維護(hù)性好,什么文檔啊、注釋啊都素那浮云,閱讀代碼就是享受,如沐春風(fēng)。起得不好,注定被后人唾棄,或者拖出來(lái)吊打(這讓我想起給我兒子起名字的那段不堪回首的歲月……)。最終,spin lock的命名規(guī)范定義如下:
(1)spinlock,在rt linux(配置了PREEMPT_RT)的時(shí)候可能會(huì)被搶占(實(shí)際底層可能是使用支持PI(優(yōu)先級(jí)翻轉(zhuǎn))的mutext)。
(2)raw_spinlock,即便是配置了PREEMPT_RT也要頑強(qiáng)的spin
(3)arch_spinlock,spin lock是和architecture相關(guān)的,arch_spinlock是architecture相關(guān)的實(shí)現(xiàn)
對(duì)于UP平臺(tái),所有的arch_spinlock_t都是一樣的,定義如下:
typedef struct { } arch_spinlock_t;什么都沒(méi)有,一切都是空啊。當(dāng)然,這也符合前面的分析,對(duì)于UP,即便是打開的preempt選項(xiàng),所謂的spin lock也不過(guò)就是disable preempt而已,不需定義什么spin lock的變量。
對(duì)于SMP平臺(tái),這和arch相關(guān),我們?cè)谙旅婷枋觥?/p>
在具體的實(shí)現(xiàn)面,我們不可能把每一個(gè)接口函數(shù)的代碼都呈現(xiàn)出來(lái),我們選擇最基礎(chǔ)的spin_lock為例子,其他的讀者可以自己閱讀代碼來(lái)理解。
spin_lock的代碼如下:
static inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
當(dāng)然,在linux mainline代碼中,spin_lock和raw_spin_lock是一樣的,在這里重點(diǎn)看看raw_spin_lock,代碼如下:
#define raw_spin_lock(lock) _raw_spin_lock(lock)UP中的實(shí)現(xiàn):
#define _raw_spin_lock(lock) __LOCK(lock)
#define __LOCK(lock) \
do { preempt_disable(); ___LOCK(lock); } while (0)
SMP的實(shí)現(xiàn):
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
__raw_spin_lock(lock);
}
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
UP中很簡(jiǎn)單,本質(zhì)上就是一個(gè)preempt_disable而已,SMP中稍顯復(fù)雜,preempt_disable當(dāng)然也是必須的,spin_acquire可以略過(guò),這是和運(yùn)行時(shí)檢查鎖的有效性有關(guān)的,如果沒(méi)有定義CONFIG_LOCKDEP其實(shí)就是空函數(shù)。如果沒(méi)有定義CONFIG_LOCK_STAT(和鎖的統(tǒng)計(jì)信息相關(guān)),LOCK_CONTENDED就是調(diào)用 do_raw_spin_lock 而已,如果沒(méi)有定義CONFIG_DEBUG_SPINLOCK,它的代碼如下:
static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{
__acquire(lock);
arch_spin_lock(&lock->raw_lock);
}
__acquire和靜態(tài)代碼檢查相關(guān),忽略之,最終實(shí)際的獲取spin lock還是要靠arch相關(guān)的代碼實(shí)現(xiàn)。
針對(duì) ARM 平臺(tái)的?arch_spin_lock
代碼位于arch/arm/include/asm/spinlock.h和spinlock_type.h,和通用代碼類似,spinlock_type.h定義ARM相關(guān)的spin lock定義以及初始化相關(guān)的宏;spinlock.h中包括了各種具體的實(shí)現(xiàn)。
1. 回到2.6.23版本的內(nèi)核中
和arm平臺(tái)相關(guān)spin lock數(shù)據(jù)結(jié)構(gòu)的定義如下(那時(shí)候還是使用raw_spinlock_t而不是arch_spinlock_t):
typedef struct {
volatile unsigned int lock;
} raw_spinlock_t;
一個(gè)整數(shù)就OK了,0表示unlocked,1表示locked。配套的API包括__raw_spin_lock和__raw_spin_unlock。__raw_spin_lock會(huì)持續(xù)判斷l(xiāng)ock的值是否等于0,如果不等于0(locked)那么其他thread已經(jīng)持有該鎖,本thread就不斷的spin,判斷l(xiāng)ock的數(shù)值,一直等到該值等于0為止,一旦探測(cè)到lock等于0,那么就設(shè)定該值為1,表示本thread持有該鎖了,當(dāng)然,這些操作要保證原子性,細(xì)節(jié)和exclusive版本的ldr和str(即ldrex和strexeq)相關(guān),這里略過(guò)。立刻臨界區(qū)后,持鎖thread會(huì)調(diào)用__raw_spin_unlock函數(shù)是否spin lock,其實(shí)就是把0這個(gè)數(shù)值賦給lock。
這個(gè)版本的spin lock的實(shí)現(xiàn)當(dāng)然可以實(shí)現(xiàn)功能,而且在沒(méi)有沖突的時(shí)候表現(xiàn)出不錯(cuò)的性能,不過(guò)存在一個(gè)問(wèn)題:不公平。也就是所有的thread都是在無(wú)序的爭(zhēng)搶spin lock,誰(shuí)先搶到誰(shuí)先得,不管thread等了很久還是剛剛開始spin。在沖突比較少的情況下,不公平不會(huì)體現(xiàn)的特別明顯,然而,隨著硬件的發(fā)展,多核處理器的數(shù)目越來(lái)越多,多核之間的沖突越來(lái)越劇烈,無(wú)序競(jìng)爭(zhēng)的spinlock帶來(lái)的performance issue終于浮現(xiàn)出來(lái),根據(jù)Nick Piggin的描述:
On an 8 core (2 socket) Opteron, spinlock unfairness is extremely noticable, with a
userspace test having a difference of up to 2x runtime per thread, and some threads are
starved or "unfairly" granted the lock up to 1 000 000 (!) times.
多么的不公平,有些可憐的thread需要饑餓的等待1000000次。本質(zhì)上無(wú)序競(jìng)爭(zhēng)從概率論的角度看應(yīng)該是均勻分布的,不過(guò)由于硬件特性導(dǎo)致這么嚴(yán)重的不公平,我們來(lái)看一看硬件block:
lock本質(zhì)上是保存在main memory中的,由于cache的存在,當(dāng)然不需要每次都有訪問(wèn)main memory。在多核架構(gòu)下,每個(gè)CPU都有自己的L1 cache,保存了lock的數(shù)據(jù)。假設(shè)CPU0獲取了spin lock,那么執(zhí)行完臨界區(qū),在釋放鎖的時(shí)候會(huì)調(diào)用smp_mb invalide其他忙等待的CPU的L1 cache,這樣后果就是釋放spin lock的那個(gè)cpu可以更快的訪問(wèn)L1cache,操作lock數(shù)據(jù),從而大大增加的下一次獲取該spin lock的機(jī)會(huì)。
2、回到現(xiàn)在:arch_spinlock_t
ARM平臺(tái)中的arch_spinlock_t定義如下(little endian):
typedef struct {
union {
u32 slock;
struct __raw_tickets {
u16 owner;
u16 next;
} tickets;
};
} arch_spinlock_t;
本來(lái)以為一個(gè)簡(jiǎn)單的整數(shù)類型的變量就搞定的spin lock看起來(lái)沒(méi)有那么簡(jiǎn)單,要理解這個(gè)數(shù)據(jù)結(jié)構(gòu),需要了解一些ticket-based spin lock的概念。如果你有機(jī)會(huì)去九毛九去排隊(duì)吃飯(聲明:不是九毛九的飯托,僅僅是喜歡面食而常去吃而已)就會(huì)理解ticket-based spin lock。大概是因?yàn)楸阋?#xff0c;每次去九毛九總是無(wú)法長(zhǎng)驅(qū)直入,門口的笑容可掬的靚女會(huì)給一個(gè)ticket,上面寫著15號(hào),同時(shí)會(huì)告訴你,當(dāng)前狀態(tài)是10號(hào)已經(jīng)入席,11號(hào)在等待。
回到arch_spinlock_t,這里的owner就是當(dāng)前已經(jīng)入席的那個(gè)號(hào)碼,next記錄的是下一個(gè)要分發(fā)的號(hào)碼。下面的描述使用普通的計(jì)算機(jī)語(yǔ)言和在九毛九就餐(假設(shè)九毛九只有一張餐桌)的例子來(lái)進(jìn)行描述,估計(jì)可以讓吃貨更有興趣閱讀下去。最開始的時(shí)候,slock被賦值為0,也就是說(shuō)owner和next都是0,owner和next相等,表示unlocked。當(dāng)?shù)谝粋€(gè)個(gè)thread調(diào)用spin_lock來(lái)申請(qǐng)lock(第一個(gè)人就餐)的時(shí)候,owner和next相等,表示unlocked,這時(shí)候該thread持有該spin lock(可以擁有九毛九的唯一的那個(gè)餐桌),并且執(zhí)行next++,也就是將next設(shè)定為1(再來(lái)人就分配1這個(gè)號(hào)碼讓他等待就餐)。也許該thread執(zhí)行很快(吃飯吃的快),沒(méi)有其他thread來(lái)競(jìng)爭(zhēng)就調(diào)用spin_unlock了(無(wú)人等待就餐,生意慘淡啊),這時(shí)候執(zhí)行owner++,也就是將owner設(shè)定為1(表示當(dāng)前持有1這個(gè)號(hào)碼牌的人可以就餐)。姍姍來(lái)遲的1號(hào)獲得了直接就餐的機(jī)會(huì),next++之后等于2。1號(hào)這個(gè)家伙吃飯巨慢,這是不文明現(xiàn)象(thread不能持有spin lock太久),但是存在。又來(lái)一個(gè)人就餐,分配當(dāng)前next值的號(hào)碼2,當(dāng)然也會(huì)執(zhí)行next++,以便下一個(gè)人或者3的號(hào)碼牌。持續(xù)來(lái)人就會(huì)分配3、4、5、6這些號(hào)碼牌,next值不斷的增加,但是owner巋然不動(dòng),直到欠扁的1號(hào)吃飯完畢(調(diào)用spin_unlock),釋放飯桌這個(gè)唯一資源,owner++之后等于2,表示持有2那個(gè)號(hào)碼牌的人可以進(jìn)入就餐了。?
3、ARM 結(jié)構(gòu)體系?arch_spin_lock?接口實(shí)現(xiàn)
3.1 加鎖
同樣的,這里也只是選擇一個(gè)典型的API來(lái)分析,其他的大家可以自行學(xué)習(xí)。我們選擇的是 arch_spin_lock,其ARM32的代碼如下:
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
unsigned long tmp;
u32 newval;
arch_spinlock_t lockval;
prefetchw(&lock->slock);------------------------(0)
__asm__ __volatile__(
"1: ldrex %0, [%3]\n"-------------------------(1)
" add %1, %0, %4\n" --------------------------(2)
" strex %2, %1, [%3]\n"------------------------(3)
" teq %2, #0\n"----------------------------(4)
" bne 1b"
: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
while (lockval.tickets.next != lockval.tickets.owner) {-------(5)
wfe();--------------------------------(6)
lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);----(7)
}
smp_mb();---------------------------------(8)
}
(0)和preloading cache相關(guān)的操作,主要是為了性能考慮
(1)lockval =?lock->slock (如果lock->slock沒(méi)有被其他處理器獨(dú)占,則標(biāo)記當(dāng)前執(zhí)行處理器對(duì)lock->slock地址的獨(dú)占訪問(wèn);否則不影響)
(2)newval =?lockval + (1 << TICKET_SHIFT)
(3)strex tmp, newval, [&lock->slock] (如果當(dāng)前執(zhí)行處理器沒(méi)有獨(dú)占lock->slock地址的訪問(wèn),不進(jìn)行存儲(chǔ),返回1給temp;如果當(dāng)前處理器已經(jīng)獨(dú)占lock->slock內(nèi)存訪問(wèn),則對(duì)內(nèi)存進(jìn)行寫,返回0給temp,清除獨(dú)占標(biāo)記) lock->tickets.next = lock->tickets.next + 1
(4)檢查是否寫入成功 lockval.tickets.next
(5)初始化時(shí)lock->tickets.owner、lock->tickets.next都為0,假設(shè)第一次執(zhí)行arch_spin_lock,lockval = *lock,lock->tickets.next++,lockval.tickets.next 等于 lockval.tickets.owner,獲取到自旋鎖;自旋鎖未釋放,第二次執(zhí)行的時(shí)候,lock->tickets.owner = 0, lock->tickets.next = 1,拷貝到lockval后,lockval.tickets.next != lockval.tickets.owner,會(huì)執(zhí)行wfe等待被自旋鎖釋放被喚醒,自旋鎖釋放時(shí)會(huì)執(zhí)行 lock->tickets.owner++,lockval.tickets.owner重新賦值
(6)暫時(shí)中斷掛起執(zhí)行。如果當(dāng)前spin lock的狀態(tài)是locked,那么調(diào)用wfe進(jìn)入等待狀態(tài)。更具體的細(xì)節(jié)請(qǐng)參考ARM WFI和WFE指令中的描述。
(7)其他的CPU喚醒了本cpu的執(zhí)行,說(shuō)明owner發(fā)生了變化,該新的own賦給lockval,然后繼續(xù)判斷spin lock的狀態(tài),也就是回到step 5。
(8)memory barrier的操作,具體可以參考memory barrier中的描述。
3.1 釋放鎖
static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
smp_mb();
lock->tickets.owner++; ---------------------- (0)
dsb_sev(); ---------------------------------- (1)
}
(0)lock->tickets.owner增加1,下一個(gè)被喚醒的處理器會(huì)檢查該值是否與自己的lockval.tickets.next相等,lock->tickets.owner代表可以獲取的自旋鎖的處理器,lock->tickets.next你一個(gè)可以獲取的自旋鎖的owner;處理器獲取自旋鎖時(shí),會(huì)先讀取lock->tickets.next用于與lock->tickets.owner比較并且對(duì)lock->tickets.next加1,下一個(gè)處理器獲取到的lock->tickets.next就與當(dāng)前處理器不一致了,兩個(gè)處理器都與lock->tickets.owner比較,肯定只有一個(gè)處理器會(huì)相等,自旋鎖釋放時(shí)時(shí)對(duì)lock->tickets.owner加1計(jì)算,因此,先申請(qǐng)自旋鎖多處理器lock->tickets.next值更新,自然先獲取到自旋鎖
(1)執(zhí)行sev指令,喚醒wfe等待的處理器
自旋鎖的變體
| 接口API的類型 | spinlock中的定義 | raw_spinlock的定義 |
| 定義spin lock并初始化 | DEFINE_SPINLOCK | DEFINE_RAW_SPINLOCK |
| 動(dòng)態(tài)初始化spin lock | spin_lock_init | raw_spin_lock_init |
| 獲取指定的spin lock | spin_lock | raw_spin_lock |
| 獲取指定的spin lock同時(shí)disable本CPU中斷 | spin_lock_irq | raw_spin_lock_irq |
| 保存本CPU當(dāng)前的irq狀態(tài),disable本CPU中斷并獲取指定的spin lock | spin_lock_irqsave | raw_spin_lock_irqsave |
| 獲取指定的spin lock同時(shí)disable本CPU的bottom half | spin_lock_bh | raw_spin_lock_bh |
| 釋放指定的spin lock | spin_unlock | raw_spin_unlock |
| 釋放指定的spin lock同時(shí)enable本CPU中斷 | spin_unlock_irq | raw_spin_unock_irq |
| 釋放指定的spin lock同時(shí)恢復(fù)本CPU的中斷狀態(tài) | spin_unlock_irqstore | raw_spin_unlock_irqstore |
| 獲取指定的spin lock同時(shí)enable本CPU的bottom half | spin_unlock_bh | raw_spin_unlock_bh |
| 嘗試去獲取spin lock,如果失敗,不會(huì)spin,而是返回非零值 | spin_trylock | raw_spin_trylock |
| 判斷spin lock是否是locked,如果其他的thread已經(jīng)獲取了該lock,那么返回非零值,否則返回0 | spin_is_locked | raw_spin_is_locked |
static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
{
unsigned long flags;
local_irq_save(flags);
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
/*
* On lockdep we dont want the hand-coded irq-enable of
* do_raw_spin_lock_flags() code, because lockdep assumes
* that interrupts are not re-enabled during lock-acquire:
*/
#ifdef CONFIG_LOCKDEP
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
#else
do_raw_spin_lock_flags(lock, &flags);
#endif
return flags;
}
static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
local_irq_disable();
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
static inline void __raw_spin_lock_bh(raw_spinlock_t *lock)
{
local_bh_disable();
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
#endif /* CONFIG_PREEMPT */
static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
spin_release(&lock->dep_map, 1, _RET_IP_);
do_raw_spin_unlock(lock);
preempt_enable();
}
static inline void __raw_spin_unlock_irqrestore(raw_spinlock_t *lock,
unsigned long flags)
{
spin_release(&lock->dep_map, 1, _RET_IP_);
do_raw_spin_unlock(lock);
local_irq_restore(flags);
preempt_enable();
}
static inline void __raw_spin_unlock_irq(raw_spinlock_t *lock)
{
spin_release(&lock->dep_map, 1, _RET_IP_);
do_raw_spin_unlock(lock);
local_irq_enable();
preempt_enable();
}
static inline void __raw_spin_unlock_bh(raw_spinlock_t *lock)
{
spin_release(&lock->dep_map, 1, _RET_IP_);
do_raw_spin_unlock(lock);
preempt_enable_no_resched();
local_bh_enable_ip((unsigned long)__builtin_return_address(0));
}
static inline int __raw_spin_trylock_bh(raw_spinlock_t *lock)
{
local_bh_disable();
preempt_disable();
if (do_raw_spin_trylock(lock)) {
spin_acquire(&lock->dep_map, 0, 1, _RET_IP_);
return 1;
}
preempt_enable_no_resched();
local_bh_enable_ip((unsigned long)__builtin_return_address(0));
return 0;
}
?
小結(jié)
spin_lock 的時(shí)候,禁止內(nèi)核搶占
如果涉及到中斷上下文的訪問(wèn),spin lock需要和禁止本 CPU 上的中斷聯(lián)合使用(spin_lock_irqsave?/?spin_unlock_irqstore)
涉及 half bottom 使用:spin_lock_bh /?spin_unlock_bh
總結(jié)
以上是生活随笔為你收集整理的自旋锁使用场景和实现分析(转载)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 微信实现Java
- 下一篇: python复制word全部内容,包括图