深入理解Page Fault处理

故事背景:前天晚上,在极客星球的群里,有人提出一个问题:

80051125d6a60aaeef13035f477f30a4.png

他的问题(场景)可以理解为这样:当一个进程要访问的文件物理页已经被其他进程加载进系统了(类似场景:共享内存,加载共享lib库等),那么这个进程自己第一次访问文件页时(读或者写),是否发生缺页中断?

65b9f196caf835d5c2c8a12ac6b1d285.png

球友们各抒已见,发现问题涉及的细节很多,随着讨论深入,很多基础概念理解大家都得到了加深。

16c743739d334382c4e25dd29ca3ba42.png

最后我们都会总结一下问题和答案(问题闭环), 今天先分享一篇经典图解缺页中断处理文章给大家,希望大家喜欢,可以为后续掌握Linux内存子系统核心知识打良好基础。

315f6e24aed82dd0fc394a010b9e67fb.png

对问题感兴趣的同学,也可以加入极客星球一起来学习讨论,加深理解。

永远不要停止思考

1. 概述

Linux内核中的Page Fault异常处理很复杂,涉及的细节也很多,虚拟地址到物理地址的映射操作。这部分就是在Page Fault异常错误处理中实现的。malloc/mmap的物理内存映射只是它的一个子集功能,下图大概涵盖了出现Page Fault的情况:

df06b1239c1d59f848ad0a6baf887c0a.png


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

分析环境:

  1. Kernel版本:4.14

  2. ARM64处理器,Contex-A53,双核

  3. 使用工具:Source Insight 3.5, Visio

2. Arm64处理

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

b6586a76ea5dc27d90fd8f089a488ebd.png


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\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

118b94a1eed6866284d408820a832fad.png

do_page_fault

6b6a242dee49732c3bd7a3d6169dc894.png

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来实现的。

流程如下图:

6b6ca12a5407bba1a7a3365d48ef1ca2.png

3.1 do_fault

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

  1. 读文件页错误;

  2. 写私有文件页错误;

  3. 写共享文件页错误;

96f443586b5cba41fe33bdbb58d4386d.png


3.2 do_anonymous_page

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

  1. malloc/mmap分配了进程地址空间区域,但是没有进行映射处理,在首次访问时触发;

  2. 用户栈不够的情况下,进行栈区的扩大处理;

be5fcd469c08ed7d298f77b5a3722449.png


3.3 do_swap_page

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

fc478968f66835d7aa9ddfb4b2478d90.png


3.4 do_wp_page

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

  1. 创建子进程时,父子进程会以只读方式共享私有的匿名页和文件页,当试图写的时候,触发页错误异常,从而复制物理页,并创建映射;

  2. 进程创建私有文件映射,读访问后触发异常,将文件页读入到page cache中,并以只读模式创建映射,之后发生写访问后,触发COW

d7450748b24232ec94c1792a084f8ccd.png


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

62c5fdc3250265c3b10fe7de81375cfc.png



机会总是留给有准备的人,欢迎大家加入极客星球,极客星球是一个全面提升技术的社区,极客星球会分享很多核心技术的理解,相互交流,帮助大家快速成长,学习和掌握后台核心技术,深入理解Linux系统,理解技术核心概念,夯实基本功,扩展技术视野,疑难解答,带领大家长期坚持学习,掌握核心技术,努力成为技术专家,对星球感兴趣的,点击查看-> 极客星球

d236d99eb75364899367b0ddafd8b5fc.png

进腾讯了|学习技术哪家强

90c312d6bc977067c21c34cf4e24a6a1.png

- END -


看完一键三连在看转发点赞

是对文章最大的赞赏,极客重生感谢你5363f118874ad1eaf672a9cb41f70b72.png

推荐阅读

600443e5938830805100bb1d50b95c5a.png

定个目标|建立自己的技术知识体系


9859c4fb0f7b6d356d27b937cd86e5ce.png

大厂后台开发基本功修炼路线和经典资料

c911870d9110192595e2b92527625176.png

难走的路,从不拥挤

你好,这里是极客重生,我是阿荣,大家都叫我荣哥,从华为->外企->到互联网大厂,目前是大厂资深工程师,多次获得五星员工,多年职场经验,技术扎实,专业后端开发和后台架构设计,热爱底层技术,丰富的实战经验,分享技术的本质原理,希望帮助更多人蜕变重生,拿BAT大厂offer,培养高级工程师能力,成为技术专家,实现高薪梦想,期待你的关注!点击蓝字查看我的成长之路

校招/社招/简历/面试技巧/大厂技术栈分析/后端开发进阶/优秀开源项目/直播分享/技术视野/实战高手等, 极客星球希望成为最有技术价值星球,尽最大努力为星球的同学提供面试,跳槽,技术成长帮助!详情查看->极客星球

                                                                求点赞,在看,分享三连7d6cc4d539900a47071821d73cacef89.png

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值