Linux驱动中并发问题的相关函数

前言

目前我们接触到的复杂点的系统都是SMP(对称多处理器)和preempt(抢占式)的。这篇文章我们会讲解好几种情况下,处理驱动并发问题的函数应该怎么使用,避免大材小用或者用得不巧当造成没有效果。

正文

1、进程和中断上下文,单CPU,非抢占内核

这种情况比较简单,因为是非抢占的,所以同一个CPU上的两个进程之间不用相互顾忌对方,而且又是单CPU的,又不用怕其它CPU的中断来打扰,所以只需要提防是否有本CPU的中断过来就行,我们可以使用下面的函数。

local_irq_disable();  /*disable interrupts in local CPU*/
/*do something*/
local_irq_enable(); /*enable interrupts in local CPU*/

但是上面的函数会有问题,因为执行local_irq_enable后会重新使能系统中断,而不是恢复之前的中断状态,我们举个例子说明一下。

进程上下文
local_irq_disable();
/*critical section*/

/*在关闭中断过程中,再次关闭中断*/
local_irq_disable();
local_irq_enable();  /* 这时候本地中断已经又开启了,第一个关闭中断的操作已经无效*/

local_irq_enable(); /*这个已经无关紧要,前面已经全部打开了*/

所以为了解决这种的问题,kernel为我们提供了另外一组函数:

unsigned long flags;
local_irq_save(flags);
/*critical section*/
local_irq_restore(flags);

这两个宏相对于local_irq_disable和local_irq_enable最大的区别在于,local_irq_save会在关闭中断前,将处理器当前的标志位保持在一个unsigned long flags中,在调用local_irq_restore时,在将保存的flags恢复到处理器的FLAGS寄存器中。这样做是为了防止在一个关闭中断的环境中因为调用local_irq_disable和local_irq_enable破坏之前的中断响应状态。如果调用链中有多个函数需要禁止中断, 应该使用local_irq_save。

后面啊在2.3小节,我们同样能看到类似的问题,看一下那个例子的说明,我们就应该明白为什么在开闭中断前要保存中断状态,开启中断不是全部放开而是恢复之前的状态了。

2、进程和中断上下文,SMP,抢占内核

这种场景是遇到的最复杂的场景了,我们先一步步来,由浅入深的分析。

2.1、

对于一些极短的临界区,或者在处于不能睡眠的中断上下文,这时候我们都应该使用自旋锁,最简单的可以使用下面的函数:

extern spinlock_t lock;
// ...
spin_lock(&lock);
// critical section
spin_unlock(&lock);

在大部分场景下,这么执行是正确的,但是我们考虑如果,在进程A执行的时候有中断发生呢?

2.2、

进程上下文                              硬件中断
/* in normal run level */
extern spinlock_t lock;
spin_lock(&lock);
/* critical section */
                                       /* interrupted by IRQ */
                                       extern spinlock_t lock;
                                       spin_lock(&lock);

在一个进程的上下文,发生了硬件中断,而且在中断程序中也申请了同样的lock,这时候就发生了死锁,为了解决这种问题,可以使用下面的函数

extern spinlock_t lock;
spin_lock_irq(&lock); /* 先关闭当前CPU中断再申请锁,避免发生中断产生上述的死锁问题 */
/* critical section */
spin_unlock_irq(&lock);

现在的系统是做的越来越复杂,上面的场景还不能保证完全没有问题,我们看一下下面的另外一种场景。

2.3、

进程上下文                       硬件中断
spin_lock_irq(&lock1);
spin_lock_irq(&lock2);
spin_unlock_irq(&lock2);
                                spin_lock(&lock1)   /*因为lock1被占用了,所以无法获取自旋锁而不停等待,导致了死锁的情况*/
                                spin_unlock(&lock1)
spin_unlock_irq(&lock1); 

因为spin_unlock_irq是将本地的中断都开启,所以执行完spin_unlock_irq(&lock2)后,硬件又可以申请中断了,但是这时候lock1还处于临界区,所以如果再次申请lock1的话,就会发生死锁了。为了解决这个问题,可以使用下面的变体函数:

unsigned long flags;
spin_lock_irqsave(&lock, flags);
/* do something */
spin_unlock_irqrestore(&lock, flags);     

参考链接

https://blog.csdn.net/wesleyluo/article/details/8807919

https://www.byteisland.com/%E8%87%AA%E6%97%8B%E9%94%81-spin_lock%E3%80%81-spin_lock_irq-%E4%BB%A5%E5%8F%8A-spin_lock_irqsave-%E7%9A%84%E5%8C%BA%E5%88%AB/
 

http://kernel.meizu.com/linux-dead-lock-detect-lockdep.html

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值