Linux 大内核锁原理

上一篇 / 下一篇  2012-07-11 09:37:39 / 个人分类:Linux

gNda1^By$A0  大内核锁(BKL)的设计是在kernel hacker们对多处理器的同步还没有十足把握时,引入的大粒度锁。他的设计思想是,一旦某个内核路径获取了这把锁,那么其他所有的内核路径都不能再获取到这把锁。51Testing软件测试网W:A iL0KT

}&z)C({1z9p`,WZQ0  自旋锁加锁的对象一般是一个全局变量,大内核锁加锁的对象是一段代码,里面可能包含多个全局变量。那么他带来的问题是,虽然A只需要互斥访问全局变量a,但附带锁了全局变量b,从而导致B不能访问b了。

|w,j3Fp6M0 51Testing软件测试网7}Y!G*FVJez

  大内核锁最先的实现靠一个全局自旋锁,但大家觉得这个锁的开销太大了,影响了实时性,因此后来将自旋锁改成了mutex,但阻塞时间一般不是很长,所以加锁失败的挂起和唤醒也是非常costly 所以后来又改成了自旋锁实现。

3u(h|O1g2K'N!Vvc:[0

$M%Wb"r-T!eB0  大内核锁一般是在文件系统,驱动等中用的比较多。目前kernel hacker们仍然在努力将大内核锁从linux里铲除。

:x!_9\_1z[#\]#x0 51Testing软件测试网y:T8AqV

  下面来分析大内核锁的实现。

*VG n;q}0

(H$h]gX0  我们之前说了大内核锁有两种实现,分别是自旋锁和mutex锁。

!y'GDT}^0

$s e b5~9V6G4td0  如果是mutex锁实现,自然不能在中断环境下使用大内核锁,因为中断下禁止调度是金科玉律。51Testing软件测试网`-G}$K$D

51Testing软件测试网;hfV&F&^C.T

  那么在大内核锁内调度是否可以?我们知道,如果一个内核流程获取到资源后就应该尽快完成操作释放资源,以便下一个竞争者获取到资源。所以资源持 有者不得睡眠是一个普遍共识。可是大内核锁不这么认为,持有大内核锁的用户是允许睡眠的-虽然我们并不鼓励这样,但是内核的大内核锁的设计方案里,会在进 程切换时,检查当前进程是否持有大内核锁并释放,当重新获取到cpu后,再尝试抓这把大内核锁。也就是说,进程在持有大内核锁时是可以睡眠的,这就带来资 源starvation51Testing软件测试网/r3]_-bs,M9~S f

51Testing软件测试网u0ZNe#KNW-G A

  来看基于自旋锁的大内核锁实现

[ \m3hu#x0 51Testing软件测试网jwq&Uo0CX,N

51Testing软件测试网E!HD3?S

+{#WIgq$?8O0static inline void __lock_kernel(void)
mD+|,g$q+r-\#Ui0{
3Wc`Tk.Z$ra0preempt_disable();
@ v)z#lP@*[3W-UiQ0if (unlikely(!_raw_spin_trylock(&kernel_flag))) {51Testing软件测试网R.|5h6D9H F#kR%_
/*51Testing软件测试网OiJh\0`+pp
* If preemption was disabled even before this51Testing软件测试网-y1@v(]J
* was called, there's nothing we can be polite51Testing软件测试网B_L {0h0B,c p?F
* about - just spin.
j6gm0L;c.lJ0*/
!o(b4ZgF~ B.u.|0if (preempt_count() > 1) {
Z2OIZJ7X"CZ:x0_raw_spin_lock(&kernel_flag);
N6P-SKz_hk0return;51Testing软件测试网V}d3O^N7m7~)U{;e
}
51Testing软件测试网.K:bW;guc2Q(_

8x1A+w1v's4K0
MSGI5K!Sa-l0/*51Testing软件测试网'kiqt0p.@P kD
* Otherwise, let's wait for the kernel lock
/KZgyk0jPf0* with preemption enabled..51Testing软件测试网\ \7?x'iNT#w
*/51Testing软件测试网;M ]m Y)ks
do {
nz"m+t+]*P0preempt_enable();51Testing软件测试网| Lug;G&oz O3t
while (spin_is_locked(&kernel_flag))51Testing软件测试网:G3T|!h6i Vq1ZWL
cpu_relax();
q6d2Ar{p@d0preempt_disable();
*gT,\i R p0} while (!_raw_spin_trylock(&kernel_flag));51Testing软件测试网1x x-a8p{t\9o5w*u
}51Testing软件测试网 ny LBU?#V;[
}
51Testing软件测试网-V:M:o;_(Y1XP d

51Testing软件测试网 M%J*`@%g#f8E


7fC+bP ?.mR,@ t.V0void __lockfunc lock_kernel(void)
"~r6r7x V!Q{3u5@t ]0{51Testing软件测试网?Z(B3[-B#a
int depth = current->lock_depth+1;51Testing软件测试网3^'o4\,e L.K^)`#x#{
if (likely(!depth))51Testing软件测试网6i4}W:LD,Z
__lock_kernel();
%k.]J+l&rps+Za0current->lock_depth = depth;51Testing软件测试网ikv,y8a.If
}

