gNda1^By$A0 大内核锁(BKL)的设计是在kernel hacker们对多处理器的同步还没有十足把握时,引入的大粒度锁。他的设计思想是,一旦某个内核路径获取了这把锁,那么其他所有的内核路径都不能再获取到这把锁。51Testing软件测试网W:A
iL0KT
}&z)C({1z9p`,WZQ0 自旋锁加锁的对象一般是一个全局变量,大内核锁加锁的对象是一段代码,里面可能包含多个全局变量。那么他带来的问题是,虽然A只需要互斥访问全局变量a,但附带锁了全局变量b,从而导致B不能访问b了。
|w,j3F p6M0
51Testing软件测试网7}Y!G*FVJez 大内核锁最先的实现靠一个全局自旋锁,但大家觉得这个锁的开销太大了,影响了实时性,因此后来将自旋锁改成了mutex,但阻塞时间一般不是很长,所以加锁失败的挂起和唤醒也是非常costly 所以后来又改成了自旋锁实现。
3u(h|O1g2K'N!Vvc:[0
$M%W b"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
eb5~9V6G4td0 如果是mutex锁实现,自然不能在中断环境下使用大内核锁,因为中断下禁止调度是金科玉律。51Testing软件测试网`-G}$K$D
51Testing软件测试网;hfV&F&^C.T 那么在大内核锁内调度是否可以?我们知道,如果一个内核流程获取到资源后就应该尽快完成操作释放资源,以便下一个竞争者获取到资源。所以资源持
有者不得睡眠是一个普遍共识。可是大内核锁不这么认为,持有大内核锁的用户是允许睡眠的-虽然我们并不鼓励这样,但是内核的大内核锁的设计方案里,会在进
程切换时,检查当前进程是否持有大内核锁并释放,当重新获取到cpu后,再尝试抓这把大内核锁。也就是说,进程在持有大内核锁时是可以睡眠的,这就带来资
源starvation51Testing软件测试网/r3]_-bs,M9~S f
51Testing软件测试网u0ZNe#KN W-GA 来看基于自旋锁的大内核锁实现
[
\m3hu#x0
51Testing软件测试网jwq&Uo0CX,N
51Testing软件测试网E!H D3?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.@PkD * 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,\iRp0} while (!_raw_spin_trylock(&kernel_flag));51Testing软件测试网1x
x-a8p{t\9o5w*u }51Testing软件测试网 nyLBU?#V;[ }51Testing软件测试网-V:M:o;_(Y1XP d
51Testing软件测试网 M%J*`@%g#f8E 7fC+bP?.mR,@ t.V0void __lockfunc lock_kernel(void) "~r6r7xV!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
GV(T%p8Kc9]0 1)如果当前进程如果不是重复加锁的话,就尝试去抓这把锁,并把锁深度加1。这么做的目的是避免锁重入。51Testing软件测试网"X:Y asrV g
51Testing软件测试网+R
Z U3e0u$a 2)实际加锁的时候,先关抢占,如果尝试加锁失败,则会根据调用lock_kernel之前关抢占与否,来决定是闷头死转,还是大开门户的轮询。
Us)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软件测试网.CVL!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软件测试网2uF t B
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(UT3zj
5Z0nT h Nq Mmx0 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[mk mutex_lock(&kernel_sem); C3{'RK ZKc]/N]0
51Testing软件测试网D;nTe(z/rrpreempt_disable();51Testing软件测试网_ {u-[Cw~ current->lock_depth = saved_lock_depth; mU*SO8X
lZG)P1kc0
51Testing软件测试网`r Rs$bn)Ireturn 0;51Testing软件测试网 zfVmX } K0B Q*h/Q-VT&n2L
O,G0 |
)mwl4e x0 我们之前看到kernel_lock的mutex实现就是一句mutex_lock 而这里的__reacquire_kernel_lock有些细节差别。
,{nH&`GHh;~5r0
E;@ @+{/aZYV: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软件测试网Y M`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/Dl;PUAx
51Testing软件测试网}6\*dC6C8Jv 总而言之,关于大内核锁,记住两点就可以了:
'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