内存映射
    也是进程间通信的一种方式,效率相对于上面基于文件的方法更高(对内存直接操作 而非对物理磁盘操作)。内存映射(Memory-mapped I/O)是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
    将文件数据映射到虚拟地址空间(进程地址空间)—-加载到动态库(share)的那块区域,可以根据off,len参数选择部分文件内容映射到内存中。对这块内存操作等同于对文件操作,对文件操作也等同于对内存进行修改。

    2 Linux多进程开发5 内存映射 - 图1
    2 Linux多进程开发5 内存映射 - 图2


    两个进程将同一磁盘文件映射到内存中,直接通过对内存的读写就能完成进程间通信。

    1. #include <sys/mman.h>
    2. void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset) //映射文件到内存中
    3. int munmap(void *addr,size_t length)//解除映射 释放映射的内存


    内存映射实现父子进程通信

    1. /*
    2. #include <sys/mman.h>
    3. void *mmap(void *addr, size_t length, int prot, int flags,
    4. int fd, off_t offset);
    5. 映射or解除映射 文件、设备 到进程虚拟地址空间
    6. addr 映射内存的起始地址 传入NULL 则由内核决定(内核帮你去找一块相应大小的空间)
    7. length 内存大小 不能为0,一般使用文件的长度。
    8. 获取文件的长度: stat lseek
    9. 分配的内存空间并不是length个字节 而是大于length且与length最接近一页内存空间大小的整数倍
    10. prot 对申请的内存映射区的操作权限 不能与open文件的权限有冲突!!!
    11. PROT_EXEC 页内存可执行
    12. PROT_READ 页内存可读
    13. PROT_WRITE 页内存可写
    14. PROT_NONE 无对这块内存的任何权限
    15. 想要操作至少要有读权限、不能是单独的写权限 至少是PROT_READ|PROT_WRITE
    16. flags
    17. MAP_SHARED 映射区内存数据会自动与磁盘文件同步 进程间通信 必须要设置这个选项
    18. MAP_PRIVATE 不同步 内存映射区的数据改变了 不会修改对应的磁盘文件而是创建一个新的文件 copy on write
    19. fd 需要映射的文件的文件描述符 注意文件的大小不能为0!!
    20. open指定的权限不能和prot有冲突 prot为PROT_READ 则之前open时给 读/读写 权限
    21. prot为PROT_READ|PROT_WRITE 则之前open时给 读写 权限\
    22. 即prot的权限要小于等于open给的权限
    23. offset 映射时的偏移量 从文件的offset个字节开始 向下length个字节的内容 映射到内存中,指定的大小必须是一页内存4k的整数倍,0表示不偏移
    24. 返回值 成功 内存映射空间的首地址 失败返回 一个宏MAP_FAILED (that is, (void *) -1)
    25. int munmap(void *addr, size_t length);
    26. 释放映射的内存
    27. addr 释放内存的首地址
    28. length 空间大小 与mmap中length参数的大小一样
    29. 返回值 成功0 失败-1
    30. */
    31. /*
    32. 文件映射
    33. 有血缘关系的进程
    34. 在还没fork时 创建文件 以及创建内存映射区(在进程内存空间的内核区中 fork后也是直接拷贝给子进程的)
    35. fork后共享内存映射区
    36. 匿名映射
    37. 准备一个大小不是0的磁盘文件
    38. 进程1 通过磁盘文件创建 内存映射区
    39. 进程2 通过磁盘文件(同一文件)创建 内存映射区
    40. 可以使用内存映射区通信
    41. 内存映射区通信是非阻塞的!!(管道默认是阻塞的,没数据后读是阻塞的)
    42. */
    43. #include <sys/mman.h>
    44. #include <stdio.h>
    45. #include <sys/types.h>
    46. #include <sys/stat.h>
    47. #include <stdlib.h>
    48. #include <unistd.h> //acess lseek
    49. #include <fcntl.h> //open
    50. #include <sys/types.h> //lseek
    51. #include <stdlib.h>
    52. #include <wait.h>
    53. int main()
    54. {
    55. //打开文件 文件大小不要为0
    56. int fd = open("mmap-parent-child-ipc", O_RDWR);
    57. //获取文件大小
    58. int size = lseek(fd, 0, SEEK_END); //从文件末尾 偏移0
    59. //创建内存映射区
    60. void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    61. if (ptr == MAP_FAILED) //创建失败
    62. {
    63. perror("mmap");
    64. exit(0);
    65. }
    66. //文件映射
    67. pid_t pid = fork();
    68. if (pid > 0) //父进程
    69. {
    70. wait(NULL); //回收子进程 子进程没结束就一直阻塞 即等待子进程向文件写数据完成后 再向下执行
    71. char buf[1024];
    72. strcpy(buf, (char *)ptr); //通过strcpy从文件读数据到buf中
    73. printf("data from son: %s \n");
    74. }
    75. else if (pid == 0) //子进程的第一次fork返回值是0
    76. {
    77. strcpy((char *)ptr, "hello par"); //通过strcpy向文件写数据
    78. }
    79. //释放映射空间
    80. munmap(ptr, size);
    81. return 0;
    82. }



    mmap调用的一些问题,
    1如果对mmap的返回值(内存映射空间的首地址)ptr做++,那么释放时munmap(ptr,size)是否能够成功
    无法成功 size是完整空间的大小
    2如果open时O_RDONLY,mmap时prot参数指定PROT_READ|PROT_WRITE会怎样
    错误,mmap返回MAP_FAILED(void)-1 prot权限<=open时给的文件权限
    3文件偏移量offset为1000
    偏移量必须是4k的整数倍,会返回MAP_FAILED
    4 mmap什么情况下调用失败
    (1)第二个参数 length = 0
    (2)prot权限>open时给fd的文件权限
    (3)prot只给了写权限
    (4)3
    5 可以在open的时候O_CREAT一个新文件,此新文件的fd用于mmap来创建映射区吗
    可以 但是创建文件的大小如果为0 肯定不行
    可用lseek或truncate拓展新文件的大小
    6 mmap后关闭文件描述符fd 对mmap映射有什么影响
    int fd = open(…);
    void
    ptr = mmap(….,fd,.);
    close(fd);
    close后对ptr这块空间做操作,映射区还在但是创建映射区的fd被关闭了 无影响
    关闭文件描述符不取消映射的区域


    内存映射复制文件

    2 Linux多进程开发5 内存映射 - 图3
    1. //将两个txt文件映射到内存中 并将其中一个文件拷贝到另一个文件中
    2. //注意内存映射一般不做文件拷贝 因为文件太大 比如几个G内存会爆掉
    3. #include <sys/mman.h>
    4. #include <stdio.h>
    5. #include <sys/types.h>
    6. #include <sys/stat.h>
    7. #include <stdlib.h>
    8. #include <unistd.h> //acess lseek
    9. #include <fcntl.h> //open
    10. #include <sys/types.h> //lseek
    11. #include <stdlib.h>
    12. #include <wait.h>
    13. #include <string.h>
    14. int main()
    15. {
    16. // 1对原始文件进行内存映射
    17. int fd = open("mmap-parent-child-ipc", O_RDWR);
    18. int len = lseek(fd, 0, SEEK_END); //获取原始文件大小
    19. void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    20. if (ptr == MAP_FAILED)
    21. {
    22. perror("mmap:");
    23. return -1;
    24. }
    25. // 2创建一个新的文件
    26. int newfd = open("becopy.txt", O_RDWR | O_CREAT, 0664);
    27. // 3拓展新文件大小
    28. truncate("becopy.txt", len);
    29. write(newfd, "", 1); //文件大小拓展后需要写一下才能实现
    30. // 4把新文件的数据映射到内存中
    31. void *newptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, newfd, 0);
    32. if (newptr == MAP_FAILED)
    33. {
    34. perror("mmap:");
    35. return -1;
    36. }
    37. // 5通过内存拷贝将原始文件的内存数据 拷贝到新的文件中
    38. memcpy(newptr, newptr, len); //string.h 拷贝目的地,拷贝源,大小
    39. // 6释放资源
    40. munmap(newptr, len);
    41. munmap(ptr, len);
    42. close(newfd);
    43. close(fd);
    44. return 0;
    45. }


    匿名映射

    1. /*
    2. 匿名映射 不需要通过文件实体来进行内存映射
    3. 之前的都是文件映射 文件映射可用于进程间有或没有关系都可以
    4. 匿名映射必须在进程间有关系才可以
    5. */
    6. #include <sys/mman.h>
    7. #include <stdio.h>
    8. #include <sys/types.h>
    9. #include <sys/stat.h>
    10. #include <stdlib.h>
    11. #include <unistd.h> //acess lseek
    12. #include <fcntl.h> //open
    13. #include <sys/types.h> //lseek
    14. #include <stdlib.h>
    15. #include <wait.h>
    16. int main()
    17. {
    18. //创建内存映射区
    19. //MAP_ANONYMOUS匿名映射 此种内存映射不需要任何文件支持 内存中的数据都被初始化为0
    20. // fd参数指定了也会被忽略(有些实现需要指定fd为-1) offset 必须是0
    21. int size = 4096;
    22. void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    23. if (ptr == MAP_FAILED) //创建失败
    24. {
    25. perror("mmap:");
    26. exit(-1);
    27. }
    28. //父子进程间通信
    29. pid_t pid = fork();
    30. if (pid > 0) //父
    31. {
    32. //父进程先写 写完等待子进程结束 并回收子进程资源
    33. strcpy((char *)ptr, "hello");
    34. wait(NULL);
    35. }
    36. else if (pid == 0) //子
    37. {
    38. //读数据
    39. //因为内存映射区通信是非阻塞的在没写完之前 读到的为空 所以要先等待父进程写完
    40. sleep(1);
    41. printf("%s\n", (char *)ptr);
    42. }
    43. //释放映射空间
    44. munmap(ptr, size);
    45. return 0;
    46. }