Linux 进程之间的通信IPC

文章目录

    • 消息队列
    • 信号量集
    • 共享存储区
    • Posiz IPC与System-v IPC
    • 其他
    • 讨论

  • 在linux环境下,进程地址空间相互独立,彼此隔离,因此进程间的数据之间不能访问,如果要交换数据,就必须通过内核,在内核开辟一块缓冲区,进程a把数据从用户空间拷贝到内核缓冲区,进程b再把数据从内核缓冲区中拷贝走,内核提供的这种机制称为进程通信
  • 进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的程序进程,使之能在一个操作系统里同时运行。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。

进程间通信主要包括系统IPC(进程间通信)(包括消息队列,信号量集,共享存储), 套接字(SOCKET),管道(PIPLE)

消息队列

  • 消息队列是System V 中的一种进程间的通信机制(信号量、消息队列、共享内存),消息队列就好比是快递柜,发送方发送消息的时候会把要发送的数据放在快递柜中,接收方在方便的时候可以从快递柜中把消息拿出来,在linux系统中,消息队列的本质是内核维护的一片内存
  • 消息队列可以有效避免管道的弊端:无名管道的通信需要进程之间有血缘关系,有名管道通信的数据是单向流动的(半双工),并且管道通信智能单播(单对单通信)
  • 消息队列有如下特点:①消息队列中的消息是有类型的;②消息队列的消息是有格式的;③消息队列的消息可以随机查看,不一定按照次序读取,可以只读取某一个执行的数据;④消息队列允许多个线程向队列写入或者读取;⑤与管道相同,读取到的消息会被队列删除;⑥消息队列会有消息队列标识符,消息队列标识符在整个系统唯一;⑦只有内核重启或者人工删除消息队列时。消息队列才会被删除,否则消息队列一直存在于系统中;⑧在一个系统中可能有若干个消息队列,由所有的消息队列的头标组成一个头标数组。**
//running.c
#include
#include
#include
#include
#include
#include

#define MAX_TEXT 512
struct my_msg_st {
	long int my_msg_type;
	char some_text[MAX_TEXT];
};

int main(){
	int running=1;
	struct my_msg_st some_data;
	int msgid;
	char buffer[BUFSIZ];

	msgid=msgget((key_t)1234,0666|IPC_CREAT);
	if(msgid==-1){
		fprintf(stderr,"msgget failed with error:%d\n",errno);
		exit(EXIT_FAILURE);
	}

	while(running){
		printf("Enter some text:");
		fgets(buffer,BUFSIZ,stdin);
		some_data.my_msg_type=1;
		strcpy(some_data.some_text,buffer);
		if(msgsnd(msgid,(void*)&some_data,MAX_TEXT,0)==-1){//send 发送消息到msgid中
			fprintf(stderr,"msgsnd failed\n");
			exit(EXIT_FAILURE);
		}


		if(strncmp(some_data.some_text,"end",3)==0) running=0;
	}
	exit(EXIT_SUCCESS);

}

//running2.c
#include
#include
#include
#include
#include
#include

#define MAX_TEXT 512
struct my_msg_st {
	long int my_msg_type;
	char some_text[MAX_TEXT];
};

int main(){
	int running=1;
	int msgid;
	struct my_msg_st some_data;
	long int msg_to_receive=0;//接受消息类型设置,按照先进先出的顺序接收数据

	//获取消息队列
	msgid=msgget((key_t)1234,0666|IPC_CREAT);

	if(msgid==-1){
		fprintf(stderr,"msgget failed with error:%d\n",errno);
		exit(EXIT_FAILURE);
	}
	while(running){
		if(msgrcv(msgid,(void*)&some_data,BUFSIZ,msg_to_receive,0)==-1){
			fprintf(stderr,"msgrcv failed with error:%d\n",errno);
			exit(EXIT_FAILURE);
		}
		printf("You wrote:%s",some_data.some_text);
		if(strncmp(some_data.some_text,"end",3)==0) running=0;

	}

	if(msgctl(msgid,IPC_RMID,0)==-1){//把消息队列删除
		fprintf(stderr,"msgctl(IPC_RMID)failed\n");
		exit(EXIT_FAILURE);
	}
	exit(EXIT_SUCCESS);

}

int msgget(key_t key, int msgflg):创建一个新的或打开一个已经存在的消息队列。不同的进程调用此函数,只要用相同的key值就能得到同一个消息队列的标识。key:IPC键值;msgflg:消息队列的权限值和mode_t一样,无可执行权限。相关宏如下:
IPC_CREAT:创建消息队列
IPC_EXCL:监测消息队列是否存在
函数返回值:成功返回消息队列标识符,失败返回-1

消息队列的消息格式:消息队列的消息类型必须是长整型,且必须是结构体的第一个成员,消息下面是消息正文正文可以有多个成员(正文的成员可以是任意类型)如上面代码的struct my_msg_st

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg):将新消息添加到消息队列;msqid:消息队列标识符;msgp: 待发送消息结构体的地址;msgsz:消息正文的字节数;msgflg: 函数的控制属性
0 : msgsnd调用阻塞直到条件满足为止(eg:如果消息队列满了,阻塞;推荐);
IPC_NOWAIT:若消息没有立即发送则调用该函数的进程会立即返回。
函数返回值:成功返回0,失败返回-1;

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg):从标识符为msqid的消息队列中接收一个消息。一旦接收消息成功,则消息在消息队列中删除。msqid:消息队列的标识符,代表要从哪个消息队列中获取消息。msgp:存放消息结构体的地址;msgsz:消息正文的字节数;msgtyp:消息的类型、可以有以下几种类型:
msgtyp = 0:返回队列中的第一个消息
msgtyp >0:返回队列中消息类型为msgtyp的消息
msgtyp <0:返回队列中消息类型值小于或等于msgtyp绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。
注意:若消息队列中有多种类型的消息,msgrcv获取消息的时候按消息类型获取,不是先进先出的。在获取某类型消息的时候,若队列中有多条此类型的消息,则获取最先添加的消息,即先进先出原则。
msgflg:函数的控制属性
0 :msgrcv调用阻塞直到接收消息成功为止
MSG_NOERROR:若返回的消息字节数比nbytes字节数多,则消息就会截短到nbytes字节,且不通知消息发送进程。
IPC_NOWAIT:调用进程会立即返回。若没有收到消息则立即返回-1.
函数返回值:成功返回读取消息的长度,失败返回-1

int msgctl(int msqid, int cmd, struct msqid_ds *buf):对消息队列进行各种控制,如修改消息队列的属性,或删除消息队列。msqid:消息队列的标识符;cmd:函数功能的控制
IPC_RMID:删除由msqid指示的消息队列,将它从系统中删除并破坏相关数据结构。
IPC_STAT:将msqid相关的数据结构中各个元素的当前值存入到由buf指向的结构体中。
IPC_SET:将msqid相关的数据结构中的元素设置为由buf指向的结构中的对应值。
buf:msqid_ds数据类型的地址,用来存放或更改消息队列的属性。
函数返回值:成功返回0,失败返回-1

ipcs 命令可以查看系统中已经创建的消息队列;参数:
-m 查看系统共享内存信息
-q 查看系统消息队列信息
-s 查看系统信号量信息
-a 显示系统内所有的IPC信息
移除:
ipcrm -m shmid 移除用shmid标识的共享内存段
ipcrm -s semid 移除用semid标识的信号量
ipcrm -q msgid 移除用msgid标识的消息队列

运行结果:

[cch@aubin ipc]$ gcc running.c -o running
[cch@aubin ipc]$ gcc running2.c -o running2
[cch@aubin ipc]$ ./running
Enter some text:hello 
Enter some text:hja     
Enter some text:jejje
Enter some text:qkkoq
Enter some text:jwjs
Enter some text:zid
Enter some text:end
[cch@aubin ipc]$ 

[cch@aubin ipc]$ ./running2
You wrote:hello
You wrote:hja
You wrote:jejje
You wrote:qkkoq
You wrote:jwjs
You wrote:zid
You wrote:end
[cch@aubin ipc]$ ipcs
  1. 消息的发送
      当进程要与其它进程通信时,可利用msgsnd( )系统调用来发送消息。对于msgsnd( )系统调用,核心检查消息队列描述符和许可权是否合法,消息长度是否超过系统规定的长度。 通过检查后,核心为消息分配消息数据区,并将消息从用户消息缓冲区拷贝到消息数据区。分配消息首部,将它链入消息队列的末尾;在消息首部中填写消息的类型、大小以及指向消息数据区的指针等;还要修改消息队列头标中的数据(如消息队列中的消息数、字节数等)。然后,唤醒在等待消息到来的睡眠进程。
  2. 消息的接收
      进程可利用msgrcv( )系统调用,从指定消息队列中读一个消息。对于msgrcv( )系统调用,先由核心检查消息队列标识符和许可权,继而根据用户指定的消息类型做相应的处理。消息类型msgtyp的参数可能有三种情况:当msgtyp=0时,核心寻找消息队列中的第一个消息,并将它返回给调用进程;当msgtyp为正整数时,核心返回指定类型的第一个消息;当msgtyp为负整数时,核心应在其类型值小于或等于msgtyp绝对值的所有消息中,选出类型值最低的第一个消息返回。如果所返回消息的大小等于或小于用户的请求,核心便将消息正文拷贝到用户区,再从队列中删除该消息,并唤醒睡眠的发送进程;如果消息长度比用户要求的大,则系统返回出错信息。用户也可忽略对消息大小的限制,此时,核心无需理会消息的大小而一概把消息内容拷贝到用户区。

信号量集

头文件

#include 
#include
#include
#include
#include
#include

int main(){
	//创建一个信号量集,并且设置信号量集中信号量的值为1
	key_t key=12345;
	//int sem_id=semget(key,1,0666|IPC_CREAT);//不存在就创建
	int sem_id=semget(IPC_PRIVATE,1,0666|IPC_CREAT);//不存在就创建
	if(sem_id==-1)
	{
		perror("semget error");
		exit(1);
	}

	union semun{
		int val;
		struct semid_ds *buf;
		ushort *array;
	}argument;//联合体

	argument.val=1;
	if(semctl(sem_id,0,SETVAL,argument)==-1){//设置信号量的值为1
		perror("semctl error");
		exit(1);
	}


	//对信号p操作
	struct sembuf sops;
	sops.sem_num=0;
	sops.sem_op=-1;//p
	sops.sem_flg=0;
	if(semop(sem_id,&sops,1)<0){
		perror("semop error");
		return -1;
	}

	//互斥区
	printf("互斥区操作\n");
	sops.sem_op=1;//v操作
	if(semop(sem_id,&sops,1)<0){
		perror("semop error");
		return -1;
	}

	//删除信号量
	if(semctl(sem_id,0,IPC_RMID)<0)
	{
		perror("semctl error");
		return -1;
	}

	return 0;
}
[cch@aubin os]$ cd ipc
[cch@aubin ipc]$ gcc sem.c -o sem
[cch@aubin ipc]$ ./sem
互斥区操作
[cch@aubin ipc]$ 

int semget(key_t key, int nsems, int semflg):是 Linux 中的一个系统调用,用于创建或获取一个信号量集的标识符。信号量集是一个包含若干个信号量的数据结构,每个信号量都是一个整型值,用于在进程之间进行同步和互斥。参数:
key:一个整型值,用于标识信号量集。如果 key 为 0,则系统会自动生成一个唯一的 key 值。
nsems:表示信号量集中包含的信号量个数。
semflg:表示创建信号量集的权限和行为。可以使用以下值:
IPC_CREAT:如果信号量集不存在,则创建一个新的信号量集;如果信号量集已存在,则获取它的标识符。
IPC_EXCL:与 IPC_CREAT 一起使用时,如果信号量集已存在,则调用 semget() 函数失败,并返回错误。
返回值:
成功:返回信号量集的标识符。
失败:返回 -1,并设置 errno 为相应的错误码。

int semop(int semid, struct sembuf *sops, unsigned nsops):用于对信号量集进行操作的函数。通过修改信号量集中每个信号量的值来实现对信号量的 P 操作和 V 操作。semid 指信号量集的标识符;sops 指向一个信号操作结构体的指针;nsops 为要执行的信号操作的数量。
返回值为 0 表示执行成功,-1 表示执行失败。

