信号量机构是一种功能较强的机制,可用来解决互斥与同步的问题。在长期且广泛的应用中,信号量机制得到了很大的发展。由最初的整形信号量,经过记录性信号量、AND信号量,最后发展为“信号量集”。
一、信号量的分类
1.整型信号量
整型信号量被定义为一个用于表示资源数目的整型量S,它只能被两个标准的原语wait(S)和signal(S)来访问,也可以记为“P操作”和“V操作”。可描述为:
wait(S){
while (S<=0);
S=S-1;
}//p操作
signal(S){
S=S+1;
}//v操作
wait操作中,只要信号量S<=0,就会不断地测试。因此,该机制并未遵循“让权等待” 的准则,而是使进程处于“忙等”的状态。
2.记录型信号量
记录型信号量是不存在“忙等”现象的进程同步机制。除了需要一个用于代表资源数目的整型变量value外,再增加一个进程链表L,用于链接所有等待该资源的进程,记录型信号量是由于釆用了记录型的数据结构得名。记录型信号量可描述为:
typedef struct{
int value;
struct process *L;
} semaphore;
相应的wait(S)和signal(S)的操作如下:
void wait (semaphore S) { //相当于申请资源
S.value--;
if(S.value<0) {
add this process to S.L;
block(S.L);
}
}
wait操作,S.value--,表示进程请求一个该类资源,当S.value<0时,表示该类资源已分配完毕,因此进程应调用block原语,进行自我阻塞,放弃处理机,并插入到该类资源的等待队列S.L中,可见该机制遵循了“让权等待”的准则。
void signal (semaphore S) { //相当于释放资源
S.value++;
if(S.value<=0){
remove a process P from S.L;
wakeup(P);
}
}
signal操作,表示进程释放一个资源,使系统中可供分配的该类资源数增1,故S.value++。若加1后仍是S.value<=0,则表示在S.L中仍有等待该资源的进程被阻塞,故还应调用wakeup 原语,将S.L中的第一个等待进程唤醒。
3.AND信号量
上述进程互斥问题都是针对一个临界资源而言的,在有些应用场合,一个进程需要同时获得两个或者更多的资源。AND信号量可以解决多临界资源申请问题。假设有S1,...Sn,N个资源,进程必须申请到所有资源后才可执行,则其wait 和signal描述为:
void wait(S1, S2, ... , Sn){
if (S1>=1 && S2>=1 && ... && Sn>=1 )
for (int i=1; i<n; i++)
Si = Si - 1;
else
place this process//将当前进程放置在第一个不满足Si>=1的阻塞队列中
}
void signal(S1, S2, ... , Sn){
for (int i=1; i<n; i++)
Si = Si + 1;
romove all the process waiting in the queue associated with Si into ready queue
}
4.信号量集
在记录型信号量机制中,wait和signal操作只能进行加一减一的操作。当需要一次性需要申请N个同类资源时,需要进行N次操作,这显然是低效的。为方便对资源的控制,每种资源在分配前需要检查其数量是否在其极限值之上。为此,对AND信号量进行扩充。S为信号量,d为需求量,t为下限值:
void wait(S1, d1, t1, S2, d2, t2, ... , Sn, dn, tn){
if (S1>=t1 && S2>=t2 && ... && Sn>=tn )
for (int i=1; i<n; i++)
Si = Si - dn;
else
place this process//将当前进程放置在第一个不满足Si>=1的阻塞队列中
}
void signal(S1, d1, S2, d2, ... , Sn, dn,){
for (int i=1; i<n; i++)
Si = Si + dn;
romove all the process waiting in the queue associated with Si into ready queue
}
二、信号量的应用
1.利用信号量实现同步
信号量机构能用于解决进程间各种同步问题。设S为实现进程P1、P2同步的公共信号量,初值为0。进程P2中的语句y要使用进程P1中语句x的运行结果,所以只有当语句x执行完成之后语句y才可以执行。其实现进程同步的算法如下:
semaphore S = 0; //初始化信号量
P1 ( ) {
P1 process
x; //语句x
V(S); //告诉进程P2,语句乂已经完成
}
P2()){
P2 process1
P(S) ; //检查语句x是否运行完成
y; // 检查无误,运行y语句
P2 process2
}
2.利用信号量实现进程互斥
信号量机构也能很方便地解决进程互斥问题。设S为实现进程Pl、P2互斥的信号量,由于每次只允许一个进程进入临界区,所以S的初值应为1(即可用资源数为1)。只需把临界区置于P(S)和V(S)之间,即可实现两进程对临界资源的互斥访问。其算法如下:
semaphore S = 1; //初化信号量
P1 ( ) {
P1 process1
P(S); // 准备开始访问临界资源,加锁
// 进程P1的临界区
V(S); // 访问结束,解锁
P1 process2
}
P2() {
P2 process1
P(S); //准备开始访问临界资源,加锁
// 进程P2的临界区;
V(S); // 访问结束,解锁
P2 process2
}
3.利用信号量实现前驱关系
信号量也可以用来描述程序之间或者语句之间的前驱关系。下图给出了一个前驱图,其中S1, S2, S3, …, S6是最简单的程序段。为保证S1 -> S2、 S1 -> S3、 S2 -> S4、S2 ->S5、S3 -> S6、S4 -> S6、S5 -> S6等前驱关系需要设置信号量a1、a2、bl、b2、c、d、e,并初始化为0。
实现算法如下:
semaphore al=a2=bl=b2=c=d=e=0; //初始化信号量
S1() {
S1 process
V(al); V(a2) ; //S1已经运行完成
}
S2() {
P(a1); //检查S1是否运行完成
S2 process
V(bl); V(b2); // S2已经运行完成
}
S3() {
P(a2); //检查S1是否已经运行完成
S3 process
V(c); //S3已经运行完成
}
S4() {
P(b1); //检查S2是否已经运行完成
S4 process
V(d); //S4已经运行完成
}
S5() {
P(b2); //检查S2是否已经运行完成
S5 process
V(e); // S5已经运行完成
}
S6() {
P(c); //检查S3是否已经运行完成
P(d); //检查S4是否已经运行完成
P(e); //检查S5是否已经运行完成
S6 process
}