5 内核探测 #
内核探测是用于收集 Linux 内核调试信息和性能信息的一组工具。开发人员和系统管理员通常使用这些工具来调试内核或查找系统性能瓶颈。然后,可以使用报告的数据来微调系统,以改善性能。
您可将这些探测插入到任何内核例程,并指定在命中特定断点后要调用的处理程序。内核探测的主要优势在于,在探测中进行更改后,您无需再重构建内核并重引导系统。
要使用内核探测,通常需要编写或获取特定的内核模块。此类模块包括 init 和 exit 函数。init 函数(例如 register_kprobe()
)可注册一个或多个探测,而 exit 函数可取消注册这些探测。注册函数定义了要将探测插入到何处,以及在命中探测后要调用哪个处理程序。要一次性注册或取消注册一组探测,可以使用相关的 register_<PROBE_TYPE>probes()
或 unregister_<PROBE_TYPE>probes()
函数。
通常使用 printk
内核例程报告调试和状态消息。printk
是一个内核空间,相当于用户空间 printf
例程。有关 printk
的详细信息,请参见 Logging kernel messages(记录内核消息)。正常情况下,您可以通过检查 systemd
日记的输出(请参见第 17 章 “journalctl
:查询 systemd
日记”)查看这些消息。有关日志文件的详细信息,请参见第 3 章 “分析和管理系统日志文件”。
5.1 支持的体系结构 #
可在下列体系结构上完全实施内核探测:
x 86
AMD64/Intel 64
Arm
POWER
可在下列体系结构上部分实施内核探测:
IA64(不支持对指令
slot1
使用探测)sparc64(尚未实施返回探测)
5.2 内核探测的类型 #
有三种内核探测:Kprobe、Jprobe 和 Kretprobe。Kretprobe 有时称作返回探测。您可以找到 Linux 内核中所有三种类型探测的源代码示例。请查看目录 /usr/src/linux/samples/kprobes/
(软件包 kernel-source
)。
5.2.1 Kprobe #
Kprobe 可附加到 Linux 内核中的任何指令。注册 Kprobe 后,它会在所探测指令的第一个字节处插入一个断点。当处理器命中此断点时,将保存处理器注册,然后由 Kprobe 接管后续处理。首先执行一个前处理程序,然后执行所探测的指令,最后执行一个后处理程序。随后,控制权将传递给探测点后面的指令。
5.2.2 Jprobe #
Jprobe 是通过 Kprobe 机制实施的。它将插入到函数的入口点,允许直接访问正在探测的函数的参数。其处理程序例程的参数列表与返回值必须与所探测函数相同。要结束 Jprobe,请调用 jprobe_return()
函数。
命中某个 jprobe 后,将保存处理器注册,并将指令指针定向到 jprobe 处理程序例程。然后,控制权将传递给与所要探测函数具有相同注册内容的处理程序。最后,该处理程序调用 jprobe_return()
函数,并将控制权切回给控制函数。
一般情况下,您可以在一个函数中插入多个探测。但是,Jprobe 仅限每个函数一个实例。
5.2.3 返回探测 #
返回探测也是通过 Kprobe 实施的。调用 register_kretprobe()
函数时,会将一个 kprobe 附加到所探测函数的入口。命中探测后,内核探测机制将保存所探测函数的返回地址,并调用用户定义的返回处理程序。然后,控制权将传回给所探测的函数。
在调用 register_kretprobe()
之前,需要设置一个 maxactive
参数,用于指定可以同时探测多少个函数实例。如果设置值太小,您会错过某些探测。
5.3 Kprobe API #
Kprobe 的编程接口由用于注册和取消注册所有已用内核探测及关联探测处理程序的函数组成。有关这些函数及其参数的更详细说明,请参见第 5.5 节 “更多信息”中的信息源。
register_kprobe()
在指定的地址上插入一个断点。命中该断点时,将调用
pre_handler
和post_handler
。register_jprobe()
在指定的地址中插入一个断点。该地址须是所探测函数的第一个指令的地址。命中该断点时,将运行指定的处理程序。该处理程序的参数列表与返回类型应与所探测函数相同。
register_kretprobe()
为指定的函数插入一个返回探测。当所探测函数返回值时,将运行指定的处理程序。此函数在成功时返回 0,在失败时返回带负号的错误编号。
unregister_kprobe()
、unregister_jprobe()
、unregister_kretprobe()
去除指定的探测。注册探测后便可随时使用该探测。
register_kprobes()
、register_jprobes()
、register_kretprobes()
在指定的阵列中插入每个探测。
unregister_kprobes()
、unregister_jprobes()
、unregister_kretprobes()
去除指定阵列中的每个探测。
disable_kprobe()
、disable_jprobe()
、disable_kretprobe()
暂时禁用指定的探测。
enable_kprobe()
、enable_jprobe()
、enable_kretprobe()
暂时启用已禁用的探测。
5.4 debugfs
接口 #
在最新的 Linux 内核中,Kprobe 工具使用内核的 debugfs
接口。此接口可以列出所有已注册的探测并全局打开或关闭所有探测。
5.4.1 列出已注册的内核探测 #
所有当前已注册探测的列表位于 /sys/kernel/debug/kprobes/list
文件中。
saturn.example.com:~ # cat /sys/kernel/debug/kprobes/list c015d71a k vfs_read+0x0 [DISABLED] c011a316 j do_fork+0x0 c03dedc5 r tcp_v4_rcv+0x0
第一列列出探测所要插入到的内核中的地址。第二列列显探测的类型:k
表示 kprobe,j
表示 jprobe,r
表示返回探测。第三列指定探测的符号、偏移和可选模块名称。后面的可选列包含探测的状态信息。如果探测插入到不再有效的虚拟地址,将以 [GONE]
标记。如果探测被暂时禁用,将以 [DISABLED]
标记。
5.4.2 如何打开或关闭所有内核探测 #
/sys/kernel/debug/kprobes/enabled
文件表示一个开关,可用于全局以及强制性打开或关闭所有已注册的内核探测。要关闭这些探测,只需在命令行中输入
root #
echo "0" > /sys/kernel/debug/kprobes/enabled
(以 root
身份)。要再次打开这些探测,请输入
root #
echo "1" > /sys/kernel/debug/kprobes/enabled
请注意,这不会更改探测的状态。如果某个探测被暂时禁用,在输入后一条命令之后,该探测不会自动启用,而是保持 [DISABLED]
状态。
5.5 更多信息 #
有关内核探测的详细信息,请查看以下信息源:
/usr/src/linux/Documentation/kprobes.txt
(软件包kenrel-source
)中提供了有关内核探测的全面信息(但侧重于技术方面)。/usr/src/linux/samples/kprobes/
目录(软件包kenrel-source
)中提供了所有三类探测的示例(以及相关的Makefile
)。《The Linux Kernel Module Programming Guide》(Linux 内核模块编程指南) 中提供了有关 Linux 内核模块和
printk
内核例程的深入信息在《Kernel debugging with Kprobes》(使用 Kprobe 进行内核调试)中可以找到有关内核探测用法的实践(但这些信息有点过时)