【原创】(十四)Linux内存管理之page fault处理
背景
Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基
说明:
- Kernel版本:4.14
- ARM64处理器,Contex-A53,双核
- 使用工具: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在取指令或者访问数据时,需要把虚拟地址转换成物理地址,这个过程需要进行几种检查,在不满足的情况下都能造成异常:
- 地址的合法性,比如以39有效位地址为例,内核地址的高25位为全1,用户进程地址的高25位为全0;
- 地址的权限检查,这里边的权限位都位于页表条目中;
从上图中可以看到,最后都会调到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\n",
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
函数用于处理文件页异常,包括以下三种情况:
- 读文件页错误;
- 写私有文件页错误;
- 写共享文件页错误;
3.2 do_anonymous_page
匿名页的缺页异常处理调用本函数,在以下情况下会触发:
- malloc/mmap分配了进程地址空间区域,但是没有进行映射处理,在首次访问时触发;
- 用户栈不够的情况下,进行栈区的扩大处理;
3.3 do_swap_page
如果访问Swap页面
出错(页面不在内存中),则从Swap cache
或Swap文件
中读取该页面。
由于在4.14内核
版本中,do_swap_page
调用的很多函数都是空函数,无法进一步的了解,大体的流程如下图:
3.4 do_wp_page
do_wp_page
函数用于处理写时复制(copy on write
),会在以下两种情况处理:
- 创建子进程时,父子进程会以只读方式共享私有的匿名页和文件页,当试图写的时候,触发页错误异常,从而复制物理页,并创建映射;
- 进程创建私有文件映射,读访问后触发异常,将文件页读入到
page cache
中,并以只读模式创建映射,之后发生写访问后,触发COW
;
关键的复制工作是由wp_page_copy
完成的:
【原创】(十四)Linux内存管理之page fault处理的更多相关文章
- 【原创】(十)Linux内存管理 - zoned page frame allocator - 5
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- 【原创】(六)Linux内存管理 - zoned page frame allocator - 1
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- Linux内存管理 (11)page引用计数
专题:Linux内存管理专题 关键词:struct page._count._mapcount.PG_locked/PG_referenced/PG_active/PG_dirty等. Linux的内 ...
- 【读书笔记】C#高级编程 第十四章 内存管理和指针
(一)后台内存管理 1.值数据类型 Windows使用一个虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址,该任务由Windows在后台管理(32位每个进程可使用4GB虚拟内存,64 ...
- 【原创】(七)Linux内存管理 - zoned page frame allocator - 2
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- 【原创】(九)Linux内存管理 - zoned page frame allocator - 4
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- 【原创】(八)Linux内存管理 - zoned page frame allocator - 3
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
- Linux内存描述之内存页面page–Linux内存管理(四)
服务器体系与共享存储器架构 日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.7 X86 & arm gatieme LinuxDeviceDriver ...
- 伙伴系统之避免碎片--Linux内存管理(十六)
1 前景提要 1.1 碎片化问题 分页与分段 页是信息的物理单位, 分页是为了实现非连续分配, 以便解决内存碎片问题, 或者说分页是由于系统管理的需要. 段是信息的逻辑单位,它含有一组意义相对完整的信 ...
随机推荐
- UIView 判断是否visible
if(self.view == [(MyAppDelegate *)[[UIApplication sharedApplication] delegate].window.subviews objec ...
- cume_dist(),名次分析——-最大排名/总个数
函数:cume_dist() over(order by id) select id,area,score, cume_dist() over(order by id) a, --按ID最大排名/总个 ...
- 设计模式 - 工厂模式(factory pattern) 具体解释
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u012515223/article/details/27081511 工厂模式(factory pa ...
- Resharper 如何把类里的类移动到其他文件
有时候,看到一个类里有很多类,需要把他移动其他文件 假如有一个类 class A { class B { } } 如何把 B 移动文件 B里? 一般使用 快捷键是 Resharper 的快捷键,如果不 ...
- Oracle dbms_random包的用法
1.dbms_random.value方法 dbms_random是一个可以生成随机数值或者字符串的程序包.这个包有initialize().seed().terminate().value().no ...
- oracle函数 DUMP(w[,x[,y[,z]]])
[功能]返回数据类型.字节长度和在内部的存储位置. [参数] w为各种类型的字符串(如字符型.数值型.日期型……) x为返回位置用什么方式表达,可为:8,10,16或17,分别表示:8/10/16进制 ...
- UTF-8与UTF-8 BOM
在我们通常使用的windows系统中,我发现了一个有趣的现象.我新建一个空的文本文档,点击文件-另存为-编码选择UTF-8,然后保存.此时这个文件明明是空的,却占了3字节大小.原因在于:此时保存的编码 ...
- 2019-11-13-如何在国内发布-UWP-应用
title author date CreateTime categories 如何在国内发布 UWP 应用 lindexi 2019-11-13 08:46:44 +0800 2019-02-17 ...
- JavaScript 拖曳和居中问题
今天遇到了一个问题,是这样的,有一个div盒子,实现盒子居中,居中的样式是这样的见下 #box{ width:300px; height:150px; position:absolute; left: ...
- ThinkPHP5.1接收post、get参数
我们要先认识的是请求对象Request类 <?php//要用Request类 第一步就要引入他,才能在当前控制器上使用//知识点:use 与 namespace前面不可有空格等其他操作.name ...