微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

进程间的通信-信号量

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

什么是信号量

  • 问题:
    • 在程序中,有时会存在一种特殊代码,同一时间只允许一个进程执行该部分代码。这部分区域,被称为"临界区"
    • 然后在多进程并发执行中,当一个进程进入临界区,因某种原因被挂起时,其他进程就有可能也进入该区域。
  • 解决办法:——使用信号量
  • 什么是信号量?
  • 信号量是一种特殊的变量。
  • 我们只能对信号量执行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,使操作系统跟踪信号,
                       // 并在进程没有释放该信号量而终止时,操作系统释放信号量         
         }        
      
    • 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;
        }
        
      • 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     
        
  • 返回值:略,详见-semctl(2) — Linux manual page

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


示例

  • 示例1:不使用信号量,并发执行多个程序,观察对临界区的访问。
#include <stdlib.h>
#include <stdio.h>

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;
}

image-20220824164003543

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


  • 示例2:使用信号量,并发指定多个进程,观察对临界区的访问。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <stdio.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     

//信号的初始化
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;
}

image-20220824170933245

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

原文地址:https://www.jb51.cc/wenti/3281995.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