科技大本营|Linux内核虚拟内存管理之匿名映射缺页异常分析

韩传华 , 就职于南京大鱼半导体有限公司 , 主要从事linux相关系统软件开发工作 , 负责Soc芯片BringUp及系统软件开发 , 乐于分享喜欢学习 , 喜欢专研Linux内核源代码 。
前面讲到过写时复制缺页异常(COW),一般用于父子进程之间共享页 , 而我们会常见一种缺页异常是匿名映射缺页异常 , 今天我们就来讨论下这种缺页异常 , 让大家彻底理解它 。 注:本文使用linux-5.0内核源代码 。 文章分为以下几节内容:
1.匿名映射缺页异常的触发情况 2.0页是什么?为什么使用0页?
3.源代码分析
3.1 触发条件
3.2 第一次读匿名页
3.3 第一次写匿名页
3.4 读之后写匿名页
4.应用层实验
5.总结
在讲解匿名映射缺页异常之前我们先要了解以下什么是匿名页?与匿名页相对应的是文件页 , 文件页我们应该很好理解 , 就是映射文件的页 , 如:通过mmap映射文件到虚拟内存然后读文件数据,进程的代码数据段等 , 这些页有后备缓存也就是块设备上的文件 , 而匿名页就是没有关联到文件的页 , 如:进程的堆、栈等 。 还有一点需要注意:下面讨论的都是私有的匿名页的情况 , 共享匿名页在内核演变为文件映射缺页异常(伪文件系统) , 后面有机会我们会讲解 , 感兴趣的小伙伴可以看一看mmap的代码实现对共享匿名页的处理 。
一 , 匿名映射缺页异常的触发情况前面我们讲解了什么是匿名页 , 那么思考一下什么情况下会触发匿名映射缺页异常呢?这种异常对于我们来说非常常见:
1.当我们应用程序使用malloc来申请一块内存(堆分配) , 在没有使用这块内存之前 , 仅仅是分配了虚拟内存 , 并没有分配物理内存 , 第一次去访问的时候才会通过触发缺页异常来分配物理页建立和虚拟页的映射关系 。
2.当我们应用程序使用mmap来创建匿名的内存映射的时候 , 页同样只是分配了虚拟内存 , 并没有分配物理内存 , 第一次去访问的时候才会通过触发缺页异常来分配物理页建立和虚拟页的映射关系 。
3.当函数的局部变量比较大 , 或者是函数调用的层次比较深 , 导致了当前的栈不够用了 , 这个时候需要扩大栈 。 当然了上面的这几种场景对应应用程序来说是透明的 , 内核为用户程序做了大量的处理工作 , 下面几节会看到如何处理 。
二 , 0页是什么?为什么使用0页?这里为什么会说到0页呢?什么是0页呢?是地址为0的页吗?答案是:系统初始化过程中分配了一页的内存 , 这段内存全部被填充0 。 下面我们来看下0页如何分配的:在arch/arm64/mm/mmu.c中:
61 /*62* Empty_zero_page is a special page that is used for zero-initialized data63* and COW.64*/65 unsigned long empty_zero_page[PAGE_SIZE / sizeof(unsigned long)] __page_aligned_bss;66 EXPORT_SYMBOL(empty_zero_page);可以看到定义了一个全局变量 , 大小为一页 , 页对齐到bss段 , 所有这段数据内核初始化的时候会被清零 , 所有称之为0页 。
那么为什么使用0页呢?一个是它的数据都是被0填充 , 读的时候数据都是0 , 二是节约内存 , 匿名页面第一次读的时候数据都是0都会映射到这页中从而节约内存(共享0页) , 那么如果有进程要去写这个这个页会怎样呢?答案是发生COW重新分配页来写 。
三 , 源代码分析3.1 触发条件当第一节中的触发情况发生的时候 , 处理器就会发生缺页异常 , 从处理器架构相关部分过渡到处理器无关部分 , 最终到达handle_pte_fault函数:
3742 static vm_fault_t handle_pte_fault(struct vm_fault *vmf)3743 {3744pte_t entry;...3782if (!vmf->pte) {3783if (vma_is_anonymous(vmf->vma))3784return do_anonymous_page(vmf);3785else3786return do_fault(vmf);3787}


推荐阅读