【linux应用开发】进程通信总结——使用管道、消息队列、共享内存、信号量实现l进程通信的详细教程

2024-01-07 19:52:43

简介

进程间通信(IPC, Inter-Process Communication)是指在操作系统中,不同进程之间交换数据、信息和命令的过程。在一个多任务的操作系统中,多个进程可以同时运行,但是这些进程是相互独立的,它们有自己的地址空间和上下文,无法直接访问对方的内存空间。如果多个进程需要协作来完成某项任务,或者需要共享某些数据,就需要使用进程间通信机制来进行通信和协作。

进程间通信是实现多个进程之间协同工作的必要手段,实现进程通信的作用:

  • 实现进程间数据共享:进程间通信可以使不同进程之间共享数据,避免了数据复制
    的开销,提高了系统的性能。
  • 提高系统的可靠性:进程间通信可以将多个进程组织成一个整体,使得它们可以协
    同工作,提高了系统的可靠性
  • 实现进程间协作:进程间通信可以使进程之间相互协作,共同完成任务。例如,一
    个进程可以向另一个进程发送请求,请求另一个进程提供某些服务,如打印文件等。管道、消
    息队列、信号量、共享内存等机制都可以用来实现进程间协作。
  • 提高系统的安全性:进程间通信可以实现不同进程之间的数据隔离和保护,从而提
    高了系统的安全性。

根据 IPC 机制所依赖的资源类型可以划分为基于系统资源的 IPC 机制和基于文件系统的 IPC机制。对于各种具体的实现方式总结如下:
在这里插入图片描述


无名管道

无名管道是一种单向的字节流的通信管道,可以在进程之间传递数据。事实上无名管道是一种特殊的文件,存在于内存中,无名管道使用系统调用pipe()会创建两个文件描述符,一个用于读取数据,另一个用于写入数据,其特点为:

  • 单向传输:无名管道是单向的,数据只能从管道的一端进入,从另一端出去。
  • 字节流传输:管道中的数据是以字节流的形式传输的,没有消息边界。
  • 亲缘关系:只有具有亲缘关系的进程才能使用无名管道进行通信。
  • 阻塞式读写:管道的读写操作是阻塞式的,即如果管道为空(写端没有写入数据),读操作会一直阻塞等待,直到有数据写入为止。

函数编写时所需要的头文件有

#include <unistd.h>
#include <stdio.h>

无名管道的创建需要使用系统调用pipe(),函数原型为:

int pipe(int pipefd[2]);

该函数有一个 int 类型的参数 pipefd,是一个有两个元素的数组,分别代表管道的读端和写端。其中,pipefd[0]代表管道的读端,pipefd[1]代表管道的写端。调用 pipe()函数后,系统会自动创建一个无名管道,并将读端和写端返回给调用进程。如果成功创建管道,返回 0;否则返回-1,表示创建失败。

示例程序中实现:

1.使用pipe()函数创建无名管道;
2.使用fork()函数创建父子进程,其中父进程完成从无名管道中读取数据,而子进程实现每隔一秒向无名管道中写入数据。

#include <unistd.h>
#include <stdio.h>

int main(void)
{
	int fd[2];
	/* 创建无名管道 */
	int ret = pipe(fd);
	if(ret == 0) printf("creating pipe is successful!\n");
	printf("%d  %d\n",fd[0],fd[1]);
	char buff[32];

	/* 创建父子进程 */
	pid_t pid = fork();

	/* 区分父子进程 */
	if(pid < 0)
		printf("creating subprocess is failed!\n");
	else if(pid == 0) // 子进程
	{
		int i = 0;
		while(1){
			sprintf(buff,"child data is %d",i++);
			// 向无名管道中写入数据
			write(fd[1],buff,strlen(buff));
			sleep(1);
		}
	}
	else{ // 父进程
		while(1)
		{
			// 读取无名管道中数据
			read(fd[0],buff,sizeof(buff));
			if(strlen(buff) != 0)
				printf("buff: %s\n",buff);
			buff[0] = '0';
		}
	}
return 0;
}

先使用 gcc progress.c -o target(test.c为程序所在.c文件名),后使用命令./target执行,其结果为:
在这里插入图片描述

示例程序中父子进程的写入读取逻辑如图:

在这里插入图片描述

若想要实现父子进程相互写入读取数据,可创建两个无名管道,一个管道实现父进程读取数据,子进程写入数据;另一个进程反之,即可。


有名管道

有名管道是一种特殊类型的 Unix/Linux 文件,也被称为 FIFO(First-In-First-Out)管道,用来在进程之间传输数据的,与匿名管道不同,有名管道是通过文件系统路径命名的管道,可以在进程之间进行通信。有名管道的操作方式类似于打开文件,即进程可以打开有名管道来读取或写入其中的数据。

有名管道使用函数mkfifo()创建,创建时需要包含头文件 #include <sys/type.h>#incldue<sys/stat.h> ,其函数原型为:

int mkfifo(const char *pathname, mode_t mode);

函数参数1表示所在路径以及名字,参数2表示创建时的权限;其函数函数返回值表示是否创建成功,若返回-1,则表示创建失败;否则,表示创建成功。

示例程序:

1.在程序1中使用函数mkfifo()创建有名管道,再通过write()向管道中写入数据;
2.在程序使用函数access()判断管道文件是否创建,若不存在则给出提示信息;若存在则读取管道管道中的数据;
(注:函数access()有俩参数,参数1表示文件路径,参数2可选择检查文件是否存在、可读或可写,使用时需要包含头文件#include <unistd.h>)

  1. 向管道中read数据
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
        char path[] = "/home/zxj/workplace/my_fifo";
        if(access(path,F_OK) == -1)
                printf("path is error\n");
        /* 打开管道文件 */
        int fd = open(path,O_RDWR);
        /* 向管道中read数据 */
        while(1)
        {
                char buff[32];
                read(fd,buff,sizeof(buff));
                printf("read buff:%s\n",buff);
                memset(buff,0,sizeof(buff));
        }
        return 0;
}

  1. 向管道中write数据
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
	char path[] = "/home/zxj/workplace/my_fifo";
	/* 创建有名管道 */

	int ret = mkfifo (path, 0644);
	if(ret == -1)
	{
		printf("creating is failed!\n");
		exit(-1);
	}

	/* 打开管道文件 */
	int fd = open(path,O_WRONLY);
	if(fd == -1) printf("opening is failed!\n");
	/* 向管道中写入数据 */
	int i = 0;;
	while(++i)
	{
		char buff[32];
		sprintf(buff,"input data:%d",i);
		int wret = write(fd,buff,strlen(buff));
		printf("buff:%s   size:%d\n",buff,wret);
		sleep(1);
	}	
	return 0;
}

执行结果如下:
在这里插入图片描述

在输入命令ls -al | grep my_fifo后可见上述程序创建的有名管道文件类型为p,学过linux文件类型的小伙伴不难看出字母p就表示管道文件。

在这里插入图片描述


IPC key标识

IPC key 是一个唯一的长整型数值,可以由任意一个进程指定,是为了方便不同进程之间对 IPC 对象的访问和操作而产生的。IPC key 和 IPC 对象与文件描述符和文件之间的关系相似,每个 IPC 对象都会有一个唯一的 IPC key,当一个进程创建或获取一个 IPC key 时,就可以使用该 IPC key 来创建或连接相应的 IPC 对象,并对其进行访问。

为了避免 IPC key 重复,Linux 提供了一个名为 ftok() 的函数来将一个普通的文件路径和一个整数值转换为一个唯一的 IPC key,使用该函数时需要包含头文件#icnlude <sys/types.h> 与 #include <sys/ipc.h>,具体函数原型为:

key_t ftok(const char *pathname, int proj_id);

该函数接受两个参数:文件路径和一个整数值。它会根据文件的 inode 节点号和整数值来生成一个唯一的 IPC key。由于不同的文件具有不同的 inode 节点号,因此不同的文件路径和整数值组合会生成不同的 IPC key值。

消息队列

消息队列是一种先进先出的消息缓冲区,用于在多个进程之间传递消息。在 Linux 中,每个消息队列都由一个唯一的消息队列标识符(Message QueueIdentifier)进行标识。进程可以通过该标识符来连接和访问相应的消息队列,从而进行进程间通信。

在linux中消息队列的使用步骤如下:

1.创建或者获取IPC key值
IPC key 可以通过调用ftok()函数来创建,或直接传入一个已知的IPC key值。(创建IPC key值见上文)

2.打开或创建消息队列

要打开或创建消息队列,需要使用函数msgget(),函数原型为:

int msgget(key_t key, int msgflg);
  • key:消息队列关键字,即使用 ftok()函数生成的 IPC key 值;
  • msgflag:标志位参数,用于指定创建或获取消息队列的方式和权限控制,可以用|符号组合多个标志位。

函数调用成功,会返回一个非负整数表示消息队列的标识符(即消息队列的唯一标识),调用失败返回-1,并设置 errno。
函数参数含义为:

3.发送消息
在消息队列中要发送消息,需要使用msgsnd函数,函数原型如下所示:

int msgsnd(int msqid, const void *msgp, size_t msgsz,int msgflg);

函数参数的解释为:

  • msqid:消息队列的标识符。
  • msgp:指向要发送的消息的指针。
  • msgsz:要发送的消息的大小,不能超过消息队列的最大容量。
  • msgflg:用于指定函数的操作方式和阻塞行为。可以使用多个标志位,用按位或运算符 | 进行组合。以下是可能的标志位:
    IPC_NOWAIT:如果消息队列已满或无法立即接收消息,则不阻塞进程,而是立即返回 -1 并设置 errno 为 EAGAIN。
    MSG_NOERROR:如果消息大小超过了消息队列的最大容量,将截断消息而不是返回错误。

函数调用成功返回 0。函数调用失败,则返回 -1 并设置 errno 来指示错误类型。

4.接收消息

要从消息队列中读取消息,需要使用 msgrcv 函数,函数原型如下所:

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

参数释义:

  • msqid:消息队列的标识符;
  • msgp:指向接收消息的缓冲区的指针;
  • msgsz:接收消息缓冲区的大小,一般设置为消息数据部分的大小。
  • msgtyp:指定接收消息的类型,如果指定为 0,则表示接收队列中第一消息。
  • msgflg:接收消息的标志,可以是以下值之一:
    (1)0:阻塞模式,如果队列中没有消息,则一直等待,直到有消息到来。
    (2)PC_NOWAIT:非阻塞模式,如果队列中没有消息,则立即返回-1,并设
    置 errno 为 ENOMSG;
    (3)MSG_EXCEPT:在接收消息时,接收消息队列中第一个类型字段不等于
    msgtyp 的消息;
    (4)MSG_NOERROR:若消息的长度大于 msgsz 字段的长度,则消息会被截
    断;
    (5)MSG_COPY:消息从内核中拷贝到用户空间,如果消息队列中的数据是
    静态分配的内存,使用这个标志位将导致系统性能降低。

函数调用成功,会返回一个非负整数表示消息队列的标识符(即消息队列的唯一标识),调用失败返回-1,并设置 errno。

5.操作消息队列(包括删除,获取,设置)
使用 msgctl 函数可以删除消息队列、获取和设置消息队列的属性信息,其函数原型为:

int msgctl(int msqid, int cmd, struct msqid_ds*buff);

参数释义:

  • msqid:要操作的消息队列的标识符
  • cmd:要执行的操作,cmd 参数有以下几个可选值:
    IPC_STAT:获取消息队列的属性信息,将信息存储在 buf 指向的结构体。
    IPC_SET:设置消息队列的属性信息,通过 buf 指向的结构体来设置。
    IPC_RMID:删除消息队列。
  • buff:一个指向 msqid_ds 结构体的指针,用于传递消息队列的属性信息。当执行删除操作时可传入NULL。

函数调用成功之后返回值为 0,否则表示执行失败。

函数实例:

创建父子进程,实现子进程每间隔1s使用消息队列向父进程发送数据,而父进程负责读取消息队列中的数据并且打印到cmd中。

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
	key_t key = ftok("/home/zxj/workplace/t.c",'t');
	int msgid = msgget(key,0666|IPC_CREAT);
		if(msgid < 0)
			printf("creating msg is error!\n");
		printf("[%d]msgid is %d\n",msgid,getpid());
		pid_t pid = fork();
	if(pid < 0)
		printf("error!\n");
	// 子进程
	else if(pid ==0)
	{	
		int count = 0;
		while((++count) < 10)
		{
			struct msgbuf msg;
			msg.mtype = count;
			sprintf(msg.mtext,"data is %d",count*count);
			int ret = msgsnd(msgid,&msg,strlen(msg.mtext),0);
			printf("[child(%d)]->: ret is %d  .mtype is %d ",getpid(),ret,msg.mtype);
			printf("mtext is %s.\n",msg.mtext);
			sleep(1);
		}
	}
	// 父进程
	else
	{
		while(1)
		{
			struct msgbuf msg;
			int ret = msgrcv(msgid,(void*)&msg,128,0,0);
			if(ret == -1) break;
			printf("[father(%d)]->: ret is %d  .mtype is %d ",getpid(),ret,msg.mtype);
			printf("mtext is %s.\n",msg.mtext);
		}
	}
	msgctl(msgid,IPC_RMID,NULL);
	return 0;
}