struct sembuf 
{ 
 unsigned short int sem_num; /* 信号量的序号从 0~nsems-1 */ 
 short int sem_op; /* 对信号量的操作,>0, 0, <0 */ 
 short int sem_flg; /* 操作标识:0, IPC_WAIT, SEM_UNDO */ 
};
其中,sem_num 为信号量在信号量集中的编号;
sem_op 为要执行的信号操作(P操作或V 操作)的操作数;
sem_flg 为执行信号操作的标志(如是否阻塞等)。
//一个p操作
int sem_id; // 信号量的标识符
struct sembuf sops; // 信号量操作结构体
sops.sem_num = 0; // 信号量的编号(对应信号量集中的第几个信号量)
sops.sem_op = -1; // 要执行的操作(-1 表示 P 操作,1 表示 V 操作)
sops.sem_flg = 0; // 操作标志
semop(sem_id, &sops, 1); // 执行信号量操作

//两个p操作
struct sembuf sops[2];
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[0].sem_flg = 0;
sops[1].sem_num = 1;
sops[1].sem_op = -1;
sops[1].sem_flg = 0;
semop(sem_id, sops, 2);

int semctl(int semid, int semnum, int cmd, ... /* arg */):用于控制信号量集的函数。它支持多种操作,可以用于获取信号量集的属性、设置信号量集的属性、控制信号量的值等。semid:信号量集的标识符。semnum:要操作的信号量的编号。是信号量在信号量集中的编号;cmd 是要执行的命令,后面的参数是根据 cmd 的值而定的。
如果 cmd 为 SETVAL 或 SETALL,则此参数被忽略。
cmd:指定要执行的操作。(操作命令)
可以是以下值之一:
GETVAL:获取信号量的值。
SETVAL:设置信号量的值,取自 arg 的 val 元素。
GETPID:返回最后一个执行 semop 函数的进程的进程号。
GETNCNT:获取当前等待信号量为正值的进程数。
GETZCNT:获取当前等待信号量为 0 的进程数。
GETALL:获取信号量集中所有信号量的值。
SETALL:设置信号量集中所有信号量的值。
IPC_STAT:获取信号量集的属性。
IPC_SET:设置信号量集的属性。
IPC_RMID:删除信号量集。
常用的有:
semctl() 函数有多种用途,常用的命令有以下几种:
GETVAL:获取信号量的值。
SETVAL:设置信号量的值。
IPC_RMID:删除信号量集。
IPC_STAT:获取信号量集的属性。
IPC_SET:设置信号量集的属性。
arg:可变参数,依赖于 cmd 的值而不同。如果 cmd 为 SETVAL 或 SETALL,则 arg 指向一个 int 类型的值,表示要设置的信号量的值。

共享存储区

头文件

#include 
#include 

int shmget(key_t key, size_t size, int shmflg):创建或获取共享内存; key: 共享内存的键值,是共享内存在系统中的编号,不同共享内存编号不能相同,用十六进制比较好; size: 共享内存的大小,单位byte; shmflg: 共享内存的访问权限,与文件差不多, 0666 | IPC_CREAT 表示所有用户都可读写,IPC_CREAT 表示如果不存在则创建;返回值:成功返回一个id,失败返回-1,并设置errno

void *shmat(int shmid, const void *shmaddr, int shmflg):将共享内存链接到当前进程的地址空间;shmid: shmflg返回的id;shmaddr: 共享内存链接到当前进程中的地址位置,通常为NULL,表示让系统来选择; shmflg: 标志位,通常为0;返回值:成功返回共享内存段的地址,失败返回 (void *)-1, 并设置errno

int shmdt(const void *shmaddr):将共享内存从当前进程分离;shmaddr: 共享内存地址,返回值: 成功返回0, 失败返回-1, 设置errno

int shmctl(int shmid, int cmd, struct shmid_ds *buf):操作共享内存,可以用来删除一个共享内存;shmid: 共享内存id;cmd: 执行的操作, IPC_RMID表示删除;buf: 设置为NULL;返回值:失败返回-1, 设置errno

//share2.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXBUFSIZE 4096
#define SHAREMEMESIZE 4096

