8 操作系统第二章 进程管理 信号量 PV操作 用信号量机制实现 进程互斥、同 步、前驱关系

1 信号量机制

  • 用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。
  • 信号量其实就是一个变量 ,可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为1的信号量。
  • 原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。
  • 软件解决方案实现临界区的互斥主要问题是由“进入区的各种操作无法一气呵成”,因此如果能把进入区、退出区的操作都用“原语”实现,使这些操作能“一气呵成”就能避免问题。

一对原语:wait(S)原语和signal(S) 原语,可以把原语理解为我们自己写的函数,函数名分别为waitsignal,括号里的信号量S其实就是函数调用时传入的一个参数。
wait、signal原语常简称为P、V操作。因此常把 wait(S)、signal(S)两个操作分别写为P(S)、V(S)

1.1 整形信号量

用一个整数型的变量作为信号量,用来表示系统中某种资源的数量。

int S = 1;        //初始化整形信号量s,表示当前系统中,某种可用资源数
wait(S){          //wait原语,相当于“进入区”
   while(S<=0);   //若资源数不够用,则一直循环等待
    S=S-1;        //若资源够用,则占用一个资源
 }
signals(S){       //signals原语,相当于“退出区”
    S=S+1;        //使用完资源后,在退出区释放资源
 } 

整形信号量:

  • “检查”和“上锁”一气呵成, 避免了并发、异步导致的问题
  • 存在的问题:不满足“让权等待” 原则,会发生“忙等”
1.2 记录形信号量

整型信号量的缺陷是存在“忙等”问题,因此人们又提出了“记录型信号量”,即用记录型数据结构表示的信号量。

/*记录型信号量的定义*/
typedef struct {
      int value;              //剩余资源数
      struct process *L;      //等待队列
} semaphore

若某个进程需要使用资源时,通过wait原语申请:

void wait( semaphore S){  //相当于申请资源
        S.value--;
        if(S.value<0){
             bolck(S.L) //如果剩余资源数不够,使用block原语使进程从运行态进入阻塞态,并把挂到信号量S的等待队列(即阻塞队列)中
        }
}

进程使用完资源后,通过signal原语释放:

void signal ( semaphore S ){   //相当于释放资源
       s.value++;
       if(S.value<=0){
       wakeup(S.L);
       //释放资源后,若还有别的进程在等待资源,则使用wakeup原语唤醒等待队列中的一个进程,该进程从阻塞态变为就绪态
       }
}

说明:

  • S.value的初值表示系统中某种资源的数目。
  • 对信号量S的一次P操作意味着进程请求一个单位的该类资源,因此需要执行S.value--,表示资源数减1,当S.value<0时表示该类资源已分配完毕,因此进程应调用block原语进行自我阻塞(当前运行的进程从运行态变成阻塞态),主动放弃处理机,并插入该类资源的等待队列S.L中。可见,该机制遵循了“让权等待”原则, 不会出现“忙等”现象。
  • 对信号量S的一次V操作意味着进程释放一个单位的该类资源,因此需要执行S.value++,表示资源数加1, 若加1后仍是S.value<=0,表示依然有进程在等待该类资源,因此应调用wakeup原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态变成就绪态)。
1.3 信号量机制小结

在这里插入图片描述

信号量的值=这种资源的剩余数量(信号量的值如果小于0,说明此时有进程在等待这种资源)
P(S)——申请一个资源S,如果资源不够就阻塞等待
V(S)——释放一个资源S,如果有进程在等待该资源,则唤醒一个进程

2 用信号量机制实现进程互斥、同 步、前驱关系

2.1 信号量机制实现进程互斥

思想步骤:

  1. 分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)
  2. 设置互斥信号量mutex,初值为1
  3. 在进入区P(mutex)——申请资源
  4. 在退出区V(mutex)——释放资源

代码实现:

/*记录型信号量定义*/
typedef struct {
	 int value;            //剩余资源数
	 struct process *L;    //等待队列
}  semaphore;

/*信号量机制实现互斥*/
 semaphore  mutex=1;     //初始化信号量

P1(){
	...
	P(mutex);           //使用临界资源前需要加锁
	临界区代码段...
	V(mutex);          //使用临界资源后需要解锁
	...
}

P2(){
	...
	P(mutex);          //使用临界资源前需要加锁
	临界区代码段...
	V(mutex);          //使用临界资源后需要解锁
	...
}

注意问题:

  1. 要会自己定义记录型信号量,但 如果题目中没特别说明,可以把信号量的声明简写成“semaphore mutex=1;”这种形式
  2. 对不同的临界资源需要设置不同的互斥信号量。
  3. P、V操作必须成对出现。缺少P(mutex)就不能保证临界资源的互斥访问。缺少V(mutex)会导致资源永不被释放,等待进程永不被唤醒。