使用命令gcc progress.c -o target与命令./target后,执行结果为:
在这里插入图片描述

关于消息队列的使用步骤及其相应的函数就讲解完成了,但在使用过程中仍然有一些需要注意的地方,如下所示:

  • (1)消息类型的选择:消息类型是消息队列中的重要属性,应该根据实际需求进行选择。
    一般来说,可以将不同类型的消息分配到不同的消息类型中,以方便接收方对不同类型的消息
    进行处理。
  • (2)消息长度的限制:Linux 消息队列的消息长度是有限制的,通常为 8192 字节。如
    果消息长度超过了限制,就会导致发送和接收失败。
  • (3)消息发送和接收顺序的保证:在多进程或多线程环境中,由于程序执行的不确定性,
    可能会导致消息发送和接收的顺序与预期的不一致。因此,需要设计合理的同步机制,保证消
    息的发送和接收顺序。
  • (4)消息队列的清理:在使用消息队列之后,需要及时清理消息队列,以免消息队列占
    用过多的系统资源。可以使用 msgctl 函数来删除消息队列。
  • (5)错误处理:在使用消息队列的过程中,可能会发生各种错误,比如消息队列已满、
    消息类型不匹配等等。因此,在编写程序时需要适当处理这些错误,以保证程序的稳定性和可
    靠性。
  • (6)使用消息队列时需要包含结构体:
struct msgbuf {
   int mtype;       /* message type, must be > 0 */
   char mtext[128];    /* message data */
};

共享内存

共享内存是通过操作系统内核在不同进程之间共享内存区域的一种机制。在创建共享内存时,操作系统会分配一块内存区域,并将其映射到各个进程的地址空间中。进程可以直接读写这个内存区域,而不需要进行任何数据传输的操作。共享内存的优点是速度快,因为不需要数据的复制操作,而且不需要操作系统进行上下文切换,所以它通常比其他 IPC 机制(如管道和消息队列)更快。
使用时需要包含的头文件

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

1.创建或者获取IPC key值
IPC key 可以通过调用 ftok 函数来创建,或者直接传入一个已知的IPC key值。(创建IPC key值见上文
2.创建共享内存

函数原型

int shmget(key_t key, size_t size,  int shmflg);

参数解释:

  • key:消息队列的关键字,即使用 ftok()函数生成的 IPC key 值
  • size: 共享内存大小,以字节为单位。
  • msgflg:标志位参数,用于指定创建或获取消息队列的方式和权限控制,可以用|符号组合多个标志位。可选标志位如下:
    (1)IPC_CREAT:如果共享内存不存在则创建它。
    (2)IPC_EXCL:如果同时指定了 IPC_CREAT 和 IPC_EXCL,并且
    共享内存已经存在,则创建共享内存失败并返回错误。
    ( 3 ) IPC_NOWAIT : 如 果 同 时 指 定 了 IPC_CREAT 和
    IPC_NOWAIT,并且共享内存已经存在,则不等待直接返回错误。
    (4)SHM_HUGETLB:指定使用 huge page 来映射共享内存。
    (5)SHM_NORESERVE:指定不保留物理内存。

函数调用成功,返回值为共享内存标识符(shmid),用于标识创建或获取的共享内存对象。如果出错,则返回 -1 并设置 errno 错误

3.映射共享内存

函数原型

void *shmat(int shmid, const void *shmaddr, int  shmflg);

参数解释

  • shmid 共享内存标识符,通过 shmget() 函数创建或获取。
  • shmaddr 指定共享内存段的连接地址,通常设置为 NULL,让系统自动选
    择一个合适的地址。
  • shmflg 连接共享内存的标志位,可选值为以下几个:
    (1)SHM_RDONLY:以只读方式连接共享内存,即不能修改共享内存中的数据。
    (2)SHM_RND:指定连接地址必须按页面大小(4096 字节)
    的整数倍进行对齐。
    (3)SHM_REMAP:如果共享内存已经连接到其他进程,则重
    新映射共享内存而不是创建新的映射。

函数返回值为共享内存段的首地址,类型为 void *。如果出错,则返回 -1 并设置 errno 错误码。
4.使用完共享内存后解除

函数原型

int shmdt(const void  *shmaddr);

参数释义:

  • shmaddr:表示共享内存的首地址。
    函数返回值为 0 表示成功,如果出错,则返回-1 并设置 errno 错误码。

5.操作共享内存

int shmctl(int shmid, int cmd,  struct shmid_ds *buf);

参数释义:

  • shmid:共享内存标识符,通过 shmget() 函数创建或

  • 控制命令,可选值为以下几个:
    IPC_STAT:获取共享内存的状态信息,将共享内存的信息保存到 buf 结构体中。
    IPC_SET:设置共享内存的状态信息,将 buf 结构体中的信息设置到共享内存中。
    IPC_RMID:销毁共享内存。
    SHM_LOCK:锁定共享内存,防止被换出到磁盘上。
    SHM_UNLOCK:解锁共享内存。

  • buf:指向 shmid_ds 结构体的指针,用于存储共享内存的状态信息。如果cmd 为 IPC_STAT 或 IPC_SET,则需要传递该参数,否则可以设置为 NU

删除共享内存时,可如此使用函数 shmctl(shmid,IPC_RMID,NULL);

使用实例:

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
	/* 生成一个key */
	key_t key = ftok("/home/zxj/workplace/",1);
	if(key == -1) 
	{
		printf("creating key is failed!\n");
		_exit(-1);
	}
	printf("key:%d\n",key);
	/* 创建一个共享内存段 */
	int shmid = shmget(key,1024,IPC_CREAT|0666);
	if(shmid == -1)
	{
		printf("creating shmget si failed!\n");
		_exit(-2);
	}
	printf("shmid:%d\n",shmid);
	
	// 创建父子进程
	pid_t pid = fork();
	if(pid < 0) 
		printf("fork is failed!\n");
	// 子进程
	else if(pid == 0)
	{
		/* 链接共享内存段 */
		char *shmaddr = shmat(shmid,NULL,0);
		for(int i=0;i<2;++i)
			strncpy(shmaddr,"hello",5);
		printf("child sned:%s\n",shmaddr);
		/* 分离共享内存 */ 
		shmdt(shmaddr);
	}
	// 父进程
	else
	{
		/* 链接共享内存段 */
		char *shmaddr = shmat(shmid,NULL,0);
		sleep(2);
		printf("father recive:%s\n",shmaddr);
		/* 分离共享内存 */ 
		shmdt(shmaddr);
		/* 删除共享内存段 */
		shmctl(shmid,IPC_RMID,NULL);
		
	}
	return 0;
}

其运行结果为:

在这里插入图片描述

此时可以使用命 ipcs -m查看程序运行时与运行后各个共享内存变化情况:
在这里1232321图片描述
(运行时)

在这里插入图片描述
(运行后)

关于共享内容的使用步骤及其相应的函数就讲解完成了,但在使用过程中仍然有一些需要注意的地方,如下所示:

  • (1)内存泄漏:由于共享内存段在多个进程之间共享,因此需要注意避免出现内存泄漏的情况。特别是在共享内存段不再需要时,应当及时将其从当前进程中分离,并且在最后一个使用该共享内存的进程退出时销毁该共享内存段。
  • (2)进程同步:由于多个进程可以同时访问共享内存,因此需要进行进程同步,避免出现竞争条件和数据不一致的情况。可以使用信号量等机制进行进程同步。
  • (3)内存访问:由于多个进程可以同时访问共享内存,因此需要注意避免出现数据访问冲突的情况。特别是在对共享内存进行写操作时,需要考虑到多个进程同时修改共享内存的情况,需要采用相应的机制进行同步。
  • (4)数据结构:在共享内存中存储的数据结构需要考虑到不同进程的字节对齐和大小端等问题,需要进行兼容性处理

信号量

信号量是一种计数器,用于对多个进程共享的资源进行计数和控制。它是一种 IPC 对象,通常用于进程间互斥和同步,确保多个进程对共享资源的访问顺序和正确性。信号量通常用于解决并发访问共享资源的同步问题。

所涉及的头文件有:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

1.创建或者获取IPC key值
IPC key 可以通过调用 ftok()函数来创建,或者直接传入一个已知的IPC key值。(创建IPC key值见上文)

2.创建或获取信号量

函数原型

int semget(key_t key, int nsems,  int semflg);

