什么是page fault

在Linux内核中,每一个过程都有一个独立的虚拟地址空间,而过程自身感知不到真正的物理内存的存在(比方某过程感知到的内存是间断的,然而实际上它被调配的内存是物理内存中扩散的空间)。MMU(内存治理单元)负责实现对于这种虚拟地址-物理内存地址的转换工作。Linux为每一个过程保护了一张页表,用于记录虚拟地址和物理内存地址之间的关系,并在过程运行时实时进行地址转换从而使得过程拜访到真正的物理内存。

图1 内存治理单元MMU

而咱们要提到的page fault便是在这种场景下产生的,page fault大抵能够分为三类:

  • major page fault:过程要拜访的页面不在真正的物理内存中,可能须要从磁盘中载入(比方Linux开启了swap机制,内核通过LRU页面置换算法将页面置换到了磁盘中暂存),这个时候将会产生一个major page fault。

图2 swap页面置换机制

  • minor page fault:过程要拜访的页面在物理内存中,然而MMU还没有建设过程的虚拟地址和相应物理地址的映射关系,这个时候就会触发soft page fault(比如说咱们应用malloc函数在堆上申请内存,在过程不拜访这片虚拟内存之前它的物理内存页是不会被理论调配的,当过程第一次拜访应用malloc调配的内存空间时,因为会调配物理内存,这时候只须要再建设虚拟内存-物理内存的映射关系即可)。
  • invalid fault:就是segment fault,过程的内存越界。

在kindling中,咱们重点关注前两种page fault,事实上,结合实际需要,咱们其实往往只须要关注major page fault即可,因为minor page fault从某种意义上来说并不算是一种"fault",它是内核中广泛且常见的一种景象,而major page fault的呈现往往意味着零碎开始咱们内存可能不太够用了,这时候咱们就须要重点关注是哪些线程触发了这些major page fault,从而帮忙咱们更好的定位和排查问题。

无关page fault的tracepoint

Linux内核为page fault提供了tracepoint,/sys/kernel/debug/tracing/events/exceptions外面有相干的tracepoint构造体形容,/sys/kernel/debug/tracing/events/exceptions分为pagefaultkernel和pagefaultuser,对应于内核和用户态的page fault(内核也可能page fault,比方copyfromuser的时候)。咱们关上外面的format文件能够看到如下构造:

name: page_fault_kernelID: 115format:  field:unsigned short common_type; offset:0; size:2; signed:0;  field:unsigned char common_flags; offset:2; size:1; signed:0;  field:unsigned char common_preempt_count; offset:3; size:1; signed:0;  field:int common_pid; offset:4; size:4; signed:1;  field:unsigned long address;  offset:8; size:8; signed:0;  field:unsigned long ip; offset:16;  size:8; signed:0;  field:unsigned long error_code; offset:24;  size:8; signed:0;print fmt: "address=%pf ip=%pf error_code=0x%lx", (void *)REC->address, (void *)REC->ip, REC->error_codename: page_fault_userID: 116format:  field:unsigned short common_type; offset:0; size:2; signed:0;  field:unsigned char common_flags; offset:2; size:1; signed:0;  field:unsigned char common_preempt_count; offset:3; size:1; signed:0;  field:int common_pid; offset:4; size:4; signed:1;  field:unsigned long address;  offset:8; size:8; signed:0;  field:unsigned long ip; offset:16;  size:8; signed:0;  field:unsigned long error_code; offset:24;  size:8; signed:0;print fmt: "address=%pf ip=%pf error_code=0x%lx", (void *)REC->address, (void *)REC->ip, REC->error_code

这个构造外面有个error_code的字段,含意如下:

  • error_code:当异样产生时,硬件压入栈中的错误代码。
  • 当第0位被置0时,则异样是由一个不存在的页所引起的,否则是由有效的拜访权限引起的。
  • 如果第1位被置0,则异样由读拜访或者执行拜访所引起,如果被设置,则异样由写访问引起。
  • 如果第2位被置0,则异样产生在内核态,否则异样产生在用户态。

    page fault事件可观测性实现计划

    计划一 基于switch等事件

    可在switch,execeve,fork,vfork,clone这些事件中引入pgftmaj,pgftmin等参数,区别于tracepoint的形式,其pgftmaj和pgftmin是通过内核的taskstruct构造体中的参数获取的。以switch为例,这种实现形式会在线程进行上下文切换时获取pgftmaj&pgft_min的值,这样做的益处是能够实时监测到每一次线程调度时的page fault的变动状况,对于单过程来说,这样的实现形式是没有太大问题的。然而在目前kindling的实现中,因为probe和collector之间通过unix socket domain来通信,通过文件系统进行跨过程的数据传输,经测试,通过文件系统进行跨过程通信对于大量的switch事件的性能敏感度是十分高的。

计划二 基于page fault事件

在kindling中,咱们尝试在pagefaultuser和kernel的tracepoint点中获取过程构造体信息,从而实现这一部分性能,因为page_fault只在产生页谬误时触发,因而事件数量失去了管制,防止了过多事件导致的性能问题。

图3 page fault可观测性实现原理

总结

为了兼顾性能,咱们抉择了基于page fault tracepoint的形式来实现,并在其中通过内核task_struct补充所需参数的信息。整个机制以事件为驱动,只有当内核产生了page fault事件时才会对数据进行获取。咱们能够通过page fault信息(尤其是major page fault)帮忙咱们定位和排查问题。

Kindling是一款基于eBPF的云原生可观测性开源工具,旨在帮忙用户更好更快的定界云原生零碎问题,并致力于打造云原生全故障域的定界能力。欢送这方面的使用者和爱好者与咱们分割。

退出咱们

关注咱们