4 SystemTap — 过滤和分析系统数据 #
SystemTap 提供了命令行界面和脚本语言,用于细致检查运行中 Linux 系统(特别是内核)的活动。SystemTap 脚本采用 SystemTap 脚本语言编写,随后会编译为 C 代码内核模块并插入到内核中。您可以设计脚本来提取、过滤和汇总数据,以便对复杂的性能问题或功能问题进行诊断。SystemTap 提供的信息与 netstat
、ps
、top
和 iostat
等工具的输出类似。不过,它提供了更多用于过滤和分析所收集信息的选项。
4.1 概念概述 #
每当您运行 SystemTap 脚本时,都会启动一个 SystemTap 会话。需要针对脚本传递几个参数,脚本才能运行。然后,该脚本会编译为内核模块并加载到内核中。如果以前执行过该脚本,并且任何系统组件都未更改(例如,不同的编译器或内核版本、库路径或脚本内容),SystemTap 不会再次编译该脚本,而是使用 SystemTap 缓存 (~/.systemtap
) 中存储的 *.c
和 *.ko
数据。
当 tap 完成运行时,将卸载模块。有关示例,请参见第 4.2 节 “安装和设置”中的测试运行和相关说明。
4.1.1 SystemTap 脚本 #
SystemTap 的用法基于 SystemTap 脚本 (*.stp
)。这些脚本会告知 SystemTap 要收集哪类信息,以及收集信息后要执行哪项操作。脚本采用与 AWK 和 C 类似的 SystemTap 脚本语言编写。有关语言定义,请参见 https://sourceware.org/systemtap/langref.pdf。https://www.sourceware.org/systemtap/examples/ 中提供了大量有用示例脚本。
SystemTap 脚本的基本运作原理是指定 events
并为其提供 handlers
。当 SystemTap 运行脚本时,它会监控特定的事件。发生某个事件时,Linux 内核会将处理程序作为子例程运行,然后继续。因此,事件充当了运行处理程序的触发器。处理程序可以记录指定的数据,并以特定的方式列显数据。
SystemTap 语言仅使用少量几种数据(整数、字符串,以及这些类型的关联数组)和完整控制结构(块、条件、循环、函数)。它包含轻量标点符号(可选用分号),且不需要详细的声明(系统会自动推断并检查类型)。
有关 SystemTap 脚本及其语法的详细信息,请参见第 4.3 节 “脚本语法”,以及 systemtap-docs
软件包中提供的 stapprobes
和 stapfuncs
手册页。
4.1.2 Tapset #
Tapset 是可在 SystemTap 脚本中使用的预先编写的探测和函数库。当用户运行某个 SystemTap 脚本时,SystemTap 会根据 tapset 库检查该脚本的探测事件和处理程序。然后,SystemTap 会先加载相应的探测和函数,然后再将脚本转换为 C。与 SystemTap 脚本本身类似,tapset 使用文件扩展名 *.stp
。
但是,与 SystemTap 脚本不同的是,tapset 不可直接执行。它们构成了可供其他脚本提取定义的库。因此,tapset 库是一个抽象层,旨在方便用户定义事件和函数。Tapset 为用户可能想要作为事件指定的函数提供别名。了解正确的别名通常比记住特定的内核函数更容易(不同内核版本的内核函数可能会不同)。
4.1.3 命令和特权 #
与 SystemTap 关联的主要命令包括 stap
和 staprun
。要执行这些命令,您需要拥有 root
特权,或者必须是 stapdev
或 stapusr
组的成员。
stap
SystemTap 前端。运行 SystemTap 脚本(通过文件或标准输入)。此命令会将该脚本转换为 C 代码,并将生成的内核模块加载到正在运行的 Linux 内核中。然后执行请求的系统跟踪或探测函数。
staprun
SystemTap 后端。加载和卸载 SystemTap 前端生成的内核模块。
如需每个命令的选项列表,请使用 --help
。有关细节,请参见 stap
和 staprun
手册页。
为了避免单纯出于让用户能够使用 SystemTap 的目的而向其授予 root
访问权限,请使用以下 SystemTap 组之一。SUSE Linux Enterprise Server 上默认未提供这些组,但您可以创建组,并相应地修改访问权限。另外,您还可调整 staprun
命令的权限,前提是这不会对您的环境安全产生不当影响。
stapdev
此组的成员可以使用
stap
运行 SystemTap 脚本,或使用staprun
运行 SystemTap 工具模块。由于运行中的stap
涉及到将脚本编译为内核模块并将其加载到内核中,此组的成员仍拥有有效的root
访问权限。stapusr
此组的成员只能使用
staprun
运行 SystemTap 工具模块。此外,他们只能通过/lib/modules/KERNEL_VERSION/systemtap/
运行这些模块。此目录必须由root
拥有,且仅供root
用户写入。
4.1.4 重要文件和目录 #
以下列表概述了 SystemTap 主要文件和目录。
/lib/modules/KERNEL_VERSION/systemtap/
保存 SystemTap 工具模块。
/usr/share/systemtap/tapset/
保存标准的 tapset 库。
/usr/share/doc/packages/systemtap/examples
保存用于不同目的的多个示例 SystemTap 脚本。仅当已安装
systemtap-docs
软件包时才可用。~/.systemtap/cache
缓存的 SystemTap 文件的数据目录。
/tmp/stap*
SystemTap 文件的临时目录,包含已转换的 C 代码和内核对象。
4.2 安装和设置 #
由于 SystemTap 需要内核相关信息,必须安装一些额外的内核相关软件包。对于您要使用 SystemTap 探测的每个内核,需要安装下面一组软件包。这组软件包应该与内核版本和变种(在下面的概述中以 *
表明)完全匹配。
如果您为系统订阅了联机更新,便可以在 SUSE Linux Enterprise Server 15 SP6 相关的 *-Debuginfo-Updates
联机安装储存库中找到 “debuginfo” 软件包。使用 YaST 启用该储存库。
对于经典 SystemTap 设置,请安装以下软件包(使用 YaST 或 zypper
)。
systemtap
systemtap-server
systemtap-docs
(可选)kernel-*-base
kernel-*-debuginfo
kernel-*-devel
kernel-source-*
gcc
要访问手册页和用于不同目的的有用示例 SystemTap 脚本集合,另外还需安装 systemtap-docs
软件包。
要检查是否在计算机上正确安装了所有软件包以及是否已可使用 SystemTap,请以 root
身份执行以下命令。
#
stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}'
此命令通过运行一个脚本并返回输出,来探测当前使用的内核。如果输出类似于以下内容,则表示 SystemTap 已成功部署并可供使用:
Pass 1: parsed user script and 59 library script(s) in 80usr/0sys/214real ms. Pass 2: analyzed script: 1 probe(s), 11 function(s), 2 embed(s), 1 global(s) in 140usr/20sys/412real ms. Pass 3: translated to C into "/tmp/stapDwEk76/stap_1856e21ea1c246da85ad8c66b4338349_4970.c" in 160usr/0sys/408real ms. Pass 4: compiled C into "stap_1856e21ea1c246da85ad8c66b4338349_4970.ko" in 2030usr/360sys/10182real ms. Pass 5: starting run. read performed Pass 5: run completed in 10usr/20sys/257real ms.
根据任何所用 tapset 的 | |
检查脚本的各个组成部分。 | |
将脚本转换为 C。运行系统 C 编译器以基于脚本创建内核模块。生成的 C 代码 ( | |
在脚本中通过挂接到内核来加载模块并启用所有探测(事件和处理程序)。探测的事件是虚拟文件系统 (VFS) 读取操作。当该事件在任何处理器上发生时,一个有效的处理程序会执行(列显文本 | |
终止 SystemTap 会话后,已禁用探测并卸载内核模块。 |
如果在测试期间出现了任何错误消息,请检查输出以获取有关缺少哪些软件包的提示,并确保正确安装这些软件包。另外,可能还需要重引导并加载相应的内核。
4.3 脚本语法 #
SystemTap 脚本包含以下两个组成部分:
- SystemTap 事件(探测点)
为要执行的关联处理程序上的内核事件命名。事件的示例包括进入或退出特定的函数、计时器即将失效,或者启动或终止会话。
- SystemTap 处理程序(探测主体)
用于指定每当发生特定事件时要执行操作的脚本语言语句系列。通常包括从事件环境提取数据、将数据存储到内部变量,或列显结果。
事件及其相应的处理程序统称为 probe
。SystemTap 事件也称为probe
points
。探测的处理程序也称为probe
body
。
您可以在 SystemTap 脚本中的任意位置插入不同样式的注释:使用 #
、/* */
或 //
作为标记。
4.3.1 探测格式 #
一个 SystemTap 脚本可以包含多个探测。必须采用以下格式编写探测:
probe EVENT {STATEMENTS}
每个探测有一个对应的语句块。此语句块必须括在 { }
中,并包含要针对每个事件执行的语句。
以下示例演示了一个简单的 SystemTap 脚本。
probe1 begin2 {3 printf4 ("hello world\n")5 exit ()6 }7
探测开始。 | |
事件 | |
处理程序定义开始,以 | |
处理程序中定义的第一个函数: | |
| |
处理程序中定义的第二个函数: | |
处理程序定义结束,以 |
事件 begin
2(SystemTap 会话开始)触发了括在 { }
中的处理程序。在本例中,该处理程序是 printf
函数 4。在本例中,该函数列显 hello world
后接换行符 5。然后脚本退出。
如果您的语句块包含多个语句,SystemTap 将按顺序执行这些语句 — 您无需在多个语句之间插入特殊分隔符或终止符。还可以将一个语句块嵌套在另一个语句块中。一般情况下,SystemTap 脚本中的语句块使用的语法和语义与 C 编程语言中使用的相同。
4.3.2 SystemTap 事件(探测点) #
SystemTap 支持多个内置事件。
一般事件语法是带点符号序列。这可将事件名称空间分成不同的部分。可以使用类似于函数调用的语法,通过字符串或数字文本将每个组成部分标识符参数化。组成部分可以包含 *
字符以扩展到其他匹配的探测点。可以在探测点后面添加一个 ?
字符,以指示它是可选的,如果它无法扩展,将不会导致错误。
或者,可以在探测点后面添加一个 !
字符,以指示它是可选的,同时足以满足要求。
SystemTap 支持每个探测有多个事件 — 需以逗号 (,
) 分隔这些事件。如果在一个探测中指定了多个事件,当发生任何指定事件时,SystemTap 将执行处理程序。
事件可分为以下类别:
同步事件:当任一进程在内核代码中的特定位置执行指令时发生。它为其他事件提供了一个参照点(指令地址),从中可以获得更多环境数据。
同步事件的示例包括
vfs.FILE_OPERATION
:虚拟文件系统 (VFS) 的 FILE_OPERATION 事件项。例如,在 第 4.2 节 “安装和设置” 中,read
是用于 VFS 的 FILE_OPERATION 事件。异步事件:不与代码中的特定指令或位置相关联。此探测点系列主要包含计数器、计时器和类似构造。
异步事件的示例包括:
begin
(SystemTap 会话开始) — 运行 SystemTap 脚本时;end
(SystemTap 会话结束)或计时器事件。计时器事件指定要定期执行的处理程序,例如example timer.s(SECONDS)
或timer.ms(MILLISECONDS)
。与其他收集信息的探测一起使用时,计时器事件可让您列显定期更新,了解这些信息在一段时间内的变化。
例如,以下探测每隔 4 秒列显文本 “hello world”:
probe timer.s(4) { printf("hello world\n") }
有关支持的事件的详细信息,请参见 stapprobes
手册页。该手册页的 See
Also 部分还包含了其他手册页的链接,其中讨论了特定子系统和组件的支持事件。
4.3.3 SystemTap 处理程序(探测主体) #
每个 SystemTap 事件附带一个对应的处理程序,该处理程序是为该事件定义的,包含一个语句块。
4.3.3.1 函数 #
如果您需要在多个探测中使用相同的语句集,可将这些语句放到一个函数中,以方便重复使用。函数是由关键字 function
后接名称定义的。函数接受任意数量的字符串或数字参数(以值的形式指定),可返回单个字符串或数字。
function FUNCTION_NAME(ARGUMENTS) {STATEMENTS} probe EVENT {FUNCTION_NAME(ARGUMENTS)}
执行 EVENT 的探测时,将执行 FUNCTION_NAME 中的语句。ARGUMENTS 是传入函数的可选值。
可在脚本中的任意位置定义函数。函数可以接受任意
例 4.1 “简单 SystemTap 脚本”中已介绍了一个常用函数:printf
函数,用于列显带格式的数据。使用 printf
函数时,您可以使用格式字符串指定要列显参数的方式。格式字符串括在引号中,可以包含进一步的格式说明符(以 %
字符引入)。
要使用哪些格式字符串取决于您的参数列表。格式字符串可以包含多个格式说明符 — 每个说明符与相应的参数相匹配。可使用逗号分隔多个参数。
printf
函数 #
上述示例以字符串形式列显当前可执行文件名 (execname()
),并以整数(括在方括号中)形式列显进程 ID (pid()
)。然后,依次列显一个空格、单词 open
和一个换行符,如下所示:
[...] vmware-guestd(2206) open held(2360) open [...]
除了例 4.3 “带格式说明符的 printf
函数”中使用的两个函数(execname()
和 pid()
)以外,还可将其他各种函数用作 printf
参数。
最常用的 SystemTap 函数如下:
- tid()
当前线程的 ID。
- pid()
当前线程的进程 ID。
- uid()
当前用户的 ID。
- cpu()
当前 CPU 编号。
- execname()
当前进程的名称。
- gettimeofday_s()
自 Unix 纪元(1970 年 1 月 1 日)起经过的秒数。
- ctime()
将时间转换为字符串。
- pp()
用于描述当前正在处理的探测点的字符串。
- thread_indent()
用于组织列显结果的有用函数。它会(在内部)存储每个线程 (
tid()
) 的缩进计数器。该函数接受一个参数,即缩进增量,用于指示要在线程的缩进计数器中添加或去除多少个空格。它会返回一个字符串,其中包含一些泛型跟踪数据,以及相应的缩进空格数。返回的泛型数据包括时戳(自线程初始缩进以来的微秒数)、进程名称和线程 ID 本身。这样您便可以识别调用了哪些函数、谁调用了这些函数,以及花费了多长时间。调用入口和出口通常不会彼此紧靠(否则很容易匹配)。在第一个调用入口及其出口之间,通常还创建了其他调用入口和出口。缩进计数器可帮助您将某个入口与其对应的出口进行匹配,因为它会在不是上一个函数调用出口的下一个函数调用缩进。
有关支持的 SystemTap 函数的详细信息,请参见 stapfuncs
手册页。
4.3.3.2 其他基本构造 #
除函数外,您还可以在 SystemTap 处理程序中使用其他常见构造,包括变量、条件语句(例如 if
/else
、while
循环、for
循环)、数组或命令行参数。
4.3.3.2.1 变量 #
可在脚本中的任意位置定义变量。要定义变量,只需选择一个名称,并通过函数或表达式为其赋值:
foo = gettimeofday( )
然后便可以在表达式中使用该变量。SystemTap 根据变量的赋值类型自动推断每个标识符的类型(字符串或数字)。任何不一致性都将报告为错误。在上述示例中,foo
将自动分类为数字,可通过 printf()
并结合使用整数格式说明符 (%d
) 进行列显。
不过,默认情况下,变量位于包含它们的探测本地。系统每次调用处理程序时,会初始化、使用并处置这些变量。要在两个探测之间共享变量,请在脚本中的任意位置将其声明为全局变量。为此,请在探测的外部使用 global
关键字:
global count_jiffies, count_ms probe timer.jiffies(100) { count_jiffies ++ } probe timer.ms(100) { count_ms ++ } probe timer.ms(12345) { hz=(1000*count_jiffies) / count_ms printf ("jiffies:ms ratio %d:%d => CONFIG_HZ=%d\n", count_jiffies, count_ms, hz) exit () }
此示例脚本使用用于统计 jiffy 和毫秒数然后进行相应计算的计时器来计算内核的 CONFIG_HZ 设置。(Jiffy 是指一次系统计时器中断的持续时间。它不是一个绝对时间间隔单位,因为其持续时间取决于特定硬件平台的时钟中断频率)。借助 global
语句,还可以在探测 timer.ms(12345)
中使用变量 count_jiffies
和 count_ms
。指定 ++
时,变量值将会加 1
。
4.3.3.2.2 条件语句 #
可以在 SystemTap 脚本中使用多种条件语句。最常见的条件语句如下:
- if/else 语句
这些语句使用以下格式表达:
if (CONDITION)1STATEMENT12 else3STATEMENT24
if
语句将整数值表达式与零进行比较。如果条件表达式 1 不为零,则执行第一个语句 2。如果条件表达式为零,则执行第二个语句 4。else 子句(3 和 4)是可选子句。2 和 4 也可以是语句块。- While 循环
这些语句使用以下格式表达:
while (CONDITION)1STATEMENT2
当
condition
不为零时,执行语句 2。2 也可以是语句块。它必须更改某个值,因此condition
最终为零。- For 循环
这些语句是
while
循环的快捷方式,使用以下格式表达:for (INITIALIZATION1; CONDITIONAL2; INCREMENT3) statement
1 中指定的表达式用于初始化循环迭代次数的计数器,在开始执行循环之前执行。循环执行将持续到循环条件 2 为 false。(在每个循环迭代的开头检查此表达式)。3 中指定的表达式用于递增循环计数器。它在每个循环迭代的末尾执行。
- 条件运算符
可在条件语句中使用以下运算符:
==: 等于
! =: 不等于
>=: 大于等于
<=: 小于等于
4.4 示例脚本 #
如果您已安装 systemtap-docs
软件包,可以在 /usr/share/doc/packages/systemtap/examples
中找到几个有用的 SystemTap 示例脚本。
本节将详细介绍一个相当简单的示例脚本:/usr/share/doc/packages/systemtap/examples/network/tcp_connections.stp
。
tcp_connections.stp
监控传入的 TCP 连接 ##! /usr/bin/env stap probe begin { printf("%6s %16s %6s %6s %16s\n", "UID", "CMD", "PID", "PORT", "IP_SOURCE") } probe kernel.function("tcp_accept").return?, kernel.function("inet_csk_accept").return? { sock = $return if (sock != 0) printf("%6d %16s %6d %6d %16s\n", uid(), execname(), pid(), inet_get_local_port(sock), inet_get_ip_source(sock)) }
此 SystemTap 脚本可监控传入的 TCP 连接,并帮助您实时识别未经授权或不必要的网络访问请求。它会显示计算机接受的每个新传入 TCP 连接的以下信息:
用户 ID (
UID
)接受连接的命令 (
CMD
)命令的进程 ID (
PID
)连接使用的端口 (
PORT
)TCP 连接的来源 IP 地址 (
IP_SOURCE
)
要运行该脚本,请执行
stap /usr/share/doc/packages/systemtap/examples/network/tcp_connections.stp
并按照屏幕上的输出操作。要手动停止脚本,请按 Ctrl–C。
4.5 用户空间探测 #
为帮助调试用户空间应用程序(就像 DTrace 可做到的那样),SUSE Linux Enterprise Server 15 SP6 支持使用 SystemTap 进行用户空间探测。在任何用户空间应用程序中都可插入自定义探测点。因此,通过 SystemTap,您可以使用内核空间和用户空间探测来调试整个系统的行为。
要获取所需的 utrace 基础架构和 uprobes 内核模块进行用户空间探测,除了第 4.2 节 “安装和设置”中所列的软件包外,还需要安装 kernel-trace
软件包。
utrace
实施一个用于控制用户空间任务的框架。它提供了可由各种跟踪“引擎”使用的接口,该接口作为可加载内核模块实施。引擎会注册特定事件的回调函数,然后挂接到它们想要跟踪的任何线程。由于回调是从内核中的“安全”位置发出的,因此使得函数拥有很大的自由度可以执行各种处理。通过 utrace 可以监控多个事件。例如,您可以监测系统调用进入和退出、fork() 以及正向任务发送信号等事件。https://sourceware.org/systemtap/wiki/utrace 上提供了有关 utrace 基础结构的更多细节。
SystemTap 支持探测进入用户空间进程中的函数并从中返回、探测用户空间代码中的预定义标记,以及监控用户进程事件。
要检查当前运行的内核是否提供所需的 utrace 支持,请使用以下命令:
>
sudo
grep CONFIG_UTRACE /boot/config-`uname -r`
有关用户空间探测的更多细节,请参见 https://sourceware.org/systemtap/SystemTap_Beginners_Guide/userspace-probing.html。
4.6 更多信息 #
本章仅提供了简短的 SystemTap 概述。有关 SystemTap 的详细信息,请参考以下链接:
- https://sourceware.org/systemtap/
SystemTap 项目主页。
- https://sourceware.org/systemtap/wiki/
囊括了有关 SystemTap 的大量有用信息,从详细的用户和开发人员文档,到评论以及与其他工具的比较,或者常见问题和提示。另外还包含 SystemTap 脚本、示例和使用案例集合,并列出了有关 SystemTap 的最近研讨主题和论文。
- https://sourceware.org/systemtap/documentation.html
提供 PDF 和 HTML 格式的 SystemTap Tutorial、SystemTap Beginner's Guide、Tapset Developer's Guide 和 SystemTap Language Reference。另外还列出了相关的手册页。
您还可以在所安装系统中的 /usr/share/doc/packages/systemtap
下找到 SystemTap 语言参考和 SystemTap 教程。example
子目录中提供了示例 SystemTap 脚本。