Linux下进程间通讯-信号量(信号灯)

本文详细介绍了信号量的概念和特点,用于解决多进程对共享资源的访问问题。通过P操作(减法)和V操作(加法)实现进程间的同步,并展示了如何使用信号量API(如semget、semctl和semop)进行操作。同时,通过实例演示了信号量在抢糖果游戏和协调共享内存访问中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 1.概念(解决什么问题)
          信号量是用来协调多个进程对于共享资源(临界区资源)的访问
          类比刚才抢糖果的例子
     特点:
           特点1(核心特点) 信号量的值如果减少到0,你还要进行P操作(减法),会阻塞当前进程
                           如果信号量的值小于你P操作的值,会阻塞当前进程
           特点2: 信号量的值不可能为负数
           特点3: 信号量进行V操作不会阻塞当前进程
     查看信号量:
           ipcs -s
     删除信号量:
           ipcrm  -s 信号量的ID

   2.相关的接口函数
        (1)申请信号量--》申请积分卡
               #include <sys/sem.h>
               int semget(key_t key,int nsems,int semflg);
                    返回值:成功 返回信号量的ID
                            失败 -1
                      参数:nsems --》你打算申请多少个信号量(申请多少张积分卡)
                            semflg --》IPC_CREAT  IPC_EXCL  0777
        (2)设置/获取信号量的值,删除信号量
               int semctl(int semid,int semnum,int cmd,...);
                      参数:semid --》信号量的ID
                            semnum --》信号量的序号,序号从0开始 
                            cmd --》GETVAL    获取信号量的值
                                        函数的返回值就是信号量的当前值=semctl(ID号,0,GETVAL); //获取第一个信号量(序号为0)的值
                                    SETVAL    设置信号量的值
                                        semctl(ID号,0,SETVAL,你要设置的新值);
                                        semctl(ID号,0,SETVAL,5) //把第一个信号量(序号为0)的值设置为5
                                    IPC_RMID  删除信号量
                                        semctl(ID号,0,IPC_RMID)
        (3)信号量的加减法--》PV操作
               int semop(int semid,struct sembuf *sops,size_t nsops)
                      参数:semid --》信号量的ID
                            sops --》
                                struct sembuf
                                {
                                      unsigned short sem_num;  //信号量的序号
                                      short sem_op;            //决定对信号量进行何种操作
                                                               P操作(减法)  -1
                                                               V操作(加法)  +1
                                      short sem_flg;           //一般设置SEM_UNDO 对信号量做了PV操作改变了值以后,重新运行程序,信号量的值又会自动恢复成原来的值
                                }
                            nsops --》结构体变量的个数struct sembuf,一般写1
    3.总结信号量使用的套路
           进程1
           while(1)
           {
                p操作;
                共享资源的访问
                v操作;
           }
           进程2
           while(1)
           {
                p操作;
                共享资源的访问
                v操作;
           }

 进程的生老病死
================================
   1.进程的几种状态
        就绪态:进程已经运行了,但是还没有取得cpu的执行权
        执行态:cpu的执行权已经切换到当前进程,当前进程就处于执行态
        睡眠态/挂起态:进程调用sleep()  pause()函数
        暂停态:外界给进程发送了暂停信号
        僵尸态:进程结束的时候最开始处于僵尸态,如果父进程有回收子进程,那么子进程就会进入死亡态
                                                如果父进程没有回收子进程,那么子进程就永远处于僵尸态
        死亡态:

 演示信号量p操作的特点

#include "myhead.h"

//演示信号量的特点--》理解代码细节

int main()
{
	int semid;
	struct sembuf pbuf;
	bzero(&pbuf,sizeof(pbuf));
	pbuf.sem_num=0; //对序号为0的信号量进行操作
	pbuf.sem_op=-2; //P操作,减1
	pbuf.sem_flg=SEM_UNDO; 
	
	//申请积分卡(申请信号量)
	semid=semget(74541,1,IPC_CREAT|IPC_EXCL|0777);
	if(semid==-1)
	{
		if(errno==EEXIST)
			semid=semget(74541,1,0777);
		else
		{
			perror("申请积分卡失败!\n");
			return -1;
		}
	}
	
	//给信号量赋值为5
	semctl(semid,0,SETVAL,5);
	
	//打印当前信号量的值
	printf("当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	//对信号量进行P操作,做减法
	semop(semid,&pbuf,1);
	printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&pbuf,1);
	printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&pbuf,1);
	printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&pbuf,1);
	printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&pbuf,1);
	printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&pbuf,1);
	printf("P操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
}

