什么是 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_kernel
ID: 115
format:
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
name: page_fault_user
ID: 116
format:
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 的云原生可观测性开源工具,旨在帮忙用户更好更快的定界云原生零碎问题,并致力于打造云原生全故障域的定界能力。欢送这方面的使用者和爱好者与咱们分割。
退出咱们
关注咱们