Linux 大内核锁原理

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

*s]m3_;J0  大内核锁(BKL)的设计是在kernel hacker们对多处理器的同步还没有十足把握时,引入的大粒度锁。他的设计思想是,一旦某个内核路径获取了这把锁,那么其他所有的内核路径都不能再获取到这把锁。

`VR Pb`(K#Gl*LJ0

h)D_EdJ;~0  自旋锁加锁的对象一般是一个全局变量,大内核锁加锁的对象是一段代码,里面可能包含多个全局变量。那么他带来的问题是,虽然A只需要互斥访问全局变量a,但附带锁了全局变量b,从而导致B不能访问b了。51Testing软件测试网za3BCB%l

51Testing软件测试网 |L%t!Fn\kYT8x

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

+AQ[K8K*C ^n/bH~0  大内核锁一般是在文件系统,驱动等中用的比较多。目前kernel hacker们仍然在努力将大内核锁从linux里铲除。

f7{%}Pq[%a0 51Testing软件测试网-LRNT$S~2BI6vr

  下面来分析大内核锁的实现。51Testing软件测试网 W J e8Gs ? u` Mz7q

51Testing软件测试网IJ3xdHF

  我们之前说了大内核锁有两种实现,分别是自旋锁和mutex锁。51Testing软件测试网;P6W)]?7y Ok&M#b0cN

51Testing软件测试网 a5UNt q

  如果是mutex锁实现,自然不能在中断环境下使用大内核锁,因为中断下禁止调度是金科玉律。51Testing软件测试网t iXjG j8cn7k;S!_1a

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

51Testing软件测试网XQ ctW0AH;W?b

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

-?Z[&Gr0

o%ML9K9LS0 51Testing软件测试网&~3}%Yn;?Y

51Testing软件测试网F*e1T Mt.GbWK,ik-Z

static inline void __lock_kernel(void)51Testing软件测试网!VOi TRcI8} c7q
{
'LN{J_3i4X0preempt_disable();
[6Ov5v%_-to0if (unlikely(!_raw_spin_trylock(&kernel_flag))) {51Testing软件测试网"B$f v$N.@xs
/*51Testing软件测试网H v V&C3j fV(g
* If preemption was disabled even before this
;M ^!pq]$@mww2D/O \0* was called, there's nothing we can be polite
9qZ} ] s/{|0hl0* about - just spin.51Testing软件测试网x&Q7Q3A:Y1Z:}s?'~m0p
*/
q `-sZh?g"dC0if (preempt_count() > 1) {51Testing软件测试网 }Ze-X9O$T;y
_raw_spin_lock(&kernel_flag);51Testing软件测试网 q3S6Om8ZXK
return;
6{BW@;oLN1{0}
51Testing软件测试网LlS)r4T

51Testing软件测试网 H^B)n!C7G

51Testing软件测试网.Ag"s'_qq8uLc;}|
/*51Testing软件测试网 a$w IP n ?)w}3a#e7i
* Otherwise, let's wait for the kernel lock51Testing软件测试网DO+I,VSny8Z6w0Vp
* with preemption enabled..
n0d(A5HrHF0*/51Testing软件测试网g@x-Ri6c
do {
PH2]$yZz0preempt_enable();
%} vFp)Uw0]l6at0while (spin_is_locked(&kernel_flag))
iuf!sl&^0cpu_relax();51Testing软件测试网;h Vq | ehMv&I
preempt_disable();
/O%v&ui$I HG6]0} while (!_raw_spin_trylock(&kernel_flag));51Testing软件测试网NU%zm)v
}
%@os jw0nY0}
51Testing软件测试网;n'] D G/pC

6] ~vMP5`lB051Testing软件测试网8g/b(J0^8z`/g
void __lockfunc lock_kernel(void)51Testing软件测试网%]rL3Nai'lj j
{
;f w0r3L)F0int depth = current->lock_depth+1;51Testing软件测试网F(U xJg `5vC
if (likely(!depth))
I:\C9Z`0_zm:@0__lock_kernel();51Testing软件测试网(SK4@5J)k-GF
current->lock_depth = depth;
NO"W8rI%d0}
51Testing软件测试网rly qu)z'f

