目录
1.是什么
2. 映射类型
2.1 文件映射和匿名映射
2.2私有映射和共享映射
2.3 brk的实现
3.实例
3.1 实现文件映射
3.2实现进程共享
2.3 实现内核驱动和进程共享
4.mmap的调用流程
5.反向映射
·匿名映射的反向映射:
文件映射的反向映射:
6. 相关问题
7.参考
1.是什么
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。
常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同,数据不通的繁琐过程。因此mmap效率更高。对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。
mmap在用户空间映射调用系统中作用很大。我们平时使用的malloc/free 在申请小块内存的时候使用的是提前初始化好的内存池,一旦要申请大块的内存还是会使用mmap去做映射。
mmap函数主要用途有三个(应用和内核/驱动交互,进程间交互,大规模数据传输/大文件读写)
2. 映射类型
2.1 文件映射和匿名映射
文件映射就是映射具体的文件到内存区处理,但是和read、write不同,他不能扩大文件的大小;匿名映射的不是具体文件,而是一块普通内存,用完释放,类似于malloc。
2.2私有映射和共享映射
如上,进程的空间内,各种不同快的映射一共分成四种:
·文件私有: 每个进程对其读写都是私有的,但是不会写入到磁盘,而是在各自的内存空间,比如不同进程使用同样的代码段, 数据段,数据是进程私有的。
·文件共享,基于文件的进程间通信
·匿名私有,一块内存的私有,用于单个进程内部使用,malloc使用的brk就是这种映射方式
·匿名共享:基于内存的进程间通信
mmap区四种映射类型都支持。
2.3 brk的实现
malloc分配使用的heap堆内存其实也是通过mmap来实现的一段匿名映射空间!
SYSCALL_DEFINE1(brk, unsigned long, brk){unsigned long rlim, retval;unsigned long newbrk, oldbrk;struct mm_struct *mm = current->mm;unsigned long min_brk;down_write(&mm->mmap_sem);min_brk = mm->start_brk;newbrk = PAGE_ALIGN(brk); // 新的brk地址oldbrk = PAGE_ALIGN(mm->brk); //线程原来的brk地址if (oldbrk == newbrk)goto set_brk;/* Always allow shrinking brk. 新的比原来的还小,说明需要释放掉一些区域 */if (brk brk) {if (!do_munmap(mm, newbrk, oldbrk-newbrk))goto set_brk;goto out;}/* Check against existing mmap mappings. 查找已经映射过了的的mmap内存空间 */if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))goto out;/* Ok, looks good - let it rip.* 必须重新映射 申请虚拟内存,然后更新brk的位置, * 但是不分配物理内存,要等到实际访问才会产生缺页异常来分配 */if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)goto out;set_brk:mm->brk = brk;out:retval = mm->brk;up_write(&mm->mmap_sem);return retval;}
3.实例
3.1 实现文件映射
#include#include#include#include#include#include#includeint main(int argc, char* argv[]){int fd = open("map.txt",O_RDWR);if(fd == -1){printf("文件打开失败\n");return -1;}// 创建映射 大小1024字节 可读写可共享char* buf = mmap(NULL,1024,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);if(buf == MAP_FAILED){printf("映射失败\n");return 0;}// 写入printf("输出:%s\n",buf);sprintf(buf,"aaa:%d",10);printf("输出:%s\n",buf);// munmap 写入内容同步到磁盘文件int ret = munmap(buf,1024);if(ret == -1)printf("删除失败\n");close(fd);return 0;}
3.2实现进程共享
注意这里的fork会返回两次,因此if else两个发呢只都会被执行到!其实两个分支是被父子进程分别执行的,具体原因可见详解fork()函数的两个返回值_是桃萌萌鸭~的博客-CSDN博客_fork函数返回值
#include#include#include#include#include#include#include#include#include#define MAX_SIZE 1024int main(){int fd = open("map.txt",O_RDWR|O_CREAT|O_TRUNC,0664);if(fd == -1){printf("文件打开失败\n");return -1;}ftruncate(fd,MAX_SIZE);// 映射char *buf = mmap(NULL,MAX_SIZE,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);if(buf == MAP_FAILED){printf("映射失败\n");return -1;}// 映射完成 文件描述符可以关闭close(fd);// 创建子进程pid_t pid = fork();if(pid == 0)// 子进程{printf("子进程开始写\n");int i = 0;char *p = buf;for(i; i 0) // 父进程{sleep(1);printf("父进程开始读\n");char name[10] = {0};int id = 0, i = 0;for(i; i< 10; i++){sscanf(buf+i*8,"%s %2d",name,&id);printf("读取到的是: %s %2d\n",name,id);}sprintf(buf+i*8,"我读完了\n");wait(NULL); // 回收子进程}int ret = munmap(buf,MAX_SIZE);if(ret == -1){printf("取消文件映射失败\n");return -1;}return 0;}
2.3 实现内核驱动和进程共享
… …
4.mmap的调用流程
系统调用SYSCALL_DEFINE6(mmap_pgoff) —-> do_mmap_pgoff(获取文件节点inode信息并把文件和地址进行关联) —–>get_unmapped_area(从用户映射去获取可以用来map的虚拟地址addr)—->get_area = current->mm->get_unmapped_area (优先使用文件系统自己定义的接口 其次使用进程定义的接口来获取空闲虚拟地址)—->
SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,unsigned long, prot, unsigned long, flags,unsigned long, fd, unsigned long, pgoff){struct file *file = NULL;unsigned long retval = -EBADF;if (!(flags & MAP_ANONYMOUS)) { // 非匿名页映射audit_mmap_fd(fd, flags);if (unlikely(flags & MAP_HUGETLB))return -EINVAL;file = fget(fd);//根据文件描述符fd获取传入文件的控制结构体if (!file)goto out;} else if (flags & MAP_HUGETLB) {// 大页映射...........}flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);down_write(¤t->mm->mmap_sem);retval = do_mmap_pgoff(file, addr, len, prot, flags, pgoff);up_write(¤t->mm->mmap_sem);}static unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,unsigned long len, unsigned long prot,unsigned long flags, unsigned long pgoff){struct mm_struct * mm = current->mm;struct inode *inode;vm_flags_t vm_flags;int error;unsigned long reqprot = prot;/* Obtain the address to map to. we verify (or select) it and ensure * that it represents a valid section of the address space. * 这个函数从 找到没有map过的空余虚拟地址 并且确保这块地址有效 */addr = get_unmapped_area(file, addr, len, pgoff, flags);if (addr & ~PAGE_MASK)return addr;/* Do simple checking here so the lower-level routines won't have * to. we assume access permissions have been handled by the open * of the memory object, so we don't do any here. */vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) |mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;// 获取文件的inode节点信息inode = file " />5.反向映射通过虚拟地址查找物理地址叫做正向映射;通过物理空间直接查找虚拟地址(VMA)叫做反向映射。应用场景:内存回收,页面迁移;碎片整理,CMA回收,巨型页。
·匿名映射的反向映射:
在进程分配VMA地址空间,产生缺页异常之后,会使用anon_vma结构体来管理映射在所有在物理页上的VMA。
文件映射的反向映射:
文件映射的反向映射和匿名映射同理,是通过address_space结构体来实现反向查找,它和进程无关,
6. 相关问题
mmap几问:
·如果更改mem变量的地址, 释放的时候munmap, 传入mem还能成功吗" />可以使用ftruncate设置大小.
·open文件选选择O_WRONLY, 可以吗?
答 : 不可以. 因为加载的时候需要读一次.
·当选择MAT_SHARED的时候,open选择O_RDONLY, prot可以选择PROT_READ | PROT_WRITE 吗?
答 : 不可以 映射区权限 <= open文件权限
·如果不判断返回值会怎样?
答 : 会死的很难看
务必判断返回值
7.参考
mmap内存映射在应用和内核/驱动交互,进程间交互,大规模数据传输/大文件读写中的使用_王道泼的博客-CSDN博客