内存映射(Memory-mmaped I/O)
内存映射是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
-
内存映射相关系统调用
-
mmap函数
#include<sys/mman.h> void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset);
-
功能:将一个文件或设备的数据映射到内存中
-
参数:
-addr:地址,一般设置为NULL,有内核指定
-length:要映射的数据的长度,不能为0。建议使用文件的长度。
获取文件长度:lseek stat
-prot:对申请的内存映射区的操作权限,可以设置为:
-PROT_EXEC:可执行权限
-PROT_READ:读权限
-PROT_WRITE:写权限
-PROT_NONE:无权限
要操作映射内存,必须要有读权限。
-flags:
-MAP_SHARED:映射区的数据会自动和磁盘文件同部,进程间通信必须设置这个选项
-MAP_PRIVATE:不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件(copy on write)
-MAP_ANONYMOUS:匿名映射
-fd:需要映射的那个文件的文件描述符
通过open得到,open的是一个磁盘文件。
注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。
-off_t:偏移量,一般不用。必须指定为4k的整数倍,0表示不偏移。
-
返回值:
成功:返回创建的内存的首地址
失败:返回MAP_FAILED(其实就是void* -1),并设置errno
-
-
munmap函数
#include<sys/mman.h> int munmap(void *addr,size_t length);
-
功能:释放内存映射
-
参数:
-addr:要释放的内存的首地址
-length:要释放的内存的大小,要和mmap函数中的length参数的值一样
-
-
使用内存映射实现进程间通信:
-
有关系的进程(父子进程)
(1)还没有子进程时,通过唯一的父进程先创建内存映射区;
(2)有了内存映射区后创建子进程
(3)父子进程共享创建的内存映射区
#include<stdio.h> #include<sys/mman.h> #include<fcntl.h> #include<sys/types.h> #include<sys/wait.h> #include<unistd.h> #include<string.h> #include<stdlib.h> int main(){ //1.打开一个文件 int fd=open("test.txt",O_RDWR); //获取文件的大小 int size=lseek(fd,0,SEEK_END); //2.创建内存映射区 void *ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(ptr==MAP_FAILED){ perror("mmap"); exit(0); } //3.创建子进程 pid_t pid=fork(); if(pid>0){ //父进程 wait(NULL); char buf[64]; strcpy(buf,(char*)ptr); printf("read data :%s\n",buf); }else if(pid==0){ //子进程 strcpy((char*)ptr,"hello,son!"); } //关闭内存映射区 munmap(ptr,size); return 0; }
-
没有关系的进程间通信
(1)准备一个大小不是0的磁盘文件
(2)进程1:通过磁盘文件创建内存映射区,并得到一个操作这块内存的指针
进程2:通过磁盘文件创建内存映射区,并得到一个操作这块内存的指针
(3)使用内存映射区通信
注意:内存映射区通信不会阻塞
-
内存映射的注意事项
-
对mmap的返回值(ptr)直接做++操作,而没有保存,munmap会出错
void *ptr=mmap(...); ptr++;//可以对其进行++操作 munmap(ptr,len);//错误,要保存地址
-
prot参数的权限应少于或等于open()函数的权限,否则会出错。
建议open()函数中的权限和prot参数的权限保持一致。
例如:open时的权限为O_RDONLY,mmap时prot参数指定PROT_READ | PROT_WRITE会出错,mmap会返回MAP_FAILED
-
偏移量必须是4k的整数倍,否则会返回MAP_FAILED
-
mmap调用失败的情况:
-
第一个参数:length=0
-
第三个参数:prot
-只指定了写权限
-prot参数指定的权限与open函数指定的权限冲突
-
-
可以通过open的时候O_CREAT一个新文件来创建映射区,但是创建的文件大小不能为0,否则会调用失败,可以用lseek或者truncate函数对新的文件进行扩展
-
mmap后关闭文件描述符,对mmap映射没有影响
int fd=open("xxx"); mmap(...,fd,0); close(fd); //mmap映射区还存在,只是创建映射区的fd被关闭,没有任何影响。
-
-
复制文件
内存映射处理用于进程间通信,还可以进行文件复制
思路:
(1)对原文件的内容进行内存映射
(2)创建一个新文件(拓展该文件)
(3)把新文件的数据映射到内存中
(4)通过内存拷贝将第一个文件的内存数据拷贝到新的文件内存中
(5)释放资源
代码实例:
#include<stdio.h> #include<stdlib.h> #include<sys/mman.h> #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<string.h> int main(){ //1.对原始文件进行内存映射 int fd=open("english.txt",O_RDWR); if(fd==-1){ perror("open"); exit(0); } //获取原始文件的大小 int size=lseek(fd,0,SEEK_END); //2.创建新文件(拓展该文件) int fd1=open("cpy.txt",O_RDWR | O_CREAT,0664); if(fd==-1){ perror("open"); exit(0); } //对新文件进行拓展 truncate("cpy.txt",size); write(fd1," ",1); //3.分别做内存映射 void *ptr=mmap(NULL,size,PROT_READ |PROT_WRITE,MAP_SHARED,fd,0); void *ptr1=mmap(NULL,size,PROT_READ |PROT_WRITE,MAP_SHARED,fd1,0); if(ptr==MAP_FAILED){ perror("mmap"); exit(0); } if(ptr1==MAP_FAILED){ perror("mmap"); exit(0); } //4.内存拷贝 memcpy(ptr1,ptr,size); //5.释放资源 munmap(ptr1,size); munmap(ptr,size); close(fd1); close(fd); }
-
匿名映射:不需要文件实体进程的内存映射
通过匿名映射实现进程间的通信:
#include<stdio.h> #include<stdlib.h> #include<sys/mman.h> #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<string.h> #include<wait.h> int main(){ //1.创建匿名内存映射区 int len=4096; void *ptr=mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0); if(ptr==MAP_FAILED){ perror("mmap"); exit(0); } //2.父子进程间通信 pid_t pid=fork(); if(pid>0){ //父进程 strcpy((char*)ptr,"hello,world"); wait(NULL); }else if(pid==0){ //子进程 sleep(1); printf("I am child,pid:%d,info:%s\n",getgid(),(char*)ptr); } //3.释放内存映射区 int ret=munmap(ptr,len); if(ret==-1){ perror("munmap"); exit(0); } return 0; }