原理图
进程间通信需要让不同进程看到同一块内存资源。用户使用操作系统提供的接口在物理内存中申请一块资源,通过进程的页表将这段物理空间映射至进程地址空间,进程将这段虚拟地址的起始地址返回给用户
共享内存原理:
共享内存也是通过先描述再组织方式;共享内存=物理内存块+共享内存的相关属性,共享内存的属性中会存在一个字段表示key
共享内存的生命周期随OS内核,所以需要在使用完毕后取消进程与内存的映射关系并释放内存
ipcs -m/q/s 查看共享内存
ipcrm -m [shmid](共享内存id)
删除共享内存:删除指令在应用层,要用到上层的shmid
可通过man 2/3查看接口具体信息
形成标识符key
key_t ftok(const char pathname, int proj_id )
获取key,参数为 路径名 | 八位非零有效数字
申请共享内存:
int shmget(key_t key, size_t size, int shmflg);
key:来源于ftok(),能够唯一标识一块内存,设置进共享内存属性中,保证该共享内存在内核中的唯一性 size:shm(共享内存大小)
shmflg:标记位 IPC_CREAT不存在shm就创建,存在就获取
IPC_EXCL|IPC_CREAT 不存在创建,存在出错返回,保证新创建的是新的shm
成功时,将返回一个有效的共享内存标识符
shm与进程关联
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr:要把共享空间映射到虚拟地址哪里,一般是nullptr
shmflg:读写权限,一般是0
返回值:成功->共享空间的起始地址 失败-1
解除关联:shmdt(const void *shmaddr)
参数shmaddr:shmat()返回值
控制共享内存
**int shmctl(int shmid, int cmd, struct shmid_ds *buf);
应用层使用,要用上层的共享内存id
cmd:各种宏,IPC_PMID表示立即删除共享内存
buf:是一个结构体类型指针,暂时设置成nullptr即可
共享内存级进程通信
1.创建共享内存
2.关联共享内存
3.开始通信
4.通信结束,取消关联,删除共享内存
工具实现
//comm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include
#include
#include
#include
#include
#include
#include
#define PATHNAME "."//共享内存路径
#define PROJ_ID 0x66 //八位非零有效数字
//系统分配共享内存是以4KB为单位的,如果是4097,内核会向上取整
#define MAX_SIZE 4096
key_t getKey()
{
//能够唯一标识一块内存,设置进共享内存属性中,保证该共享内存在内核中的唯一性
key_t k= ftok(PATHNAME,PROJ_ID);
if(k<0)
{
//cin,cout,cerr对应stdin,stdout,stderr,0,1,2
//标准错误输出流,输出当前发生的错误信息
std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
exit(-1);
}
return k;
}
//IPC_CREAT不存在shm就创建,存在就获取
//IPC_EXCL|IPC_CREAT 不存在创建,存在出错返回,保证新创建的是新的shm
int getShmHelper(key_t k,int flags)
{
//shmid和k 类似于于锁和钥匙的关系,
//相当于现实世界中学号和身份证号,都是指定某一个人,为了解耦,用两个标识
//应用层shmid,系统调用用K,即便系统调用改变,也不影响上层使用
//shmid要把k设置进共享内存属性中,用来在内核中标识唯一性
int shmid=shmget(k,MAX_SIZE,flags);
if(shmid<0)
{
std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
exit(2);
}
return shmid;
}
int getShm(key_t k)//获取shm
{
return getShmHelper(k,IPC_CREAT);
}
int CreateShm(key_t k)//创建shm
{
//0600共享内存权限
return getShmHelper(k,IPC_CREAT|IPC_EXCL|0600);
}
//shm与进程关联
void* attachShm(int shmid)
{
void * mem = shmat(shmid,nullptr,0);//linux下指针占8个字节
if((long long)mem==-1L)
{
std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
exit(3);
}
return mem;
}
//解除关联
void detachShm(void* start)
{
if(shmdt(start)==-1)
{
std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
}
}
//删除共享空间
void delShm(int shmid)
{
//IPC_PMID立即删除共享内存
if(shmctl(shmid,IPC_RMID,nullptr)==-1)
{
std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
}
}
#endif
获取共享内存,通信的的发送方
//shm_client.cc
#include "comm.hpp"
#include
int main()
{
key_t k=getKey();
printf("key: 0x%x\n",k);
int shmid=getShm(k);
printf("shmid: %d\n",shmid);
//sleep(5);
//关联shm
char* start=(char*)attachShm(shmid);
printf("attach success,address start:%p\n",start);
//使用
const char* str="我是client,我正在给你通信";
pid_t id=getpid();
int cnt=1;
while (true)
{
sleep(3);
//不需要建立缓冲区,直接往shm里面拷贝数据
snprintf(start,MAX_SIZE,"%s[my_pid:%d][消息编号:%d]",str,id,cnt++);
// snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt++);
// memcpy(start, buffer, strlen(buffer)+1);
}
//取消关联
detachShm(start);
return 0;
}
负责共享内存的创建以及删除,通信的接收方
//shm_server.cc
#include "comm.hpp"
#include
//由于此文件负责创建shm,一定要先运行
int main()
{
key_t k=getKey();
printf("key: 0x%x\n",k);
int shmid=CreateShm(k);
printf("shmid: %d\n",shmid);
// sleep(5);
//关联shm
char* start=(char*)attachShm(shmid);
printf("attach success,address start:%p\n",start);
//使用
while (true)
{
printf("client say:%s\n",start);
// struct shmid_ds ds;
// shmctl(shmid,IPC_STAT,&ds);
// printf("获取属性:size: %d,pid: %d,myself: %d,key:0x%x",\
// ds.shm_segsz,ds.shm_cpid,getpid(),ds.shm_perm.__key);
sleep(1);
}
//取消关联
detachShm(start);
sleep(10);
//删除共享内存
delShm(shmid);
return 0;
}
Makefile文件
.PHONY:all
all:shm_client shm_server
shm_client:shm_client.cc
g++ -o $@ $^ -std=c++11
shm_server:shm_server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f shm_client shm_server
或许对以下代码存在疑惑,让我们用man查一下shmctl
shmid_ds是存放着共享内存的属性的结构体,每一个字段都对应一种属性
其中最关键的是ipc_prem里的__key字段,当使用shmget的时候,key会被设置进共享内存的属性中,用来标识该共享内存在内核中的唯一性
1)是所有通信方式最快的,能大大减少数据的拷贝次数【不考虑输入输出的情况:管道需要4次 ,IPC只需要两次】
2)共享内存生命周期随OS,不是随着进程----->进程退出他并没有消失
3)共享内存不对数据做任何保护,没有同步和互斥操作,同一个数据可能被读多次。客户端和服务端没做保护,如果想做保护要用到信号量,对共享内存进行保护,写完通过读端进行读取。
.