(二十八)进程间通信——内存共享映射mmap和munmap

来源:互联网 发布:吸毒的人有多可怕知乎 编辑:程序博客网 时间:2024/05/20 06:28

  mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存地址,对文件的读写可以直接用指针来操做而不需要read/write函数。

#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);int munmap(void *addr, size_t length);

  
  如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。建立映射后,真正的映射首地址通过返回值可以得到len参数是需要映射的那一部分文件的长度。off参数是从文件的什么位置开始映射,必须是页大小的整数倍(在32位体系统结构上通常是4K)。filedes是代表该文件的描述符。
  
prot参数有四种取值:
  1)PROT_EXEC 表示映射的这一段可执行,例如映射共享库
  2)PROT_READ 表示映射的这一段可读
  3)PROT_WRITE 表示映射的这一段可写
  4) PROT_NONE 表示映射的这一段不可访问
   
flag参数有很多种取值,这里只讲两种常用的,其它取值可查看mmap(2)

  • MAP_SHARED 多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。
  • MAP_PRIVATE 多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。

如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射。munmap成功返回0,出错返回-1。
  这里写图片描述
举例:

/* 运行前准备 */book@ubuntu:~$ vi hellobook@ubuntu:~$ cat hellohelloworldbook@ubuntu:~$ od -tx1 -tc hello0000000 68 65 6c 6c 6f 77 6f 72 6c 64 0a        h  e  l  l  o  w  o  r  l  d  \n0000013
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>#include <sys/mman.h>int main(void){    int fd, *p, len;    fd = open("hello",O_RDWR);    if(fd < 0)    {        perror("open");        exit(1);    }    //获取文件长度    len = lseek(fd, 0, SEEK_END);    //建立映射    p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);    if(p == MAP_FAILED)    {//如果失败,记住这种检测方式        perror("mmap");        exit(1);    }    //即使关闭文件也不会释放映射    close(fd);    //由于是共享映射,所以映射源也会被修改    p[0] = 0x30313233;    munmap(p, len);    return 0;}/* 运行后结果(小端存储的原因) */book@ubuntu:~$ cat hello3210oworldbook@ubuntu:~$ od -tx1 -tc hello0000000 33 32 31 30 6f 77 6f 72 6c 64 0a        3  2  1  0  o  w  o  r  l  d  \n0000013



利用内存映射的特性,也可以用于进程间的通信,但是要注意的是:
1、用于进程间通信时,一般设计成结构体,来传输通信的数据
2、进程间通信的文件,应该设计成临时文件(即创建文件,使用文件,删除文件
3、 当报总线错误时,优先查看共享文件是否有存储空间

例子:

/* mmap-proc_r.c */#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/mman.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#define MAPLEN 0x1000void sys_err(char *error,int exitno){    perror("error");    exit(exitno);}int main(int argc, char *argv[]){    char *mm;    int fd, i = 0;    if(argc < 2)    {        printf("./app <filename>\n");        exit(1);    }    //打开一个文件    fd = open(argv[1], O_RDWR);    if(fd < 0)        sys_err("open", 1);    /*     *第一个NULL:  内核在进程地址空间中选择合适的地址建立映射     *            其返回映射的首地址     *第二个MAPLEN: 映射的长度是MAPLEN     *第三个参数:  表示映射的这一页可读,可写     *第四个MAP_SHARED:  以共享方式打开     *第五个fd:  将fd映射到该进程中     *第六个0:   从fd的偏移量为0的位置开始映射     */    mm = mmap(NULL, MAPLEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);    if(mm == MAP_FAILED)        sys_err("mmap", 2);    //读数据    while(1)    {        printf("%s\n", mm);        sleep(3);    }    close(fd);    munmap(mm, MAPLEN);    return 0;}/* mmap-proc_w.c */#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/mman.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#define MAPLEN 0x1000void sys_err(char *error,int exitno){    perror("error");    exit(exitno);}int main(int argc, char *argv[]){    char *mm;    int fd, i = 0;    if(argc < 2)    {        printf("./app <filename>\n");        exit(1);    }    //创建一个文件    fd = open(argv[1], O_RDWR|O_CREAT,0777);    if(fd < 0)        sys_err("open", 1);    //下面两句话,使创建的文件长度变为4k,这是为了映射4k    lseek(fd,MAPLEN - 1, SEEK_SET);    write(fd, "\0", 1);    /*     *第一个NULL:   内核在进程地址空间中选择合适的地址建立映射     *             其返回映射的首地址     *第二个MAPLEN:  映射的长度是MAPLEN     *第三个参数:  表示映射的这一页可读,可写     *第四个MAP_SHARED:  以共享方式打开     *第五个fd:   将fd映射到该进程中     *第六个0:    从fd的偏移量为0的位置开始映射     */    mm = mmap(NULL, MAPLEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);    if(mm == MAP_FAILED)        sys_err("mmap", 2);    //写数据    while(1)    {        sprintf(mm, "hello %d\n", i++);        sleep(3);    }    close(fd);    munmap(mm, MAPLEN);    return 0;}运行方式:分别编译为mmap-proc_r 和 mmap-proc_ww 可执行文件在一个文件中输入$ ./mmap-proc_w share在另一个终端中输入$ ./mmap-proc_r share


但是在实际的开发过程中常常是用一下的方式进行进程间通信的:

/* process_mmap_w.c */#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/mman.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#define MAPLEN 0x1000struct STU {    int id;    char name[20];    char sex;};void sys_err(char *str, int exitno){    perror(str);    exit(exitno);}int main(int argc, char *argv[]){    struct STU *mm;    int fd, i = 0;    if (argc < 2) {        printf("./a.out filename\n");        exit(1);    }    fd = open(argv[1], O_RDWR | O_CREAT, 0777);    if (fd < 0)        sys_err("open", 1);    if (lseek(fd, MAPLEN-1, SEEK_SET) < 0)        sys_err("lseek", 3);    if (write(fd, "\0", 1) < 0)        sys_err("write", 4);    mm = mmap(NULL, MAPLEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);    if (mm == MAP_FAILED)        sys_err("mmap", 2);    close(fd);    while (1) {        mm->id = i;        sprintf(mm->name, "zhang-%d", i);        if (i % 2 == 0)            mm->sex = 'm';        else            mm->sex = 'w';        i++;        sleep(1);    }    munmap(mm, MAPLEN);    return 0;}/* process_mmap_r.c */#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/mman.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#define MAPLEN 0x1000struct STU {    int id;    char name[20];    char sex;};void sys_err(char *str, int exitno){    perror(str);    exit(exitno);}int main(int argc, char *argv[]){    struct STU *mm;    int fd, i = 0;    if (argc < 2) {        printf("./a.out filename\n");        exit(1);    }    fd = open(argv[1], O_RDWR);    if (fd < 0)        sys_err("open", 1);    mm = mmap(NULL, MAPLEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);    if (mm == MAP_FAILED)        sys_err("mmap", 2);    close(fd);    unlink(argv[1]);   //删除该文件,收尾工作    while (1) {        printf("%d\n", mm->id);        printf("%s\n", mm->name);        printf("%c\n", mm->sex);        sleep(1);    }    munmap(mm, MAPLEN);    return 0;}
6 0