- 靠临界区保护信号量(保护信号量的修改处,如前面的 P(semaphore s)、V(semaphore s) ),靠信号量实现进程之间的同步。
L17 信号量临界区保护
信号量的保护
靠临界区保护信号量(保护信号量的修改处,如前面的 P(semaphore s)、V(semaphore s) ),靠信号量实现进程之间的同步。
为什么保护信号量?
因为信号量是多进程能够按正常顺序向前推进合作的保障,如果不对信号量加以保护,就会导致进程同步出现问题,考虑如下情况:
初始时 empty = -1,表示此时有一个生产者进程处于
empty等待队列
,然后生产者进程 P1 和 P2 由于调度问题(比如时间片减小到0,由P1调度到P2),则有可能出现以下执行顺序,此时明明又有两个进程进入empty等待队列
,empty 应该 = -3,但 empty 却 = -2,empty 信号量代表的信息出现了问题,使得进程同步出现问题。图中两个小人的目的是为了增加一些空循环来延长时间片,使得P1和P2能运行在合理的执行顺序上,但这种方法有很大的随机性,并不可取。
★★★临界区
解决方法是可以给 empty 信号量上锁,如下图所示,当生产者进程 P1 运行时,检查并给 empty 上锁,然后继续向下运行,如果此时因为时间片耗完,而由 P1 调度到 P2,那生产者进程 P2 会检查 empty 的锁,当 P2 发现 empty 上锁时,会陷入空循环来消耗时间片,直到时间片耗完又由 P2 调度到 P1,此时 P1 继续向下运行,将 empty 改完并开锁,然后又调度到 P2,P2检查并给 empty 上锁,经过一系列操作后,将 empty 改完并给 empty 开锁。
综上所述,可以总结为:★当一个进程进入了该进程的修改信号量的那一段代码时,其他进程不能进入其对应的修改信号量的那一段代码,“那一段代码”即为临界区。 (读写信号量的代码一定是临界区)
如下图所示,进程的代码结构可以分为“剩余区”、“进入区”、“临界区”和“退出区”,为了保护临界区中的信号量,保证合理地对信号量进行修改,需要在“进入区”和“退出区”执行一些操作,这就是该章节接下来的主要内容。
临界区保护信号量时,应该遵循以下三个原则:
最基本的原则 - 互斥原则:如果一个进程在临界区中执行,则其他进程不允许进入;
有空让进原则:当临界区空闲时,应该尽快让一个进程能进入临界区,避免浪费CPU资源;
有限等待原则:进程在等待进入临界区时,应该等待有限的时间,而不能无限等待。
轮换法
- 轮换法满足互斥的基本原则,但不满足有空让进原则(如果P0连续两次运行,则P0第二次运行时,临界区就空着了)。
标记法
标记法是轮换法的升级版,在轮换法的基础之上,又增加了标记,以满足“有空让进”的原则。
但根据下图的两个进程的运行顺序,会发现标记法并不满足“有限等待”的原则。
★非对称标记法
非对称标记法是标记法的进一步改进,以解决进程“无限等待”的问题。
由于进程 P0 和 P1 共用一个变量 turn,就引入了非对称,即结合了标记和轮转两种思想。
★多个进程的信号量临界区保护
多个进程的信号量临界区保护就是前面两个进程的信号量临界区保护的扩展,仍采用非对称标记法:
- 对进程 Pi,首先让
choosing[i] = true;
,表示该进程正在“选号”; - 然后
num[i] = max(num[0],...,num[n-1])+1;
,表示其选的号应该比目前所有已经选好号的进程的号要大; - 在进程 Pi 选好号以后,
choosing[i] = false
,表示该进程已经选好号了; - 然后对所有的进程循环判断,如果其他进程的
choosing[j] == true
,表示其他进程还在选号,那就应该等待; - 在所有选号的进程都已经选好号以后,进行比较判断,只有当当前进程的
num[i] != 0
(表示该进程想要进入临界区)并且该进程的号是所有号中最小的时,该进程才能进入临界区,否则应该陷入等待; - 在进程退出临界区后,应该让
num[i] = 0
,表示该进程退出临界区,序号变为 0 。
- 对进程 Pi,首先让
信号量临界区保护硬件法
- 以上所说的信号量临界区保护是通过软件的方式来进行保护的,但也可以通过硬件的方式进行保护,而且更为简单。
关中断法
进程之间之所以会出现并发修改信号量的问题是因为出现了调度,那如果能通过硬件的方法来阻止调度,只有当一个进程修改完信号量后才能继续进行调度,就可以解决并发修改信号量的问题,如下图所示,
cli()
用于关中断,sti()
用于开中断。注意:关中断法只适用于单CPU,对于多CPU(多核)是不适用的。
★硬件原子指令法
在“L16 进程同步与信号量”中可以看到,为了保证进程的同步,用到了 mutex 变量,通过
P(mutex)
和V(mutex)
来保证一次只有一个进程在操作文件,我们可以借鉴这种方法,在保护信号量时,也可以用类似的方法,但如果在保护信号量时也用P(mutex)
和V(mutex)
是不对的,因为我们用 mutex 信号量来保护别的信号量时,mutex 信号量本身也需要被保护,所以应该通过硬件来实现保护,通过原子操作来对信号量进行保护。注意:初始值等于 1 的 mutex 信号量其实就是锁信号量。
★总结:用临界区保护信号量,用信号量实现进程之间的同步。