内存映射
也是进程间通信的一种方式,效率相对于上面基于文件的方法更高(对内存直接操作 而非对物理磁盘操作)。内存映射(Memory-mapped I/O)是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
将文件数据映射到虚拟地址空间(进程地址空间)—-加载到动态库(share)的那块区域,可以根据off,len参数选择部分文件内容映射到内存中。对这块内存操作等同于对文件操作,对文件操作也等同于对内存进行修改。
![]() |
|---|
![]() |
|---|
两个进程将同一磁盘文件映射到内存中,直接通过对内存的读写就能完成进程间通信。
#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)//解除映射 释放映射的内存
内存映射实现父子进程通信
/*#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);映射or解除映射 文件、设备 到进程虚拟地址空间addr 映射内存的起始地址 传入NULL 则由内核决定(内核帮你去找一块相应大小的空间)length 内存大小 不能为0,一般使用文件的长度。获取文件的长度: stat lseek分配的内存空间并不是length个字节 而是大于length且与length最接近一页内存空间大小的整数倍prot 对申请的内存映射区的操作权限 不能与open文件的权限有冲突!!!PROT_EXEC 页内存可执行PROT_READ 页内存可读PROT_WRITE 页内存可写PROT_NONE 无对这块内存的任何权限想要操作至少要有读权限、不能是单独的写权限 至少是PROT_READ|PROT_WRITEflagsMAP_SHARED 映射区内存数据会自动与磁盘文件同步 进程间通信 必须要设置这个选项MAP_PRIVATE 不同步 内存映射区的数据改变了 不会修改对应的磁盘文件而是创建一个新的文件 copy on writefd 需要映射的文件的文件描述符 注意文件的大小不能为0!!open指定的权限不能和prot有冲突 prot为PROT_READ 则之前open时给 读/读写 权限prot为PROT_READ|PROT_WRITE 则之前open时给 读写 权限\即prot的权限要小于等于open给的权限offset 映射时的偏移量 从文件的offset个字节开始 向下length个字节的内容 映射到内存中,指定的大小必须是一页内存4k的整数倍,0表示不偏移返回值 成功 内存映射空间的首地址 失败返回 一个宏MAP_FAILED (that is, (void *) -1)int munmap(void *addr, size_t length);释放映射的内存addr 释放内存的首地址length 空间大小 与mmap中length参数的大小一样返回值 成功0 失败-1*//*文件映射有血缘关系的进程在还没fork时 创建文件 以及创建内存映射区(在进程内存空间的内核区中 fork后也是直接拷贝给子进程的)fork后共享内存映射区匿名映射准备一个大小不是0的磁盘文件进程1 通过磁盘文件创建 内存映射区进程2 通过磁盘文件(同一文件)创建 内存映射区可以使用内存映射区通信内存映射区通信是非阻塞的!!(管道默认是阻塞的,没数据后读是阻塞的)*/#include <sys/mman.h>#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <stdlib.h>#include <unistd.h> //acess lseek#include <fcntl.h> //open#include <sys/types.h> //lseek#include <stdlib.h>#include <wait.h>int main(){//打开文件 文件大小不要为0int fd = open("mmap-parent-child-ipc", O_RDWR);//获取文件大小int size = lseek(fd, 0, SEEK_END); //从文件末尾 偏移0//创建内存映射区void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) //创建失败{perror("mmap");exit(0);}//文件映射pid_t pid = fork();if (pid > 0) //父进程{wait(NULL); //回收子进程 子进程没结束就一直阻塞 即等待子进程向文件写数据完成后 再向下执行char buf[1024];strcpy(buf, (char *)ptr); //通过strcpy从文件读数据到buf中printf("data from son: %s \n");}else if (pid == 0) //子进程的第一次fork返回值是0{strcpy((char *)ptr, "hello par"); //通过strcpy向文件写数据}//释放映射空间munmap(ptr, size);return 0;}
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被关闭了 无影响
关闭文件描述符不取消映射的区域
内存映射复制文件
![]() |
|---|
//将两个txt文件映射到内存中 并将其中一个文件拷贝到另一个文件中//注意内存映射一般不做文件拷贝 因为文件太大 比如几个G内存会爆掉#include <sys/mman.h>#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <stdlib.h>#include <unistd.h> //acess lseek#include <fcntl.h> //open#include <sys/types.h> //lseek#include <stdlib.h>#include <wait.h>#include <string.h>int main(){// 1对原始文件进行内存映射int fd = open("mmap-parent-child-ipc", O_RDWR);int len = lseek(fd, 0, SEEK_END); //获取原始文件大小void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED){perror("mmap:");return -1;}// 2创建一个新的文件int newfd = open("becopy.txt", O_RDWR | O_CREAT, 0664);// 3拓展新文件大小truncate("becopy.txt", len);write(newfd, "", 1); //文件大小拓展后需要写一下才能实现// 4把新文件的数据映射到内存中void *newptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, newfd, 0);if (newptr == MAP_FAILED){perror("mmap:");return -1;}// 5通过内存拷贝将原始文件的内存数据 拷贝到新的文件中memcpy(newptr, newptr, len); //string.h 拷贝目的地,拷贝源,大小// 6释放资源munmap(newptr, len);munmap(ptr, len);close(newfd);close(fd);return 0;}
匿名映射
/*匿名映射 不需要通过文件实体来进行内存映射之前的都是文件映射 文件映射可用于进程间有或没有关系都可以匿名映射必须在进程间有关系才可以*/#include <sys/mman.h>#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <stdlib.h>#include <unistd.h> //acess lseek#include <fcntl.h> //open#include <sys/types.h> //lseek#include <stdlib.h>#include <wait.h>int main(){//创建内存映射区//MAP_ANONYMOUS匿名映射 此种内存映射不需要任何文件支持 内存中的数据都被初始化为0// fd参数指定了也会被忽略(有些实现需要指定fd为-1) offset 必须是0int size = 4096;void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (ptr == MAP_FAILED) //创建失败{perror("mmap:");exit(-1);}//父子进程间通信pid_t pid = fork();if (pid > 0) //父{//父进程先写 写完等待子进程结束 并回收子进程资源strcpy((char *)ptr, "hello");wait(NULL);}else if (pid == 0) //子{//读数据//因为内存映射区通信是非阻塞的在没写完之前 读到的为空 所以要先等待父进程写完sleep(1);printf("%s\n", (char *)ptr);}//释放映射空间munmap(ptr, size);return 0;}