参数释义:

  • key:标识信号量集的关键字,它必须与其他进程共享,因此通常是通过 ftok
    函数生成的。
  • nsems: 信号量集中包含的信号量数目。
  • semflg:标志位,用于指定函数的行为。它可以是 0,也可以是以下标志的按位或运算结果:
    IPC_CREAT:如果关键字对应的信号量集不存在,则创建一个新的信号量集,并返回标识符。如果已经存在,则返回其标识符。
    IPC_EXCL:如果关键字对应的信号量集已经存在,则返回一个错误。它只能和 IPC_CREAT

函数调用成功,返回值为获取或创建的信号量集的标识符。如果出错,则返回 -1 并设置errno 错误码

3.初始化信号量

所涉及的头文件有:

#include <sys/sem.h>

函数原型:

int semctl(int semid, int semnum, int cmd, union semun arg);

参数释义:

  • semid 信号量集的标识符
  • semnum 要操作的信号量编号
  • cmd 是要执行的命令 semctl 函数的命令参数 cmd 主要有以下几种:
    (1)IPC_STAT:获取信号量的信息,并将其保存在 arg 中的
    semid_ds 结构体中。
    (2)IPC_SET:设置信号量的信息,使用 arg 中的 semid_ds 结
    构体中的信息进行设置。
    (3)IPC_RMID:删除信号量集,此时 arg 必须是一个空指针。
    (4)GETVAL:获取信号量的当前值,并将其保存在 arg 中的
    整型变量中。
    (5)SETVAL:设置信号量的值,使用 arg 中的整型变量进行设
  • arg 是一个联合体参数,用于传递不同类型的参数值

4.信号量的PV操作

int  semop(int semid, struct sembuf *sops, size_t nsops);

参数释义:

  • semid :信号量集的标识符。
  • sops :指向一组 sembuf 结构的指针,每个 sembuf 结构用于描述一个操作。
  • nsops :需要执行的操作数

函数返回值为 0 表示成功,如果出错,则返回-1 并设置 errno 错误码。

函数实例:

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
	key_t key = ftok("/home/zxj/workplace/t.c",'t');
	/* 创建信号量 */
	int sem_id= semget(key,1,0666|IPC_CREAT);
	printf("sem_id of semget is %d.\n",sem_id);
	/* 初始化信号量 */
	semctl(sem_id, 0, SETVAL, 0);

	/* 创建父子进程 */
	pid_t pid = fork();
	if(pid < 0)
		printf("error\n");
	// 子进程
	else if(pid == 0)
	{
		sleep(2);
		struct sembuf sem_buf;
		sem_buf.sem_num  = 0; //信号量编号
		sem_buf.sem_op = 1;  //P操作
		sem_buf.sem_flg = 0;//设置成阻塞模式
		semop(sem_id,&sem_buf,1);
		printf("I am son.\n");
	}
	// 父进程
	else
	{
		struct sembuf sem_buf;
		sem_buf.sem_num  = 0; //信号量编号
		sem_buf.sem_op = -1;  //V操作
		sem_buf.sem_flg = 0;//设置成阻塞模式
		semop(sem_id,&sem_buf,1);
		printf("I am father.\n");
		sem_buf.sem_num  = 0; //信号量编号
		sem_buf.sem_op = 1;  //P操作
		sem_buf.sem_flg = 0;//设置成阻塞模式
		semop(sem_id,&sem_buf,1);
	}
	/* 删除信号量 */
	semctl(sem_id, 0, IPC_RMID, 0);
	return 0;
}

在这里插入图片描述


消息队列、共享内存、信号量应用场景的比较:

机制应用场景
消息队列一对多或多对一的进程通信,日志记录,任务分发,进程间通信
共享内存多进程协作,高速缓存,大型数据处理,共享数据缓存
信号量进程同步,进程互斥,进程间通信,控制共享资源的访问,保证资源的独占性

在ubuntu中可输入命令查看消息队列、共享内存、信号量的相关情况:

  • 1.输入ipcs -q查看消息队列的相关情况:
    在这里插入图片描述
  • 2.输入ipcs -s查看信号量的相关情况:

在这里插入图片描述

  • 3.输入ipcs -m查看共享内存的相关情况:

在这里插入图片描述

  • 4.输入ipcs -q可查看上述三者的相关情况:

在这里插入图片描述

文章来源:https://blog.csdn.net/qq_53960242/article/details/135328630
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。