• (十四)Linux内存管理之page fault处理【转】


    转自:https://www.cnblogs.com/LoyenWang/p/12116570.html

    背景

    • Read the fucking source code! --By 鲁迅
    • A picture is worth a thousand words. --By 高尔基

    说明:

    1. Kernel版本:4.14
    2. ARM64处理器,Contex-A53,双核
    3. 使用工具:Source Insight 3.5, Visio

    1. 概述

    上篇文章分析到malloc/mmap函数中,内核实现只是在进程的地址空间建立好了vma区域,并没有实际的虚拟地址到物理地址的映射操作。这部分就是在Page Fault异常错误处理中实现的。

    Linux内核中的Page Fault异常处理很复杂,涉及的细节也很多,malloc/mmap的物理内存映射只是它的一个子集功能,下图大概涵盖了出现Page Fault的情况:

    下边就开始来啃啃硬骨头吧。

    2. Arm64处理

    Page Fault的异常处理,依赖于体系结构,因此有必要来介绍一下Arm64的处理。
    代码主要参考:arch/arm64/kernel/entry.S

    Arm64在取指令或者访问数据时,需要把虚拟地址转换成物理地址,这个过程需要进行几种检查,在不满足的情况下都能造成异常:

    1. 地址的合法性,比如以39有效位地址为例,内核地址的高25位为全1,用户进程地址的高25位为全0;
    2. 地址的权限检查,这里边的权限位都位于页表条目中;

    从上图中可以看到,最后都会调到do_mem_abort函数,这个函数比较简单,直接看代码,位于arch/arm64/mm/fault.c

    /*
     * Dispatch a data abort to the relevant handler.
     */
    asmlinkage void __exception do_mem_abort(unsigned long addr, unsigned int esr,
    					 struct pt_regs *regs)
    {
    	const struct fault_info *inf = esr_to_fault_info(esr);
    	struct siginfo info;
    
    	if (!inf->fn(addr, esr, regs))
    		return;
    
    	pr_alert("Unhandled fault: %s (0x%08x) at 0x%016lx
    ",
    		 inf->name, esr, addr);
    
    	mem_abort_decode(esr);
    
    	info.si_signo = inf->sig;
    	info.si_errno = 0;
    	info.si_code  = inf->code;
    	info.si_addr  = (void __user *)addr;
    	arm64_notify_die("", regs, &info, esr);
    }
    

    该函数中关键的处理:根据传进来的esr获取fault_info信息,从而去调用函数。struct fault_info用于错误状态下对应的处理方法,而内核中也定义了全局结构fault_info,存放了所有的情况。
    主要的错误状态和处理函数对应如下:

    static const struct fault_info fault_info[] = {
    	{ do_bad,		SIGBUS,  0,		"ttbr address size fault"	},
    	{ do_bad,		SIGBUS,  0,		"level 1 address size fault"	},
    	{ do_bad,		SIGBUS,  0,		"level 2 address size fault"	},
    	{ do_bad,		SIGBUS,  0,		"level 3 address size fault"	},
    	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 0 translation fault"	},
    	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 1 translation fault"	},
    	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 2 translation fault"	},
    	{ do_translation_fault,	SIGSEGV, SEGV_MAPERR,	"level 3 translation fault"	},
    	{ do_bad,		SIGBUS,  0,		"unknown 8"			},
    	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 1 access flag fault"	},
    	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 2 access flag fault"	},
    	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 3 access flag fault"	},
    	{ do_bad,		SIGBUS,  0,		"unknown 12"			},
    	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 1 permission fault"	},
    	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 2 permission fault"	},
    	{ do_page_fault,	SIGSEGV, SEGV_ACCERR,	"level 3 permission fault"	},
         ...
    };
    

    从代码中可以看出:

    • 出现0/1/2/3级页表转换错误时,会调用do_translation_fault,实际中do_translation_fault最终也会调用到do_page_fault
    • 出现1/2/3级页表访问权限的时候,会调用do_page_fault
    • 其他的错误则调用do_bad,其中未列出来的部分还包括do_sea等操作函数;

    do_translation_fault

    do_page_fault

    do_page_fault函数为页错误异常处理的核心函数,与体系结构相关,上图中的handle_mm_fault函数为通用函数,也就是不管哪种处理器结构,最终都会调用到该函数。

    3. handle_mm_fault

    handle_mm_fault用于处理用户空间的页错误异常:

    • 进程在用户模式下访问用户虚拟地址,触发页错误异常;
    • 进程在内核模式下访问用户虚拟地址,触发页错误异常;
      do_page_fault函数的流程图中也能看出来,当触发异常的虚拟地址属于某个vma,并且拥有触发页错误异常的权限时,会调用到handle_mm_fault函数,而handle_mm_fault函数的主要逻辑是通过__handle_mm_fault来实现的。

    流程如下图:

    3.1 do_fault

    do_fault函数用于处理文件页异常,包括以下三种情况:

    1. 读文件页错误;
    2. 写私有文件页错误;
    3. 写共享文件页错误;

    3.2 do_anonymous_page

    匿名页的缺页异常处理调用本函数,在以下情况下会触发:

    1. malloc/mmap分配了进程地址空间区域,但是没有进行映射处理,在首次访问时触发;
    2. 用户栈不够的情况下,进行栈区的扩大处理;

    3.3 do_swap_page

    如果访问Swap页面出错(页面不在内存中),则从Swap cacheSwap文件中读取该页面。
    由于在4.14内核版本中,do_swap_page调用的很多函数都是空函数,无法进一步的了解,大体的流程如下图:

    3.4 do_wp_page

    do_wp_page函数用于处理写时复制(copy on write),会在以下两种情况处理:

    1. 创建子进程时,父子进程会以只读方式共享私有的匿名页和文件页,当试图写的时候,触发页错误异常,从而复制物理页,并创建映射;
    2. 进程创建私有文件映射,读访问后触发异常,将文件页读入到page cache中,并以只读模式创建映射,之后发生写访问后,触发COW

    关键的复制工作是由wp_page_copy完成的:

    作者:LoyenWang
    出处:https://www.cnblogs.com/LoyenWang/
    公众号:LoyenWang
    版权:本文版权归作者和博客园共有
    转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    MongoDB 删除文档
    MongoDB 删除文档
    C#标记 [已弃用] 的方法
    C#标记 [已弃用] 的方法
    MySQL 正则表达式
    MySQL 正则表达式
    SQLcase when then用法
    SQLcase when then用法
    衣服尺码自定义排序sql
    衣服尺码自定义排序sql
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/13646063.html
Copyright © 2020-2023  润新知