SyStem V共享内存
操作系统会申请一块内存,然后将这块内存映射到对应进程的进程地址空间,这块内存就是共享内存。进程之间可以通过访问这块内存从而实现通信
步骤:
1、操作系统创建内存
2、将内存映射到进程地址空间
3、取消进程和内存的映射关系,释放内存
共享内存是一种通信方式,所有需要通信的进程都可以使用,并且在操作系统中存在着大量的共享内存。也就是说通过让不同进程看到同一块内存的方式就叫做共享内存
一般共享内存的大小为4KB的整数倍
每一个共享内存都有一个key值标识唯一性,而每一个共享内存都由属于自己的id,进程间为了保证访问的是同个内存就必须通过key值去创建或者获取共享内存
共享内存 = 物理内存块 + 共享内存的相关属性
操作系统在创建共享内存时,同时会创建一个结构体对象(struct shm{})出来里面包含了所有共享内存的属性,而对共享内存的管理就变成了对结构体对象的管理。因为操作系统中有很多的共享内存,所以这些共享内存都会有对应的结构体对象,而操作系统会将这些对象组织起来统一管理。
每个共享内存结构体对象里都有唯一且不同的key值,这就能保证共享内存的唯一性,进程之间也就可以用过key值找到对应的共享内存。shmid也是共享内存的标识符,是基于用户层面的。
共享内存的生命周期是随着操作系统的,并不是跟随进程的,只要不关闭操作系统,即使进程退出,共享内存还是会存在的。除非使用接口将其删除
共享内存的特点:
所有的进程通信最快的方式,能大大的减少数据的拷贝次数。
没有同步互斥,不对数据进行保护
共享内存只有在当前映射连接数为0时才会被删除释放
共享内存的接口
ftok — 创建key值
为了能够使进程间能够访问的是同一块内存,则需要一个唯一的key值标识。也就是shmget接口的第一个参数。这个key值也需要使用接口创建出来
#include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
参数是可以自定义的,只要进程是使用由相同的两个参数创建出来的key值去创建共享内存,那么它们访问的内存就一定是同一块内存。
相同的参数创建出来的key值是一样的
#define PATHNAME "." #define PROJ_ID 22 //创建key值 key_t getKey(){ key_t keynum = ftok(PATHNAME, PROJ_ID); //key值小于0则创建失败 if(keynum < 0){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(1); } return keynum; }
shmget — 创建共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
参数一为保证进程访问的是同一块内存的唯一性的标识。
参数二为空间大小
参数三为创建内存的选项,IPC_CREAT(如果创建的内存不存在则创建,如果存在则获取已存在的内存)、IPC_EXCL(无法单独使用,需要搭配IPC_CREAT使用。代表如果不存在则创建,存在则报错返回。意义代表着创建出来的内存一定是新的内存)
返回值:成功创建会返回内存的标识符,不兼容文件的标识符。失败则返回-1。
//因为shmget是有不同的选项的,因此可以分为只创建和获取不存在则创建两种 //不同类型创建共享内存 int GetOrCreat(key_t keynum, int flags){ int shmid = shmget(keynum, 1024, flags); if(shmid < 0){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(2); } return shmid; } //只创建不获取,存在则报错 //因为共享内存也是文件,因此创建时要加上文件的权限 int CreatrSHM(key_t keynum){ return GetOrCreat(keynum, IPC_CREAT | IPC_EXCL | 0666); } //创建获取共享内存,不存在则创建 int GetSHM(key_t keynum){ return GetOrCreat(keynum, IPC_CREAT); }
命令行的操作:
ipcs -m — 查看共享内存
ipcrm -m XXX(共享内存的id) — 删除共享内存
shmat — 将共享内存映射到进程地址空间
进程间想要访问共享内存就必须把共享内存映射到自己的地址空间中。
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr);
参数一 共享内存的id
参数二 进程地址空间的指定区域,因为这个不关心所以可以设为空
参数三 映射的选项,默认为0即可
失败返回-1
//映射共享内存 void* AttchSHM(int shmid){ void* mem = shmat(shmid, nullptr, 0); if((long long)mem == (long long)-1){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(4); } return mem; }
shmctl — 控制共享内存 — 获取属性/设置属性/删除共享内存
一般而言这个接口常用于删除共享内存
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数一为 共享内存的id
参数二为 控制的选项 — IPC_STAT(获取属性)、IPC_SET(设置属性)、IPC_RMID(删除)
参数三为 放置属性容器,删除共享内存时设为nullptr
删除失败返回-1,成功返回0
//删除共享内存 void DeleteSHM(int shmid){ if(shmctl(shmid, IPC_RMID, nullptr) == -1){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(3); } }
shmdt — 取消进程与共享内存的关联
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shmaddr);
参数和shmat一样
//取消关联 void DeleteProcSHM(void* start){ if(shmdt(start) == -1){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(5); } }
操作示例
comm.hpp
#ifndef _COMM_HPP_ #define _COMM_HPP_ #include<iostream> #include<sys/ipc.h> #include<sys/shm.h> #include<sys/types.h> #include<cerrno> #include<cstring> #include<cstdio> #include<unistd.h> #define PATHNAME "." #define PROJ_ID 22 //创建key值 key_t getKey(){ key_t keynum = ftok(PATHNAME, PROJ_ID); if(keynum < 0){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(1); } return keynum; } //不同类型创建共享内存 int GetOrCreat(key_t keynum, int flags){ int shmid = shmget(keynum, 1024, flags); if(shmid < 0){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(2); } return shmid; } //只创建不获取,存在则报错 int CreatrSHM(key_t keynum){ return GetOrCreat(keynum, IPC_CREAT | IPC_EXCL | 0666); } //创建获取共享内存,不存在则创建 int GetSHM(key_t keynum){ return GetOrCreat(keynum, IPC_CREAT); } //删除共享内存 void DeleteSHM(int shmid){ if(shmctl(shmid, IPC_RMID, nullptr) == -1){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(3); } } //映射共享内存 void* AttchSHM(int shmid){ void* mem = shmat(shmid, nullptr, 0); if((long long)mem == (long long)-1){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(4); } return mem; } //取消关联 void DeleteProcSHM(void* start){ if(shmdt(start) == -1){ //将错误打印到标准错误 std::cerr << errno << ":" << strerror(errno) << std::endl; exit(5); } } #endif
Rshm.cc
#include"comm.hpp" int main(){ key_t keynum = getKey(); int shmid = GetSHM(keynum); std::cout << shmid << std::endl; char* mem = (char*)AttchSHM(shmid); while(1){ printf("好的收到:%s\n", mem); sleep(1); } DeleteProcSHM(mem); DeleteSHM(shmid); return 0; }
Wshm.cc
#include"comm.hpp" int main(){ key_t keynum = getKey(); int shmid = CreatrSHM(keynum); std::cout << shmid << std::endl; char* mem = (char*)AttchSHM(shmid); int cnt = 1; while(1){ snprintf(mem, 1024, "发送数据了,请接收: %d", cnt++); sleep(1); } DeleteProcSHM(mem); return 0; }
需要注意运行的先后顺序,必须要先运行只创建内存的进程
总结
进程间通信的最主要步骤就是让进程们能够访问的到同一块空间,不同的通信方式的本质就是访问的空间不同