linux内核之内核同步

内核同步

      • 内核同步
          • 临界区和竞争条件
          • 并发
      • 内核同步方法
          • 原子操作
          • 原子性与顺序性
        • 自旋锁
          • 读写自旋锁
        • 信号量
          • 计数信号量和二值信号量
          • 读写信号量
        • 互斥体
        • 完成变量
        • 大内核锁BLK
        • 顺序锁
      • 关闭内核抢占
      • 顺序和屏障
      • 总结一些帮助理解的话

内核同步

临界区和竞争条件

临界区,指的是访问和操作共享数据的代码段。如果两个执行线程有可能处于同一个临界区中同时执行,我们称它为竞争条件。这是一种非常不易重视的错误。

并发

内核中有多种可能造成并发执行的原因:

  • 中断,几乎可以在任何时刻异步发生
  • 软中断和tasklet,内核能在任何时刻唤醒或调度软中断和tasklet
  • 内核抢占,一个任务可能会被另一个任务抢占
  • 睡眠和用户空间同步,睡眠导致另一个进程开始执行
  • 对称多处理,两个或多个处理器可以同时执行代码

内核同步方法

原子操作

原子操作可以保证指令以原子的方式执行,执行的过程不会被打断。它是其他同步方法的基石。

内核提供了两组原子操作接口,一组针对整数操作,另一组针对单独的位进行操作。
针对整数的原子操作只能对atomic_t/atomic64_t类型的数据进行处理。内核提供了一组专门的接口来原子操作atomic_t类型数据。
内核也提供了一组针对位操作的接口,它们与体系结构相关,定义在文件中。

原子性与顺序性

原子性保证了一个字长地读取总是原子发生的,绝不可能对同一个字同时进行读和写操作。例如,如果一个整数初始化为42,然后又置为64,那么读取这个整数肯定只会返回42或64这两种情况,而绝不可能是二者的混合。而顺序性要求的是读必须在待定的写之前发生(或是之后),强调的是有序。
原子性确保指令执行期间不会被打断,要么全部执行,要么根本不执行;顺序性确保多条指令是有序的执行。两者不可混谈。

自旋锁

自旋锁(spin lock)是linux内核中最常见的锁,它最多只能被一个可执行线程持有。如果一个执行线程试图获取一个已经被持有的自旋锁,那么该线程会一直进行忙循环等待。
自旋锁最大的特点是:等待他的线程会一直处于自旋状态(忙循环),特别浪费处理器时间。因此自旋锁不应该被长时间持有。

自旋锁的接口定义在文件中。特别注意的是,linux自旋锁是不可以递归的(死锁)

如果在中断处理程序中使用了自旋锁,那么,在其他地方获取锁之前(例如,可以被中断的进程上下文中),需要先禁止本地中断。

读写自旋锁

一个或多个任务可以并发地持有读的锁;而用于写的锁最多只能被一个任务持有,此时也不可以有并发的读操作。
在获取写锁时,不仅需要自旋等待写锁被释放,同时还需要保证所有的读锁也释放了。因此,这种锁机制对读的照顾要比写多一点点。

信号量

信号量是一种睡眠锁。如果一个任务试图获取一个已经被占用的信号量时,信号量会将其推进一个等待队列中,然后让其睡眠。当信号量被释放后,处于等待队列中的任务将被唤醒,并获得该信号量。

信号量适合锁会被长时间持有的情况。因为睡眠、等待队列维护、唤醒是有时间花销的,如果这个时间开销大于了锁的占有时间,那么无疑自旋锁是真更好的选择。

信号量和自旋锁不可以同时占用,因为自旋锁不允许睡眠。

计数信号量和二值信号量

信号量有一个重要的特性:在声明信号量的时候指定信号量同时允许持有者的数量。如果在某一时刻只允许有一个持有者(声明计数为1),这样的信号量被称为二值信号量(或互斥信号量)。如果声明的计数数量大于1,则被称为计数信号量。

