谈谈操作系统中的信号量与PV操作

在临界区的调度原则中有:

  • 互斥使用
  • 有空让进
  • 忙则等待
  • 有限等待
  • 择一而入
  • 算法可行

在实际应用中,我们考虑对临界区的管理有软件算法,也有硬件设施,但是这些偏软,偏硬的方法,或存在复杂、效率低下,或存在浪费CPU时间等问题。下面笔者将和大家谈谈一种新的同步工具:信号量和PV操作。

PV操作

PV操作是属于原语操作,原语操作即是执行时是不可被打断的,如原子一般不可再分,通过PV操作我们可以保证执行时不可打断且信号量值的完整性。

  • P(s):将信号量的值减一(s.value - 1)

    • 如果结果小于0(s.value < 0),执行P操作的进程将被阻塞,放入到与s信号量有关的list所指向的队列中
    • 如果结果大于0(s.value>=0),则执行P操作的进程继续执行。
  • V(s):将信号量的值加一(s.value+1)

    • 若结果不大于0(s.value <= 0),则执行V操作的进程从与信号量有关的list所指向的队列中释放一个进程,使其转于就绪态,自己继续执行;
    • 若结果大于0(s.value>0),则执行V操作的进程继续执行。

    PV操作的推论:

    • 若信号量s.value为正值,此值等于在封锁之前对信号量s可施行的P操作数,即s所代表的实际可用的物理资源数。
    • 若信号量s.value为负值,其绝对值等于登记排列在s信号量队列之中等待的进程个数,即恰好等于对信号量s实施P操作而被封锁的并进入信号量s等待队列的进程数
    • 通常P操作意味着请求一个资源,V操作意味着释放一个资源,在一定条件下,P操作代表挂起进程的操作,V操作代表被唤醒被挂起的进程的操作。

信号量

在操作系统中将用信号量来表示物理资源的实体,与队列有关。同时,须知:

信号量是一种变量类型,有如下两个分量:

  • 信号量的值(value)
    • value>0,表示实际可用资源数
    • value=0,表示资源为0
    • value<0,表示正在等待资源的进程数
  • 信号量的指针:用于指向等待的进程

按用途来分,信号量分为两种:

  1. 公用信号量:
    1. 联系一组并发进程,相关进程可以在此信号量上做PV操作,初值为1,这个为1,是为了实现进程的一个互斥;
  2. 私有信号量:
    1. 联系一组并发进程,仅允许此信号量所拥有的进程执行P操作,其他相关的进程可实施V操作,初值一般为0或正整数,在进程同步中常用。

按取值来分,又有如下两种:

  • 二值信号量
    • 仅能取值为0或1,解决进程互斥
  • 一般信号量
    • 允许取值大于1,常用于解决进程同步。

信号量的数据结构以及PV操作的细节

typedef struct semaphore
{
    int value; //信号量的值
    struct pcb * list;//信号量的队列指针
}

void P(semaphore s){
    s.value --; 
    if(s.value < 0) // 小于0 被阻塞
        sleep(s.list);
    
}
void V(semaphore s){
    s.value ++;
    if(s.value <= 0)
        wakeup(s.list); //小于等于0 从信号量队列中释放一个等待的进程,并转为就绪态
}

信号量实现互斥

通过使用信号量和PV操作来管理并发进程进入临界区的形式:

semaphore mutex;
mutex=1;
cobegin
    process Pi(){
    	P(mutex);
    /**临界区*/
    	V(mutex);
}

当有进程在临界区中时,mutex的值为0或者负值,否则mutex为1.因为只能有一个进程的P操作能把mutex的值减0,从而能够保证互斥操作

实际应用(C语言实现)

现在有以下场景:

有一家人,小明专门吃苹果,小红专门吃橘子,爸爸放苹果,妈妈放橘子,盘子上面只能有一个水果,而只有相对应的水果孩子们(孩子专门吃其对应的水果)才能拿来吃,只有当盘子空的时候,爸爸妈妈才能放水果,试用进程并发实现这个操作。

首先要思考的是,临界资源是什么?显然,是盘子,盘子有多少种状态?三种!分别是:

  • 盘子是空的
  • 盘子上有橘子
  • 盘子上有苹果

当有多个进程并发时,我们要保证资源区在每时每刻都能保证:互斥使用、有空让进、忙则等待、有限等待、择一而入,算法可行。下面我将使用PV操作实现这个操作:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include<semaphore.h>


#define P sem_wait
#define V sem_post
#define orange &orange_
#define apple &apple_
#define empty &empty_real1


sem_t orange_;
sem_t apple_;
sem_t empty_real1;

int num=0;

void * mother(){
    while(num < 30)
    {

        P(empty);
        num++;
        printf("妈妈放橘子 %d\n",num);
        V(orange);

    }

}
void * father()
{
    while(num < 30)
    {
        P(empty);
        num++;
        printf("爸爸放苹果 %d\n",num);
        V(apple);
    }

}
void * xiaoming()
{
    while(num < 30)
    {
        P(apple);
        num++;
        printf("小明吃苹果 %d\n",num);
        V(empty);
    }

}
void * xiaohong(){
    while(num < 30)
    {
        P(orange);
        num++;
        printf("小红吃橘子 %d\n",num);
        V(empty);
    }

}

int main()
{
    sem_init(apple,0,0); //初始化信号量的值为0,并且设定其为进程内的线程共享
    sem_init(orange,0,0);
    sem_init(empty,0,1); //初始信号量的值为1,表示刚开始的时候盘子是空的,资源可用


    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
    pthread_t tid4;

    pthread_create(&tid1,NULL,mother,NULL);
    pthread_create(&tid2,NULL,father,NULL);
    pthread_create(&tid3,NULL,xiaoming,NULL);
    pthread_create(&tid4,NULL,xiaohong,NULL);

    pthread_exit(0);

    return 0;
}

以上代码的执行结果:

在这里插入图片描述

这里可能大家看到为啥num的值为啥会大于30,这时思考以下num的范围,并且在线程等待的时候,是否num值已经被判断过了了?即可得知答案。
好了相关内容就分享到这里,如果你觉得有用的话可以给我点个赞,谢谢!

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值