zoukankan      html  css  js  c++  java
  • (十四)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_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    无法重用Linq2Entity Query
    The Joel Test
    MSBuilder directly instead of default VSComplie with keyborad shotcut 原创
    客户端缓存(Client Cache)
    关于代码重构和UT的一些想法,求砖头
    ExtJS2.0实用简明教程 应用ExtJS
    Perl information,doc,module document and FAQ.
    使用 ConTest 进行多线程单元测试 为什么并行测试很困难以及如何使用 ConTest 辅助测试
    史上最简单的Hibernate入门简介
    汽车常识全面介绍 传动系统
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/13688656.html
Copyright © 2011-2022 走看看