int read_file(char *buf, int size, const char *filename);

int main() {
    int n, shm_id;
    char buf[MAXBUFSIZE] = {0};
    char *shm_p = NULL;

    /* 
        获取共享内存,如果不存在则创建
    */
    if ( (shm_id = shmget(0x666, SHAREMEMESIZE, 0644 | IPC_CREAT)) < 0) {
        perror("shmget error");
        return 1;
    }

    /*
        把共享内存链接到当前进程的地址空间
     */
    shm_p = (char *)shmat(shm_id, NULL, 0);
    if (shm_p == (void *) -1) {
        perror("shmat error:");
        return 1;
    }

    // 从文件中读取数据
    n = read_file(buf, MAXBUFSIZE, "./read_text");
    if (n == -1) {
        return 1;
    }

    printf("read from file: %s\n", buf);

    // 将数据拷贝到共享内存
    memcpy(shm_p, buf, strlen(buf));

    // 把共享内存从当前进程分离
    shmdt(shm_p);

    return 0;
}

// 读文件内容到buf
int read_file(char *buf, int size, const char *filename) {
    int fd, n;

     // 打开read_test
    if ( (fd = open(filename, O_RDONLY)) < 0) {
        perror("open file_read_test error:");
        return -1;
    }
    // 读取文件内容
    n = read(fd, buf, size);
    if (n < 0) {
        perror("read file error:");
        return -1;
    }

    // 关闭文件
    close(fd);

    return n;
}

//share

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXBUFSIZE 4096
#define SHAREMEMESIZE 4096

int write_file(const char *buf, int size, const char *filename);

int main() {
    int shm_id;
    char buf[MAXBUFSIZE] = {0};
    char *shm_p = NULL;
    
    /* 
        获取共享内存,如果不存在则创建
    */
    if ( (shm_id = shmget(0x666, SHAREMEMESIZE, 0644)) < 0) {
        perror("shmget error");
        return 1;
    }

    /*
        把共享内存链接到当前进程的地址空间
     */
    shm_p = (char *)shmat(shm_id, NULL, 0);
    if (shm_p == (void *) -1) {
        perror("shmat error:");
        return 1;
    }

    // 拷贝共享内存中的数据
    memcpy(buf, shm_p, MAXBUFSIZE);

    // 将共享内存中的内容写入write_text文件中
    write_file(buf, strlen(buf), "write_text");

    printf("write to file: %s\n", buf);

    // 把共享内存从当前进程分离
    shmdt(shm_p);

    //删除共享内存
    if (shmctl(shm_id, IPC_RMID, 0) == -1) { 
        printf("shmctl failed\n"); 
        return -1; 
    }

    return 0;
}

// 将buf中的数据写入文件
int write_file(const char *buf, int size, const char *filename) {
    int fd, n;

    // 打开文件,不存在则创建
    if ((fd = open(filename, O_WRONLY | O_CREAT, 0644)) < 0) {
        perror("open file error:");
        return -1;
    } 

    // 写入内容
    n = write(fd, buf, size);
    if (n < 0) {
        perror("write file error");
        return -1;
    }

    close(fd);
    return n;
}

[cch@aubin ipc]$ gcc share.c -o share
[cch@aubin ipc]$ gcc share2.c -o share2
[cch@aubin ipc]$ ./share2
read from file: this is read text

[cch@aubin ipc]$ ./share
write to file: this is read text

[cch@aubin ipc]$ 

Posiz IPC与System-v IPC

  • System-V IPC 接口:来自较早的Unix操作系统,是Unix众多分支中的一员
  • Posix IPC 接口:则是来自IEEE所开发的一簇标准,基于Unix的经验和实践所实现的一堆调用服务接口,致力于在多种操作系统之间源代码级别移植。
  • Posix IPC和System-V IPC都是应用于系统级的接口,不仅适用于多进程间通信,也适用于多线程间通信。Posix相对于System-V可以说是比较新的标准,语法相对简单。

两者在信号量上的优缺点:
POSIX在无竞争条件下是不会陷入内核的,而System-V IPC则是无论何时都要陷入内核,因此性能稍差。
POSIX的sem_wait函数成功获取信号量后,进程如果意外终止,将无法释放信号量,而System V则提供了SEM_UNDO选项来解决这个问题。因此,相比而言,后者更加可靠。

