关于 System V 标准,一共有三种通信方式,分别为:共享内存、信号量和消息队列三种通信方式。本篇将较为详细的讲解三种通信方式的实现原理,以及介绍在 Linux 系统下调用这三种的通信方式的接口,其中以共享内存为例,较为详细的讲解和用代码实现这种通信方式。
最后我们得出这三种通信方式存在很大的共同点,以及总结了操作系统对这三种通信方式的管理。目录如下:
目录
共享内存
1. 实现原理
共享内存为进程间通信方案,则一定会遵守进程间通信的的原则:让不同进程看见同一份资源。对于共享内存的实现原理为:操作系统会在物理内存中专门给需要通信的进程开辟一段物理内存,然后分别映射到不同进程的虚拟地址中,不同的进程就可以看到同一份的内存资源。原理图如下:
如上的步骤1:操作系统在物理内存中开辟出同于共享内存通信的物理内存;
步骤2:操作系统将物理共享内存通过页表映射到对应的虚拟地址中去。
共享内存实现的几个关键点:
1. 关于实现共享内存的所有操作,都是由OS(操作系统)完成的;
2. OS给需要共享内存通信的进程提供步骤1、2的系统调用,让他们通过系统调用来通信。
3. 共享内存在系统中可以同时存在多份,不同对进程之间可以同时进行通信。
4. 操作系统需要对共享内存进行管理,所以会有对应的共享内存数据结构,以及匹配的算法。
5. 共享内存 = 内存空间 + 共享内存的属性。
2. 代码实现进程的共享内存通信
共享内存的创建
关于使用系统调用实现共享内存的一个重要的系统调用,shmget,也就是用来获取在物理内存中的共享内存,使用方法如下:
int shmget(key_t key, size_t size, int shmflg); 系统调用中的参数: key:用于唯一的表示物理内存中存在的共享内存; size:需要开辟的物理内存的大小,通常建议为4096的倍数 shmflg:获取共享内存的方式,常用的为IPC_EXCL 和 IPC_CREAT, 关于这两个的搭配有: IPC_CREAT:若不存在共享内存则创建,存在则获取对应的共享内存且返回 总能获取一个 IPC_EXCL:不能单独的使用 IPC_CREAT | IPC_EXCL:对应的共享内存不存在则创建,存在则出错返回 只能获取新的
关于在 shmget 中的 key 参数,不同的进程想要进程通信,则填入的 key 值则需要相同,对于这个 key 值,我们一般不建议直接由我们自己来填,而是建议使用系统提供的 ftok 函数给我们随机生成一个,如下:
key_t ftok(const char *pathname, int proj_id);
只需要提供同样的 pathname 和同样的 proj_id,我们就可以生成同样的 key 值,也就可以让不同的进程之间实现通信。
共享内存的释放
还有一个需要注意的点:我们使用进程创建出的共享内存,并不会随着进程的结束而释放,进程结束仍然会保留在内存中,因为这是由底层的操作系统创建出的共享内存,所以我们需要在每一次进程结束的时候,将对应的共享内存关闭,防止内存泄漏。(共享内存的生命周期随内核,文件的生命周期随进程)
我们可以使用 ipcs -m 查看当前系统中存在哪些共享内存,然后使用 ipcrm -m shmid 删除对应的共享内存,如下:
共享内存的 key 与 shmid 的比较:
key:属于用户形成,内核使用的一个字段,内核用于区分共享内存的唯一性,用户不能使用 key 来进行共享内存的管理。
shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的 id 值。
使用系统调用删除共享内存为 shmctl,如下:
int shmctl(int shmid, int cmd, struct shmid_ds *buf); 该系统调用用于对共享内存的控制,可以为增删查改 但本篇只需要将共享内存释放,所以只需要填入的参数为: 共享内存的shmid 选择模式,删除为 IPC_RMID buf 为 nullptr
共享内存挂接
现在我们既然已经创建出对应的共享内存,只需要将共享内存挂接到对应的共享内存,然后就可以让对应进程之间开始通信了,如下:
void *shmat(int shmid, const void *shmaddr, int shmflg); 用于挂接共享内存 shmaddr表示将挂接共享内存的位置,通常可以设置为nullptr shmflg表示共享内存的访问权限 shmat的返回值:挂接成功为共享内存的起始地址,连接失败为nullptr int shmdt(const void *shmaddr); 用于将挂接上的共享内存给去掉 去挂接成功返回0,失败返回-1
共享内存间通信代码
Shm.hpp
#include <iostream> #include <string> #include <cerrno> #include <cstring> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <unistd.h> // 路径,随便使用一个路径都可以 const std::string SHPathName = "/home/JZhong/code/linux-code/test_7_20"; const int SHProj_Id = 0x8585; #define SHCreater 1 #define SHUser 0 #define SHsize 4096 class Shm { std::string ToHex(int n) { char buff[128]; int cx = snprintf(buff, sizeof(buff) - 1, "0x%x", n); buff[cx] = '\0'; return buff; } int GetShm(int shmflg) { int sh = shmget(_key, SHsize, shmflg); if (sh == -1) { std::cout << "Create shm fail!" << "the reason is " << strerror(errno) << std::endl;