内核同步介绍

  • ​​内核同步介绍​​
  • ​​临界区和竞争条件​​
  • ​​为什么我们需要保护​​
  • ​​单个变量​​
  • ​​加锁​​
  • ​​造成并发执行的原因​​
  • ​​死锁​​
  • ​​争用和扩展性​​

本系列博客追寻《Linux内核设计与实现-Robert Love》,各个Linux机中的内核源代码不一,因此直接下载官网内核源码

参考书籍:《Linux内核设计与实现-Robert Love》

内核同步介绍

本节是为下一节做铺垫,因此内容较少,只做简要概述。

在使用共享内存的应用程序中,程序员必须特别留意保护共享资源,放置共享资源并发访问。内核也不例外。

为什么要防止并发访问,因为会出现数据覆盖或者共享,造成被访问数据处于不一致状态

临界区和竞争条件

所谓临界区(也称为临界段)就是访问和操作共享数据的代码段。编程者必须保证这些代码原子的执行

也就是说,操作在执行结束前不可被打断

如果两个线程有可能处于同一临界区同时执行(一般不会发生),那么这个叫做竞争条件(race conditions),这样命名是因为存在线程竞争。

避免并发和防止竞争条件称为同步(synchronization)

为什么我们需要保护

​银行家算法​

单个变量

考虑一个非常简单的共享资源:一个全局整型变量和一个简单的临界区,其中的操作仅仅是将整型变量的值加1

得到当前遍历i的值并且拷贝到一个寄存器中
将寄存器中的值加1
把i的新值写回到内存

加锁

任何要访问全局变量的代码首先都需要占住相应的锁,这样该锁能阻止来自其他执行线程的并发访问

线程1       线程2
试图锁定 试图锁定
成功:获得锁 失败:等待
访问 等待
解锁 等待
成功:获得锁
访问
解锁

造成并发执行的原因

用户空间之所以需要同步,是因为用户程序会被调度程序抢占和重新调度。

由于用户进程可能在任何时刻被抢占,而调度程序完全可能选择另一个高优先级的进程到处理器上执行,所以就会使得一个程序正属于临界区,被非自愿的抢占了。如果新调度的进程随后也进入同一个临界区,前后两个进程就会产生竞争。另外,因为信号处理是异步发生的,所以,即使是单线程的多个进程共享文件,或者在一个程序内部处理信号,也有可能产生竞争条件,称为伪并发执行(串行

内核中有类似可能造成并发执行的原因,他们是:

  • 中断
  • 软中断和tasklet
  • 内核抢占
  • 睡眠及与用户空间的同步
  • 对称多处理(两个或者多个处理器可以同时执行代码)

死锁

产生条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了。所有线程都在相互等待,但他们永远不会释放已经占有的资源。于是任何线程都无法继续,这便意味着死锁的发生。

仍可见“银行家算法”

最简单的死锁例子是自死锁:如果一个执行线程试图去获得一个字节已经持有的锁,他将不得不等待锁被释放,但因为他正在忙着等待这个锁,所以自己永远也不会有机会释放锁,最终结果就是死锁。

争用和扩展性

锁的争用(lock contention),或简称争用,是指当锁正在被占用时,有其他线程试图获得该锁。说一个锁处于高度争用状态,就是指的是有多个其他线程在等待获得该锁。

扩展性(scalability)是对系统可扩展成都的一个度量。
加锁粒度用来描述加锁保护的数据规模,一个过粗的锁保护大块数据(比如一个子系统用到的所有数据结构);相反过细的锁保护小块数据。
在实际使用中,绝大多数锁的加锁范围都处于上述两种极端之间,保护的可能是一个单独的数据结构。
当锁争用验证时,加锁太粗会降低可扩展性;而锁争用不明显时,加锁太细会加大系统开销,带来浪费,这两种情况都有可能造成系统性能下降。因此在设计初期加锁方案应该力求简单,仅当需要时再进一步细化加锁方案。

精髓在于力求简单。

第9节将重点介绍内核同步方法