2023-06-11  阅读(2)
原文作者:奇小葩 原文地址:https://blog.csdn.net/u012489236/category_9614673.html

上一章重点学习了内核对于异常处理的总体的流程,从异常向量为入口,最终调用到真正的异常处理的接口__do_page_fault,本章主要是学习之前提到的内存缺页异常的常见场景中如何实现

    static int __kprobes
    __do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
    		unsigned int flags, struct task_struct *tsk)
    {
    	struct vm_area_struct *vma;
    	int fault;
    
    	vma = find_vma(mm, addr);                                 -------------------(1)
    	fault = VM_FAULT_BADMAP;
    	if (unlikely(!vma))
    		goto out;
    	if (unlikely(vma->vm_start > addr))                      --------------------(2)
    		goto check_stack;
    
    	/*
    	 * Ok, we have a good vm_area for this
    	 * memory access, so we can handle it.
    	 */
    good_area:                                                   --------------------(3)
    	if (access_error(fsr, vma)) {
    		fault = VM_FAULT_BADACCESS;
    		goto out;
    	}
    
    	return handle_mm_fault(vma, addr & PAGE_MASK, flags);    --------------------(4)
    
    check_stack:
    	/* Don't allow expansion below FIRST_USER_ADDRESS */
    	if (vma->vm_flags & VM_GROWSDOWN &&
    	    addr >= FIRST_USER_ADDRESS && !expand_stack(vma, addr))
    		goto good_area;
    out:
    	return fault;
    }
  • 首先find_vma通过失效地址addr来查找vma,如果没有找到vma,说明addr地址还没有在进程地址空间中分配任何一个VMA的线性区,这将是一种严重的错误,返回VM_FAULT_BADMAP错误,内核将会杀掉该进程

  • 找到了vma,如果发现addr不在vma的映射区,可能是下面两种原因

    • 该区域的VM_GROWSDOWN标志位置位,意味着该区域是栈区,自顶向下增长,接下来调用expand_stack适当地增大栈
    • 找到的区域不是栈,访问无效

    expand_stack函数主要完成

    • 开展堆栈区间的VMA,重新调整VMA的起始地址以可以容纳addr,这个前提是不会导致进程区间超限或者进程动态分配的页面超限
    • expand_stack只是更改了堆栈区的vm_area_struct结构,没有建立物理内存映射
    • 当返回值为非0 ,就返回对应的错误码,并退出,交由上级程序处理
  • 当地址存在后,调用access_error判断当前vma是否具有可写或可执行权限,如果发生一个写错误的缺页异常,首先判断vma属性是否具有可写,如果没有就返回VM_FAULT_BADACCESS

  • 最后调用handle_mm_fault函数,它是缺页中断的核心处理函数,正常情况下将返回VM_FAULT_MAJOR或VM_FAULT_MINOR,返回错误码fault并加一task的maj_flt或min_flt成员;

确定异常是在允许的地址触发,内核必须确定将所需数据读取到物理内存的适当方法,该任务委托给handle_mm_fault是一个体系结构无关的,用于选择适当的异常恢复方法(按需调页/换入等),并应用选择的方法。其大致的处理流程如下图所示

202306111243553961.png

handle_mm_fault为引发缺页的进程分配一个物理页框,它先确定与引发缺页的线性地址对应的各级页目录项是否存在,如不存在则分进行分配。具体如何分配这个页框是通过调用handle_pte_fault()完成的,注意最后一个参数flag,它来源于fsr。

  • 创建各级页表目录,该函数支持四级页表,而ARM32只支持2级页表,那么pgd = pua = pmd,该函数主要是分配pmd,然后调用handle_pte_fault

202306111243579842.png

handle_mm_fault函数确认在各级页目录中,通向对应于异常地址页表项的各个页目录都存在,handle_pte_fault函数分析缺页的原因,并进行处理,其大致的处理流程如下

202306111243585783.png

  • 如果页不在物理内存中,那么就分为以下3中情况

    • 匿名映射: 如果PTE的内容为空,没有找到对应的页表项,对于anonymous page,将通过调用do_anonymous_page()函数来分配和映射新页面,也就是按需分配。

      用户空间使用malloc()进行内存申请时(对应底层的实现是mmap或者brk),内核并不会立刻为其分配物理内存,而只是为请求的进程的rbtree管理的vma信息中记录(添加或更改)诸如内存范围和标志之类的信息。只有当内存被真正使用,触发page fault,才会真正分配物理页面和对应的页表项,即demand alloction,对应的函数实现是 do_anonymous_page() 。通过mmap映射建立的heap和stack等内存区域,在初始未使用时,也适用于这样的规则。

    • 文件映射: 如果PTE的内容为空,处理文件页发生异常,将会通过do_fault来分配和映射新页面

      对于page cache, 在发生内存回收后,部分text(code)段的页面会被discard,部分data段的页面会被writeback,之后再次访问这些页面,也将出现page fault。此时,需要从外部存储介质中,将页面内容调回内存,即demand paging,对应的函数实现是 do_fault()

    • 换入或按需调页: 如果该页标记为不存在,而页表中保存了相关的信息,则意味着页已经被换出,因而必须从系统的某个交换分区换入,则调用do_swap_page来分配和映射新页面

  • 如果页面在物理内存中

    • 写时复制: 如果页在物理内存中,当用户向共享内存发出写请求时,它将现有的共享页面复制到新页面

Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文