Linux:详解进程间通信——共享内存和消息队列相关接口及其代码验证(图文并茂)(二)


1. 共享内存

1.1 原理

  • 首先在物理内存中创建了一块内存空间。
  • 不同的进程通过页表映射,将同一块物理内存映射到自己的虚拟地址。
  • 不同的进程,操作进程虚拟地址,通过页表映射,就相当于操作同一块内存,从而完成了数据交换。

原理如图所示:
在这里插入图片描述

1.2 接口

共享内存的接口均在#include<shm.h>头文件中

1.2.1 创建共享内存

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

参数:

  • key:共享内存的标识符,用来标识一块共享内存,在操作系统中,共享内存的标识是不能重置的。可以直接给一个32位的16进制的数字。
  • size:共享内存的大小
  • shmflg:
    ① IPC_CREAT:如果key标识的共享内存不存在,则创建。
    ② IPC_EXEC | IPC_CREAT:如果key标识的共享内存存在,则保存。该参数通常是要搭配IPC_CREAT一起使用。
    ③ 权限:需要按位或上一个8进制的数字

返回值:

  • -1:失败
  • >0:成功,返回的是共享内存的操作句柄。(相当于open函数返回的对应的文件描述符一样)

查看共享内存的命令:ipcs m

在这里插入图片描述

1.2.2 附加共享内存到进程

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

参数:

  • shmid:共享内存的操作句柄,shmget的返回值
  • shmaddr:将共享内存附加到shmaddr,一般情况下,都不会自己去指定映射到共享区当中的那一块虚拟地址,而是传递NULL值,让操作系统去选择
  • shmflg:标志着将共享内存附加到进程之后,进程对共享内存的读写属性
  • 0:读写失败
  • SHM_RDONLY:只读

返回值:

  • 附加成功:返回值为附加到共享内存的中的虚拟地址,类型为 void*
  • 附加失败:返回值为NULL

1.2.3 分离共享内存

int shmdt(const void* shmaddr)

参数:

  • shmaddr:调用shmat函数获取到的共享内存的虚拟地址

1.2.4 共享内存操作函数

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

该函数可以类比获取 / 设置文件描述符属性的fcntl函数。

参数:

  • shmid:共享内存的操作句柄

  • cmd:
    ①IPC_STAT:获取共享内存参数
    ②IPC_SET:设置共享内存的属性
    ③IPC_RMID:删除共享内存

  • struct shmid_ds:是共享内存的属性对应的结构体。具体定义如下:

在这里插入图片描述
该结构体可以类比Linux基础IO中的struct file结构体。

参数cmd的使用:

① IPC_STAT:获取共享内存参数

struct shmid_ds tmp;
shmctl(shmid,IPC_STAT,&tmp);

②IPC_SET:设置共享内存的属性

struct shmid_ds tmp;
tmp.segsz = 1024;
shmctl(shmid,IPC_SET,&tmp);

③IPC_RMID:删除共享内存

shmctl(shmid,IPC_RMID,NULL);

1.2.5 共享内存接口的代码验证

我们可以创建两个文件testA.c和testB.c,利用shmget函数创建出共享内存,然后testA.c在共享内存中写入"It’s test to share memory,I am testA ",然后在testB.c中对共享内存中的数据进行读,并将其输出在屏幕上,规定共享内存的标识符为0x86868686

代码如下:
testA.c

#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>

#define key 0x86868686

int main()
{
    puts("start to testA to write shm");
    int shmid = shmget(key,1024,IPC_CREAT | 0664);

    void* addr = shmat(shmid,NULL,0);

    sprintf((char*)addr,"It's test to share memory,I am testA");
    puts("end to testA to write shm");
    return 0;
}

碎片知识:在往共享内存中写数据的时候,可以用strncpy、sprintf、memcpy

testB.c

#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>

#define key 0x86868686

int main()
{
    puts("start to testB to read shm");
    int shmid = shmget(key,1024,IPC_CREAT | 0664);

    void* addr = shmat(shmid,NULL,0);

    printf("%s\n",(char*)addr);
    puts("end to testB to read shm");
    return 0;
}

运行结果:

在这里插入图片描述

使用ipcs -m查看刚才的共享内存