读写信号量

与自旋锁一样,信号量也有区分读写访问的。与读写自旋锁和普通自旋锁之间的关系差不多。所有的读写信号量都是互斥信号量(虽然它们只对写者互斥,读者依然可以并发持有)。

所有读写锁的睡眠都不会被信号打断。

互斥体

一种实现互斥的特定睡眠锁,其行为和使用计数为1的信号量类似,但是使用接口更简单,实现也更加高效。
限制:
(1)只能在同一个上下文中上锁和解锁;
(2)不允许递归对锁操作;
(3)持有互斥体的进程,未释放前不可以退出;
(4)不能在中断或下半部中使用;
(5)只能使用特定的API操作。

完成变量

如果在内核中一个任务需要发出信号来通知另一个任务发生了某个特定事件,利用完成变量是使两个任务得以同步最简单的方法。例如,如果一个任务要执行某些工作时,另一个任务就会在完成变量上等待,当这个工作完成后,会使用完成变量去唤醒等待任务。功能和信号量类似,完成变量只是在某些场景下更适合替代信号量的另一种更加简单有效的方式。

大内核锁BLK

顺序锁

顺序锁,通常称为seq锁。这种锁提供了一种简单的机制,用于读写共享数据。其原理是依靠一个序列计数器,当需要写入数据,会得到一个锁,并增加序列值;在读取数据前后,序列值都会被读取,如果读取的序列值相同,则说明读操作过程中没有新的数据写入。
注意:如果读取的值是偶数,则表明写操作没有发生或已经完成,因为序列值初始为偶数0,加写锁时会让序列值加1变为奇数,而释放的时候又会变成偶数。

和读写锁不同之处在于,顺序锁对写操作加锁更加友好,只有没有其他写者,写锁总是能够获取的。

linux内核之内核同步_第1张图片

关闭内核抢占

不同进程可能会访问同一个临界区,因此对于单处理器来说,在访问临界区期间不允许内核抢占也是一种同步方法。

顺序和屏障

当处理多处理器之间或硬件设备之间的同步问题时,有时需要在程序中以指定的顺序发出读内存和写内存的指令(指令的指令要按照特定的顺序)。但是为了提高效率,编译器或处理器可能会对程序指令重新排序(例如,处理器在执行指令期间,会在取指令和分派时,把表面上看上去无关的指令按照自认为最好的方式排序)。值得庆幸的是,所有可能重新排序指令的处理器都提供了机器指令来确保顺序的要求。同样,也可以指示编译器不要对给定点周围的指令序列进行重新排序。这些确保顺序的指令称为屏障。

相关的指令有rmb、wmb、mb、read_barrier_depends等等;具体的细节可以百度查询下,这个东西是一个比较关键的东西。总的来说,它能够保证处理器能够按照我们在程序中指定的顺序去执行指令。

总结一些帮助理解的话

1.我们希望ISR是原子的,没有人能够抢占ISR。 因此,ISR禁用本地中断(即当前处理器上的中断),并且一旦ISR调用ret_from_intr()函数(即我们已完成ISR),则在当前处理器上再次启用中断。 如果此期间发生中断,它将由另一个处理器(在SMP系统中)提供服务,并且与该中断相关的ISR将开始运行。因此,在SMP系统中,我们还需要在ISR中包含适当的同步机制(自旋锁)。

2.注意中断上下文中无法睡眠,无法进行进程调度,一旦执行,就会一直执行下去,直到结束。

3.中断下半部也可以抢占进程上下文,同一个处理器下半部之间不会互相抢占,例如软中断,当一个软中断在执行时,当前处理器的软中断被禁止了。

4.如果内核具有抢占性,那么内核中的进程在任何时候都可能停下来以便具有更高优先权的进程运行。

你可能感兴趣的:(#,Linux历程,linux,java,运维)