Linux内核锁实现原理,linux 大内核锁原理
大內核鎖(BKL)的設計是在kernel hacker們對多處理器的同步還沒有十足把握時,引入的大粒度鎖。
他的設計思想是,一旦某個內核路徑獲取了這把鎖,那么其他所有的內核路徑都不能再獲取到這把鎖。
自旋鎖加鎖的對象一般是一個全局變量,大內核鎖加鎖的對象是一段代碼,里面可能包含多個全局變量。
那么他帶來的問題是,雖然A只需要互斥訪問全局變量a,但附帶鎖了全局變量b,從而導致B不能訪問b了。
大內核鎖最先的實現靠一個全局自旋鎖,但大家覺得這個鎖的開銷太大了,影響了實時性,因此后來將自旋鎖
改成了mutex,但阻塞時間一般不是很長,所以加鎖失敗的掛起和喚醒也是非常costly 所以后來又改成了自旋鎖實現。
大內核鎖一般是在文件系統,驅動等中用的比較多。目前kernel hacker們仍然在努力將大內核鎖從linux里鏟除。
下面來分析大內核鎖的實現。
我們之前說了大內核鎖有兩種實現,分別是自旋鎖和mutex鎖。
如果是mutex鎖實現,自然不能在中斷環境下使用大內核鎖,因為中斷下禁止調度是金科玉律。
那么在大內核鎖內調度是否可以?我們知道,如果一個內核流程獲取到資源后就應該盡快完成操作釋放資源,
以便下一個競爭者獲取到資源。所以資源持有者不得睡眠是一個普遍共識。可是大內核鎖不這么認為,
持有大內核鎖的用戶是允許睡眠的-雖然我們并不鼓勵這樣,但是內核的大內核鎖的設計方案里,會在進程切換時,
檢查當前進程是否持有大內核鎖并釋放,當重新獲取到cpu后,再嘗試抓這把大內核鎖。也就是說,進程在
持有大內核鎖時是可以睡眠的,這就帶來資源starvation
來看基于自旋鎖的大內核鎖實現
static inline void __lock_kernel(void)
{
preempt_disable();
if (unlikely(!_raw_spin_trylock(&kernel_flag))) {
/*
* If preemption was disabled even before this
* was called, there's nothing we can be polite
* about - just spin.
*/
if (preempt_count() > 1) {
_raw_spin_lock(&kernel_flag);
return;
}
/*
* Otherwise, let's wait for the kernel lock
* with preemption enabled..
*/
do {
preempt_enable();
while (spin_is_locked(&kernel_flag))
cpu_relax();
preempt_disable();
} while (!_raw_spin_trylock(&kernel_flag));
}
}
void __lockfunc lock_kernel(void)
{
int depth = current->lock_depth+1;
if (likely(!depth))
__lock_kernel();
current->lock_depth = depth;
}
這段代碼的意思是,
1) 如果當前進程如果不是重復加鎖的話,就嘗試去抓這把鎖,
并把鎖深度加1。這么做的目的是避免鎖重入。
2)實際加鎖的時候,先關搶占,如果嘗試加鎖失敗,則會
根據調用lock_kernel之前關搶占與否,來決定是悶頭死轉,還是大開門戶的輪詢。
如果是mutex實現的大內核鎖kernel_lock,則第2步直接mutex_lock--要么成功要么阻塞。
之前我們提到,在進程發生切換時,會檢查當前進程是否持有大內核鎖,這是在schedule
里做的。
asmlinkage void __sched schedule(void)
{
release_kernel_lock(prev);
context_switch();
reacquire_kernel_lock(current);
}
release_kernel_lock會判斷如果當前進程持有大內核鎖,則釋放鎖。
reacquire_kernel_lock在進程再次被調度回來后,檢查當前進程在切換之前是否
因為持有大內核鎖。如果有的話,說明在進程切換時,當前進程的大內核鎖被強行釋放了,
需要再次獲取。
需要說明的是自旋鎖版本:
release_kernel_lock在釋放鎖之后還會開搶占,因為獲取到大內核鎖之后會關閉;
reacquire_kernel_lock在重新獲取到鎖之后,會關閉搶占。
重點關注__reacquire_kernel_lock的實現
自旋鎖的實現版本:
成功抓到鎖之后關搶占,如果抓不到鎖,則一直遍歷need resched標志直至退出。注意和lock_kernel比較。
mutex版本就比較扯淡了:
int __lockfunc __reacquire_kernel_lock(void)
{
int saved_lock_depth = current->lock_depth;
BUG_ON(saved_lock_depth < 0);
current->lock_depth = -1;
preempt_enable_no_resched();
mutex_lock(&kernel_sem);
preempt_disable();
current->lock_depth = saved_lock_depth;
return 0;
}
我們之前看到kernel_lock的mutex實現就是一句mutex_lock 而這里的__reacquire_kernel_lock
有些細節差別。
1)首先將當前進程的加鎖深度設置為-1,代表無人加鎖。這么做的意義是,第2步的mutex_lock如果產生調度,
再次進入shedule時,不會重復釋放大內核鎖,因為__reacquire_kernel_lock之前已經釋放鎖了。
2)接著臨時強行開搶占后執行mutex_lock
因為在schedule里是關搶占的,此時不能發生進程切換。
3)如果抓到鎖則關搶占
恢復到schedule里調__reacquire_kernel_lock之前的搶占狀態
4)將加鎖深度恢復到__reacquire_kernel_lock之前的深度
恢復到schedule里調__reacquire_kernel_lock之前的大內核鎖持有狀態
總而言之,關于大內核鎖,記住兩點就可以了:
1)由spinlock或者mutex_lock鎖住一個全局變量來實現
2)進程切換時會檢查當前進程是否持有大內核鎖,而采取釋放和重獲的操作,以支持持有大內核鎖的用戶代碼睡眠。
總結
以上是生活随笔為你收集整理的Linux内核锁实现原理,linux 大内核锁原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 北京环球影城在地铁几号线
- 下一篇: linux服务器基本常识,服务器搭建-L