其他

管道:linux进程管道通信
信号:信号处理signal函数
互斥锁:互斥锁
内存映射:文件内存映射

讨论

使用代码展示信号量、共享内存和消息队列的典型应用场景。

信号量:

#include
#include
#include
#include
#include

int main(){
	//创建一个信号量集,并且设置信号量集中信号量的值为1
	key_t key=12345;
	int sem_id=semget(key,1,0666|IPC_CREAT);//不存在就创建
	if(sem_id==-1)
	{
		perror("semget error");
		exit(1);
	}


	//对信号了p操作
	struct sembuf sops;
	sops.sem_num=0;
	sops.sem_op=-1;//p
	sop.sem_flg=0;
	if(semop(sem_id,&sops,1)<0){
		perror("semop error");
		return -1;
	}

	//互斥区
	printf("互斥区操作\n");
	sops.sem_op=1;//v操作
	if(semop(sem_id,&sops,1)<0){
		perror("semop error");
		return -1;
	}

	//删除信号量
	if(semctl(sem_id,0,IPC_RMID)<0)
	{
		perror("semctl error");
		return -1;
	}
	
/*
	union semun{
		int val;
		struct semid_ds *buf;
		ushort *array;
	}argument;
	argument.val=1;
	if(semctl(sem_id,0,SETVAL,argument)==-1){//设置信号量的值
		perror("semctl error");
		exit(1);
	}
*/
	return 0;
}

共享内存:

//share2.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXBUFSIZE 4096
#define SHAREMEMESIZE 4096

int read_file(char *buf, int size, const char *filename);

int main() {
    int n, shm_id;
    char buf[MAXBUFSIZE] = {0};
    char *shm_p = NULL;

    /* 
        获取共享内存,如果不存在则创建
    */
    if ( (shm_id = shmget(0x666, SHAREMEMESIZE, 0644 | IPC_CREAT)) < 0) {
        perror("shmget error");
        return 1;
    }

    /*
        把共享内存链接到当前进程的地址空间
     */
    shm_p = (char *)shmat(shm_id, NULL, 0);
    if (shm_p == (void *) -1) {
        perror("shmat error:");
        return 1;
    }

    // 从文件中读取数据
    n = read_file(buf, MAXBUFSIZE, "./read_text");
    if (n == -1) {
        return 1;
    }

    printf("read from file: %s\n", buf);

    // 将数据拷贝到共享内存
    memcpy(shm_p, buf, strlen(buf));

    // 把共享内存从当前进程分离
    shmdt(shm_p);

    return 0;
}

// 读文件内容到buf
int read_file(char *buf, int size, const char *filename) {
    int fd, n;

     // 打开read_test
    if ( (fd = open(filename, O_RDONLY)) < 0) {
        perror("open file_read_test error:");
        return -1;
    }
    // 读取文件内容
    n = read(fd, buf, size);
    if (n < 0) {
        perror("read file error:");
        return -1;
    }

    // 关闭文件
    close(fd);

    return n;
}

//share

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXBUFSIZE 4096
#define SHAREMEMESIZE 4096

int write_file(const char *buf, int size, const char *filename);

int main() {
    int shm_id;
    char buf[MAXBUFSIZE] = {0};
    char *shm_p = NULL;
    
    /* 
        获取共享内存,如果不存在则创建
    */
    if ( (shm_id = shmget(0x666, SHAREMEMESIZE, 0644)) < 0) {
        perror("shmget error");
        return 1;
    }

    /*
        把共享内存链接到当前进程的地址空间
     */
    shm_p = (char *)shmat(shm_id, NULL, 0);
    if (shm_p == (void *) -1) {
        perror("shmat error:");
        return 1;
    }

    // 拷贝共享内存中的数据
    memcpy(buf, shm_p, MAXBUFSIZE);

    // 将共享内存中的内容写入write_text文件中
    write_file(buf, strlen(buf), "write_text");

    printf("write to file: %s\n", buf);

    // 把共享内存从当前进程分离
    shmdt(shm_p);

    //删除共享内存
    if (shmctl(shm_id, IPC_RMID, 0) == -1) { 
        printf("shmctl failed\n"); 
        return -1; 
    }

    return 0;
}

