Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基說明:
Mutex
互斥鎖是Linux內(nèi)核中用于互斥操作的一種同步原語;optimistic spinning
)也應(yīng)用到了讀寫信號量上;前戲都已經(jīng)講完了,來看看實(shí)際的實(shí)現(xiàn)過程吧。
Mutex
在實(shí)現(xiàn)過程中,采用了optimistic spinning
自旋等待機(jī)制,這個機(jī)制的核心就是基于MCS鎖機(jī)制
來實(shí)現(xiàn)的;MCS鎖機(jī)制
是由John Mellor Crummey
和Michael Scott
在論文中《algorithms for scalable synchronization on shared-memory multiprocessors》
提出的,并以他倆的名字來命名;MCS鎖機(jī)制
要解決的問題是:在多CPU系統(tǒng)中,自旋鎖都在同一個變量上進(jìn)行自旋,在獲取鎖時會將包含鎖的cache line
移動到本地CPU,這種cache-line bouncing
會很大程度影響性能;MCS鎖機(jī)制
的核心思想:每個CPU都分配一個自旋鎖結(jié)構(gòu)體,自旋鎖的申請者(per-CPU
)在local-CPU變量
上自旋,這些結(jié)構(gòu)體組建成一個鏈表,申請者自旋等待前驅(qū)節(jié)點(diǎn)釋放該鎖;osq(optimistci spinning queue)
是基于MCS算法的一個具體實(shí)現(xiàn),并經(jīng)過了迭代優(yōu)化;optimistic spinning
,樂觀自旋,到底有多樂觀呢?當(dāng)發(fā)現(xiàn)鎖被持有時,optimistic spinning
相信持有者很快就能把鎖釋放,因此它選擇自旋等待,而不是睡眠等待,這樣也就能減少進(jìn)程切換帶來的開銷了。
看一下數(shù)據(jù)結(jié)構(gòu)吧:
osq_lock
如下:
osq_unlock
如下:
osq_wait_next
,來等待獲取下一個節(jié)點(diǎn),并在獲取成功后對下一個節(jié)點(diǎn)進(jìn)行解鎖;在加鎖和解鎖的過程中,由于可能存在操作來更改osq隊(duì)列,因此都調(diào)用了osq_wait_next
來獲取下一個確定的節(jié)點(diǎn):
終于來到了主題了,先看一下數(shù)據(jù)結(jié)構(gòu):
struct mutex {
atomic_long_t owner; //原子計(jì)數(shù),用于指向鎖持有者的task struct結(jié)構(gòu)
spinlock_t wait_lock; //自旋鎖,用于wait_list鏈表的保護(hù)操作
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */ //osq鎖
#endif
struct list_head wait_list; //鏈表,用于管理所有在該互斥鎖上睡眠的進(jìn)程
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
在使用mutex
時,有以下幾點(diǎn)需要注意的:
memset
或者拷貝來進(jìn)行初始化;tasklets, timer
)中使用;從mutex_lock
加鎖來看一下大概的流程:
mutex_lock
為了提高性能,分為三種路徑處理,優(yōu)先使用快速和中速路徑來處理,如果條件不滿足則會跳轉(zhuǎn)到慢速路徑來處理,慢速路徑中會進(jìn)行睡眠和調(diào)度,因此開銷也是最大的。__mutex_trylock_fast
中實(shí)現(xiàn)的,該函數(shù)的實(shí)現(xiàn)也很簡單,直接調(diào)用atomic_long_cmpxchg_release(&lock->owner, 0UL, curr)
函數(shù)來進(jìn)行判斷,如果lock->owner == 0
表明鎖未被持有,將curr
賦值給lock->owner
標(biāo)識curr
進(jìn)程持有該鎖,并直接返回;lock->owner
不等于0,表明鎖被持有,需要進(jìn)入下一個路徑來處理了;__mutex_lock_common
中實(shí)現(xiàn)的;__mutex_lock_common
的傳入?yún)?shù)為(lock, TASK_INTERRUPTIBLE, 0, NULL, _RET_IP_, false
),該函數(shù)中很多路徑覆蓋不到,接下來的分析也會剔除掉無效代碼;中速路徑的核心代碼如下:
當(dāng)發(fā)現(xiàn)mutex鎖的持有者正在運(yùn)行(另一個CPU)時,可以不進(jìn)行睡眠調(diào)度,而可以選擇自選等待,當(dāng)鎖持有者正在運(yùn)行時,它很有可能很快會釋放鎖,這個就是樂觀自旋的原因;
自旋等待的條件是持有鎖者正在臨界區(qū)運(yùn)行,自旋等待才有價值;
__mutex_trylock_or_owner
函數(shù)用于嘗試獲取鎖,如果獲取失敗則返回鎖的持有者。互斥鎖的結(jié)構(gòu)體中owner
字段,分為兩個部分:1)鎖持有者進(jìn)程的task_struct(由于L1_CACHE_BYTES對齊,低位比特沒有使用);2)MUTEX_FLAGS
部分,也就是對應(yīng)低三位,如下:
MUTEX_FLAG_WAITERS
:比特0,標(biāo)識存在非空等待者鏈表,在解鎖的時候需要執(zhí)行喚醒操作;MUTEX_FLAG_HANDOFF
:比特1,表明解鎖的時候需要將鎖傳遞給頂部的等待者;MUTEX_FLAG_PICKUP
:比特2,表明鎖的交接準(zhǔn)備已經(jīng)做完了,可以等待被取走了;mutex_optimistic_spin
用于執(zhí)行樂觀自旋,理想的情況下鎖持有者執(zhí)行完釋放,當(dāng)前進(jìn)程就能很快的獲取到鎖。實(shí)際需要考慮,如果鎖的持有者如果在臨界區(qū)被調(diào)度出去了,task_struct->on_cpu == 0
,那么需要結(jié)束自旋等待了,否則豈不是傻傻等待了。
mutex_can_spin_on_owner
:進(jìn)入自旋前檢查一下,如果當(dāng)前進(jìn)程需要調(diào)度,或者鎖的持有者已經(jīng)被調(diào)度出去了,那么直接就返回了,不需要做接下來的osq_lock/oqs_unlock
工作了,節(jié)省一些額外的overhead;osq_lock
用于確保只有一個等待者參與進(jìn)來自旋,防止大量的等待者蜂擁而至來獲取互斥鎖;for(;;)
自旋過程中調(diào)用__mutex_trylock_or_owner
來嘗試獲取鎖,獲取到后皆大歡喜,直接返回即可;mutex_spin_on_owner
,判斷不滿足自旋等待的條件,那么返回,讓我們進(jìn)入慢速路徑吧,畢竟不能強(qiáng)求;慢速路徑的主要代碼流程如下:
for(;;)
部分的流程可以看到,當(dāng)沒有獲取到鎖時,會調(diào)用schedule_preempt_disabled
將本身的任務(wù)進(jìn)行切換出去,睡眠等待,這也是它慢的原因了;
MUTEX_FLAG
來進(jìn)行判斷處理,并最終喚醒等待在該鎖上的任務(wù);Generic Mutex Subsystem
MCS locks and qspinlocks