【Linux系统编程】Linux IPC 的使用

文章目录

Linux共享内存、消息队列、信号量 学习笔记

基于 SYSTEM-V IPC 通信的学习整理,涵盖共享内存、消息队列、信号量三大主题,并附对应可运行 C 代码示例。

一、SYSTEM-V IPC 概述

  • IPC 是 Inter-Process Communication(进程间通信)的英文缩写。
  • 共享内存、消息队列、信号量 三者合称为 SYSTEM-V IPC 通信。
  • 适用范围 :本部分学习的所有进程间通信方法只能用于同一台主机内部的不同进程;不同主机之间通信需要学习网络编程。

二、共享内存

2.1 原理与命令

  • 原理:多个进程申请同一块内存区域,用于通信。
  • 查看共享内存:ipcs -m
  • 删除共享内存:ipcrm -m 共享内存的id号

2.2 接口函数

(1) 申请共享内存

c 复制代码
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
  • 返回值:成功返回共享内存的 ID 号;失败返回 -1。
  • 参数:
    • key:键值。相同的键值可以确保申请的共享内存相同。
      • 键值生成两种写法:
        • 写法一:任意一个无符号整数。

        • 写法二:使用 ftok() 函数生成。

          c 复制代码
          #include <sys/types.h>
          #include <sys/ipc.h>
          key_t ftok(const char *pathname, int proj_id);

          ftok 内部有算法,依据路径和整数计算得到唯一的键值;路径或整数有一个不同,计算得到的键值也不同。返回值即计算得到的键值。

    • size:要申请的共享内存大小,一般要求为 512 字节(块大小)的整数倍。
    • shmflgIPC_CREATIPC_EXCL0777 等权限标志。

(2) 映射得到共享内存的首地址(mmap)

c 复制代码
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 返回值:成功返回首地址;失败返回 NULL

  • 参数:

    • shmid:共享内存的 ID 号。
    • shmaddr:一般设置为 NULL,表示由操作系统分配共享内存的首地址。
    • shmflg:设置为 0,表示可读写。
  • 通过首地址结合 C 语言指针操作实现存取数据,例如:

    c 复制代码
    char *p = shmat(shmid, NULL, 0);
    strcpy(p, "hello");

(3) 解除映射(munmap)

c 复制代码
int shmdt(const void *shmaddr);
  • 参数:shmaddr ------ 共享内存的首地址。

(4) 删除共享内存、设置/获取属性(多功能函数)

c 复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 参数:

    • shmid:共享内存的 ID。

    • cmdIPC_STAT(获取属性)、IPC_SET(设置属性)、IPC_RMID(删除共享内存,此时不需要第三个参数)。

    • buf:存放获取或设置的属性信息,结构体 struct shmid_ds 至少包含:

      c 复制代码
      struct shmid_ds {
          struct ipc_perm shm_perm; // 权限
          size_t          shm_segsz; // 大小
      };
      struct ipc_perm {
          key_t           __key;   // 共享内存的键值
          unsigned short  mode;    // 共享内存的权限
      };
  • 总结:

    • 设置共享内存属性只能设置权限(且设置一次后,下次修改权限必须删除共享内存重新申请);大小无法设置。
    • 共享内存所有者若属于 root,必须用 root 身份执行删除代码才能删除。

2.3 示例代码

使用ftok生成键值的写法.c
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>

/*
	ftok产生键值
*/
int main()
{
	key_t mykey=ftok("/home/user",4);
	printf("生成的键值是: %d\n",mykey);
	
	mykey=ftok("/home/user",4);
	printf("生成的键值是: %d\n",mykey);
	
	mykey=ftok("/home/user",5);
	printf("生成的键值是: %d\n",mykey);
	
	mykey=ftok("/home",4);
	printf("生成的键值是: %d\n",mykey);

	return 0;
}
shmctl获取共享内存的属性.c
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>

/*
	shmctl获取共享内存的属性
*/
int main()
{
	char *p;
	int shmid;

	//第一步:申请/打开共享内存
	shmid=shmget(4557,1024,IPC_CREAT|IPC_EXCL|0777);
	if(shmid==-1)
	{
		if(errno==EEXIST) //共享内存已经申请过了
		{
			//打开共享内存
			shmid=shmget(4557,1024,0777);
		}
		else
		{
			perror("申请共享内存失败了!\n");
			return -1;
		}
		
	}
	
	//第二步:通过ID号映射得到共享内存的首地址
	p=shmat(shmid,NULL,0);
	if(p==NULL)
	{
		perror("映射共享内存失败了!\n");
	}
	
	//获取当前申请的这个共享内存的属性信息
	struct shmid_ds myds;
	shmctl(shmid,IPC_STAT,&myds);
	printf("共享内存的大小是: %lu\n",myds.shm_segsz);
	printf("共享内存的键值是: %d\n",myds.shm_perm.__key);
	printf("共享内存的键值是: %#o\n",myds.shm_perm.mode);
	return 0;
}
shmctl设置共享内存的属性.c
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>

