歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> 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在進程再次被調度回來後,檢查當前進程在切換之前是否因為持有大內核鎖。如果有的話,說明在進程切換時,當前進程的大內核鎖被強行釋放了,需要再次獲取。

Copyright © Linux教程網 All Rights Reserved