锁和原语
操作系统如何实现锁?
在硬件层面,CPU提供了原子操作、关中断、锁内存总线的机制;OS基于这几个CPU硬件机制,就能够实现锁;再基于锁,就能够实现各种各样的同步机制(信号量、消息、Barrier等) 在多线程编程中,为了保证数据操作的一致性,操作系统引入了锁机制,用于保证临界区代码的安全。通过锁机制,能够保证在多核多线程环境中,在某一个时间点上,只能有一个线程进入临界区代码,从而保证临界区中操作数据的一致性。 锁机制的一个特点是它的同步原语都是原子操作 那么操作系统是如何保证这些同步原语的原子性呢? 操作系统之所以能构建锁之类的同步原语,是因为==硬件已经为我们提供了一些原子操作==,例如: 中断禁止和启用(interrupt enable/disable) 内存加载和存入(load/store)测试与设置(test and set)指令 禁止中断这个操作是一个硬件步骤,中间无法插入别的操作。同样,中断启用,测试与设置均为一个硬件步骤的指令。在这些硬件原子操作之上,我们便可以构建软件原子操作:锁,睡觉与叫醒,信号量等。
操作系统使用锁的原语操作有哪些?
可以使用中断禁止,测试与设置两种硬件原语来实现软件的锁原语。这两种方式比较起来,显然测试与设置更加简单,也因此使用的更为普遍。此外,test and set还有一个优点,就是可以在多CPU环境下工作,而中断启用和禁止则不能
使用中断启用与禁止来实现锁:
要防止一段代码在执行过程中被别的进程插入,就要考虑在一个单处理器上,一个线程在执行途中被切换的途径。我们知道,要切换进程,必须要发生上下文切换,上下文切换只有两种可能:
一个线程自愿放弃CPU而将控制权交给操作系统调度器(通过yield之类的操作系统调用来实现); 一个线程被强制放弃CPU而失去控制权(通过中断来实现) 原语执行过程中,我们不会自动放弃CPU控制权,因此要防止进程切换,就要在原语执行过程中不能发生中断。所以采用禁止中断,且不自动调用让出CPU的系统调用,就可以防止进程切换,将一组操作变为原子操作。
中断禁止:就是禁止打断,使用可以将一系列操作变为原子操作
中断启用:就是从这里开始,可以被打断,允许操作系统进行调度
缺点:使用中断实现锁,繁忙等待,不可重入
使用测试与设置指令来实现锁
测试与设置(test & set)指令:以不可分割的方式执行如下两个步骤:
判断值是否与预期一致,如果一致就修改值变为新值 设置操作:将1写入指定内存单元; 读取操作:返回指定内存单元里原来的值(写入1之前的值) 缺点:繁忙等待,不可重入