在这里插入图片描述
可以看到,共享内存创建成功了,并且随着程序的结束,共享内存并没有释放。

删除共享内存:

方法一:在文件中使用shmctl函数删除。

在testB.c文件中加上shmctl(shmid,IPC_RMID,NULL);语句,结果如下:
在这里插入图片描述

方法二:在命令行中使用ipcrm -m [shmid]命令进行删除

在这里插入图片描述

1.3 特性

  • 共享内存的生命周期跟随操作系统的内核
  • 共享内存的读取,采用的是拷贝读,而不是拿走读(这是和管道的一个本质区别)
  • 共享内存的写是覆盖写

那么问题来了,如果删除一个被进程附加的共享内存,会发生什么情况?

操作:让testA程序不结束,一直运行,然后删除其对应的共享内存

在这里插入图片描述

现象:

当前共享内存的shmid会变成0x00000000,并且共享内存的status会变为dest(destory)
在这里插入图片描述

并且当,testA程序结束后,该共享内存就会被释放。

本质:

我们能通过ipcs -m查看当前被删除共享内存的信息,说明在操作系统内核,描述该内存的结构体没有被释放,但是共享内存所使用的空间已经被释放掉了,所以,附加的进程如果再次操作共享内存就会面临崩溃的风险。

2. 消息队列

2.1 相关概念

① 消息队列首先是一个队列,具有先进先出的性质。

消息队列的本质上也是内核当中维护的一个双向链表,但是满足了先进先出的特性,所以被称为队列。

③ 消息队列当中的消息,带有类型的数据,类型和类型之间是有优先级的。

消息队列可以获取同种类的,距离队首最近的元素

2.2 接口

消息队列的接口均在#include<msg.h>头文件中

2.2.1 创建消息队列

int megget(key_t key,int msgflg)

参数:

  • key:消息队列的标识符
  • msgflg:IPC_CREAT,……,同shmget中的属性

返回值:

返回消息队列的操作句柄

2.2.2 将消息写入到消息队列

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

参数:

  • msqid:消息队列的操作句柄
  • msgp:要发送到消息队列的信息(和shmctl 函数的struct shmid_ds一样)该信息是一个消息队列的结构体

在这里插入图片描述
long mtype:一定要大于0,它表示的是消息的类型
char mtext[1]:发送的数据只是一个类型,程序员在发送数据的时候,传递的结构体中一定要有一个变量为long的类型,表示消息的优先级,至于小的数据,程序员可以自己指定。

  • msgsz:指定发送的数据大小,只计算自己发送的数据大小,不算long类型
  • msgflg:
    ① IPC_NOWAIT:非阻塞的发送方式
    ② 0 :阻塞发送

返回值:

成功发送0,失败返回-1.

碎片知识:long类型在Linux中32位OS下是4Byte,64位OS下是8Byte,64位Windows OS下是4Byte。

2.2.3 从消息队列读取消息

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

参数:

  • msqid:消息队列的操作句柄

  • msgp:最大接收能力(即最大能接收多大)

  • msgsz:
    ① >0:表示获取队列中距离队首最近的同类型的元素
    ② ==0:直接获取队首的元素
    ③ <0:详见下方解释

  • msgflg:同上msgsnd函数的属性

详解msgsz参数的 < 0 属性:

① 需要将 < 0 的msgtype的值取绝对值
② 从队首一直到 |msgtype|区间的消息全部过滤出来
③ 从区间中获取和绝对值msgtype一样的消息,若是没有获取到,则进行步骤④
④ 在区间中取类型最小的消息

举个例子:

例子1:若传入的msgsz是 - 5,且消息队列中的消息是[1,2,3,4,5,6,7,8,9]。

由于msgsz < 0,则过滤消息队列中 -5 到 5 这个区间的消息,即[1,2,3,4,5],然后获取和绝对值一样的消息 5。

例子2:若传入的msgsz是 - 5,且消息队列中的消息是[1,2,3,4,6,7,8,9]。

由于msgsz < 0,则过滤消息队列中 -5 到 5 这个区间的消息,即[1,2,3,4,6],但是不存在和绝对值相同的消息,那么就获取该消息队列中的最小值 1

碎片知识:System V标准是不支持跨平台的,而Posix标准是支持跨平台的

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值