在线咨询
eetop公众号 创芯大讲堂 创芯人才网
切换到宽版

EETOP 创芯网论坛 (原名:电子顶级开发网)

手机号码,快捷登录

手机号码,快捷登录

找回密码

  登录   注册  

快捷导航
搜帖子
芯片精品文章合集(500篇!)    创芯人才网--重磅上线啦!
查看: 1800|回复: 0

[资料] 介绍:Linux大内核锁原理

[复制链接]
发表于 2013-9-23 12:14:32 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
大内核锁(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在进程再次被调度回来后,检查当前进程在切换之前是否
    因为持有大内核锁。如果有的话,说明在进程切换时,当前进程的大内核锁被强行释放了,
    需要再次获取。
    需要说明的是自旋锁版本:
    release_kernel_lock在释放锁之后还会开抢占,因为获取到大内核锁之后会关闭;
    reacquire_kernel_lock在重新获取到锁之后,会关闭抢占。
    重点关注__reacquire_kernel_lock的实现
    自旋锁的实现版本:
    成功抓到锁之后关抢占,如果抓不到锁,则一直遍历need resched标志直至退出。注意和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实现就是一句mutex_lock 而这里的__reacquire_kernel_lock
    有些细节差别。
    1)首先将当前进程的加锁深度设置为-1,代表无人加锁。这么做的意义是,第2步的mutex_lock如果产生调度,
    再次进入shedule时,不会重复释放大内核锁,因为__reacquire_kernel_lock之前已经释放锁了。
    2)接着临时强行开抢占后执行mutex_lock
    因为在schedule里是关抢占的,此时不能发生进程切换。
    3)如果抓到锁则关抢占
    恢复到schedule里调__reacquire_kernel_lock之前的抢占状态
    4)将加锁深度恢复到__reacquire_kernel_lock之前的深度
    恢复到schedule里调__reacquire_kernel_lock之前的大内核锁持有状态
    总而言之,关于大内核锁,记住两点就可以了:
    1)由spinlock或者mutex_lock锁住一个全局变量来实现
    2)进程切换时会检查当前进程是否持有大内核锁,而采取释放和重获的操作,以支持持有大内核锁的用户代码睡眠。
    推荐嵌入式linux开发书籍排行榜:http://emb.sunplusedu.com/questions/2013/0509/1297.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

站长推荐 上一条 /1 下一条

关闭

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-3-29 16:14 , Processed in 0.035887 second(s), 9 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
快速回复 返回顶部 返回列表