之前写道:进程间通信的首要前提是,两个进程需要看到同一份资源,这份资源可以是一份文件或者一块内存,文件即管道,这次来聊一下被多进程都可以看到的内存快----共享内存
一:共享内存实现原理
首先,当A进程通过系统调用创建出一份共享内存时,操作系统会在进程的地址空间中划分一块区域作为进程所使用的共享内存,进程使用该共享内存时,操作系统会通过页表映射,找到物理内存中的共享内存,不了解进程地址空间的话可以看一下我往期博客
当A进程创建好共享内存后,B进程会通过内存标识来访问这块内存,并同样将这块空间映射到进程地址空间,进程将共享内存通过页表映射进自己的地址空间这一过程,叫做共享内存和进程进行关联,当未来进程不想要时,可以选择解除与共享内存的关联或者释放掉共享内存,一般情况下我们遵守谁创建谁释放原则,这里要多说一句,每个进程都可以创建共享内存,所以实际物理内存中可能存在很多共享内存
二:共享内存各个接口介绍及实现进程通信
首先创建共享内存的函数为
shmget:
int shmget(key_t key, size_t size, int shmflg);
key:
共享内存在内存中的标识,这个标识由调用者提供,此标识必须具有唯一性,可以把他想象成一把锁,将共享内存锁住,必须由特定钥匙才可以打开,这个钥匙呢,就是他的返回值,返回值称之为shmid,当我们想要访问这块内存时,需要提供shmid
size:
想创建共享内存的大小,一般为4KB的整倍数,因为一块存页是4kb,当你申请空间小于4kb,会分配4kb,大于4kb,如申请5kb,会分配两块内存页,造成了空间浪费
shmflg:
标识位,以位图结构标识指令,类似open时传入的O_RDONLY等标识,我们这里介绍两个最常用的
IPC_CREAT:如果key值共享内存不存在则创建,存在则返回,而如果带上IPC_EXCL的话,就代表如果存在则直接报错,中止该进程!而且创建共享内存和文件创建时一样,需要指明权限,如果不指明权限,则无法使用,权限指定规则与文件创建相同
returnvalue: 成功返回shmid,失败返回-1并设置errno
ftok:
key_t ftok(const char *pathname, int proj_id);
参数为文件名和项目id,传入后ftok会进行一套算法处理,并生成一个key值,失败返回-1并设置errno
shmat:
void *shmat(int shmid, const void *shmaddr, int shmflg);
按我们刚才所说,创建好后,需要让共享内存与进程的地址空间进行关联,关联的操作函数为shmat
shmid:刚才在shmget的key中有写,这就像一把钥匙,能开key的锁,实际上shmid是专门提供给上层使用的,而key值仅用来标识
shmaddr:指明关联到进程地址空间的那一部分,此参数非大佬慎用
shmflg: 权限相关,一般设置为0
return value: 返回进程地址空间中共享内存的起始地址,失败返回-1并设置errno
shmdt:
int shmdt(const void *shmaddr);
此函数用于解除关联,参数为由shmat返回的起始地址,成功返回0,失败返回-1并设置errno
shmctl:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
此函数作用很多,可以用于查看共享内存属性或控制共享内存,共享内存被OS组织在struct_ds结构体中,这个结构体里包含了共享内存属性,并可以对其进行设置,而cmd即要对共享内存进行的操作,我们这次主要是将他用来进行释放共享内存
传入shmid,IPC_RMID即可,最后一个参数如果不用来查看其内容可以设置为nullptr
三.进程通信实现:
同样使用以上接口来模拟一下client向server发送信息
client代码:
#include"shm_command.hh"
int main()
{
//生成共享内存的key值
key_t key = get_key();
cout << "key success key = "<<key<<endl;
//使用key开辟一块共享内存空间
int shm_id = get_shmid(key,IPC_CREAT);
cout << "shmid success shmid = " << shm_id <<endl;
//进程与共享内存进行关联
char* mem = (char*)shm_attach(shm_id);
cout << "mem success"<<endl;
int cnt = 0;
while(1)
{
snprintf(mem,MAX_SIZE,"hello world mypid=%d 这是我的第%d条消息",getpid(),++cnt);
sleep(1);
}
//进程解除关联
shmdt(mem);
return 0;
}
server端代码:
#include"shm_command.hh"
int main()
{
//生成共享内存的key值
key_t key = get_key();
cout << "key success key = "<<key<<endl;
//使用key开辟一块共享内存空间
int shm_id = get_shmid(key,IPC_CREAT|IPC_EXCL|0600);
cout << "shmid success shmid = " << shm_id <<endl;
//进程与共享内存进行关联
char* mem = (char*)shm_attach(shm_id);
cout << "mem success"<<endl;
//进程对共享内存进行读
while(1)
{
printf("client say:%s\n",mem);
sleep(1);
}
//进程解除关联
shmdt(mem);
//释放共享内存空间
shmctl(shm_id,IPC_RMID,nullptr);
return 0;
}
comand.hh代码:
#include<iostream>
#include<cstdio>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<cassert>
#include<cstring>
#include<errno.h>
#include<unistd.h>
#define PATH_NAME "./shm_server.cc"
#define PROJ_ID 336699
#define MAX_SIZE 4096
using namespace std;
key_t get_key()
{
key_t key = ftok(PATH_NAME,PROJ_ID);
assert(key >= 0);
return key;
}
int get_shmid(key_t key,int flags)
{
int shm_id = shmget(key,MAX_SIZE,flags);
cout << shm_id<<endl;
assert(shm_id >= 0);
return shm_id;
}
void* shm_attach(int shm_id)
{
void* mem = shmat(shm_id,nullptr,0);
if((long long)mem == -1L)
{
cout << "shmat error" << strerror(errno);
exit(1);
}
return mem;
}
执行结果: