layout | title | category | description | tags |
---|---|---|---|---|
post |
内核一致性 |
基础 |
内核一致性... |
一致性 抢占式 中断 信号量 内核 |
内核的另一个重要的保证就是一致性(consistent)状态,如果内核对某一个控制路径所需要的数据进行挂起,那么其他的内核控制路径1就不应该再对这个数据进行修改,除非重新被设置成一致性状态。否则会给计算机造成非常严重的数据伤害2。
当某个计算结果取决于如何调度两个或多个进程时,相关代码就时不正确的,这是一种竞争条件(race condition)。要避免这种情况,对全局变量的安全访问通过原子操作(atomic operation)。这种问题和线程安全面临的是同样问题,但内核需要更加严谨的对待。不过,现在已经有几种技术被采用,以便同步内核控制路径。
一个最简单的解决办法就是使用非抢占式内核,大多数传统的Unix内核都是非抢占式的,当进程在内核态执行时,它不能被任意一个程序挂起,也不能被任意另一个进程代替。因此,在这种系统上,中断或异常处理程序不能修改所有内核的数据结构,内核对它们来说时安全的。
除了内核态的进程能自愿放弃CPU,这种情况下,它必须保证所有的数据都处于一致性状态。此外,当这种进程恢复执行时,它必须重新检查以前访问过的数据结构的值,因为这些数据有可能被改变。
非抢占式内核在多处理器系统上是低效的,因为多CPU上的两个内核控制路径可以并发的访问相同的数据结构。
单处理器的另一种同步机制是,在进入一个临界区之前禁止所有的硬件中断,离开时再重新启动。这种机制简单但不是最佳,如果临界区比较大,那么一个相对较长的时间内都会禁止中断,使所有的硬件都处于不可用状态。这也是一种低效的解决方法。
广泛使用的一种机制是信号量(semaphore),它在单处理器系统和多处理器系统上都有效。信号量仅仅是一个与数据结构相关的计数器,所有内核线程在试图访问这个数据之前,都需要检查这个信号量3。组成如下:
- 一个整数变量。
- 一个等待进程的链表。
- 两个原子方法,down()和up()。
down方法是对信号量减1,up则相反。如果信号量新的数值小于0,此方法就把正在运行的进程加入到这个信号量链表,然后阻塞该进程。up方法的结果如果大于0或等于0,则激活这个信号量链表里的一个或者多个进程。如果信号量不是负数,则允许访问这个数据结构,否则执行内核控制路径的进程加入到这个信号量链表并阻塞进程。
在多处理器系统中,信号量不是最好的解决方法,系统不允许在不同的CPU上运行的控制路径访问相同的内核数据结构,这种情况下,信号量会比较低效4。这种情况下,多处理器系统使用了自旋锁(spin lock)。自旋锁和信号量相似,但没有等待进程链表,当一个进程发现锁被另一个进程锁着时,就不停的『旋转』,执行一个循环等待,直到锁打开。而正时因为多处理器的原因,挂起的进程不会一直挂起,因为另一个CPU终究会释放资源。
所以,自旋锁在单处理器环境下时无效的,当内核控制路径试图访问一个上锁的数据结构,则会无休止的循环,最后的结果可能导致系统挂起。
当进程A需要访问数据结构a,当B需要访问数据结构b,但是A在等待b,而B在等待a,就会出现死锁状态。很难保证操作系统不会出现死锁状态,现在的操作系统通过按规定的顺序请求信号量来避免死锁。