/*
	shmctl设置共享内存的属性
*/
int main()
{
	char *p;
	int shmid;

	//第一步:申请/打开共享内存
	shmid=shmget(4557,1024,IPC_CREAT|IPC_EXCL|0777);
	if(shmid==-1)
	{
		if(errno==EEXIST) //共享内存已经申请过了
		{
			//打开共享内存
			shmid=shmget(4557,1024,0777);
		}
		else
		{
			perror("申请共享内存失败了!\n");
			return -1;
		}
		
	}
	
	//第二步:通过ID号映射得到共享内存的首地址
	p=shmat(shmid,NULL,0);
	if(p==NULL)
	{
		perror("映射共享内存失败了!\n");
	}
	
	//改变/设置当前申请的这个共享内存的属性信息
	struct shmid_ds myds;
	myds.shm_perm.mode=0777; //修改权限,可以修改
	myds.shm_segsz=2048; //修改大小,无法修改
	myds.shm_perm.__key=7894; //修改键值,无法修改
	shmctl(shmid,IPC_SET,&myds);
	
	return 0;
}
共享内存实现进程通信p1.c
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

/*
	p1键盘输入字符串,通过共享内存给到p2
*/
char *p;
int shmid;
void fun(int sig)
{
	printf("执行了删除代码!\n");
	//第三步:解除映射
	shmdt(p);
	
	//第四步:删除共享内存
	shmctl(shmid,IPC_RMID,NULL);
	exit(0); //结束进程
}
int main()
{
	//第一步:申请共享内存
	/*
		参数1:键值,两种写法
		       写法1:程序员自己随便指定一个整数
	    参数2:共享内存大小,512的整数倍
		参数3:共享内存的权限
		       IPC_CREAT  //新建、新申请共享内存
			   IPC_EXCL   //如果共享内存已经申请过,再次申请就失败退出
			   0777 
		共享内存已经申请过,不能再次重新申请,只能打开
	*/
	
	signal(SIGINT,fun);
	
	shmid=shmget(4557,1024,IPC_CREAT|IPC_EXCL|0777);
	if(shmid==-1)
	{
		if(errno==EEXIST) //共享内存已经申请过了
		{
			//打开共享内存
			shmid=shmget(4557,1024,0777);
		}
		else
		{
			perror("申请共享内存失败了!\n");
			return -1;
		}
		
	}
	printf("申请的共享内存ID是: %d\n",shmid);
	
	//第二步:通过ID号映射得到共享内存的首地址
	p=shmat(shmid,NULL,0);
	if(p==NULL)
	{
		perror("映射共享内存失败了!\n");
	}
	
	//利用首地址来存放、取出数据
	while(1)
	{
		printf("请输入要发送给p2的信息!\n");
		scanf("%s",p);
	}
	return 0;
}
共享内存实现进程通信p2.c
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>

int main()
{
	//第一步:申请共享内存
	int shmid;  
	shmid=shmget(4557,1024,IPC_CREAT|IPC_EXCL|0777);
	if(shmid==-1)
	{
		if(errno==EEXIST) //共享内存已经申请过了
		{
			//打开共享内存
			shmid=shmget(4557,1024,0777);
		}
		else
		{
			perror("申请共享内存失败了!\n");
			return -1;
		}
		
	}
	
	//第二步:通过ID号映射得到共享内存的首地址
	char *p=shmat(shmid,NULL,0);
	if(p==NULL)
	{
		perror("映射共享内存失败了!\n");
	}
	
	//从刚才的首地址中去获取内容
	while(1)
	{
		printf("共享内存里面存放的是: %s\n",p);
	}
	
}

三、消息队列

3.1 原理与特点

  • 原理/特点:通信双方可以选择性地接收信息(通过所谓的消息类型区分不同消息);如果接收的消息类型不存在,会阻塞;如果消息队列中有多条类型相同的消息,接收时只能按发送先后顺序接收(队列 FIFO 特点)。
  • 查看消息队列:ipcs -q
  • 删除消息队列:ipcrm -q 消息队列的ID

3.2 接口函数

(1) 申请消息队列

c 复制代码
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
  • 返回值:成功返回消息队列的 ID;失败返回 -1。
  • 参数:
    • key:键值。
    • msgflgIPC_CREATIPC_EXCL0777

(2) 使用消息队列收发信息

c 复制代码
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • 返回值:成功返回 0;失败返回 -1。

  • 参数:

    • msqid:消息队列 ID。
    • msgp:存放要发送的内容,结构体变量取地址。
    • msgsz:要发送的字节数(结构体里面数组总的大小,不能超过数组大小)。
    • msgflg:默认设置为 0。
  • 要发送的信息需打包成结构体,至少包含两个成员:

    c 复制代码
    struct mymsg {  //顺序不要颠倒,类型在前面,数据在后面
        long   mtype;     //消息的类型--》必须放在最前面,而且是long类型
        char   mtext[10]; //存放真实的信息
    };
    struct mymsg msg1;
    • 有问题的写法(实际数组小于传参的数组大小,导致内存越界读取,可能引起段错误):msgsnd(msqid,&msg1,24,0);
    • 没问题的写法:msgsnd(msqid,&msg1,10,0);
c 复制代码
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • 返回值:成功返回接收的字节数;失败返回 -1。
  • 参数:
    • msgtyp(重点):要接收的消息类型,用来区分不同的消息。
    • msgsz:结构体里面数组总的大小。

(3) 删除消息队列、设置/获取属性

c 复制代码
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 参数:buf ------ 获取/设置消息队列属性才需要这个结构体。

练习思考

  • 如果要接收消息类型为 666,能接收吗?(队列中没有该类型消息,会阻塞)
  • 若发送的三条消息类型都是 456,接收 456 会怎样?(按 FIFO 顺序逐条接收)

3.3 示例代码

消息队列p1.c
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <errno.h>

/*
	p1通过消息队列发送三条信息给p2
*/
//自定义一个结构体,存放你要发送的信息和信息类型
struct mymsg
{
	//消息类型
	long type;
	//消息内容
	char mbuf[100]; 
};

int main()
{
	int msgid;
	key_t mykey=ftok("/home",4);
	//申请/打开消息队列
	msgid=msgget(mykey,IPC_CREAT|IPC_EXCL|0777);
	if(msgid==-1)
	{
		if(errno==EEXIST) //存在
		{
			msgid=msgget(mykey,0777);
		}
		else
		{
			perror("申请/打开消息队列失败!\n");
			return -1;
		}
	}
	
	//利用消息队列发送、接收信息
	struct mymsg msg1;
	bzero(&msg1,sizeof(msg1));
	msg1.type=458;
	strcpy(msg1.mbuf,"hello");
	msgsnd(msgid,&msg1,sizeof(msg1),0);
	
	struct mymsg msg2;
	bzero(&msg2,sizeof(msg2));
	msg2.type=965;
	strcpy(msg2.mbuf,"你好");
	msgsnd(msgid,&msg2,sizeof(msg2),0);
	
	struct mymsg msg3;
	bzero(&msg3,sizeof(msg3));
	msg3.type=147;
	strcpy(msg3.mbuf,"China");
	msgsnd(msgid,&msg3,sizeof(msg3),0);
	
}
消息队列p2.c
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <errno.h>

/*
	p2通过消息队列接收p1发送的三条信息
*/
//自定义一个结构体,存放你要发送的信息和信息类型
struct mymsg
{
	//消息类型
	long type;
	//消息内容
	char mbuf[100]; 
};

int main()
{
	int msgid;
	char buf[20];
	key_t mykey=ftok("/home",4);
	//申请/打开消息队列
	msgid=msgget(mykey,IPC_CREAT|IPC_EXCL|0777);
	if(msgid==-1)
	{
		if(errno==EEXIST) //存在
		{
			msgid=msgget(mykey,0777);
		}
		else
		{
			perror("申请/打开消息队列失败!\n");
			return -1;
		}
	}
	
	//接收信息
	//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
	struct mymsg rmsg;
	bzero(&rmsg,sizeof(rmsg));
	msgrcv(msgid,&rmsg,sizeof(rmsg),965,0);
	printf("我收到的信息是: %s\n",rmsg.mbuf);
	
	bzero(&rmsg,sizeof(rmsg));
	msgrcv(msgid,&rmsg,sizeof(rmsg),147,0);
	printf("我收到的信息是: %s\n",rmsg.mbuf);
	
	bzero(&rmsg,sizeof(rmsg));
	msgrcv(msgid,&rmsg,sizeof(rmsg),458,0);
	printf("我收到的信息是: %s\n",rmsg.mbuf);
}

