答案就是:缺页异常:。
由于 mmap() 系统调用并没有直接将文件的页缓存映射到虚拟内存中,所以当访问到没有映射的虚拟内存地址时,将会触发 缺页异常。当 CPU 触发缺页异常时,将会调用 do_page_fault() 函数来修复触发异常的虚拟内存地址。
我们主要来看看 do_page_fault() 函数对文件映射的实现部分,其调用链如下:
do_page_fault() └→ handle_mm_fault() └→ handle_pte_fault() └→ do_linear_fault() └→ __do_fault()登录后复制
所以我们直接来看看 __do_fault() 函数的实现:
static int __do_fault(struct mm_struct *mm, struct vm_area_struct *vma, unsigned long address, pmd_t *pmd, pgoff_t pgoff, unsigned int flags, pte_t orig_pte) { ... vmf.virtual_address = address & PAGE_MASK; // 要映射的虚拟内存地址 vmf.pgoff = pgoff; // 映射到文件的偏移量 vmf.flags = flags; // 标志位 vmf.page = NULL; // 映射到虚拟内存中的物理内存页 // 1. 如果虚拟内存管理区提供了 falut() 回调函数,那么将调用此函数来获取要映射的物理内存页, // 我们在 mmap() 系统调用的实现中看到,已经将其设置为 filemap_fault() 函数了。 if (likely(vma->vm_ops->fault)) { ret = vma->vm_ops->fault(vma, &vmf); ... } ... if (likely(pte_same(*page_table, orig_pte))) { ... // 2. 通过物理内存页生成一个页表项值 entry = mk_pte(page, vma->vm_page_prot); if (flags & FAULT_FLAG_WRITE) entry = maybe_mkwrite(pte_mkdirty(entry), vma); // 3. 将虚拟内存地址映射到物理内存 set_pte_at(mm, address, page_table, entry); ... } ... return ret; }登录后复制
__do_fault() 函数对处理文件映射部分主要分为 3 个步骤:
调用虚拟内存管理区结构的 fault() 回调函数来获取到文件的页缓存。 通过页缓存的物理内存页来生成一个页表项值,可以参考《漫画解说 “内存映射”》一文。 将虚拟内存地址映射到页缓存的物理内存页。对于 filemap_fault() 函数是怎样读取文件页缓存的,本文不作解释,有兴趣的可以自行阅读源码。
最后,我们以一幅图来描述一下虚拟内存是如何与文件进行映射的:
从上图可以看出,mmap() 是通过将虚拟内存地址映射到文件的页缓存来实现的。当对映射后的虚拟内存进行读写操作时,其效果等价于直接对文件的页缓存进行读写操作。对文件的页缓存进行读写操作,也等价于对文件进行读写操作。
本文来自投稿,不代表本站立场,如若转载,请注明出处: