内核同步介绍
- 内核同步介绍
- 临界区和竞争条件
- 为什么我们需要保护
- 单个变量
- 加锁
- 造成并发执行的原因
- 死锁
- 争用和扩展性
本系列博客追寻《Linux内核设计与实现-Robert Love》,各个Linux机中的内核源代码不一,因此直接下载官网内核源码
参考书籍:《Linux内核设计与实现-Robert Love》
内核同步介绍
本节是为下一节做铺垫,因此内容较少,只做简要概述。
在使用共享内存的应用程序中,程序员必须特别留意保护共享资源,放置共享资源并发访问。内核也不例外。
为什么要防止并发访问,因为会出现数据覆盖或者共享,造成被访问数据处于不一致状态
临界区和竞争条件
所谓临界区(也称为临界段)就是访问和操作共享数据的代码段。编程者必须保证这些代码原子的执行
也就是说,操作在执行结束前不可被打断
如果两个线程有可能处于同一临界区同时执行(一般不会发生),那么这个叫做竞争条件(race conditions),这样命名是因为存在线程竞争。
避免并发和防止竞争条件称为同步(synchronization)
为什么我们需要保护
银行家算法
单个变量
考虑一个非常简单的共享资源:一个全局整型变量和一个简单的临界区,其中的操作仅仅是将整型变量的值加1
得到当前遍历i的值并且拷贝到一个寄存器中
将寄存器中的值加1
把i的新值写回到内存
加锁
任何要访问全局变量的代码首先都需要占住相应的锁,这样该锁能阻止来自其他执行线程的并发访问
造成并发执行的原因
用户空间之所以需要同步,是因为用户程序会被调度程序抢占和重新调度。
由于用户进程可能在任何时刻被抢占,而调度程序完全可能选择另一个高优先级的进程到处理器上执行,所以就会使得一个程序正属于临界区,被非自愿的抢占了。如果新调度的进程随后也进入同一个临界区,前后两个进程就会产生竞争。另外,因为信号处理是异步发生的,所以,即使是单线程的多个进程共享文件,或者在一个程序内部处理信号,也有可能产生竞争条件,称为伪并发执行(串行)
内核中有类似可能造成并发执行的原因,他们是:
- 中断
- 软中断和tasklet
- 内核抢占
- 睡眠及与用户空间的同步
- 对称多处理(两个或者多个处理器可以同时执行代码)
死锁
产生条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了。所有线程都在相互等待,但他们永远不会释放已经占有的资源。于是任何线程都无法继续,这便意味着死锁的发生。
仍可见“银行家算法”
最简单的死锁例子是自死锁:如果一个执行线程试图去获得一个字节已经持有的锁,他将不得不等待锁被释放,但因为他正在忙着等待这个锁,所以自己永远也不会有机会释放锁,最终结果就是死锁。
争用和扩展性
锁的争用(lock contention),或简称争用,是指当锁正在被占用时,有其他线程试图获得该锁。说一个锁处于高度争用状态,就是指的是有多个其他线程在等待获得该锁。
扩展性(scalability)是对系统可扩展成都的一个度量。
加锁粒度用来描述加锁保护的数据规模,一个过粗的锁保护大块数据(比如一个子系统用到的所有数据结构);相反过细的锁保护小块数据。
在实际使用中,绝大多数锁的加锁范围都处于上述两种极端之间,保护的可能是一个单独的数据结构。
当锁争用验证时,加锁太粗会降低可扩展性;而锁争用不明显时,加锁太细会加大系统开销,带来浪费,这两种情况都有可能造成系统性能下降。因此在设计初期加锁方案应该力求简单,仅当需要时再进一步细化加锁方案。
精髓在于力求简单。
第9节将重点介绍内核同步方法