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