Linux内核同步

  并发的执行单元对共享资源(硬件资源,软件上的全局变量、静态变量)的访问很容易导致竞态,避免竞态的方法:

1、中断屏蔽

  在进入临界区之前屏蔽系统的中断,但是在驱动编程中不值得推荐,驱动通常需要考虑跨平台特点而不假定自己在单核上运行。中断对于内核的运行非常重要,在屏蔽中断期间所有的中断都无法处理,长时间屏蔽中断会造成数据丢失或者系统崩溃。local_irq_disable和local_irq_enable都只能禁止和使能本CPU的中断,在SMP系统中并不能解决多CPU引发的竞态。单独使用中断屏蔽不推荐使用,适合和下文的自旋锁联合使用。

2、原子操作

  原子操作可以保证对一个整形数据的修改是排他性的,Linux内核提供了很多函数来实现内核中的原子操作,这些函数又分为对位和整形变量的原子操作,它们都依赖于底层CPU的原子操作,因此,这些函数都是和CPU的架构相关的。

3、自旋锁

  自旋锁是一种典型的对临界资源进行互斥访问的手段,为了获得一个自旋锁,在CPU上执行的代码先执行一个原子操作,该操作测试并设置某个内存变量,由于该操作时原子操作,在它完成之前其他执行单元不可能访问这个内存变量。如果测试结果表明这个锁已经空闲,则程序获得这个锁,并继续执行,如果锁被占用,则程序在一个小循环内重复执行这个“测试并设置”操作,这也是自旋锁的由来。

  自旋锁主要针对SMP或者单CPU但内核可抢占的情况,对于单CPU和内核不支持抢占的系统,自旋锁退化为空操作。在单CPU和内核可抢占的系统中,自旋锁持有期间,内核的抢占将被禁止。由于内核可抢占的单CPU系统的行为实际上很类似与SMP系统,因此,在这样的单CPU系统中使用自旋锁很有必要。另外,在多核SMP的情况下,任何一个核拿到自旋锁,这个核上的抢占也被禁止了,但是没有禁止另外一个核的抢占调度。

  在多核编程的时候如果进程和中断可能访问同一片临界资源,我们一般需要在进程的上下文中调用spin_lock_irqsave / spin_unlock_irqrestore,在中断上下文调用spin_lock / spin_unlock,这样,在CPU0上,无论是进程上下文还是中断上下文获得了自旋锁,此后,如果CPU1无论是进程上下文还是中断上下文,想获得同一个自旋锁都必须忙等待,这避免一切核间并发的可能性。同时,由于每个核的进程上下文持有锁的时候用的是spin_lock_irqsave,所以该核上的中断时不可能进入的,这避免了核内并发的可能性。

  进程不能获得自旋锁时会忙等待,十分耗费CPU,因此只有临界区非常小的时候采用自旋锁。

  自旋锁还包括读写自旋锁、顺序锁。

4、RCU(读-复制-更新)

5、信号量

  信号量适合用于临界区比较长的情况,当一个进程不能获得信号量时,就会在这个信号量上睡眠,直到有其他进程释放信号量将这个进程唤醒。信号量的值可以是0,1或者n。

6、互斥量

  信号量已经可以实现互斥的功能,但是,正宗的mutex在Linux内核中还是存在的。互斥量和自旋锁属于不同层次的互斥手段,前者依赖于后者,在互斥量本身的实现上,为了保证互斥量结构存取的原子性,需要自旋锁来互斥,所以自旋锁属于更底层的手段。互斥量适合于进程占用资源较长的情况,互斥量会引起进程睡眠。互斥量所保护的临界区可能包含引起阻塞的代码,而自旋锁不能用来保护包含可能引起阻塞的代码的临界区。因为阻塞意味着进程的切换,如果进程被切换出去,另一个进程企图获得本自旋锁,死锁就会发生。

  因为spin_lock中会使用preempt_disable()关闭抢占,spin_unlock会重新开启抢占功能。从这里面可以看出,使用自旋锁保护的区域是工作在非抢占状态,即使获取不到锁,在“自旋”状态也是禁止抢占的。到这里,大概可以理解为什么自旋锁保护的代码不能睡眠了。如果在自旋锁保护的代码中睡眠,此时发生进程调度,则可能另外一个进程会再次调用spin_lock保护这段代码,而我们知道,即使在获取不到锁的自旋状态,也是禁止抢占的,而“自旋”又是动态的,不会在睡眠了,也就是说在这个处理器上不会再有调度发生了,那么死锁自然就发生了。

  如果被保护的资源需要在中断或者软中断情况下使用,则在互斥量和自旋锁之间只能选择自旋锁,当然,如果一定要使用互斥量,则只能通过mutex_trylock方式进行,不能获取就立即返回避免阻塞。

 7、完成量

  Linux定义了完成量,它用于一个执行单元等待另一个执行单元完成某事。

  

原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9307640.html