Skip to content

Latest commit

 

History

History
50 lines (30 loc) · 4.6 KB

2014-03-17-consistent-of-kernel.md

File metadata and controls

50 lines (30 loc) · 4.6 KB
layout title category description tags
post
内核一致性
基础
内核一致性...
一致性 抢占式 中断 信号量 内核

内核的另一个重要的保证就是一致性(consistent)状态,如果内核对某一个控制路径所需要的数据进行挂起,那么其他的内核控制路径1就不应该再对这个数据进行修改,除非重新被设置成一致性状态。否则会给计算机造成非常严重的数据伤害2

当某个计算结果取决于如何调度两个或多个进程时,相关代码就时不正确的,这是一种竞争条件(race condition)。要避免这种情况,对全局变量的安全访问通过原子操作(atomic operation)。这种问题和线程安全面临的是同样问题,但内核需要更加严谨的对待。不过,现在已经有几种技术被采用,以便同步内核控制路径。

非抢占式内核

一个最简单的解决办法就是使用非抢占式内核,大多数传统的Unix内核都是非抢占式的,当进程在内核态执行时,它不能被任意一个程序挂起,也不能被任意另一个进程代替。因此,在这种系统上,中断或异常处理程序不能修改所有内核的数据结构,内核对它们来说时安全的。

除了内核态的进程能自愿放弃CPU,这种情况下,它必须保证所有的数据都处于一致性状态。此外,当这种进程恢复执行时,它必须重新检查以前访问过的数据结构的值,因为这些数据有可能被改变。

非抢占式内核在多处理器系统上是低效的,因为多CPU上的两个内核控制路径可以并发的访问相同的数据结构。

禁止中断

单处理器的另一种同步机制是,在进入一个临界区之前禁止所有的硬件中断,离开时再重新启动。这种机制简单但不是最佳,如果临界区比较大,那么一个相对较长的时间内都会禁止中断,使所有的硬件都处于不可用状态。这也是一种低效的解决方法。

信号量

广泛使用的一种机制是信号量(semaphore),它在单处理器系统和多处理器系统上都有效。信号量仅仅是一个与数据结构相关的计数器,所有内核线程在试图访问这个数据之前,都需要检查这个信号量3。组成如下:

  1. 一个整数变量。
  2. 一个等待进程的链表。
  3. 两个原子方法,down()和up()。

down方法是对信号量减1,up则相反。如果信号量新的数值小于0,此方法就把正在运行的进程加入到这个信号量链表,然后阻塞该进程。up方法的结果如果大于0或等于0,则激活这个信号量链表里的一个或者多个进程。如果信号量不是负数,则允许访问这个数据结构,否则执行内核控制路径的进程加入到这个信号量链表并阻塞进程。

自旋锁

在多处理器系统中,信号量不是最好的解决方法,系统不允许在不同的CPU上运行的控制路径访问相同的内核数据结构,这种情况下,信号量会比较低效4。这种情况下,多处理器系统使用了自旋锁(spin lock)。自旋锁和信号量相似,但没有等待进程链表,当一个进程发现锁被另一个进程锁着时,就不停的『旋转』,执行一个循环等待,直到锁打开。而正时因为多处理器的原因,挂起的进程不会一直挂起,因为另一个CPU终究会释放资源。

所以,自旋锁在单处理器环境下时无效的,当内核控制路径试图访问一个上锁的数据结构,则会无休止的循环,最后的结果可能导致系统挂起。

死锁

当进程A需要访问数据结构a,当B需要访问数据结构b,但是A在等待b,而B在等待a,就会出现死锁状态。很难保证操作系统不会出现死锁状态,现在的操作系统通过按规定的顺序请求信号量来避免死锁。

Footnotes

  1. 内核控制路径(kernel control path)表示内核处理系统调用、异常或中断所执行的指令序列。

  2. 例如,计算机中的一个内核控制路径对一个变量A进行读取,这个时候产生了一个中断,或者更高优先级的进程访问,此时后一个也使用变量A并修改数据。执行完毕后,CPU将权限归还与第一个控制路径,并重新读取A,这个时候数据已经发生了变化。

  3. 可以把信号量看成一个对象。

  4. 为了检查信号量,内核必须把进程插入到信号量链表里,然后挂起。这两种操作比较耗时,完成这些操作,其他的内核控制路径可能已经释放了信号量。