四、信号量

4.1 作用与特点

  • 作用:协调多个进程对于共享资源(临界区资源)的访问。进程 1 访问共享资源时,其它进程不允许访问(阻塞别的进程),确保任意时刻只有一个进程在访问共享资源(类比腾讯文档多人协作的"独占")。
  • 查看信号量:ipcs -s
  • 删除信号量:ipcrm -s 信号量的ID
  • 特点
    • 信号量的值不能为负数。
    • 信号量的值如果变成 0,还要进行 P 操作(减少的数量 > 拥有的数量)会阻塞当前进程。
    • V 操作不会阻塞进程。

4.2 接口函数

(1) 申请信号量

c 复制代码
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
  • 返回值:成功返回信号量的 ID;失败返回 -1。
  • 参数:
    • key:键值。
    • nsems:打算申请多少个信号量。
    • semflgIPC_CREATIPC_EXCL0777

(2) 使用信号量

信号量通过加减法改变信号量的值(加法操作 = V 操作[释放资源],减法操作 = P 操作[申请资源])。

c 复制代码
int semop(int semid, struct sembuf *sops, size_t nsops);
  • 参数:

    • sops:结构体指针

      c 复制代码
      struct sembuf {
          unsigned short sem_num; // 要操作的信号量序号,从0开始
          short           sem_op; // 操作类型:负数=减法(P操作),正数=加法(V操作)
          short           sem_flg;// SEM_UNDO:PV操作以后,下次运行信号量重新还原成原来的值
      };
    • nsops:结构体的数量。

    "正数(V 操作)释放资源,负数( P操作)申请资源" 如何理解?

    要理解这个,需要先记住信号量值的数学含义信号量的值(semval)代表当前"可用资源"的剩余数量。

    semop 执行的操作本质就是:新的信号量值 = 当前信号量值 + sem_op

    基于这个公式,理解如下:

    • 负数(申请资源 / P操作)
      • 你想要消耗 资源,所以从可用数量里减去
      • 例如 sem_op = -1:内核尝试 semval - 1。如果 semval 大于 0,说明有资源,减去后进程继续执行。如果 semval 等于 0,说明资源耗尽,进程会被阻塞(除非设置了 IPC_NOWAIT),等待别人释放资源。
      • 生活中的例子 :停车场剩余车位是 10 个。一辆车要进去(申请资源),剩余车位变为 9(10 + (-1) = 9)。
    • 正数(释放资源 / V操作)
      • 你使用完了资源,要归还 ,所以把可用数量回来。
      • 例如 sem_op = +1:内核执行 semval + 1。释放后,如果有其他进程因为等待该资源而被阻塞,内核会唤醒它们。
      • 生活中的例子 :停车场里开走了一辆车(释放资源),剩余车位从 9 变为 10(9 + (+1) = 10)。

    特别提醒 :这里有一个很容易混淆的地方------sem_op 的数值并不限制于 ±1

    • 你可以写 sem_op = -5,表示一次性申请 5 个资源(前提是剩余数量必须 >= 5)。
    • 同理,sem_op = +2 表示一次性归还 2 个资源。

    记忆口诀"减号是拿走(Wait),加号是归还(Signal)"。记住"信号量的值 = 库存",拿货就是负,补货就是正。

(3) 给信号量赋值、获取值、删除信号量

c 复制代码
int semctl(int semid, int semnum, int cmd, ...);  //变参函数
  • 参数:
    • semnum:信号量序号(从 0 开始)。如申请三个信号量,序号分别是 0、1、2。
    • cmd
      • GETVAL:获取信号量的值,如 semctl(semid,0,GETVAL) 获取第一个信号量的值。
      • SETVAL:设置信号量的值,如 semctl(semid,0,SETVAL,你想设置的值)
      • IPC_RMID:删除信号量,如 semctl(semid,0,IPC_RMID)

4.3 协调多进程访问临界资源的套路

核心思想:阻塞别人,成就自己

c 复制代码
// 假设这个进程先运行
while(1)
{
    semop(信号量1 P操作,先不要阻塞);
    共享资源
    semop(信号量2 V操作,拯救另外一个进程);
}