2.2 信号量机制实现进程同步

进程同步:要让各并发进程按要求有序地推进。

比如,P1、P2并发执行,由于存在异步性,因此二者交替推进的次序是不确定的。
在这里插入图片描述
若P2的“代码4”要基于P1的“代码1”和“代码2”的运行结果才能执行,那么我们就必须保证“代码4”一定是在“代码2”之后才会执行。
这就是进程同步问题,让本来异步并发的进程互相配合,有序推进。

思想步骤:

  1. 分析什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两个操作(或两句代码)
  2. 设置同步信号量S,初始为0
  3. 在“前操作”之后执行V(S)
  4. 在“后操作”之前执行P(S)

技巧口诀:前V后P

代码实现:
以下代码保证了P2进程中代码4一定是在P1进程中代码2之后执行:

/*信号量机制实现同步*/
semaphore  S=0;     //初始化同步信号量,初始值为0

P1(){
      代码1;
      代码2;
      V(S);
      代码3}
P2(){
      P(S);
      代码4;
      代码5;
      代码6}

注意问题:

  1. semaphore S=0理解:信号量S代表“某种资源”,刚开始是没有这种资源的。P2需要使用这种资源, 而又只能由P1产生这种资源
  2. 若先执行到V(S)操作,则S++S=1。之后当执行到P(S)操作 时,由于S=1,表示有可用资源,会执行S--,S的值变回0,P2进程不会执行block原语,而是继续往下执行代码4
  3. 若先执行到P(S)操作,由于S=0S--S=-1,表示此时没有 可用资源,因此P操作中会执行block原语,主动请求阻塞。 之后当执行完代码2,继而执行V(S)操作,S++,使S变回0, 由于此时有进程在该信号量对应的阻塞队列中,因此会在V 操作中执行wakeup原语,唤醒P2进程。这样P2就可以继续执行代码4了
2.3 信号量机制实现前驱关系

思想步骤:
其实每一对前驱关系都是一个进程同步问题(需要保证一前一后的操作) :

  1. 要为每一对前驱关系各设置一个同步信号量
  2. 在“前操作”之后对相应的同步信号量执行V操作
  3. 在“后操作”之前对相应的同步信号量执行P操作

进程P1中有句代码S1P2中有句代码S2P3中有句代码S3……P6中有句代码S6。这些代码要求按如下前驱图所示的顺序来执行:

  1. 要为每一对前驱关系各设置一个同步信号量
    在这里插入图片描述
  2. 在“前操作”之后对相应的同步信号量执行V操作,在“后操作”之前对相应的同步信号量执行P操作
    前V后P V→P
    在这里插入图片描述
    在这里插入图片描述
2.4 信号量机制实现进程互斥、同 步、前驱关系小结

在这里插入图片描述
在这里插入图片描述

  • 11
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
信号量(Semaphore)是一种用于多进程/线程同机制,它可以保证在同一时刻只有一个进程/线程访问共享资源。信号量有两种操作:P(wait)操作和V(signal)操作。 P操作也称为wait操作,是对信号量进行减1操作。如果信号量的值为0,则该操作会使当前进程/线程阻塞,直到有其他进程/线程对信号量进行V操作为止。 V操作也称为signal操作,是对信号量进行加1操作。如果有其他进程/线程因为P操作而被阻塞,那么该操作会唤醒其中一个被阻塞的进程/线程。 下面是一个基本的信号量PV操作的代码实现(用C语言实现): ``` typedef struct { int value; // 信号量的值 struct process *list; // 等待该信号量进程/线程队列 } semaphore; // P操作 void P(semaphore *s) { s->value--; // 信号量值减1 if (s->value < 0) { // 如果信号量的值小于0,当前进程/线程被阻塞 add_to_list(s->list, current_process); // 将当前进程/线程加入等待队列 block(current_process); // 阻塞当前进程/线程 } } // V操作 void V(semaphore *s) { s->value++; // 信号量值加1 if (s->value <= 0) { // 如果等待该信号量进程/线程队列不为空,则唤醒其中一个进程/线程 process *p = remove_from_list(s->list); unblock(p); // 解除该进程/线程的阻塞状态 } } ``` 在上述代码中,`add_to_list`函数和`remove_from_list`函数分别用于将进程/线程加入/移出等待该信号量的队列中。`block`函数和`unblock`函数分别用于阻塞和解除阻塞进程/线程的状态。这些函数的实现可以根据具体需求进行编写。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值