操作系统-PV操作的原理和几种常见问题

摘要:
信号量是一种可变类型,由记录数据结构表示。它有两个组件:信号量的值和信号量队列指针。除了分配的初始值之外,信号量只能通过同步原语PV对其进行操作。该值是定时,即在进程被阻止之前可以对信号量执行的P个操作的数量,即s表示实际可用的物理资源。当该值为负值时,它的绝对值是由信号量s上的P操作阻塞并进入信号量s等待队列的进程的数量,即在s信号量队列中注册等待的进程的数目。当s.value为0时,没有资源也没有进程
信号量是一种变量类型,用一个记录型数据结构表示,有两个分量:信号量的值和信号量队列指针
除了赋初值外,信号量仅能通过同步原语PV对其进行操作
s.value为正时,此值为封锁进程前对s信号量可施行的P操作数,即s代表实际可用的物理资源
s.value为负时,其绝对值为对信号量s实施P操作而被封锁并进入信号量s等待队列的进程数,即登记排列在s信号量队列之中等待的进程个数
s.value为0时,无资源且无进程等待
信号量按其取值可分为二值信号量和一般信号量(计数信号量),记录型信号量和PV操作定义为如下数据结构和不可中断过程:
typedef struct semaphore { 
    int value;
    struct pcb *list; //信号量队列指针
}
void P(semaphore s){
    s.value--;
    if(s.value<0) sleep(s.list); //若信号量值小于0,执行P操作的进程调用sleep(s.list)阻塞自己,被置成等待信号量s状态并移入s信号量队列,转向进程调度程序
}
void V(semaphore s){
    s.value++;
    if(s.value<=0) wakeup(s.list); //若信号量小于等于0,则调用wakeup(s.list)从信号量s队列中释放一个等待信号量s的进程并转换成就绪态,进程则继续运行
}
二值信号量虽然仅能取值0、1,但它和其它记录型信号量有一样的表达能力
  • 机票问题
注意:(1)P操作与V操作在执行路径上必须一一对应,有一个P操作就有一个V操作;(2)输出一张票的操作不应放在临界区内
int A[m];
Semaphore s = 1;
cobegin
process Pi {
    int Xi;
    Li:按旅客定票要求找到A[j];
    P(s);
    Xi = A[j];
    If (Xi>=1) { Xi=Xi-1; A[j]=Xi;V(s); 输出一张票;} 
    else {V(s); 输出票已售完;}   
    goto Li;
    }
coend;
只有相同航班的票数才是相关的临界资源,所以用一个信号量s处理全部机票会影响进程并发度
可以让每一个航班都有自己的临界区,把信号量改为s[m]
int A[m];
Semaphore s[m];    //每一个航班都有自己的临界区
For (int j=0;j<m;i++) s[j] = 1;
cobegin
process Pi {
    int Xi;
    L1:按旅客定票要求找到A[j];
    P(s[j]);
    Xi = A[j];
    If (Xi>=1) { Xi=Xi-1; A[j]=Xi;V(s[j]); 输出一张票;
    } else {V(s[j]); 输出票已售完;}
    goto L1;
    }
coend;
  • 生产者消费者问题
