免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12
最近访问板块 发新帖
楼主: mingzhi5288
打印 上一主题 下一主题

[CPU及多核] 使用spin_lock_irqsave为什么不允许睡眠 [复制链接]

论坛徽章:
0
11 [报告]
发表于 2014-07-19 18:17 来自手机 |只看该作者
如果不考虑实时进程的存在,在spin lock irqsave 保护的代码中调用睡眠函数只会导致性能问题,不会死锁。看实现schedule函数是会调用关中断,选择新进程后开中断,所以在撇去中断处理函数情形外,和不考虑和实时进程竞争时,不会死锁的。

论坛徽章:
0
12 [报告]
发表于 2014-07-19 18:27 来自手机 |只看该作者
如果新旧两个进程在不同核上,旧进程总会被调度然后释放锁,就不会死锁,如果在同一个核上,应该是死锁。

论坛徽章:
0
13 [报告]
发表于 2014-07-21 10:42 |只看该作者
回复 12# guobamantou


    我想问的是 为什么是死锁 死在哪里?死在哪一个函数上面?

论坛徽章:
0
14 [报告]
发表于 2014-07-30 17:27 |只看该作者
本帖最后由 l4rmbr 于 2014-07-30 18:51 编辑
mingzhi5288 发表于 2014-07-18 14:10

 在单cpu下 使用spin_lock_irqsave 会变成
{
   preempt_disable()
        local_irq_save();
}
在使用这个函数的时候 如果用了schedule 为什么不可以呢?

是因为调度另一个进程之后不行呢?

还是根本无法调度另一个进程 ?

不能睡眠的原因能够从源码角度帮我分析一下吗?


以下讨论只适用于mainline kernel,不适用linux-rt kernel.

假设在单CPU下,进入临界区可以抢占。看看会发生什么问题。

考虑以下可能的场景, 一个双进程竞争同一资源的场景:

Task 1:

        if ( is_resource_availabel() )
                 get_resource();

它做一个检查,检查资源是否可用,可用的话,就获取它。

在单CPU上,这是安全的操作。但是,一旦考虑抢占,那么就可能出现TOCTTOU问题(Time of check to time of use):
这是一个两步的操作,即先检查,状态符合后,再使用。

这种非原子化的操作,存在中间状态,如下:

     if ( is_resource_availabel() )
               <--- 中间状态 --->                 
                 get_resource();

假设Task 2在中间状态抢占了Task 1, 并且,它也执行该段代码:

   if ( is_resource_availabel() )
                 get_resource();
   
注意,Task 1 在检查资源可用后即被抢占,它实际并未改变资源状态,所以,Task 2也成功检查了资源可用,
并成功get_resource()。然后,它被调度,又回到Task 1的执行上下文。

假设资源是唯一的。此时Task 1运行get_resource()失败。这在Task 1来看,就是不一致的状态,明明它检查成功,
结果获取资源时却失败。

因此,在单CPU上,在可抢占的情况下,这段代码是一段可能引起竞争的临界区,必须获取排它的权限,如自旋锁,之后才能
进入。

   spin_lock(lock);
         if ( is_resource_availabel() )
                 get_resource();
         spin_unlock(lock);

但获得锁还没解决问题!

再考虑另一场景:

Task 1获取了锁lock,然后被Task 2抢占了。

假设Task 2执行以下代码:

    spin_lock(another_lock);
            ... do something ...
           
            spin_lock(lock);
             if ( is_resource_availabel() )
                 get_resource();
            spin_unlock(lock);

            spin_unlock(another_lock);

Task 2先获取另一个锁another_lock,然后在获取被Task 1拿着的lock时自旋等待。 
直到Task 2被调度或抢占。但内核并不能保证能调度回Task 1。

如果调度到Task 3, 而Task 3想获取another_lock!

于是,进入了一个僵死局面,可以看到这里有循环依赖的问题。这是AB-BA死锁的表现。
正常编程应该避免。但如果依赖链条太长,你是根本看不出来存在死锁的。

综上两种情况,都是抢占带来的问题。于是,一种简单的做法就是,在进入临界区时,关闭抢占!

这样,这里的spin_lock()只要实现为关闭抢占就可以了:

void spin_lock(lock) {
   preemt_disable();
}

void spin_unlock(lock) {
   preemt_enable();
}

这种简单,但粗暴的方法,却可以一箭双雕,解决以上两个问题。


呼~ 在单CPU下,关了抢占,似乎可以高枕无忧了!但!还存在一种情况,中断!

在上面的临界区中,只是关闭了抢占,但并未关闭中断。假设在上述临界区中,发生了中断,
在中断处理上下文中,理论上也可以发生上述情况.

还有更糟的情况是,假设在中断处理上下文中,也要获取该锁。因为锁被进程获取着,所以获取失败,
于是, 中断处理程序会在原地自旋, 等待锁被释放。 问题就来了,中断上下文是只能被更高优先级的中断抢占,
但这个进程却不行,进程抢占不了,它就无法释放锁; 无法释放锁, 中断处理程序就只能自旋,于是,就死锁了。

所以,新增加一个中断安全的spin_lock:

void spin_lock_irqsave(lock) {
    preemt_disable();
    local_irq_save();
}

另,注意到,在单CPU上,关闭本地中断,除非进程主动调用schedule(), 否则调度器是无法被激活的,所以,关闭中断,
也相当于把抢占给禁止了,所以,上述实现可以简化为:

void spin_lock_irqsave(lock) {
    local_irq_save();
}

论坛徽章:
0
15 [报告]
发表于 2014-08-11 12:13 |只看该作者
回复 14# l4rmbr


    感谢大神!!

 有一个问题就是 在task1 2 3 的这种情况下,如果又task1又抢占成功,那么task1 unlock,这时候task2也可以访问资源,task2也unlock,这时候task3就可以访问资源了。。最后task 3 也unlock 。不存在死锁问题呀...

论坛徽章:
1
NBA常规赛纪念章
日期:2015-05-04 22:32:03
16 [报告]
发表于 2014-08-12 10:17 |只看该作者
根据我的理解,单核上的scheduler进行调度会有三种情况:
1. current进程执行完时间片
2. 有一个优先级比较高的进程被**要抢占(这个要看具体的scheduler)
3. current进程休眠,主动让出cpu(这个跟1差不多,都是主动让出cpu)
这个时候preempt_disable(), 那么2就不允许了
如果是1,那么肯定会释放锁
如果是3的话,那么这个时候锁没释放的话,当其他的进程再访问这个资源的时候,就会发生死锁,所以在这个时候是不允许休眠的

记得不是很清楚了,轻拍

回复 8# mingzhi5288


   
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP