共享内存是进程间通信的一种方法,常用到的有mmap和shm,下面做一个比较。
在磁盘上建立一个文件,然后把文件内容映射到虚拟内存上,在每个进程的虚拟存储器里面,单独开辟一个空间来进行映射。在多进程情况下,不会对实际的物理存储器(主存)消耗太大。
即将一个文件或者其它对象映射到进程的虚拟地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一 一对映关系。
实现这样的映射关系后,进程就可以采用指针的方式操作这一段内存,而系统会自动回写到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享,也就是不再局限于具有亲缘关系的进程间通信。
简而言之,就是在进程虚拟空间上添加一个段,并将其与物理磁盘地址相连(映射),而在创建虚拟段的时候,并没有将任何文件数据拷贝至物理内存。
多个进程的地址空间都映射到同一块物理内存,这样多个进程都能看到这块物理内存,实现进程间通信,而且不需要数据的拷贝,所以速度最快。当系统断电后,其中的文件会全部自行销毁,这点与mmap不同。
1、mmap是在磁盘上建立一个文件,每个进程地址空间中开辟出一块空间进行映射。而对于shm而言,shm每个进程最终会映射到同一块物理内存。shm保存在物理内存,这样读写的速度要比磁盘要快,但是存储量不是特别大。
2、mmap:磁盘->进程,shm:进程->内存(断电清除)。共享内存shm是在内存中创建空间,然后每个进程映射到此处;内存映射mmap是在磁盘中创建一个文件,然后每个进程映射到此处;
3 当机器重启时,mmap把文件保存在磁盘上,所以不会丢失,而共享内存shm存储在内存上就会丢失;
下面看一下mmap使用过程中要用到的一些接口:
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset); //创建/访问
int munmap(void *addr, size_t length); //销毁
详细参数如下图:需要注意参数flags的含义,mmap文档的说明:
MAP_SHARED
共享这个映射。映射的更新对于映射同一区域的其他进程是可见的,并且(在文件备份映射的情况)被传送到底层文件。
MAP_PRIVATE
创建一个私有的写时复制的映射。映射的更新对映射同一文件的其他进程不可见,并且不被传送到底层文件。调用mmap()后对文件所做的更改是否在映射区域中可见,这是未指定的。
匿名映射
上面创建mmap映射区有一点不方便的地方,那就是每次建立映射区都需要依赖一个文件(.txt和fd或者其他)才能实现,通常为了建立映射区需要open一个临时文件,创建mmap好了之后再unlink、close比较麻烦。这时候可以直接使用匿名映射来代替,Linux系统提供了一个方法:借助mmap函数的标志位参数flags来制定:使用 MAP_ANONYMOUS(匿名的)
下面是一个简单的例子
#include
#include
#include
#include
#include
#include
#include
extern "C"{
size_t strlen(const char *s);
void *memcpy(void *dest, const void *src, size_t n);
}
/*
* 这个是mmap的测试,适用于没有亲缘关系的进程或者两个不同的进程直接通信
* 并且是匿名共享内存,不需要创建临时文件
*/
using std::cout;
using std::endl;
static int var = 666; //全局变量,进程间不共享
int main(){
int *p;
pid_t pid;
p = (int*)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if(p == MAP_FAILED){
perror("mmap");
exit(-1);
}
pid = fork(); //创建进程
if(pid == 0){
*p = 2000; //子进程修改 映射区的变量值
var = 100; //全局变量
cout << endl << "子进程的*p =" << *p<< ", 全局变量var = " << var << endl;
}
else{
sleep(1);
cout<<"父进程的*p = "<< *p << ", 全局变量var = " << var << endl << endl;
wait(NULL); //防止僵尸进程
}
if(munmap(p,4) < 0){ //释放共享内存
perror("munmap");
exit(-1);
}
return 0;
}
下面的简单案例中,我创建两个进程,并用指针p创建一个4字节的mmap,然后在子进程中给mmap中的内容赋值*p = 2000,然后在父进程中查看,可以看到由于设置了MAP_SHARE,所以父进程也可以读到相同的数据。顺便提一下:因为每个进程都有自己的独立地址空间,全局变量对于不同进程是不共享的,因此var是不同的。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
struct stu
{
int num;
char name[10];
char sex;
};
int main() {
int shmid; //共享内存标识符
//创建共享内存,键值为0x5005,共1024字节,全部用户可读写
if ( (shmid = shmget((key_t)0x5051, 3*sizeof(struct stu), 0666|IPC_CREAT)) == -1) {
perror("shmget");
exit(-1);
}
//将共享内存连接到当前进程的地址空间(内存->进程),用ptext指向
stu* ptext = (stu*)shmat(shmid, 0, 0);
//操作本程序的ptext指针,也就是操作共享内存,进行读写操作
cout << "上次写入的数据: " << endl;
for(int i = 0; i < 3; ++i){
cout << ptext[i].num << ' ' << ptext[i].name << ' ' << ptext[i].sex << endl;
}
for(int i = 0; i < 3; ++i){
cout << endl << "开始写入新数据: 1.排位 2.名字 3.性别" << endl;
int n;
char na[10];
char s;
cin >> n >> na >> s;
ptext[i].num = n;
strcpy(ptext[i].name, na);
ptext[i].sex = s;
}
//把共享内存从当前进程中分离
shmdt(ptext);
/*
删除共享内存
if (shmctl(shmid, IPC_RMID, 0) == -1){
perror("shmctl");
exit(-1);
}
*/
return 0;
}
可以看到,我的程序里每次写数据前,都对共享内存做了扫描,所以可以看到“上次写入的数据”,而本次写入就会覆盖原来的数据。
这里因为要提一下查看当前用户共享内存的属性的内容,就把最后一段共享内存删除的命令注释了。
linux系统下,可以用ipcs -m查看用户有"读"权限的共享内存的一些属性:
如下图第三段,键即为共享内存编号,和我在程序中调用shmget的第一个参数符合,是16进制表示方式。
shmid即为共享内存的标示,通过它我们可以删除共享内存,用到命令 ipcrm -m shmid