(1)1生产者1消费者1缓冲区问题
int B;
semaphore sput = 1/* 可以使用的空缓冲区数 */
semaphore sget = 0; /* 缓冲区内可以使用的产品数 */
process producer {
    L1:
    produce a product;
    P(sput);
    B = product;
    V(sget);
    goto L1;
}
process consumer {
    L2:
    P(sget);
    product = B;
    V(sput);
    consume a product;
    goto L2;
}
(2)1生产者1消费者N缓冲区问题
必须引入放入和取出产品的循环队列指针putptr指针和getptr指针进行管理,因为只有1生产者1消费者所以它们不需要是共享变量
int B[k]; // 共享缓冲区队列
semaphore sput = N;  //可以使用的空缓冲区数
semaphore sget = 0;  //缓冲区内可以使用的产品数
int putptr = getptr = 0; 
process producer {
    L1:produce a product;
    P(sput);
    B[putptr] = product;
    putptr = (putptr + 1) mod k;
    V(sget);
    goto L1;
}
process consumer {
    L2:P(sget);
    product = B[getptr];
    getptr = (getptr + 1) mod k;    
    V(sput);
    consume a product;
    goto L2;
}
⭐️(3)N生产者N消费者N缓冲区问题
必须引入新的信号量s1和s2对putptr指针和getptr指针进行管理
int B[k];
semaphore sput = N; /* 可以使用的空缓冲区数 */
semaphore sget = 0; /* 缓冲区内可以使用的产品数 */
int putptr = getptr = 0; 
semaphore s1 = s2 = 1/* 互斥使用putptr、getptr */
process producer_i {
    L1:produce a product;
   P(sput);
    P(s1);
    B[putptr] = product;
    putptr = ( putptr + 1 ) mod k;
   V(s1);
    V(sget);
    goto L1;
}
process consumer_j {
    L2:P(sget);
    P(s2);
    Product = B[getptr];
    getptr = ( getptr + 1 ) mod k;
    V(s2);
    V(sput);
    consume a product;
    goto L2;
}
若要求一个消费者取10次,其它消费者才能开始取,则需要再增加一个信号量mutex1并对消费者进程进行改造,循环10次后再V(mutex1)。
这里通过一个互斥信号量mutex2互斥访问缓冲区,而不是像前面那样通过两个互斥信号量s1和s2分别互斥使用缓冲区存指针putptr、取指针getptr
process consumer_j {
    P(mutex1);
    for(int i=0; i<10; i++){
        P(full);
        P(mutex2);
        互斥访问缓冲区;
        V(mutex2);
        V(empty);
    }
    V(mutex1);
}
(4)苹果橘子问题
父亲只放评估、母亲只放橙子;儿子只吃橙子、女儿只吃苹果
同步关系1:有苹果
同步关系2:有橘子
同步关系3:有空位
Semaphore sp; /* 盘子里可以放几个水果*/
Semaphore sg1; /* 盘子里有桔子*/
Semaphore sg2; /* 盘子里有苹果*/
sp = 1; /* 盘子里允许放入一个水果*/
sg1 = 0; /* 盘子里没有桔子*/
sg2 = 0; /* 盘子里没有苹果*/
process father {
L1: 削一个苹果;
    P(sp);
    把苹果放入plate;
    V(sg2);
    goto L1;
}
process mother {
    L2: 剥一个桔子;
    P(sp);
    把桔子放入plate;
    V(sg1);
    goto L2;
}
process son {
    L3: P(sg1);
    从plate中取桔子;
    V(sp);
    吃桔子;
    goto L3;
}
process daughter {
    L4: P(sg2);
    从plate中取苹果;
    V(sp);
    吃苹果;
    goto L4;
}
(5)吸烟者问题
三个消费者各有一种材料、缺另外两种材料,供应者每次随机供应两种不同材料
想法:生产者每次供应两种不同材料,其实可以理解为有三种不同的生产材料提供方式,分别供三种消费者消费
int random;
Semaphore offer1 = offer2 = offer3 = 0;
Semaphore finish = 1;
process producer(){
    while(1){
        random = 任意随机整数;
        random = random % 3;
        P(finish);
        对应两种材料放在桌子上;
        if(random==0) V(offer1);
        else if(random==1) V(offer2);
        else V(offer3);
    }
}
process consumer_1(){
    while(1){
        P(offer1);
        拿自己缺的那两种材料;
        卷成烟抽掉;
        V(finish);
    }
}
(6)1生产者2消费者N缓冲区,生产者随机生成正整数,2个消费者分别取奇数和偶数
思路:这个问题和抽烟者问题很类似,关键是要定义缓冲区里有奇数的互斥信号量odd、缓冲区里有偶数的互斥信号量even。每次生产者随机生成一个数之后,判断奇偶,分别V(odd)和V(even)
  • 哲学家就餐问题
Semaphore fork[5];
for (int i = 0; i < 5; i++)
    fork[i] = 1;
cobegin
process philsopher_i() {
    while (true) {
        think();
        P(fork[i]);
        P(fork[(i+1)%5]);
        eat();
        V(fork[i]);
        V(fork[(i+1)%5]);
    }
}
coend
如果5位哲学家同时拿起他们左手/右手的叉子,将出现死锁,可以:
(1)至多允许四个哲学家同时拿叉子
(2)奇数号哲学家先取左边叉子,再取右边叉子;偶数号哲学家则相反
(3)每位哲学家取到手边两把叉子才开始吃,否则一把也不取:可以通过引入互斥信号量mutex每次只允许一个哲学家拿叉子
Semaphore fork[5];
for (int i = 0; i < 5; i++)
    fork[i] = 1;
Semaphore mutex = 1;
cobegin
process philsopher_i() {
    while(true){
       P(mutex);
        P(fork[i]);
        P(fork[(i+1)%5]);
       V(mutex);
        eat();
        V(fork[i]);
        V(fork[(i+1)%5]);
    }
}
coend
  • 写者问题
一次只允许一个写者写,但可以N个读者同时读。写者完成写操作前不允许其它写者、读者操作
引入表示是否允许写的信号量writeblock,相当于任何进程在工作的时候都不允许写。不过单纯引入信号量不能解决此问题,还必须引入计数器readcount对读进程进行计数,读之前检查计数器,如果为1(自己是唯一的读进程)才需要P(writeblock),读之后检查计数器,如果为0(当前已无读进程)则需要V(writeblock)。
mutex是用于对计数器readcount操作的互斥信号量
int readcount = 0;
Semaphore writeblock = 1 ;
Semaphore mutex = 1;
cobegin
process read_i() {
    P(mutex);
    readcount++;
    if(readcount==1) P(writeblock); //自己是唯一的读进程,写者在写文件时自己不能开始读,自己开始读后不允许写操作
    V(mutex);
    /*读文件*/
    P(mutex);
    readcount-—; 
    if(readcount==0) V(writeblock); //自己是唯一的读进程,读文件完成后允许写操作
    V(mutex);
}
process write_j() {
    P(writeblock);
    /*写文件*/
    V(writeblock);
}
coend
此写法读者优先,当存在读者时写者将被延迟。且只要有一个读者活跃,随后而来的读者都将被允许访问文件,从而导致写者长时间等待。
改进方法:增加信号量,确保当一个写进程声明想写时,不允许后面的新读者访问共享文件。
  • 男女共浴问题、汽车过桥问题
这个问题的关键是设置一把性别锁mutex,第一个到的男人要负责抢这把锁,因此mutex的PV操作必须放在修改mancount的临界区内。一旦男人抢到了这把锁,试图抢这把锁的女人就会停留在womancount的临界区内出不来,而每次又只允许一个女人进入womancount的临界区。
最后一个走的男人要负责把这把锁释放掉。
Semaphore mutex;
Semaphore mutex_man = mutex_woman= 1;
int mancount = womancount =0;
Process man(){
    P(mutex_man)
    mancount++;
    if(mancount==1) P(mutex);
    V(mutex_man);
    洗澡;
    P(mutex_man);
    mancount—;
    if(mancount==0) V(mutex);
    V(mutex_man)
}
南大18年真题过桥问题:汽车只能单向过桥,最多12辆车同时过桥。
  • 理发师问题
引入一个计数器waiting记录等待理发的顾客坐的椅子数,初值为0最大为N。mutex是用于对计数器waiting操作的互斥信号量
引入信号量customers记录等候理发的顾客数,并用于阻塞理发师进程,初值为0
引入信号量barbers记录正在等候顾客的理发师数,并用于阻塞顾客进程,初值为0
想法:其实和N生产者1消费者N缓冲区的生产者消费者问题有些类似,但是多了P(barbers)和V(barbers);此外,椅子数改用了int数waiting再用信号量mutex进行互斥。
int waiting = 0;
Semaphore customers = 0;
Semaphore barbers = 0;
Semaphore mutex = 1;
cobegin
process barbers() {
    while(true) {
       P(customers);//判断是否有顾客,没有的话理发师睡眠
       P(mutex);
        waiting--;
        V(barbers);//理发师准备为顾客理发
        V(mutex);
        cuthair();  //理发师理发,不应放在临界区
    }
}
process customer_i() {
    P(mutex);
    if(waiting<N) {
        waiting++;
        V(customers);//唤醒理发师
        V(mutex);
        P(barbers);//如果理发师忙则等待
        get_haircut();
    }
    else V(mutex);//人满了,顾客离开
}
coend

免责声明:文章转载自《操作系统-PV操作的原理和几种常见问题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇DeeplabV3+训练自己的数据集(二)使用kubeadm部署K8S v1.17.0集群下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Ogre2.0 全新功能打造新3D引擎

不知当初是在那看到,说是Ogre2.0浪费了一个版本号,当时也没多想,以为没多大更新,一直到现在想做一个编辑器时,忽然想到要看下最新版本的更新,不看不知道,一看吓一跳,所以说,网络上的话少信,你不认识别人,别人张嘴就来,对别人也没损失,还可以装B下,靠. 从现在Ogre2.1的代码来看,大约总结下,更新包含去掉过多的设计模式,SoA的数据结构(用于SIMD...

父进程和子进程关于数据和文件描述符的继承的理解

    用fork()函数建立的子进程几乎与父进程完全一样,子进程中的所有变量均保持他们在父进程中的值(当然fork的返回值除外),因为自己称可用的数据是父进程可用数据的拷贝,并且其占用不同的内存地址空间(当然逻辑地址可能是一样的),这就保证了在一个进程中的变量数据变化不会影响到另外一个进程。这一点非常重要。     但是有一点要特别注意的,在父进程打开的...

Shell与进程

查看当前运行的进程 名称: ps使用权限: 所有使用者 使用方式: ps [options] [--help] 说明: 显示瞬间行程 (process) 的动态 参数: ps的参数非常多, 在此仅列出几个常用的参数并大略介绍含义 常用参数 -A 显示所有进程(等价于-e)(utility) -a...

06-常见的面试题目-线程

1、线程的基本概念 线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。 2、线程的基本状态及状态之间的关系(对否?) 状态:运行、阻塞、...

《Linux/UNIX系统编程手册》第43章 进程间通信简介

关键词:pipe、fifo、stream socket、datagram socket、message queue、Share Memory、memory mapping、signal、semaphore。mutex、condition variable等等。 本章是后面章节的简要介绍,包括管道和FIFO;System V和POSIX IPC的消息队列、信...

python操作excel向同一sheet循环追加数据

参考文章:https://blog.csdn.net/sdaujz/article/details/102080900?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-15.nonecase&depth_1-utm_source=dis...