5g'v"BU1YH%k0
51Testing软件测试网]d7C6n0C7[%`k

  这段代码的意思是:

ki MIJXz}eO P0

G V(T%p8Kc9]0  1)如果当前进程如果不是重复加锁的话,就尝试去抓这把锁,并把锁深度加1。这么做的目的是避免锁重入。51Testing软件测试网"X:Y asrV g

51Testing软件测试网+R Z U3e0u$a

  2)实际加锁的时候,先关抢占,如果尝试加锁失败,则会根据调用lock_kernel之前关抢占与否,来决定是闷头死转,还是大开门户的轮询。

U s)H"D5T*o051Testing软件测试网 F-]qt7FxzD

  如果是mutex实现的大内核锁kernel_lock,则第2步直接mutex_lock--要么成功要么阻塞。51Testing软件测试网%lxpj6wJ'j'i h!c

51Testing软件测试网b$Ge;]S?a:Ag

  之前我们提到,在进程发生切换时,会检查当前进程是否持有大内核锁,这是在schedule里做的。

$Sh3?B9BLT0 51Testing软件测试网'u(CSS1Wo A

Y'V5r3W#N0
asmlinkage void __sched schedule(void)51Testing软件测试网.q$j#~Y,v.H
{51Testing软件测试网.CV L!nx
  release_kernel_lock(prev);
A@o b l7?[j0  context_switch();
[~1q2xy.k0  reacquire_kernel_lock(current);51Testing软件测试网3d`b5m/l.k3J{D8V
}

} Nj^4vG h`0  release_kernel_lock会判断如果当前进程持有大内核锁,则释放锁。51Testing软件测试网2u F tB wN{

51Testing软件测试网&K{ P*soI6To

  reacquire_kernel_lock在进程再次被调度回来后,检查当前进程在切换之前是否因为持有大内核锁。如果有的话,说明在进程切换时,当前进程的大内核锁被强行释放了,需要再次获取。51Testing软件测试网YC m!U#S5JO

dhU'w`:Q(R uK0  需要说明的是自旋锁版本:

4`+tpT&LK0

'L`7XAQ}t8a\.kb0  release_kernel_lock在释放锁之后还会开抢占,因为获取到大内核锁之后会关闭;51Testing软件测试网vyOoP_Uy

51Testing软件测试网}{+VW Ae5N&tJ8A$\

  reacquire_kernel_lock在重新获取到锁之后,会关闭抢占。51Testing软件测试网:@k yV-A,X

o'hA'\u t@0  重点关注__reacquire_kernel_lock的实现

,@/l\)IiN `Rw Y0

,IS(Dl,F Q8yB!Bk9O0  自旋锁的实现版本:成功抓到锁之后关抢占,如果抓不到锁,则一直遍历need resched标志直至退出。注意和lock_kernel比较。51Testing软件测试网;~K#Tm7c3V(UT3z j

5Z0nT hNqM m x0  mutex版本就比较扯淡了:

t7y!i0S'W0 51Testing软件测试网7oDc Y5JSHC

51Testing软件测试网.w[+|UQBs:O4`

'A"l$vf&V _Q0int __lockfunc __reacquire_kernel_lock(void)51Testing软件测试网m5_g']-x5n
{51Testing软件测试网+p4r,i|@7d:sCR
int saved_lock_depth = current->lock_depth;

S@W~!QL0

V2{(g/M A0BUG_ON(saved_lock_depth < 0);51Testing软件测试网8?Bps8J#Y,~S

51Testing软件测试网 Fxe"H3xd{

current->lock_depth = -1;
;aD8j A.N2v }0preempt_enable_no_resched();51Testing软件测试网4HO1N,J:zJ2[m k
mutex_lock(&kernel_sem);

C3{'RKZKc]/N]0 51Testing软件测试网D;nTe(z/rr

preempt_disable();51Testing软件测试网_{u-[Cw~
current->lock_depth = saved_lock_depth;

mU*SO8X lZG)P1kc0 51Testing软件测试网`r Rs$bn)I

return 0;51Testing软件测试网z fVmX
}

K0B Q*h/Q-VT&n2L O,G0

)mwl4ex0  我们之前看到kernel_lock的mutex实现就是一句mutex_lock 而这里的__reacquire_kernel_lock有些细节差别。

,{nH&`GH h;~5r0

E;@@+{/aZY V:WW0  1)首先将当前进程的加锁深度设置为-1,代表无人加锁。这么做的意义是,第2步的mutex_lock如果产生调度,再次进入shedule时,不会重复释放大内核锁,因为__reacquire_kernel_lock之前已经释放锁了。

-n5ezkG?h0

}UKd7Dm0  2)接着临时强行开抢占后执行mutex_lock。因为在schedule里是关抢占的,此时不能发生进程切换。51Testing软件测试网D5m$n$Y8o3H@ I

D&d2Yz&m"^/v0  3)如果抓到锁则关抢占51Testing软件测试网YM `4I/@;o1hxu(^

w2V~7{N+zp {0  恢复到schedule里调__reacquire_kernel_lock之前的抢占状态

"ANH/Vf|(fe'g0 51Testing软件测试网-{o"l,q;W?WZ

  4)将加锁深度恢复到__reacquire_kernel_lock之前的深度51Testing软件测试网V$syX NB+Y

Ps4DvBI'@m4@ Y|0  恢复到schedule里调__reacquire_kernel_lock之前的大内核锁持有状态51Testing软件测试网go5W;j/D l;P UAx

51Testing软件测试网}6\*d C6C8Jv

  总而言之,关于大内核锁,记住两点就可以了:

'b]v2cZ-j"g%]0 51Testing软件测试网O4c1s cB4}'^

  1)由spinlock或者mutex_lock锁住一个全局变量来实现。51Testing软件测试网J-`#V-V MF8s

@(a?'FP)c"\0  2)进程切换时会检查当前进程是否持有大内核锁,而采取释放和重获的操作,以支持持有大内核锁的用户代码睡眠。51Testing软件测试网'j^ o!F_8X"w


TAG:

 

评分:0

我来说两句

Open Toolbar