进程间通信(共享内存)
目录
前置:
一 原理
二 API
1. shmgetr
2. shmctl
3. 指令操作
2. 删除
3. 挂接
4. 断开挂接
三 demo代码
四 共享内存的特征
前置:
1.前面说的不管是匿名管道还是命名管道都是基于文件的思想构建的一套进程间通信的方案,那有没有完全从0单独设计一套呢?
2. 共享内存作为 system v 标准,是由操作系统自主从0搭建的一套进程间通信机制,相对于管道而言也更快,那么下面来看看具体是怎么实现的。
一 原理
1. 进程之间通信之前必须要看到同一份资源,比如管道,看到同一个管道文件(路径),子进程继承父进程文件描述符。
2. 共享内存其实是在内存中开辟一段空间,并把起始地址映射到虚拟地址空间的共享区当中,访问这个虚拟地址也就访问到内存上的空间了,那么2个毫不相干的进程怎么看到这个共享内存呢?
3. 之前说的动态库,在程序运行期间在内存中寻找库文件,找不到在去磁盘上加载,所以在内存中有很多的动态库,势必也要管理维护起来并且提供一个字段来标识这个实例,共享内存也同样如此,在内存中也有个结构进行维护并标识起来。
4. 让不同进程看到同一份标识,系统提供了一个API
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, // 随便传入路径int proj_id // 随便传入一个id// key_t -> int);
1. 传入一个路径和id并通过算法形成一个key_t结构,本质是int类型,算法是固定的,所以不同的进程传入相同的字符串和id得到的key_t都是一样的,这是用户传入数据生成的。
2. 返回值为-1错误,其他均正确。
所以不同的进程传入相同的路径和id就能看到同一个共享内存了。
5. 建立共享内存的映射,然后就需要进行把进程和共享内存进行挂接,也就是绑在一起,进行后续通信,结束在断开挂接。
6. 共享内存只有创建和挂接/断开挂接/释放共享内存的时候使用了系统调用,通信的时候不需要,所以直接在用户层直接向物理内存写入数据,是进程间通信里最快的,仅需一次拷贝物理内存里的数据。
二 API
1. shmgetr
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, // 用户生成的 key 值 size_t size, // 共享内存的大小int shmflg // 选项: 创建/获取....);// 返回值: 内核维护的 shmid 值,用来管理共享内存的结构
2. shmctl
#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, // shmget的返回值int cmd, // 对共享内存: 删除/修改等操作struct shmid_ds *buf // 共享内存的结构里的字段);
// 返回值: 成功为0,失败-1
为什么有了 key ,还要有 shmid ?key是用户形成的只是用来获取和创建共享内存的,并不能对已经存在的共享内存进行字段修改/结构修改等,只有shmid能操作,本质就是 shmid 属于内核提供的,key是有用户提供的,他们的功能侧重点不同,用户管用户,内核管内核,变相的解耦。
3. 指令操作
1. 查看
ipcs -m // 共享内存相关信息
ipcs -q // 消息队列相关信息
ipcs -s // 信号量相关信息
查询到的字段
key: 用户形成的随机值,用来获取,创建访问共享内存。
shmid:shmget的返回值,用来管理共享内存结构的。
owner:谁创建的。
perms:权限是什么。
bytes: 共享内存多大。
nattch:谁挂接上了。
status: 状态标识。
2. 删除
ipcrm -m/q/s shmid
3. 挂接
#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, // shmget的返回值const void *shmaddr, // 共享内存的起始地址int shmflg // 权限设置);
4. 断开挂接
#include <sys/types.h>
#include <sys/shm.h>int shmdt(const void *shmaddr // shmat的返回值);
三 demo代码
#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <cstring>// htok 用来形成随机的 key_t
const static std::string pathkey = "/home/CD/linux/shared_memory";
const static int projid = 123456;// 区分创建者和使用者
const static std::string Create = "Create";
const static std::string User = "User";// 共享内存大小
const int shmbuff = 4096;// 转16进制
std::string to_hex(int val)
{char buff[1024] = {0};snprintf(buff, 1024, "0x%x", val);return buff;
}// SHM类
class SHM
{
private:// 进行挂接bool Shmat(){_buff = shmat(_shmid, nullptr, 0);if (_buff == (void *)-1)return false;return true;}// 创建共享内存并获取 shmidbool Createshmid(){_shmid = shmget(_key, shmbuff, IPC_CREAT | IPC_EXCL | 0666);if (_shmid == -1){std::cout << "shmgid create failed" << std::endl;return false;}return true;}// 获取已经存在的共享内存的shmidbool Getshmid(){_shmid = shmget(_key, shmbuff, IPC_CREAT);if (_shmid == -1){std::cout << "shmget get failed" << std::endl;return false;}std::cout << _shmid << std::endl;return true;}public:// 构造对象SHM(const std::string &path, int proj_id, const std::string &who): _path(path), _proj_id(proj_id), _who(who), _buff(nullptr){_key = ftok(_path.c_str(), proj_id);if (_key == -1){std::cout << "ftok failed" << std::endl;exit(-1);}}// 创建共享内存/获取共享内存shmid/挂接共享内存/获取共享内存的起始地址void *run(){if (_who == Create){if (Createshmid() == false)exit(-2);if (Shmat() == false){std::cout << "Shmat failed" << std::endl;exit(-3);}}else{if (Getshmid() == false)exit(-2);if (Shmat() == false){std::cout << "Shmat failed" << std::endl;exit(-3);}}memset(_buff, 0, shmbuff);return _buff;}// 释放共享内存/断开挂起~SHM(){if (_who == Create){int n = shmctl(_shmid, IPC_RMID, nullptr);if (n == -1){std::cout << "ftok failed" << std::endl;exit(-3);}}if (_buff != nullptr)shmdt(_buff);}private:std::string _path;int _proj_id;std::string _who;int _shmid;key_t _key;void *_buff;
};
四 共享内存的特征
1. 由于通信是在用户层直接写到物理内存,没有系统调用,所以不像管道有read/write接口,read的时候没有数据内核会阻塞,而共享内存则没有任何保护机制,也就是无同步无互斥,所以需要应用层进行处理。
2. 共享内存是进程间通信里最快的,用户层直接通过共享映射的虚拟地址写入即可,无任何系统调用。
3. 共享内存的大小是 4k 为基本单位的,不符合倍数就向上取整。
4. 共享内存的生命周期随内核,除非手动代码/指令进行释放。