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


    信号量是一种变量类型,用一个记录型数据结构表示,有两个分量:信号量的值和信号量队列指针
    除了赋初值外,信号量仅能通过同步原语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
  • 相关阅读:
    OpenGL的几何变换2之内观察立方体
    OpenGL的几何变换[转]
    OpenGL的glPushMatrix和glPopMatrix矩阵栈顶操作函数详解
    OpenGL的glScalef缩放变换函数详解
    [centos][ntp][administrator] chrony ntp
    [dpdk][kni] dpdk kernel network interface
    [administrator][netctl] 给未插线未UP端口设置IP
    [administrator] rpmbuild
    OWA (Office Web Access)
    [network] netfilter
  • 原文地址:https://www.cnblogs.com/yangyuliufeng/p/9983335.html
Copyright © 2020-2023  润新知