在使用共享内存的程序中程序员必须特别留意保护共享资源防止共享资源并发访问。这种错误一般难以跟踪和调试,所以应该首先意识到这个问题的重要性。
未支持对称多处理器的时候避免并发访问数据的方法相对比较简单,所以早期的内核开发工作要简单许多。
临界区和竞争条件
临界区就是访问和操作共享数据的代码段。为了避免在临界区并发访问,编程者必须保证临界区的代码原子的执行-也就是操作在执行结束前不能被打断,如同整个临界区是一个不可分割的指令。
如果两个执行线程有可能处于同一个临界区同时执行,这就是程序包含的一个bug。如果情况确实发生则称他为竞争条件,这样命名是因为这里会存在线程竞争。避免并发和防止竞争条件称为同步。
加锁
锁机制就是限制某一共享区域同一时刻只能有一个进程访问。
锁有多种多样的形式,加锁的粒度范围也各不相同-linux自身实现了几种不同的锁机制。
各种锁机制之间的区别在于:当锁已经被其他线程持有因而不可用时的行为表现,某一些锁会简单的进行忙等待,另外的一些锁会时当前任务睡眠直到锁可用为止。
锁没有解决根本问题,只是把临界区缩小到加锁和开锁之间的代码,仍然有潜在的竞争。锁是采用原子操作进行实现的,而原子操作不存在竞争,单一指令可以验证它的关键部分是否抓住,如果没有的话就抓住它。
造成并发执行的原因
用户空间之所以需要同步是因为用户程序会被调度程序抢占和重新调度。由于用户进程可能在任何时刻被抢占,调度程序完全可能选择另一个高优先级的进程到处理器上执行,所以就会使得一个程序正处于临界区时被非自愿的抢占了。如果新调度的进程随后也进入同一个临界区(共享内存,同文件写入),前后两个进程之间就会产生竞争。
信号处理是异步发生的,所以即使单线程的多个进程共享文件或者在一个程序内部处理信号也可能产生竞争条件。这种类型的并发操作成为伪并发。
如果有一台支持对称多处理器的机器,那么两个进程就可以真正地在临界区中同时执行,称为真并发。
真并发和伪并发需要同样的保护。
内核中的并发执行原因:
典型的并发bug
真正的困难在于发现上述的潜在并发执行的可能并有意识的采取某些措施来阻止并发的执行。
在中断处理程序中能避免并发访问的安全代码称作中断安全代码
在对称多处理的机器中能避免并发访问的安全代码称作SMP安全代码
在内核抢占时能避免并发访问的安全代码称作抢占安全代码
了解需要保护什么
找出那些数据需要保护是关键所在,由于任何可能被并发访问的代码都几乎无例外的需要保护,所以找出不需要保护的代码反而更相对容易些。如:
大多数的内核数据结构都需要加锁,如果有其他执行线程可以访问这些数据,那么就给这些数据加上某种形式的锁;如果任何其他什么东西能够看到他,那么就要锁住他。给数据加锁而不是给代码加锁!
内核在编译时可进行配置裁剪
CONFIG_SMP可以控制内核是否支持SMP,许多加锁问题在单处理器上是不存在的,所以当CONFIG_SMP没有被设置时,不必要的代码就不会被编入针对单处理器的内核映像中,可以避免使用自选锁带来的开销。
CONFIG_PREEMPT可以控制内核是否允许抢占,同理
内核只用维护一些简洁的基础资源,各种各样的锁机制可以在需要时随时编译进内核使用。在不同的体系结构上上述两个参数进行不同的设置。
死锁
有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源。
典型的ABBA死锁:
一些简单的规则可以尽可能的避免死锁:
如果有两个或多个锁在同一时间里被请求,那么以后其他函数请求他们也必须按照前次的加锁顺序进行。否则很有可能发生死锁,实例如图:
插图
对于有顺序的锁最好写合适的注释。
争用和扩展性
锁的争用是指当锁被占用时,有其他线程试图获得该锁。
锁的作用是使程序以串行的方式对资源进行访问,所以使用锁无疑会降低系统的性能。
被高度争用的锁会成为系统的瓶颈,严重降低系统性能。
扩展性是对系统可扩展程度的一个量度。对于操作系统,谈及可扩展性时会和大量进程,大量处理器或大量内存等联系起来。
理想情况下处理器数量加倍可以使得系统的处理性能加倍,但是实际情况下不可能达到的。
加锁粒度用来表述加锁保护的数据规模
2.0版linux引入多处理支持后对集群处理器的可扩展性大大提高,但是初期在一个时刻只能有一个任务在内核中执行。2.2版本中加锁机制发展到细粒度加锁后边取消了该限制。随着发展加锁的粒度越来越细,可扩展性也很好。
运行队列就是一个锁从粗到精细化的实例,早期的内核中调度程序有一个单独的调度队列。在2.6版内核开始每个处理器单独配备一个运行队列,每个队列都有自己的锁。于是加锁由一个全局锁精细化到了每个处理器拥有自己的锁。
提高可扩展性可以让linux在更大型、处理能力更强大的系统上的性能得以提升。但是一味地提高可扩展性因为小型机器可能用不到特别精细的锁所以只会增加复杂度,加大开销。从而导致linux在小型SMP和UP机器上的性能降低。
总结
SMP安全代码需要在整个编码过程中不断的考虑和完善。