进程间通信----信号量

更多linux知识点:linux目录索引


1. 什么是信号量

    信号量的本质是数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。

    个人理解: 信号量就是具有原子性的计数器,当使用了资源时,计数器就要减一,表示可用的资源就少了一个,当用完这个资源将其还回去时,计数器加一,表示可用的资源又多了一个

2. 信号量的工作原理

  由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

  P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

  V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

例子:

  假设桌子上只有一个盘子,那我们就记可用盘子的数量为count=1,小明现在将这个盘子拿走,则桌子上就没有盘子,(执行p操作)count = 0,此时小红也想从桌子上拿盘子,发现count = 0,没有盘子,他就在这等着,过了一会,小明用完盘子并将其放回了桌子(执行v操作),小红此时发现count = 1(表示有盘子),就拿走盘子(执行p操作)

3. 对于信号量的操作

  1. 创建

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
      //如果信号量不存在就创建,存在就打开
       int semget(key_t key, int nsems, int semflg);
       //返回值:函数成功返回一个相应信号标识符,失败返回-1

    参数:

    1. key:信号量的标识符,一般由ftok函数获得
    2. nsems:信号量的个数,表示你要创建几个信号量
    3. semflg:一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
  2. pv操作

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
        //改变信号量的值,也就是执行pv操作
       int semop(int semid, struct sembuf *sops, unsigned nsops);
    
    

    参数:信号量的标识符semget

    semget的返回值,信号量的标识符

    参数:sembuf结构体

    struct sembuf{  
        short sem_num;//除非使用一组信号量,否则它为0  
        short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,  
                    //一个是+1,即V(发送信号)操作。  
        short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,  
                    //并在进程没有释放该信号量而终止时,操作系统释放信号量  
    };  

    参数:信号量的个数

    表示你要操作几个信号量

  3. 控制

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
    
       int semctl(int semid, int semnum, int cmd, union semun arg);
       //返回值:成功返回信号量集的标识符,失败返回-1

    参数:信号量的标识符semid

    semget的返回值

    参数:信号量的编号semnum

    要操作信号在信号集中的编号。编号从0开始。

    参数:选项cmd

    cmd: 命令,表示要进行的操作。(SETVAL:设置初值;GETVAL:获取初值

    命令 解释
    IPC_STAT 从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中
    IPC_SET 设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值
    GETALL 从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中
    GETNCNT 返回当前等待资源的进程个数
    GETPID 返回最后一个执行系统调用semop()进程的PID
    GETZCNT 返回当前等待100%资源利用的进程个数
    SETALL 与GETALL正好相反
    GETVAL 返回信号量集合内单个信号量的值
    SETVAL 用联合体中val成员的值设置信号量集合中单个信号量的值
    IPC_RMID 从内核中删除信号量集合

    参数:联合体arg

     union semun {
     *          short val;             /*SETVAL用的值,一般写这一个就够了*/
     *          struct semid_ds* buf;  /*IPC_STAT、IPC_SET用的semid_ds结构*/
     *          unsigned short* array; /*SETALL、GETALL用的数组值*/
     *          struct seminfo *buf;   /*为控制IPC_INFO提供的缓存*/
     *      } arg;

4. 实例:利用信号量解决哲学家就餐问题

  1. 问题描述:

    假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。

  2. 问题分析:

    当5个哲学家进程并发执行时,某个时刻恰好每个哲学家进程都执行申请筷子,并且成功申请到第i支筷子(相当于5个哲学家同时拿起他左边的筷子), 接着他们又都执行申请右边筷子, 申请第i+1支筷子。此时每个哲学家仅拿到一支筷子, 另外一支只得无限等待下去, 引起死锁。

  3. 解决思路

    1. 对每个哲学家和筷子进行编号:0~4
    2. 对每个筷子都设置一个信号量(计数器)
    3. 每个哲学家每次取筷子时,同意给他左右两个筷子,即自己编号(num)和 num+1,并且对这两个筷子执行p操作,其他哲学家取筷子时就在等待
      4.当用完两个筷子就将其归还,执行v操作,以便其他哲学家进行使用
    4. 这样保证每个哲学家拿筷子时都能拿到足够的筷子,避免死锁

    4. 代码:

        #include <unistd.h>
        #include <sys/sem.h>
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
    
        union su
        {
            int val;
        };
    
        int semid;
        void p(int num)
        {
            struct sembuf array[2] = {
                {num,-1,0},
                {(num+1)%5,-1,0}
            };
            semop(semid,array,2);
        }
        void v(int num)
        {
            struct sembuf array[2] = {
                {num,1,0},
                {(num+1)%5,1,0}
            };
            semop(semid,array,2);
        }
        void zxj(int num)
        {
            while(1)
            {
                printf("%d 哲学家开始思考.....\n",num);
                sleep(rand()%5);
                printf("%d 哲学家开始饿了.....\n",num);
                p(num);
                printf("%d 哲学家吃饭,拿筷子\n",num);
                sleep(rand()%3);
                printf("%d 哲学家吃完,放筷子\n",num);
                v(num);
            }
        }
        //哲学家就餐,先创建信号量,用于管理筷子,在创建5个进程,表示哲学家
        int main()
        {
            srand(getpid()); 
            key_t key = ftok(".",0x06666);
            if(key < 0){
                perror("ftok");
                return -1;
            }
            semid = semget(key,5,IPC_CREAT|0644);
            if(semid < 0){
                perror("semid");
                return -2;
            }
    
            int i=0;//对5个信号量进行初始化,每个信号量初始化为1
            for(; i < 5;++i)
            {
                union su s = {1};
                semctl(semid,i,SETVAL,s);
            }
    
             int num = 0;
            for(i = 1;i<5;++i)
            {
                pid_t pid = fork();
    
                if(pid == 0){//给子进程编号
                    num = i;
                    break;
            }
        }
    
            zxj(num);
            return 0;
        }
    

    结果:

    这里写图片描述

猜你喜欢

转载自blog.csdn.net/zhangye3017/article/details/80214725