// 假设这个进程后运行
while(1)
{
    semop(信号量2 P操作,阻塞);
    共享资源
    semop(信号量1 V操作,拯救另外一个进程);
}

4.4 示例代码

信号量的使用.c
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>

/*
	信号量类比积分卡
	展示信号量的特点;
	    特点1:信号量(积分卡)的值不能是负数,只能是>=0的数
		特点2:信号量如果值减少为0,就不能在减了,再减就会阻塞当前进程
		      人类的语言习惯
				  信号量做加法:把信号量的值加某个整数
				  信号量做减法:把信号量的值减某个整数
			  行业黑话 
			      信号量做加法-->V操作
				  信号量做减法-->P操作
*/

int main()
{
	int semid;
	struct sembuf sem1;
	bzero(&sem1,sizeof(sem1));
	sem1.sem_num=0;   //我要操作第一个信号量
	sem1.sem_op=-1;   //P操作,减1
	sem1.sem_flg=SEM_UNDO; //PV操作之后,还原值
	
	//第一步:申请一个信号量
	semid=semget(4789,1,IPC_CREAT|IPC_EXCL|0777);
	if(semid==-1)
	{
		if(errno==EEXIST)
		{
			semid=semget(4789,1,0777);
		}
		else
		{
			perror("申请/打开信号量失败了!\n");
			return -1;
		}
	}
	
	//第二步:给刚才申请的信号量设置值 
	semctl(semid,0,SETVAL,5); //给序号是0(第一个信号量)的信号量设置值为5
	
	//验证信号量的值是否设置成功,是否是5
	printf("当前积分卡(信号量)积分数量是: %d\n",semctl(semid,0,GETVAL));
	
	//我给刚才的信号量做减法(P操作) ,若减到0后会阻塞
	semop(semid,&sem1,1);
	printf("当前积分卡(信号量)积分数量是: %d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&sem1,1);
	printf("当前积分卡(信号量)积分数量是: %d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&sem1,1);
	printf("当前积分卡(信号量)积分数量是: %d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&sem1,1);
	printf("当前积分卡(信号量)积分数量是: %d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&sem1,1);
	printf("当前积分卡(信号量)积分数量是: %d\n",semctl(semid,0,GETVAL));
	
	semop(semid,&sem1,1);
	printf("当前积分卡(信号量)积分数量是: %d\n",semctl(semid,0,GETVAL));
}
共享内存实现进程通信p1.c(信号量同步版)
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

/*
	p1还没有从键盘输入,p2开始死循环打印--》不合理
	用信号量来解决
	解决思路:
	    第一步:需要两个信号量
*/
char *p;
int shmid;
void fun(int sig)
{
	printf("执行了删除代码!\n");
	//第三步:解除映射
	shmdt(p);
	
	//第四步:删除共享内存
	shmctl(shmid,IPC_RMID,NULL);
	exit(0); //结束进程
}
int main()
{
	//第一步:申请共享内存
	/*
		参数1:键值,两种写法
		       写法1:程序员自己随便指定一个整数
	    参数2:共享内存大小,512的整数倍
		参数3:共享内存的权限
		       IPC_CREAT  //新建、新申请共享内存
			   IPC_EXCL   //如果共享内存已经申请过,再次申请就失败退出
			   0777 
		共享内存已经申请过,不能再次重新申请,只能打开
	*/
	
	signal(SIGINT,fun);
	
	int semid;
	struct sembuf sem1;
	bzero(&sem1,sizeof(sem1));
	sem1.sem_num=1;   //我要操作第二个信号量
	sem1.sem_op=1;   //V操作,加1
	sem1.sem_flg=SEM_UNDO; //PV操作之后,还原值
	
	struct sembuf sem2;
	bzero(&sem2,sizeof(sem2));
	sem2.sem_num=0;   //我要操作第一个信号量
	sem2.sem_op=-1;   //P操作,减1
	sem2.sem_flg=SEM_UNDO; //PV操作之后,还原值
	
	//第一步:申请两个信号量
	semid=semget(4789,2,IPC_CREAT|IPC_EXCL|0777);
	if(semid==-1)
	{
		if(errno==EEXIST)
		{
			semid=semget(4789,2,0777);
		}
		else
		{
			perror("申请/打开信号量失败了!\n");
			return -1;
		}
	}
	
	//第二步:给刚才申请的两个信号量设置值 
	semctl(semid,0,SETVAL,1); //给序号是0(第一个信号量)的信号量设置值为1
	semctl(semid,1,SETVAL,0); //给序号是1(第二个信号量)的信号量设置值为0
	
	shmid=shmget(4557,1024,IPC_CREAT|IPC_EXCL|0777);
	if(shmid==-1)
	{
		if(errno==EEXIST) //共享内存已经申请过了
		{
			//打开共享内存
			shmid=shmget(4557,1024,0777);
		}
		else
		{
			perror("申请共享内存失败了!\n");
			return -1;
		}
		
	}
	printf("申请的共享内存ID是: %d\n",shmid);
	
	//第二步:通过ID号映射得到共享内存的首地址
	p=shmat(shmid,NULL,0);
	if(p==NULL)
	{
		perror("映射共享内存失败了!\n");
	}
	
	//利用首地址来存放、取出数据
	while(1)
	{
		//对第一个信号量p操作
		semop(semid,&sem2,1);
		printf("请输入要发送给p2的信息!\n");
		scanf("%s",p);
		//拯救p2,p2一直阻塞在对第二个信号量p操作那里了,对第二个信号量进行V操作
		semop(semid,&sem1,1);
	}
	return 0;
}
共享内存实现进程通信p2.c(信号量同步版)
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>

int main()
{
	int semid;
	struct sembuf sem1;
	bzero(&sem1,sizeof(sem1));
	sem1.sem_num=1;   //我要操作第二个信号量
	sem1.sem_op=-1;   //P操作,减1
	sem1.sem_flg=SEM_UNDO; //PV操作之后,还原值
	
	struct sembuf sem2;
	bzero(&sem2,sizeof(sem2));
	sem2.sem_num=0;   //我要操作第一个信号量
	sem2.sem_op=1;   //V操作,加1
	sem2.sem_flg=SEM_UNDO; //PV操作之后,还原值
	
	//第一步:申请两个信号量
	semid=semget(4789,2,IPC_CREAT|IPC_EXCL|0777);
	if(semid==-1)
	{
		if(errno==EEXIST)
		{
			semid=semget(4789,2,0777);
		}
		else
		{
			perror("申请/打开信号量失败了!\n");
			return -1;
		}
	}
	
	
	//第一步:申请共享内存
	int shmid;  
	shmid=shmget(4557,1024,IPC_CREAT|IPC_EXCL|0777);
	if(shmid==-1)
	{
		if(errno==EEXIST) //共享内存已经申请过了
		{
			//打开共享内存
			shmid=shmget(4557,1024,0777);
		}
		else
		{
			perror("申请共享内存失败了!\n");
			return -1;
		}
		
	}
	
	//第二步:通过ID号映射得到共享内存的首地址
	char *p=shmat(shmid,NULL,0);
	if(p==NULL)
	{
		perror("映射共享内存失败了!\n");
	}
	
	//从刚才的首地址中去获取内容
	while(1)
	{
		//阻塞进程p2(让p2对第二个信号进行p操作),让它先不要打印
		semop(semid,&sem1,1);
		printf("共享内存里面存放的是: %s\n",p);
		//对第一个信号量v操作,拯救p1
		semop(semid,&sem2,1);
	}
	
}
相关推荐
Shadow(⊙o⊙)20 分钟前
HTTP——服务端、客户端格式,浏览器解析
网络·tcp/ip·http
DOLA_Tech28 分钟前
WIN电脑中VMware虚拟机网速慢的解决办法
运维·服务器
En^_^Joy1 小时前
Nginx 从入门到精通:配置、原理与实战详解
运维·nginx
玖石书1 小时前
WSL2 手动安装 Ubuntu 24.04 到指定磁盘位置
linux·运维·ubuntu·wsl
库玛西2 小时前
深入浅出传输层:UDP 与 TCP 协议全景指南
linux·服务器·网络·c++·笔记·tcp/ip·udp
随便做点啥2 小时前
32卡-64G-910B4-16后端-(Qwen3.8-27B-W8A8)集群部署报告
运维·服务器·经验分享·docker·vllm
薛定谔的悦6 小时前
储能 EMS 的功率策略:从一块电表到一次逆流的 200 毫秒
大数据·linux·能源·储能·bms
2601_962218478 小时前
万象生鲜系统区块链溯源技术帮助生鲜企业搭建食品安全数字化体系
大数据·运维·微服务·云原生·架构
智塑未来9 小时前
中小公司在线文档选型:从协作入口到安全边界
运维·安全