演示信号量v操作的特点

#include "myhead.h"

//演示信号量的特点--》理解代码细节

int main()
{
	int semid;
	struct sembuf vbuf;
	bzero(&vbuf,sizeof(vbuf));
	vbuf.sem_num=0; //对序号为0的信号量进行操作
	vbuf.sem_op=+1; //V操作,加1
	vbuf.sem_flg=SEM_UNDO; 
	
	//申请积分卡(申请信号量)
	semid=semget(74541,1,IPC_CREAT|IPC_EXCL|0777);
	if(semid==-1)
	{
		if(errno==EEXIST)
			semid=semget(74541,1,0777);
		else
		{
			perror("申请积分卡失败!\n");
			return -1;
		}
	}
	
	//给信号量赋值为5
	semctl(semid,0,SETVAL,5);
	
	//打印当前信号量的值
	printf("当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	//对信号量进行P操作,做减法
	semop(semid,&vbuf,1);
	printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&vbuf,1);
	printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&vbuf,1);
	printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&vbuf,1);
	printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&vbuf,1);
	printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&vbuf,1);
	printf("V操作之后当前信号量的值是:%d\n",semctl(semid,0,GETVAL));
}

信号量的使用例子抢糖果

#include "myhead.h"

//我侄子

int main()
{
	int semid;
	
	//对第一个信号量进行p操作
	struct sembuf pbuf;
	bzero(&pbuf,sizeof(pbuf));
	pbuf.sem_num=0; //对序号为0的信号量进行操作
	pbuf.sem_op=-1; //P操作,减1
	pbuf.sem_flg=SEM_UNDO;
	
	//对第二个信号量进行v操作
	struct sembuf vbuf;
	bzero(&vbuf,sizeof(vbuf));
	vbuf.sem_num=1; //对序号为1的信号量进行操作
	vbuf.sem_op=+1; //V操作,加1
	vbuf.sem_flg=SEM_UNDO;
	
	//申请2张积分卡(申请2个信号量)
	semid=semget(74541,2,IPC_CREAT|IPC_EXCL|0777);
	if(semid==-1)
	{
		if(errno==EEXIST)
			semid=semget(74541,2,0777);
		else
		{
			perror("申请积分卡失败!\n");
			return -1;
		}
	}
	
	//给第一个信号量赋值为1
	semctl(semid,0,SETVAL,1);
	//给第二个信号量赋值为0
	semctl(semid,1,SETVAL,0);
	
	
	while(1)
	{
		//对第一个信号量进行p操作,不会阻塞当前进程
		semop(semid,&pbuf,1);
		printf("侄子抢到一个糖果了!\n");
		sleep(3);
		//对第二个信号量进行v操作,拯救p2让p2不会永远阻塞
		semop(semid,&vbuf,1);
	}
}
#include "myhead.h"

//我邻居小孩子

int main()
{
	int semid;
	//对第二个信号量进行p操作
	struct sembuf pbuf;
	bzero(&pbuf,sizeof(pbuf));
	pbuf.sem_num=1; //对序号为1的信号量进行操作
	pbuf.sem_op=-1; //P操作,减1
	pbuf.sem_flg=SEM_UNDO;
	
	//对第一个信号量进行v操作
	struct sembuf vbuf;
	bzero(&vbuf,sizeof(vbuf));
	vbuf.sem_num=0; //对序号为0的信号量进行操作
	vbuf.sem_op=+1; //V操作,加1
	vbuf.sem_flg=SEM_UNDO;
	
	//申请2张积分卡(申请2个信号量)
	semid=semget(74541,2,IPC_CREAT|IPC_EXCL|0777);
	if(semid==-1)
	{
		if(errno==EEXIST)
			semid=semget(74541,2,0777);
		else
		{
			perror("申请积分卡失败!\n");
			return -1;
		}
	}
	
	//给第一个信号量赋值为1
	//semctl(semid,0,SETVAL,1);
	//给第二个信号量赋值为0
	//semctl(semid,1,SETVAL,0);
	
	while(1)
	{
		//对第二个信号量进行P操作
		semop(semid,&pbuf,1);
		printf("邻居小孩子抢到一个糖果了!\n");
		sleep(1);
		//对第一个信号量进行v操作,拯救p1让p1不会永远阻塞
		semop(semid,&vbuf,1);
	}
}

信号量的使用协调两个进程之间对于共享内存的访问

#include "myhead.h"

int main()
{
	int shmid;
	int semid;
	
	//对第一个信号量进行p操作
	struct sembuf pbuf;
	bzero(&pbuf,sizeof(pbuf));
	pbuf.sem_num=0; //对序号为0的信号量进行操作
	pbuf.sem_op=-1; //P操作,减1
	pbuf.sem_flg=SEM_UNDO;
	
	//对第二个信号量进行v操作
	struct sembuf vbuf;
	bzero(&vbuf,sizeof(vbuf));
	vbuf.sem_num=1; //对序号为1的信号量进行操作
	vbuf.sem_op=+1; //V操作,加1
	vbuf.sem_flg=SEM_UNDO;
	
	//申请2张积分卡(申请2个信号量)
	semid=semget(74541,2,IPC_CREAT|IPC_EXCL|0777);
	if(semid==-1)
	{
		if(errno==EEXIST)
			semid=semget(74541,2,0777);
		else
		{
			perror("申请积分卡失败!\n");
			return -1;
		}
	}
	
	//给第一个信号量赋值为1
	semctl(semid,0,SETVAL,1);
	//给第二个信号量赋值为0
	semctl(semid,1,SETVAL,0);
	//申请共享内存
	shmid=shmget(54552,1024,IPC_CREAT|IPC_EXCL|0777);
	if(shmid==-1) //发生错误
	{
		//依据刚才讲解的错误码原理,进一步分析产生错误的原因
		if(errno==EEXIST)  //说明错误的原因是共享内存已经存在
		{
			shmid=shmget(54552,1024,0777); //打开共享内存
		}
		else //说明错误是其他类型的错误
		{
			perror("申请共享内存失败了!\n");
			return -1;
		}
		
	}
	
	//映射得到共享内存的首地址
	char *p=shmat(shmid,NULL,0);
	//p指向的就是你刚才申请得到的1024字节的内存首地址
	if(p==NULL)
	{
		perror("映射共享内存失败!\n");
		return -1;
	}
	
	//p1通过共享内存发送信息给p2
	while(1)
	{
		//对第一个信号量进行p操作,不会阻塞当前进程
		semop(semid,&pbuf,1);
		printf("请输入要发送给p2的信息!\n");
		scanf("%s",p); 
		//对第二个信号量进行v操作,拯救p2让p2不会永远阻塞
		semop(semid,&vbuf,1);
	}
}
#include "myhead.h"
// p2的代码
int main()
{
	int shmid;
	int semid;
	//对第二个信号量进行p操作
	struct sembuf pbuf;
	bzero(&pbuf,sizeof(pbuf));
	pbuf.sem_num=1; //对序号为1的信号量进行操作
	pbuf.sem_op=-1; //P操作,减1
	pbuf.sem_flg=SEM_UNDO;
	
	//对第一个信号量进行v操作
	struct sembuf vbuf;
	bzero(&vbuf,sizeof(vbuf));
	vbuf.sem_num=0; //对序号为0的信号量进行操作
	vbuf.sem_op=+1; //V操作,加1
	vbuf.sem_flg=SEM_UNDO;
	
	//申请2张积分卡(申请2个信号量)
	semid=semget(74541,2,IPC_CREAT|IPC_EXCL|0777);
	if(semid==-1)
	{
		if(errno==EEXIST)
			semid=semget(74541,2,0777);
		else
		{
			perror("申请积分卡失败!\n");
			return -1;
		}
	}
	
	//申请共享内存
	shmid=shmget(54552,1024,IPC_CREAT|IPC_EXCL|0777);
	if(shmid==-1) //发生错误
	{
		//依据刚才讲解的错误码原理,进一步分析产生错误的原因
		if(errno==EEXIST)  //说明错误的原因是共享内存已经存在
		{
			shmid=shmget(54552,1024,0777); //打开共享内存
		}
		else //说明错误是其他类型的错误
		{
			perror("申请共享内存失败了!\n");
			return -1;
		}
		
	}
	
	//映射得到共享内存的首地址
	char *p=shmat(shmid,NULL,0);
	//p指向的就是你刚才申请得到的1024字节的内存首地址
	if(p==NULL)
	{
		perror("映射共享内存失败!\n");
		return -1;
	}
	
	//从共享内存去访问p1存放的信息
	while(1)
	{
		//对第二个信号量进行P操作
		semop(semid,&pbuf,1);
		printf("p1发送的信息是:%s\n",p);
		//对第一个信号量进行v操作,拯救p1让p1不会永远阻塞
		semop(semid,&vbuf,1);
	}
	
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hqb_newfarmer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值