• 【操作系统】进程间的通信——信号量


    进程间的通信-信号量

    • 信号量就类似与马路上的红绿灯,来控制人们在各个路口朝各个方向上的行进,从而更好地有规划的使用这条道路。
    • 在程序中,信号则对进程们的执行进行控制。

    什么是信号量

    • 问题:
      • 在程序中,有时会存在一种特殊代码,同一时间只允许一个进程执行该部分代码。这部分区域,被称为"临界区"
      • 然后在多进程并发执行中,当一个进程进入临界区,因某种原因被挂起时,其他进程就有可能也进入该区域。
    • 解决办法:——使用信号量
    • 什么是信号量?
    • 信号量是一种特殊的变量。
    • 我们只能对信号量执行P操作和V操作。
      • P操作:申请资源。
        • 如果信号量的值>0,则把该信号量-1。
        • 如果信号量的值=0,则挂起该进程。
      • V操作:释放资源。
        • 如果有进程因该信号量而被挂起,则恢复当前进程运行。
        • 如果没有进程因该信号量而被挂起,则把该信号量+1。
    • 注意:
      • P操作、V操作都是原子操作,即,其在执行期间,不会被中断
      • 这里指的信号量是指System V IPC的信号量,与线程所使用的信号量不同。该信号量用于进程间通信

    信号量的使用

    信号量的获取

    • semget
    • 函数原型:int semget(key_t key, int nsems, int semflg);
    • 功能:获取一个已存在的、或创建一个新的信号量,并返回该信号量的标识符。
    • 参数:
      • key:键值,该键值对应一个唯一的信号量。类似于共享内存的键值。
      • 不同的可通过该键值和semget获取唯一的信号量。
      • 特殊键值——IPC_PRIVAT,该信号量只允许创建者进程才可以访问,可用于父子进程间通信。
      • nsems:需要的信号量数目,一般为1。
      • semflag:访问权限。
        • 若设置为IPC_CREAT,则如果该信号量未存在,则创建该信号量,如果该信号量已经存在,也不会发生错误。
    • 返回值:
      • 成功:返回一个正整数。
      • 失败:返回-1。

    信号量的操作

    • semop

    • 函数原型:int semop(int semid, struct sembuf *sops, unsigned nsops);

    • 功能:改变信号量的值,即对信号量执行P操作、V操作。

    • 参数:

      • semid:信号量标识符,即semget函数的返回值。

      • sops:是一个数组,元素类型为struct sembuf。

      •   struct sembuf {
               short  sem_num;  //信号量组中的编号(即指定对哪个信号量操作)
                               //semget实际是获取一组信号量
                               //信号量组中的编号从0开始
               short  sem_op;     //操作类型:-1表示P操作;  1表示V操作
               short  sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
                         // 并在进程没有释放该信号量而终止时,操作系统释放信号量         
           }        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
      • nsops:表示第二个参数sops所表示的数组大小,即有几个struct sembuf。

    • 返回值:

      • 成功:返回0。
      • 失败:返回-1。
    • 相关参考与补充:Linux进程间通信(五):信号量 semget()、semop()、semctl()


    信号量的控制

    • semctl

    • 函数原型:int semctl(int semid, int sem_num, int cmd, …);

    • 功能:对信号量进行控制。

    • 参数:

      • semid:信号量标识符。

      • sem_num:信号量组中的编号,如果只有一个信号量,则取0。

      • cmd:通常是下面两个值的其中一个。

        • SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
        • IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
      • 参数四类型为:union semun

        • union  semun {
                           int     val;      // SETVAL命令要设置的值
                           struct  semid_ds  *buf;
                           unsigned short    *array;
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
        • union semun有些Linux发行版在sys/sem.h中定义,有些则没有定义,可自行定义:

        • #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)			#else
          union semun {
              int val;                             
              struct semid_ds *buf;    
              unsigned short int *array; 
              struct seminfo *__buf;  
          };
          #endif     
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
    • 返回值:略,详见-semctl(2) — Linux manual page

    • 相关参考与补充:Linux进程间通信(五):信号量 semget()、semop()、semctl()


    示例

    • 示例1:不使用信号量,并发执行多个程序,观察对临界区的访问。
    #include 
    #include 
    
    int main(void) {
    	int i;
    	pid_t pd = fork();
    	for (i=0; i<5; i++) {
    		
    		/* 模拟临界区----begin */
    		printf("Process(%d) In\n", getpid());		
    		sleep(1);
    		printf("Process(%d) Out\n", getpid());
             /* 模拟临界区----end */ 
    		sleep(1);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20220824164003543

    可见并不是我们想要的效果,我们想要的是一个进去了,另外一个就不可以进去了,出去一个,另外一个才可以进去。


    • 示例2:使用信号量,并发指定多个进程,观察对临界区的访问。
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)						   
    #else
        union semun {
            int val;                             
            struct semid_ds *buf;    
            unsigned short int *array; 
            struct seminfo *__buf;  
        };
    #endif     
    
    //信号的初始化
    static sem_initial(int semid){
        int ret;
        union semun semun;
        semun.val = 1;
        ret = semctl(semid,0,SETVAL,semun);
        if(ret == -1){
            fprintf(stderr, "semctl failed!\n");
        }
        return  ret;
    }
    
    //将p v操作封装成函数
    //p操作
    static int sem_p(int semid){
        int ret;
        struct sembuf sembuf;
        sembuf.sem_op = -1;//操作类型,设置为-1即p操作。
        sembuf.sem_num = 0;//指定信号量在信号量组中的编号
        sembuf.sem_flg = SEM_UNDO;//让操作系统跟踪信号,如果进程忘记释放,则操作系统进行负责释放。
        ret = semop(semid,&sembuf,1);//根据设置对信号量进行操作
        if (ret == -1) {
    		fprintf(stderr, "sem_p failed!\n");
    	}
        return ret;
    }
    //v操作
    static int sem_v(int semid){
        int ret;
        struct sembuf sembuf;
        sembuf.sem_op = 1;//操作类型,设置为1即v操作。
        sembuf.sem_num = 0;//指定信号量在信号量组中的编号
        sembuf.sem_flg = SEM_UNDO;//让操作系统跟踪信号,如果进程忘记释放,则操作系统进行负责释放。
        ret = semop(semid,&sembuf,1);//根据设置对信号量进行操作
        if (ret == -1) {
    		fprintf(stderr, "sem_v failed!\n");
    	}
        return ret;
    }
    
    int main(int argc, char* argv[]) {
        int semid;
    
        //获取信号
        semid =semget((key_t)1234,1,0666 | IPC_CREAT);
        if(semid == -1){
            printf("semget failed!\n");
    		exit(1);
        }
        //信号量的初始化
        if (argc > 1) {
    		int ret = sem_initial(semid);
    		if (ret == -1) {
    			exit(1);
    		}
    	}
        for(;;){
            if(sem_p(semid) == -1){//p操作,申请,若无可用资源,则挂起等待。
                exit(1);
            }
    
            /* 模拟临界区----begin */
    		printf("Process(%d) In\n", getpid());		
    		sleep(3);
    		printf("Process(%d) Out\n", getpid());
            /* 模拟临界区----end */ 
            if(sem_v(semid) == -1){//v操作,释放。
                exit(1);
            }
        }
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    image-20220824170933245

    可以看到,一个出来,另一个才可以进去。

  • 相关阅读:
    Oracle 数据库中 查询时如何使用日期(时间)作为查询条件
    [Python编程:从入门到实践] 变量&字符串
    4.从中缀向后缀转换表达式
    9.20总结
    python 之字典的相关知识
    Vue+element开发Simple Admin后端管理系统页面
    Windows环境下用python嵌入式环境跑程序可太方便了
    pta数据结构day9
    【leetcode】【初级算法】【链表3】反转链表
    【Java8新特性】- Stream流
  • 原文地址:https://blog.csdn.net/qq_51604330/article/details/126509224