共享存储映射

写在前面

文件实现进程间通信,使用文件也可以完成 IPC,理论依据是,fork后,父子进程共享文件描述符,也就共享打开的文件。这很容易想明白吧,在内部的原理就是内存映射 (我猜的)

存储映射I/O

存储映射I/O(Memory-mapped I/O) 使一i个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区取数据,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样就可以在不使用readwrite函数的情况下,使用地址(指针)完成 I/O 操作。

共享内存

概述

共享内存是进程间通信中最简单的方式之一 (放洋屁)。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。

共享内存的特点

  • 共享内存使进程间共享数据的一种最快的方法,一个进程向共享的内存区域写入了数据,共享这个内存区域的所有进程都可以立即看到其中的内容

  • 使用共享内存要注意多个进程对一个给定存储访问的互斥。若一个进程正在向共享内存区域写数据,则在它完成这一步操作前,别的进程不应该去读、写这些数据

创建共享内存映射

函数原型:void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
作用:创建共享内存映射

头文件:

  • #include <sys/mman.h>

参数:

  • addr:要创建的共享内存映射区的首地址,通常传NULL代表系统自动分配首地址
  • length:要创建的共享内存映射区的长度
  • port:共享内存映射区的读写属性,PROT_READPROT_WRITEPROT_READ|PROT_WRITE
  • flags:标注共享内存的共享属性,MAP_SHAREDMAP_PRIVATE
  • fd:用于创建共享内存映射区的那个文件的 文件描述符
  • offset:偏移位置,默认0,表示映射文件全部,如需更改必须是 4k 的整数倍

flags参数说明

MAP_SHARED

这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文 件,也就是说,存储操作相当于对该文件`write`。必须指定本标志或下一个标志(MAP_PRIVATE),但不能同时指定两者

MAP_PRIVATE

本标志说明,对映射区的存储操作导致创建该映射文件的一个私有副本。所有后来对该映射区的引用都是引用该副本,而不是原始文件。(此标志的一种用途是用于调试程序,它将依程序文件的正文部分映射至一存储区,但允许用户修改其中的指令。任何修改只影响程序文件的副本,但不影响原文件)

返回值:

  • 成功:返回创建的共享内存映射区的首地址
  • 失败:返回MAP_FAILED (void*(-1)),并设置errno

注意事项

  • 用于创建映射区的文件大小为 0,实际指定非0大小创建映射区,出 “总线错误”。
  • 用于创建映射区的文件大小为 0,实际制定0大小创建映射区, 出 “无效参数”mmap error: Invalid argument
  • 用于创建映射区的文件读写属性为,只读,映射区属性为读、写,出 Permission denied
  • 创建映射区,需要用于映射区的文件需要 read权限。当访问权限指定为 MAP_SHARED时,mmap的读写权限应 <=文件open 的权限,且只写不行。
  • 文件描述符fd,在mmap创建映射区完成后即可关闭,后续通过指针访问文件。
  • offset必须是 4096 的整数倍(MMU映射的最小单位4K)
  • 对申请的映射区内存,不能越界访问
  • 映射区访问权限为MAP_PRIVATE,对内存所作的修改只在内村上有效,不会反映到物理磁盘上
  • 映射区访问权限为MAP_PRIVATE,只需要open文件时,有读权限即可

案例

  1. 使用mmap实现父子进程间通信
  2. 父进程 先 创建映射区。 open( O_RDWR) mmap( MAP_SHARED );
  3. 指定 MAP_SHARED 权限
  4. fork() 创建子进程。
  5. 一个进程读, 另外一个进程写。
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;
int main(int argc, char *argv[]){
    pid_t pid;
    int *p; 
    int fd; 
    
    fd = open("testmmp",O_RDWR|O_CREAT|O_TRUNC,0644); //打开一个文件
    if(fd < 0){ 
        perror("file error");
        exit(1);
    }   
    int len = 10; 
    ftruncate(fd, len);
    p = (int *)mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); //创建共享内存映射
    if(p == MAP_FAILED){
        perror("mmap error");
        exit(1);
    }   

    pid = fork();

    if(pid < 0){ 
        perror("fork error");
        exit(1);
    } else if(pid == 0) {
        var = 200;
        *p = 5000; //在子进程中向共享内存映射区写入数据
        printf("this is child var = %d *p = %d\n", var, *p);
    } else {
        wait(NULL); //从父进程读出来,理论上var没变,*p应该被更改
        printf("this is parent var = %d *p = %d\n", var, *p);
    }
    close(fd);
    return 0;
}

执行结果确实如此,也印证了,父子进程共享 mmap 建立的映射映射区
image.png

使用mmap在无血缘关系的进程间通信

  1. 两个进程 打开同一个文件,创建映射区。
  2. 指定flags 为 MAP_SHARED。
  3. 一个进程写入,另外一个进程读出。
    【注意】无血缘关系进程间通信。mmap:数据可以重复读取。fifo:数据只能一次读取
//读端
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;
int main(int argc, char *argv[]){
    int *p, fd; 
    pid_t pid;
    fd = open("test.txt", O_RDWR|O_CREAT|O_TRUNC,0644);
    int len = 10; 
    ftruncate(fd, len);
    p = (int *)mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 
    close(fd);
    int i = 0;
    while(1)
        printf("%d\n", *p);
    return 0;
}                    
//写端口
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;
int main(int argc, char *argv[]){
    int *p, fd; 
    pid_t pid;
    fd = open("test.txt", O_RDWR|O_CREAT|O_TRUNC,0644);
    int len = 10; 
    ftruncate(fd, len);
    p = (int *)mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 
    close(fd);

    if(p == MAP_FAILED){
        perror("mmap error");
        exit(1);
    }   
    int i = 0;
    for(i = 0;i < 10;i++){
        *p = i;
        sleep(5);
    }   
    return 0;
}

运行测试一下就可以了,结果很明显.

匿名映射

只能用于 血缘关系进程间通信。
p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

回收共享内存映射区

作用:释放映射区
函数原型:int munmap(void *addr, size_t length);

参数:

  • addr:mmap 函数的返回值,且必须是没有更改过的
  • length:大小
    返回值:
  • 成功:返回 0
  • 失败: 返回 -1