Linux kernel locking - 2 - mutex

續前篇: Linux kernel locking - 1 - atomic

最近做專案 rtw88-usb ,

遇到 scheduling while atomic 和 rcu context switch
等等這些 kernel bug warning, 

這是因為在 USB read/write registers 中使用的 usb_control_msg(),
底層會呼叫 usb_start_wait_urb(), 而這是利用 struct completion 
和 USB ISR handler callback function, 用 scheduling wait for completion, 
也因此會有些限制, 譬如並無法在 spin lock 的 critical section 中使用... 

所以後來設計了 atomic read/write functions
用 busy loop waiting 來取代 usb_start_wait_urb().
目前看來這些問題初步得到解決.
( 不過仍有限制, 需要依賴 IRQ handler, 所以 irq 不能被 disable) 

在這過程中, 把 locking 又再好好研讀一下.
https://www.kernel.org/doc/html/latest/kernel-hacking/locking.html
有感而發, 再紀錄一下.

之前在 scheduling while atomic 分析 這篇, 有提到 mutex 和 spinlock
這篇來針對 mutex 繼續深入研究一下.

從前篇所述的範例
https://github.com/neojou/new_ldd/blob/master/scheduling/sched_atomic2.c
可以知道, mutex lock 是可以在 scheduling 下執行, 也因此可以在 mutex lock
所保護的 critical section 中, 執行 msleep, 但 spinlock 就不能執行 scheduling sleep.

所以 mutex lock 是一種 sleeping lock.

- 一次只有一個 task/thread 可以進入 mutex lock critical section


- mutex lock 不能用在 atomic context

在 linux/kernel.h 有一個 macro : might_sleep()
會判斷是否是在 atomic context 下 (spinlock / irq-handler / ...)
如果是的話, 會印出 stack trace 警告



- irqs_disabled() 用來判斷是否 irq 被關起來了.
- preempt_disable 並不會禁止 IRQ, 它只是增加 thread_info->preempt_count

- mutex lock 底層 owner 是一個 64bit atomic
- 每個 mutex 會有一個 wait queue(wait_list), 並用 spin lock (wait_lock) 來保護 wait_list 存取

在這篇: https://ithelp.ithome.com.tw/articles/10214373
有介紹 mutex lock 的資料結構

--
在mutex lock 做法上, Linux kernel 設計了三種方式
1. fastpath: 直接用 compare-and-swap 指令 (cmpxchg)
2. midpath: optimistic spinning
3. slowpath: blocking直到 lock 被 釋放



這邊 __mutex_trylock_fast() 是用 atomic_long_cmpxchg_acquire 直接 atomic access owner,
看看能否得到 lock, 不行直接拿到的話, 就走 __mutex_lock_slowpath() 來要 lock.

- 當要 mutex lock 要不到時, 會被放到 sleeping queue 並切換到另一個 running task/thread 執行

optimistic spinning : 讓競爭 mutex lock 的 tasks 盡量在 poll / spin 狀態, 而不是直接進入 sleep




目前 mutex_lock 有三個 API

https://stackoverflow.com/questions/18298962/which-mutex-lock-variant-should-i-use-in-linux-kernel-developing

void mutex_lock(struct mutex *lock);
int  mutex_lock_interruptible(struct mutex *lock);
int  mutex_lock_killable(struct mutex *lock);

差別在於 mutex_lock_interruptible 可以被其他signal 中斷
mutex_lock_killable 只能被 signal kill 中斷
而 mutex_lock 不能被 signal 中斷, 會一直存在等到拿到 lock.


留言

熱門文章