1.System V共享内存的原理
通过为用户提供系统调用接口,让用户可以申请一块空间,进程A/B也可以通过系统调用接口将创建好的内存通过页表映射进进程的地址空间。完成让不同的两个进程看见同一份资源的目的。如果未来不想继续通信,取消进程和内存的映射关系,释放内存。这块在物理内存上申请的这段空间称为共享内存,让进程和共享内存关联起来的行为叫做挂接,取消进程和内存的映射关系叫做去关联。
这种申请共享内存的方式不能使用Malloc,malloc也可以在物理内存上申请空间,返回它的虚拟地址;但是malloc没有提供让其它进程也看到这块内存的接口,只是本进程私有。
2.共享内存的概念
通过让不同的进程看到同一个内存块的方式就叫做共享内存。
IPC_CREAT:不存在就创建,存在即获取它。
IPC_EXCL:无法单独使用。和IPC_CREAT组合使用:不存在就创建,存在就出错返回,如果创建成功,一定是一个新的shm!
如果两个进程传入的pathname是一样的,那么key值就一样,就能唯一性看到同一个共享内存。
再谈key:
在操作系统存在多个共享内存;key值是多少没表示什么含义,能进行唯一性标识最重要。
C语言中free(p)?->语言库对申请出来的空间做管理,会多余申请空间。共享内存也是如此,先描述,再组织;共享内存=物理内存块+共享内存相关属性。只要另一个进程拿到同一个key就可以访问到需要通信的共享内存;key作为共享内存结构的一个属性struct shm,在创建共享内存时就传参设置key值,获取共享内存时就是查找key值匹配的struct shm。
shmid:
shmid->fd;key->inode;内核级的标定和上层的标定;shmid和fd都是给用户使用的用来标识的。为什么不统一使用key和inode?用于在不同场景下的解耦,防止系统改变影响应用层。
1.如何查看IPC资源
管道:通信进程退出了,文件描述符自动被释放;共享内存在进程退出后,依旧存在(用ipcs -m查看共享内存;ipcs -q消息队列;ipcs -s信号量;ipcrm -m shmid删除某个共享内存)
2.IPC资源的特征:
共享内存的生命周期是随操作系统的,不随进程;System V版本通信的共性。
让共享内存和进程关联起来
通过修改页表,创建的共享内存和当前进程关联起来,在虚拟地址空间中找到一个没有被使用的空间,建立映射关系之后把它的起始地址返回,返回值就是共享内存空间在虚拟内存的起始地址。
在用完后可以直接把共享内存删掉,但是先去关联再删除会比较完善
共享内存的特点:
优点:所有进程通信中速度最快的!只要进程1将内容写入,进程2立马就能看到,减少数据拷贝次数(不用定义buffer)。同样的代码,键盘输入和显示器输出,用管道和共享内存来实现分别需要多少次拷贝?4/2
缺点:当发的速度慢,读的快时,会一直打印陈旧数据;共享内存不进行同步和互斥的操作的,没有对数据做任何保护。改善:没通知server时,令其阻塞等待,通过再建立一个匿名管道,每当client端写入共享内存信息时,给管道也传入一个信号;当server端读取时先阻塞等待管道信号。
comm.hpp :为shm_server和shm_client提供了7个接口
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#define PATHNAME "."
#define PROJ_ID 0x66
#define MAX_SIZE 4096
//系统分配共享内存是以4KB为单位的,向上取整;尽量按照4kb的整数倍;一个4KB相当于一个Page
key_t getKey()
{key_t k = ftok(PATHNAME,PROJ_ID);//可以获取同样的keyif(k<0){//cin,cout,cerr -> stdin,stdout,stderr -> 0 1 2;//cerr向标准错误中打印,strerror(errno)把错误码转换成错误描述std::cerr<<errno<<":"<<strerror(errno)<<std::endl;exit(1);//终止程序}return k;
}
int getShmHelper(key_t k,int flags)
{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)//给之后的进程使用,获取共享内存
{//存在就获取,不存在创建return getShmHelper(k,IPC_CREAT/*0*/);//也可以设置0,0的语义和IPC_CREAT相同。
}
int createShm(key_t k)//给第一个进程使用,创建共享内存
{return getShmHelper(k,IPC_CREAT|IPC_EXCL|0600);//获取失败返回错误
}
void *attachShm(int shmid)//把共享内存贴到虚拟地址空间上
{void *mem = shmat(shmid,nullptr,0);//挂接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)
{if(shmctl(shmid,IPC_RMID,nullptr)==-1){std::cerr<<errno<<":"<<strerror(errno)<<std::endl;}
}
#endif
shm_server.cc:
#include "comm.hpp"
int main()
{//获取唯一标识keykey_t k = getKey();printf("key:0x%x\n",k);//key//创建共享内存int shmid = createShm(k);printf("shmid:%d\n",shmid);//shmidsleep(5);//将共享内存和进程关联起来char *start = (cahr*)attachShm(shmid);printf("attach success,address start:%p\n",start);while(true){//char buffer[];read(pipefd,buffer,...)printf("client say: %s\n",start);sleep(1);}//在用完后可以直接把共享内存删掉,但是先去关联再删除会比较完善detachShm(start);sleep(5);//释放共享内存delShm(shmid);return 0;
}
shm_client.cc:
#include "comm.hpp"
#include <unistd.h>
int main()
{key_t k = getKey();printf("key:0x%x\n",k);//获取共享内存int shmid = getShm(k);printf("shmid:%d\n",shmid);sleep(5);//共享内存和进程关联char *start = (char*)sttachShm(shmid);printf("attach success,address start:%p\n",start);const char* message = "hello server,我是另一个进程,正在和你通信";pid_t id = getpid();int cnt = 1;//char buffer[1024];while(true){snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,cnt);// snprintf(buffer,sizeof(buffer),"%s[pid:%d][消息编号:%d]",message,id,cnt);//将特定的字段格式化,写入字符串中// memcpy(start,buffer,strlen(buffer)+1);//pid,count,message}//在用完后可以直接把共享内存删掉,但是先去关联再删除会比较完善detachShm(start);return 0;
}