51Testing软件测试网k;A,dNx!R7`6m

  这段代码的意思是:

InoC1C0 51Testing软件测试网B ^$y5GA

  1)如果当前进程如果不是重复加锁的话,就尝试去抓这把锁,并把锁深度加1。这么做的目的是避免锁重入。

*U+?Hh$U}+yJ5P0d Vy0 51Testing软件测试网$T V$d C.pRT/j

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

3{ D,BT.B(u|O$Cvw0

vce,q-e0  如果是mutex实现的大内核锁kernel_lock,则第2步直接mutex_lock--要么成功要么阻塞。

.QUs8C'yy\$f0 51Testing软件测试网J2c%c#VWaw3BT%Z

  之前我们提到,在进程发生切换时,会检查当前进程是否持有大内核锁,这是在schedule里做的。51Testing软件测试网 X$i+Ri }

pS#ajb S h%l0 51Testing软件测试网6O5hY)LS"PD*Mr

asmlinkage void __sched schedule(void)
8H@$_8IF-m0{
{(H5y%j?0  release_kernel_lock(prev);51Testing软件测试网F&K+O R4a,w b3J
  context_switch();
v;G%U%V L JuTB1i0  reacquire_kernel_lock(current);
g\] ?Eu+s0}

*aX?8t P/^0  release_kernel_lock会判断如果当前进程持有大内核锁,则释放锁。51Testing软件测试网$k(m9RE b8{*J't

r&v d]~Vsy3aY`0  reacquire_kernel_lock在进程再次被调度回来后,检查当前进程在切换之前是否因为持有大内核锁。如果有的话,说明在进程切换时,当前进程的大内核锁被强行释放了,需要再次获取。51Testing软件测试网:{p&F4flJijU

51Testing软件测试网"W&cS4_$LI

  需要说明的是自旋锁版本:51Testing软件测试网&NpE/Rm @R

v.Q\^1mL9s0  release_kernel_lock在释放锁之后还会开抢占,因为获取到大内核锁之后会关闭;

i:w&|(^'WNk/rX|3k&@Q0

z#|N3P/@p0  reacquire_kernel_lock在重新获取到锁之后,会关闭抢占。51Testing软件测试网V,A5zb^jQ

51Testing软件测试网4p,k1GQG5y}"Ss

  重点关注__reacquire_kernel_lock的实现

.Of!x/Y|-H0 51Testing软件测试网D{pV,^Lt

  自旋锁的实现版本:成功抓到锁之后关抢占,如果抓不到锁,则一直遍历need resched标志直至退出。注意和lock_kernel比较。

Hy;KJ%f,j)o0

1@w kN:v7\0  mutex版本就比较扯淡了:

(xJs-?QDK)\"`2s0 51Testing软件测试网PU[ L |:M'x q5l2rr

51Testing软件测试网^h c!l5B.tz1V

||'M&e!R*AaF0int __lockfunc __reacquire_kernel_lock(void)51Testing软件测试网/e8XjjVR X.H-b
{
)XU!R8Q?1T#t6| X0int saved_lock_depth = current->lock_depth;

*H/e[-b!G:U_$PAJD0 51Testing软件测试网&Bx(y[ IF?@p

BUG_ON(saved_lock_depth < 0);

X'l)Jg _te0 51Testing软件测试网+Hi]}9i0Jv

current->lock_depth = -1;51Testing软件测试网}L)ydZ{$Jk X3N8I
preempt_enable_no_resched();51Testing软件测试网J]6j/[#S!U+_w$e3L v
mutex_lock(&kernel_sem);

)gTE6Gtr0 51Testing软件测试网"a6v:{,G'D|6s2k

preempt_disable();51Testing软件测试网~8bGx7n]H#A8u
current->lock_depth = saved_lock_depth;
51Testing软件测试网%JFEz&^ _F

51Testing软件测试网V0f,Gn._+SO

return 0;
/sg#N6LGu0}

;f*rO)p~&T6s0

&SXp o$M)~ N O[0  我们之前看到kernel_lock的mutex实现就是一句mutex_lock 而这里的__reacquire_kernel_lock有些细节差别。51Testing软件测试网 ^C+aW*no9\9nl

x]W8[!bG0  1)首先将当前进程的加锁深度设置为-1,代表无人加锁。这么做的意义是,第2步的mutex_lock如果产生调度,再次进入shedule时,不会重复释放大内核锁,因为__reacquire_kernel_lock之前已经释放锁了。

k4I3Q ^.l q,]0 51Testing软件测试网.g,U"Kr[

  2)接着临时强行开抢占后执行mutex_lock。因为在schedule里是关抢占的,此时不能发生进程切换。

3F:k@D| WRD'wk0

0^cj ]]4o oz0  3)如果抓到锁则关抢占

]?,X(h"n b#e0

)\;nU0X9^^K1p0  恢复到schedule里调__reacquire_kernel_lock之前的抢占状态

$b4mp0S\0 51Testing软件测试网9|r$[!e I)O

  4)将加锁深度恢复到__reacquire_kernel_lock之前的深度51Testing软件测试网 n1Iur9^+S:q:W

51Testing软件测试网g;tH P x q

  恢复到schedule里调__reacquire_kernel_lock之前的大内核锁持有状态51Testing软件测试网}GL:TL6M

51Testing软件测试网3lO.@d;]z

  总而言之,关于大内核锁,记住两点就可以了:51Testing软件测试网;]4`HQYc @ i

Dq*Y+X%y0  1)由spinlock或者mutex_lock锁住一个全局变量来实现。51Testing软件测试网U {l8y4s$n kO

+{%dX x0z%@3H0  2)进程切换时会检查当前进程是否持有大内核锁,而采取释放和重获的操作,以支持持有大内核锁的用户代码睡眠。51Testing软件测试网z5i$[xX,l8}


TAG:

 

评分:0

我来说两句

Open Toolbar