// 将buf中的数据写入文件
int write_file(const char *buf, int size, const char *filename) {
    int fd, n;

    // 打开文件,不存在则创建
    if ((fd = open(filename, O_WRONLY | O_CREAT, 0644)) < 0) {
        perror("open file error:");
        return -1;
    } 

    // 写入内容
    n = write(fd, buf, size);
    if (n < 0) {
        perror("write file error");
        return -1;
    }

    close(fd);
    return n;
}

[cch@aubin ipc]$ gcc share.c -o share
[cch@aubin ipc]$ gcc share2.c -o share2
[cch@aubin ipc]$ ./share2
read from file: this is read text

[cch@aubin ipc]$ ./share
write to file: this is read text

[cch@aubin ipc]$ 

消息队列:

//running.c
#include
#include
#include
#include
#include
#include

#define MAX_TEXT 512
struct my_msg_st {
	long int my_msg_type;
	char some_text[MAX_TEXT];
};

int main(){
	int running=1;
	struct my_msg_st some_data;
	int msgid;
	char buffer[BUFSIZ];

	msgid=msgget((key_t)1234,0666|IPC_CREAT);
	if(msgid==-1){
		fprintf(stderr,"msgget failed with error:%d\n",errno);
		exit(EXIT_FAILURE);
	}

	while(running){
		printf("Enter some text:");
		fgets(buffer,BUFSIZ,stdin);
		some_data.my_msg_type=1;
		strcpy(some_data.some_text,buffer);
		if(msgsnd(msgid,(void*)&some_data,MAX_TEXT,0)==-1){//send 发送消息到msgid中
			fprintf(stderr,"msgsnd failed\n");
			exit(EXIT_FAILURE);
		}


		if(strncmp(some_data.some_text,"end",3)==0) running=0;
	}
	exit(EXIT_SUCCESS);

}

//running2.c
#include
#include
#include
#include
#include
#include

#define MAX_TEXT 512
struct my_msg_st {
	long int my_msg_type;
	char some_text[MAX_TEXT];
};

int main(){
	int running=1;
	int msgid;
	struct my_msg_st some_data;
	long int msg_to_receive=0;//接受消息类型设置,按照先进先出的顺序接收数据

	//获取消息队列
	msgid=msgget((key_t)1234,0666|IPC_CREAT);

	if(msgid==-1){
		fprintf(stderr,"msgget failed with error:%d\n",errno);
		exit(EXIT_FAILURE);
	}
	while(running){
		if(msgrcv(msgid,(void*)&some_data,BUFSIZ,msg_to_receive,0)==-1){
			fprintf(stderr,"msgrcv failed with error:%d\n",errno);
			exit(EXIT_FAILURE);
		}
		printf("You wrote:%s",some_data.some_text);
		if(strncmp(some_data.some_text,"end",3)==0) running=0;

	}

	if(msgctl(msgid,IPC_RMID,0)==-1){//把消息队列删除
		fprintf(stderr,"msgctl(IPC_RMID)failed\n");
		exit(EXIT_FAILURE);
	}
	exit(EXIT_SUCCESS);

}

使用代码来显示POSIX信号量和IPC信号量之间的差异。

Posix信号量

#include 
#include 
#include 

int main() {
	// 创建和初始化POSIX信号量
	sem_t *posix_sem = sem_open("/my_posix_sem", O_CREAT, 0666, 1);
	
	// 在临界区使用信号量
	sem_wait(posix_sem);
	
	// 临界区操作
	printf("临界区操作\n");
	sem_post(posix_sem);
	
	// 删除POSIX信号量
	sem_close(posix_sem);
	sem_unlink("/my_posix_sem");
	return 0;
}

IPC信号量:

#include
#include
#include
#include
#include

