共享内存机制——mmap和shm

共享内存是进程间通信的一种方法,常用到的有mmap和shm,下面做一个比较。

mmap机制:

在磁盘上建立一个文件,然后把文件内容映射到虚拟内存上,在每个进程的虚拟存储器里面,单独开辟一个空间来进行映射。在多进程情况下,不会对实际的物理存储器(主存)消耗太大。
即将一个文件或者其它对象映射到进程的虚拟地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一 一对映关系。
实现这样的映射关系后,进程就可以采用指针的方式操作这一段内存,而系统会自动回写到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享,也就是不再局限于具有亲缘关系的进程间通信。
简而言之,就是在进程虚拟空间上添加一个段,并将其与物理磁盘地址相连(映射),而在创建虚拟段的时候,并没有将任何文件数据拷贝至物理内存。

shm机制

多个进程的地址空间都映射到同一块物理内存,这样多个进程都能看到这块物理内存,实现进程间通信,而且不需要数据的拷贝,所以速度最快。当系统断电后,其中的文件会全部自行销毁,这点与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和shm_第1张图片
匿名映射
上面创建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是不同的。

下面是shm机制:
共享内存机制——mmap和shm_第2张图片
共享内存机制——mmap和shm_第3张图片
同样看一个测试:

#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;
}

共享内存机制——mmap和shm_第4张图片
可以看到,我的程序里每次写数据前,都对共享内存做了扫描,所以可以看到“上次写入的数据”,而本次写入就会覆盖原来的数据。
这里因为要提一下查看当前用户共享内存的属性的内容,就把最后一段共享内存删除的命令注释了。
linux系统下,可以用ipcs -m查看用户有"读"权限的共享内存的一些属性:
共享内存机制——mmap和shm_第5张图片
如下图第三段,键即为共享内存编号,和我在程序中调用shmget的第一个参数符合,是16进制表示方式。
shmid即为共享内存的标示,通过它我们可以删除共享内存,用到命令 ipcrm -m shmid
共享内存机制——mmap和shm_第6张图片

你可能感兴趣的:(共享内存,进程间通信,多进程)