Linux内核锁实现原理,linux 大内核锁原理
大內(nèi)核鎖(BKL)的設(shè)計(jì)是在kernel hacker們對(duì)多處理器的同步還沒有十足把握時(shí),引入的大粒度鎖。
他的設(shè)計(jì)思想是,一旦某個(gè)內(nèi)核路徑獲取了這把鎖,那么其他所有的內(nèi)核路徑都不能再獲取到這把鎖。
自旋鎖加鎖的對(duì)象一般是一個(gè)全局變量,大內(nèi)核鎖加鎖的對(duì)象是一段代碼,里面可能包含多個(gè)全局變量。
那么他帶來的問題是,雖然A只需要互斥訪問全局變量a,但附帶鎖了全局變量b,從而導(dǎo)致B不能訪問b了。
大內(nèi)核鎖最先的實(shí)現(xiàn)靠一個(gè)全局自旋鎖,但大家覺得這個(gè)鎖的開銷太大了,影響了實(shí)時(shí)性,因此后來將自旋鎖
改成了mutex,但阻塞時(shí)間一般不是很長,所以加鎖失敗的掛起和喚醒也是非常costly 所以后來又改成了自旋鎖實(shí)現(xiàn)。
大內(nèi)核鎖一般是在文件系統(tǒng),驅(qū)動(dòng)等中用的比較多。目前kernel hacker們?nèi)匀辉谂⒋髢?nèi)核鎖從linux里鏟除。
下面來分析大內(nèi)核鎖的實(shí)現(xiàn)。
我們之前說了大內(nèi)核鎖有兩種實(shí)現(xiàn),分別是自旋鎖和mutex鎖。
如果是mutex鎖實(shí)現(xiàn),自然不能在中斷環(huán)境下使用大內(nèi)核鎖,因?yàn)橹袛嘞陆拐{(diào)度是金科玉律。
那么在大內(nèi)核鎖內(nèi)調(diào)度是否可以?我們知道,如果一個(gè)內(nèi)核流程獲取到資源后就應(yīng)該盡快完成操作釋放資源,
以便下一個(gè)競爭者獲取到資源。所以資源持有者不得睡眠是一個(gè)普遍共識(shí)。可是大內(nèi)核鎖不這么認(rèn)為,
持有大內(nèi)核鎖的用戶是允許睡眠的-雖然我們并不鼓勵(lì)這樣,但是內(nèi)核的大內(nèi)核鎖的設(shè)計(jì)方案里,會(huì)在進(jìn)程切換時(shí),
檢查當(dāng)前進(jìn)程是否持有大內(nèi)核鎖并釋放,當(dāng)重新獲取到cpu后,再嘗試抓這把大內(nèi)核鎖。也就是說,進(jìn)程在
持有大內(nèi)核鎖時(shí)是可以睡眠的,這就帶來資源starvation
來看基于自旋鎖的大內(nèi)核鎖實(shí)現(xiàn)
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) 如果當(dāng)前進(jìn)程如果不是重復(fù)加鎖的話,就嘗試去抓這把鎖,
并把鎖深度加1。這么做的目的是避免鎖重入。
2)實(shí)際加鎖的時(shí)候,先關(guān)搶占,如果嘗試加鎖失敗,則會(huì)
根據(jù)調(diào)用lock_kernel之前關(guān)搶占與否,來決定是悶頭死轉(zhuǎn),還是大開門戶的輪詢。
如果是mutex實(shí)現(xiàn)的大內(nèi)核鎖kernel_lock,則第2步直接mutex_lock--要么成功要么阻塞。
之前我們提到,在進(jìn)程發(fā)生切換時(shí),會(huì)檢查當(dāng)前進(jìn)程是否持有大內(nèi)核鎖,這是在schedule
里做的。
asmlinkage void __sched schedule(void)
{
release_kernel_lock(prev);
context_switch();
reacquire_kernel_lock(current);
}
release_kernel_lock會(huì)判斷如果當(dāng)前進(jìn)程持有大內(nèi)核鎖,則釋放鎖。
reacquire_kernel_lock在進(jìn)程再次被調(diào)度回來后,檢查當(dāng)前進(jìn)程在切換之前是否
因?yàn)槌钟写髢?nèi)核鎖。如果有的話,說明在進(jìn)程切換時(shí),當(dāng)前進(jìn)程的大內(nèi)核鎖被強(qiáng)行釋放了,
需要再次獲取。
需要說明的是自旋鎖版本:
release_kernel_lock在釋放鎖之后還會(huì)開搶占,因?yàn)楂@取到大內(nèi)核鎖之后會(huì)關(guān)閉;
reacquire_kernel_lock在重新獲取到鎖之后,會(huì)關(guān)閉搶占。
重點(diǎn)關(guān)注__reacquire_kernel_lock的實(shí)現(xiàn)
自旋鎖的實(shí)現(xiàn)版本:
成功抓到鎖之后關(guān)搶占,如果抓不到鎖,則一直遍歷need resched標(biāo)志直至退出。注意和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實(shí)現(xiàn)就是一句mutex_lock 而這里的__reacquire_kernel_lock
有些細(xì)節(jié)差別。
1)首先將當(dāng)前進(jìn)程的加鎖深度設(shè)置為-1,代表無人加鎖。這么做的意義是,第2步的mutex_lock如果產(chǎn)生調(diào)度,
再次進(jìn)入shedule時(shí),不會(huì)重復(fù)釋放大內(nèi)核鎖,因?yàn)開_reacquire_kernel_lock之前已經(jīng)釋放鎖了。
2)接著臨時(shí)強(qiáng)行開搶占后執(zhí)行mutex_lock
因?yàn)樵趕chedule里是關(guān)搶占的,此時(shí)不能發(fā)生進(jìn)程切換。
3)如果抓到鎖則關(guān)搶占
恢復(fù)到schedule里調(diào)__reacquire_kernel_lock之前的搶占狀態(tài)
4)將加鎖深度恢復(fù)到__reacquire_kernel_lock之前的深度
恢復(fù)到schedule里調(diào)__reacquire_kernel_lock之前的大內(nèi)核鎖持有狀態(tài)
總而言之,關(guān)于大內(nèi)核鎖,記住兩點(diǎn)就可以了:
1)由spinlock或者mutex_lock鎖住一個(gè)全局變量來實(shí)現(xiàn)
2)進(jìn)程切換時(shí)會(huì)檢查當(dāng)前進(jìn)程是否持有大內(nèi)核鎖,而采取釋放和重獲的操作,以支持持有大內(nèi)核鎖的用戶代碼睡眠。
總結(jié)
以上是生活随笔為你收集整理的Linux内核锁实现原理,linux 大内核锁原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 北京环球影城在地铁几号线
- 下一篇: 溜冰鞋多少钱一双啊?