int main(){
	//创建一个信号量集,并且设置信号量集中信号量的值为1
	key_t key=12345;
	int sem_id=semget(key,1,0666|IPC_CREAT);//不存在就创建
	if(sem_id==-1)
	{
		perror("semget error");
		exit(1);
	}


	//对信号p操作
	struct sembuf sops;
	sops.sem_num=0;
	sops.sem_op=-1;//p
	sop.sem_flg=0;
	if(semop(sem_id,&sops,1)<0){
		perror("semop error");
		return -1;
	}

	//互斥区
	printf("互斥区操作\n");
	sops.sem_op=1;//v操作
	if(semop(sem_id,&sops,1)<0){
		perror("semop error");
		return -1;
	}

	//删除信号量
	if(semctl(sem_id,0,IPC_RMID)<0)
	{
		perror("semctl error");
		return -1;
	}
	return 0;
}

posix信号量:

  1. posix信号量通常使用sem_open函数创建,信号由斜杠字符/开头的名称标识
  2. posix信号量可以在进程之间共享,进程可以使用上面的/开头的名称标识打开相同的信号量
  3. posix信号量支持使用sem_wait(P)、sem_post(V)、sem_close(关闭)等操作
  4. posix可以使用sem_ulink显示销毁,也可以在最后一个进程使用sem_close关闭信号量的时候自动销毁

System-V IPC信号量:

  1. 这种信号量通常使用semget系统调用创建,由一个整数标识符标识
  2. ipc信号量也可以在进程之间共享,但是这种共享是基于整数标识符而不是名称标识符
  3. ipc信号量的操作是使用semop系统调用来完成的,该调用允许在单个原子操作中执行一组信号量操作。
  4. ipc信号量使用带有IPC_RMID命令的semctl来删除信号量

在文件一章中,我们讨论了mmap(),它可以用于在进程之间共享内存。您能否将此技术与IPC的共享内存进行比较,并说明哪种技术更适合在进程之间共享大数据。

mmap: mmap是一种将文件内存映射到进程的虚拟地址空间的技术,在这种机制下,文件可以被视为内存的一部分,可以实现程序直接对这部分的内存进行读写操作,实现像访问内存一样对文件进行读写,这种方法提高了处理的效率,简化了文件的操作。

共享内存:共享内存使用的是shmget和shmat;共享内存提供semop,可以用于共享内存的进程之间的同步

二者区别:
数据源和持久化:
mmap: 通过 mmap 映射的数据通常来自文件系统中的文件。这意味着数据是持久化的——即使程序终止,文件中的数据依然存在。当你通过映射的内存区域修改数据时,这些更改最终会反映到磁盘上的文件中。
共享内存:共享内存是一块匿名的(或者有时与特定文件关联的)内存区域,它可以被多个进程访问。与 mmap 映射的文件不同,共享内存通常是非持久的,即数据仅在计算机运行时存在,一旦系统关闭或重启,存储在共享内存中的数据就会丢失。

使用场景:
mmap:mmap 特别适合于需要频繁读写大文件的场景,因为它可以减少磁盘 I/O 操作的次数。它也允许文件的一部分被映射到内存中,这对于处理大型文件尤为有用。
共享内存:共享内存通常用于进程间通信(IPC),允许多个进程访问相同的内存区域,这样可以非常高效地在进程之间交换数据。

性能和效率:
mmap:映射文件到内存可以提高文件访问的效率,尤其是对于随机访问或频繁读写的场景。系统可以利用虚拟内存管理和页面缓存机制来优化访问。
共享内存:共享内存提供了一种非常快速的数据交换方式,因为所有的通信都在内存中进行,没有文件 I/O 操作。

同步和一致性:
mmap:使用 mmap 时,必须考虑到文件内容的同步问题。例如,使用 msync 调用来确保内存中的更改被同步到磁盘文件中。
共享内存:在共享内存的环境中,进程需要使用某种形式的同步机制(如信号量、互斥锁)来避免竞争条件和数据不一致。

简而言之,mmap 主要用于将文件映射到内存以提高文件操作的效率,而共享内存主要用于进程间的高效数据交换。二者虽有相似之处,但各自适用于不同的应用场景,如果文件非常大,以至于无法或不方便完全加载到内存中时,可以使用mmap映射整个文件,并像访问内存数组一样访问文件的任何部分,而无需加载整个文件;如果是进程之间需要快速共享大量数据,可以使用共享内存。

你可能感兴趣的:(操作系统,linux,操作系统)