发现工作负载使用的 Linux 内核子系统

作者:
维护者:

Shuah Khan <skhan@linuxfoundation.org>

要点

  • 理解构建和运行工作负载所需的系统资源非常重要。

  • 可以使用 Linux 跟踪和 strace 来发现工作负载正在使用的系统资源。系统使用信息的完整性取决于工作负载覆盖面的完整性。

  • 可以借助以下工具来分析操作系统的性能和安全性:perfstress-ngpaxtest

  • 一旦我们发现并理解了工作负载的需求,就可以重点关注它们以避免性能退化,并利用它来评估安全性考量。

方法论

strace 是一个诊断、教学和调试工具,可用于发现工作负载正在使用的系统资源。一旦我们发现并理解了工作负载的需求,就可以重点关注它们以避免性能退化,并利用它来评估安全性考量。我们使用 strace 工具来跟踪工作负载。

这种使用 strace 进行跟踪的方法告诉我们工作负载调用的系统调用,并不包含它可能调用的所有系统调用。此外,这种跟踪方法仅告诉我们这些系统调用中被调用的代码路径。例如,如果一个工作负载打开一个文件并成功读取它,那么被跟踪的就是成功路径。该系统调用中的任何错误路径都不会被跟踪。如果存在一个能够提供完全覆盖的工作负载,那么这里概述的方法将跟踪并找到所有可能的代码路径。系统使用信息的完整性取决于工作负载覆盖面的完整性。

我们的目标是在运行默认内核的系统上跟踪工作负载,而无需安装自定义内核。

我们如何收集细粒度的系统信息?

strace 工具可用于跟踪进程发起的系统调用及其接收到的信号。系统调用是应用程序与操作系统内核之间的基本接口。它们使程序能够向内核请求服务。例如,Linux 中的 open() 系统调用用于提供对文件系统中文件的访问。strace 使我们能够追踪应用程序发起的的所有系统调用。它会列出进程发起的的所有系统调用及其结果输出。

你可以结合使用 strace 和 perf record 工具来生成剖析数据,以记录与进程相关的事件和信息。这有助于深入了解该进程。“perf annotate”工具可生成程序每条指令的统计信息。本文详细介绍了如何收集有关工作负载系统资源使用情况的细粒度信息。

我们使用 strace 跟踪了 perf、stress-ng 和 paxtest 工作负载,以说明我们发现工作负载所用资源的方法论。此过程可应用于跟踪其他工作负载。

准备好进行跟踪的系统

在开始之前,我们将向您展示如何准备好系统。我们假设您在物理系统或虚拟机上运行 Linux 发行版。大多数发行版都包含 strace 命令。让我们安装构建 Linux 内核通常不包含的其他工具。请注意,以下方法适用于基于 Debian 的发行版。在其他 Linux 发行版上,您可能需要找到等效的软件包。

安装用于构建 Linux 内核的工具以及内核仓库中的工具。scripts/ver_linux 是检查您的系统是否已具备必要工具的好方法

sudo apt-get install build-essential flex bison yacc
sudo apt install libelf-dev systemtap-sdt-dev libslang2-dev libperl-dev libdw-dev

cscope 是浏览内核源码的好工具。现在让我们安装它

sudo apt-get install cscope

安装 stress-ng 和 paxtest

apt-get install stress-ng
apt-get install paxtest

工作负载概述

如前所述,我们使用 strace 跟踪了 perf bench、stress-ng 和 paxtest 工作负载,以展示如何分析工作负载并识别这些工作负载所使用的 Linux 子系统。让我们先概述这三个工作负载,以便更好地理解它们的作用以及如何使用它们。

perf bench (all) 工作负载

perf bench 命令包含多个多线程微内核基准测试,用于执行 Linux 内核中的不同子系统和系统调用。这使我们能够轻松衡量更改的影响,从而有助于缓解性能退化。它还充当通用的基准测试框架,使开发人员能够轻松创建测试用例、进行透明集成并使用功能丰富的工具子系统。

Stress-ng netdev 压力测试工作负载

stress-ng 用于对内核执行压力测试。它允许您使用压力测试器(stressor-s)来压测计算机的各种物理子系统以及操作系统内核的接口。它们适用于 CPU、CPU 缓存、设备、I/O、中断、文件系统、内存、网络、操作系统、管道、调度程序和虚拟机。请参考 stress-ng 手册页以查找所有可用压力测试器的说明。netdev 压力测试器启动指定数量 (N) 的工作进程,在所有可用的网络设备上执行各种 netdevice ioctl 命令。

paxtest kiddie 工作负载

paxtest 是一个测试内核中缓冲区溢出的程序。它测试内核对内存使用的强制控制。通常,在某些内存段中执行会使缓冲区溢出成为可能。它运行一组试图破坏内存使用的程序。它被用作 PaX 的回归测试套件,但可能对测试内核的其他内存保护补丁有用。我们使用了寻找简单漏洞的 paxtest kiddie 模式。

什么是 strace 以及如何使用它?

如前所述,strace 是一个有用的诊断、教学和调试工具,可用于发现工作负载正在使用的系统资源。它可用于:

  • 查看进程如何与内核交互。

  • 查看进程失败或挂起的原因。

  • 对进程进行逆向工程。

  • 查找程序依赖的文件。

  • 分析应用程序的性能。

  • 排查与操作系统相关的各种问题。

此外,strace 可以生成每个系统调用的时间、调用次数和错误的运行时统计信息,并在程序退出时报告摘要,同时抑制常规输出。这试图显示独立于墙钟时间的系统时间(在内核中运行所花费的 CPU 时间)。我们计划利用这些功能来获取有关工作负载系统使用情况的信息。

strace 命令支持基本模式、详细模式和统计模式。在详细模式下运行 strace 命令可提供有关进程调用的系统调用的更详细信息。

运行 strace -c 会生成一份报告,其中包含在每个系统调用中花费的时间百分比、总时间(以秒为单位)、每次调用的微秒数、总调用次数、因错误而失败的每个系统调用的计数以及所发起的系统调用类型。

  • 用法:strace <我们要跟踪的命令>

  • 详细模式用法:strace -v <命令>

  • 收集统计信息:strace -c <命令>

我们使用“-c”选项来收集我们为本次分析所选择的三种工作负载所使用的细粒度运行时统计信息。

  • perf

  • stress-ng

  • paxtest

什么是 cscope 以及如何使用它?

现在让我们看一看 cscope,这是一个用于浏览 C、C++ 或 Java 代码库的命令行工具。我们可以用它来查找对某个符号的所有引用、全局定义、由某个函数调用的函数、调用某个函数的函数、文本字符串、正则表达式模式、包含某个文件的文件。

我们可以使用 cscope 来查找哪个系统调用属于哪个子系统。这样,我们就能找到进程在执行时所使用的内核子系统。

让我们检出最新的 Linux 仓库并构建 cscope 数据库

git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux
cd linux
cscope -R -p10  # builds cscope.out database before starting browse session
cscope -d -p10  # starts browse session on cscope.out database

注意:运行 “cscope -R -p10” 来构建数据库,运行 “cscope -d -p10” 进入浏览会话。cscope 默认使用 cscope.out 数据库。要退出此模式,请按 ctrl+d。-p 选项用于指定要显示的文件路径组件的数量。-p10 是浏览内核源码的最佳选择。

或者,内核构建系统可以生成 cscope 数据库

make cscope

要从生成的数据库中排除目录,请将 IGNORE_DIRS 传递给 cscope 目标。例如,要排除 Documentation/,请运行

make IGNORE_DIRS="Documentation" cscope

什么是 perf 以及如何使用它?

Perf 是一个基于 Linux 2.6+ 系统的分析工具,它抽象了 Linux 性能测量中的 CPU 硬件差异,并提供了一个简单的命令行接口。Perf 基于内核导出的 perf_events 接口。它对于对系统进行性能剖析和寻找应用程序中的性能瓶颈非常有用。

如果您还没有检出 Linux 主线仓库,您可以进行检出,然后构建内核和 perf 工具

git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux
cd linux
make -j3 all
cd tools/perf
make

注意:可以在不构建仓库中内核的情况下构建 perf 命令,并且它可以在较旧的内核上运行。但是,使内核和 perf 的版本保持一致可以提供关于子系统使用的更准确信息。

我们使用了 “perf stat” 和 “perf bench” 选项。有关 perf 工具的详细信息,请运行 “perf -h”。

perf stat

perf stat 命令会生成各种硬件和软件事件的报告。它借助现代 CPU 中的硬件计数器寄存器来记录这些活动的计数。“perf stat cal”显示了 cal 命令的统计信息。

Perf bench

perf bench 命令包含多个多线程微内核基准测试,用于执行 Linux 内核中的不同子系统和系统调用。这使我们能够轻松衡量更改的影响,从而有助于缓解性能退化。它还充当通用的基准测试框架,使开发人员能够轻松创建测试用例、进行透明集成并使用功能丰富的工具。

“perf bench all”运行 perf bench 框架中的所有可用基准测试。确切的基准测试集取决于 perf 版本以及构建 perf 时启用的功能。

要列出当前系统上可用的基准测试集合,请运行

perf bench

要列出某个集合中的基准测试,请运行

perf bench <collection>

例如,要列出 mem 集合中的基准测试,请运行

perf bench mem

什么是 stress-ng 以及如何使用它?

如前所述,stress-ng 用于对内核执行压力测试。它允许您使用压力测试器(stressor-s)来压测计算机的各种物理子系统以及操作系统内核的接口。它们适用于 CPU、CPU 缓存、设备、I/O、中断、文件系统、内存、网络、操作系统、管道、调度程序和虚拟机。

netdev 压力测试器启动 N 个工作进程,在所有可用的网络设备上执行各种 netdevice ioctl 命令。执行了以下 ioctl

  • SIOCGIFCONF, SIOCGIFINDEX, SIOCGIFNAME, SIOCGIFFLAGS

  • SIOCGIFADDR, SIOCGIFNETMASK, SIOCGIFMETRIC, SIOCGIFMTU

  • SIOCGIFHWADDR, SIOCGIFMAP, SIOCGIFTXQLEN

以下命令运行压力测试器

stress-ng --netdev 1 -t 60 --metrics

我们可以使用 perf record 命令来记录与进程相关的事件和信息。此命令将剖析数据记录在同一目录下的 perf.data 文件中。

使用以下命令,您可以记录与 netdev 压力测试器相关的事件、查看生成的 perf.data 报告并为输出添加注释以查看程序每条指令的统计信息

perf record -- stress-ng --netdev 1 -t 60 --metrics
perf report
perf annotate

什么是 paxtest 以及如何使用它?

paxtest 是一个测试内核中缓冲区溢出的程序。它测试内核对内存使用的强制控制。通常,在某些内存段中执行会使缓冲区溢出成为可能。它运行一组试图破坏内存使用的程序。它被用作 PaX 的回归测试套件,并且对于测试内核的其他内存保护补丁将非常有用。

paxtest 提供 kiddie 和 blackhat 模式。paxtest kiddie 模式在正常模式下运行,而 blackhat 模式则试图绕过内核的保护来测试漏洞。我们在此重点关注 kiddie 模式,并将 “paxtest kiddie” 运行与 “perf record” 结合起来,收集 paxtest kiddie 运行的 CPU 堆栈跟踪,以查看在性能配置文件中哪个函数正在调用其他函数。然后可以使用 “dwarf”(DWARF 的调用帧信息)模式来展开堆栈。

可以使用以下命令以调用图格式查看生成的报告

perf record --call-graph dwarf paxtest kiddie
perf report --stdio

跟踪工作负载

既然我们已经了解了这些工作负载,让我们开始跟踪它们。

跟踪 perf bench all 工作负载

运行以下命令来跟踪 perf bench all 工作负载

strace -c perf bench all

工作负载发起的系统调用

下表显示了工作负载调用的系统调用、每个系统调用被调用的次数以及对应的 Linux 子系统。

系统调用

调用次数

Linux 子系统

系统调用 (API)

getppid

10000001

进程管理

sys_getpid()

clone

1077

进程管理

sys_clone()

prctl

23

进程管理

sys_prctl()

prlimit64

7

进程管理

sys_prlimit64()

getpid

10

进程管理

sys_getpid()

uname

3

进程管理

sys_uname()

sysinfo

1

进程管理

sys_sysinfo()

getuid

1

进程管理

sys_getuid()

getgid

1

进程管理

sys_getgid()

geteuid

1

进程管理

sys_geteuid()

getegid

1

进程管理

sys_getegid()

close

49951

文件系统

sys_close()

pipe

604

文件系统

sys_pipe()

openat

48560

文件系统

sys_openat()

fstat

8338

文件系统

sys_fstat()

stat

1573

文件系统

sys_stat()

pread64

9646

文件系统

sys_pread64()

getdents64

1873

文件系统

sys_getdents64()

access

3

文件系统

sys_access()

lstat

1880

文件系统

sys_lstat()

lseek

6

文件系统

sys_lseek()

ioctl

3

文件系统

sys_ioctl()

dup2

1

文件系统

sys_dup2()

execve

2

文件系统

sys_execve()

fcntl

8779

文件系统

sys_fcntl()

statfs

1

文件系统

sys_statfs()

epoll_create

2

文件系统

sys_epoll_create()

epoll_ctl

64

文件系统

sys_epoll_ctl()

newfstatat

8318

文件系统

sys_newfstatat()

eventfd2

192

文件系统

sys_eventfd2()

mmap

243

内存管理

sys_mmap()

mprotect

32

内存管理

sys_mprotect()

brk

21

内存管理

sys_brk()

munmap

128

内存管理

sys_munmap()

set_mempolicy

156

内存管理

sys_set_mempolicy()

set_tid_address

1

进程管理

sys_set_tid_address()

set_robust_list

1

Futex

sys_set_robust_list()

futex

341

Futex

sys_futex()

sched_getaffinity

79

调度器

sys_sched_getaffinity()

sched_setaffinity

223

调度器

sys_sched_setaffinity()

socketpair

202

网络

sys_socketpair()

rt_sigprocmask

21

信号

sys_rt_sigprocmask()

rt_sigaction

36

信号

sys_rt_sigaction()

rt_sigreturn

2

信号

sys_rt_sigreturn()

wait4

889

时间

sys_wait4()

clock_nanosleep

37

时间

sys_clock_nanosleep()

capget

4

能力(Capability)

sys_capget()

跟踪 stress-ng netdev 压力测试器工作负载

运行以下命令来跟踪 stress-ng netdev 压力测试器工作负载

strace -c  stress-ng --netdev 1 -t 60 --metrics

工作负载发起的系统调用

下表显示了工作负载调用的系统调用、每个系统调用被调用的次数以及对应的 Linux 子系统。

系统调用

调用次数

Linux 子系统

系统调用 (API)

openat

74

文件系统

sys_openat()

close

75

文件系统

sys_close()

read

58

文件系统

sys_read()

fstat

20

文件系统

sys_fstat()

flock

10

文件系统

sys_flock()

write

7

文件系统

sys_write()

getdents64

8

文件系统

sys_getdents64()

pread64

8

文件系统

sys_pread64()

lseek

1

文件系统

sys_lseek()

access

2

文件系统

sys_access()

getcwd

1

文件系统

sys_getcwd()

execve

1

文件系统

sys_execve()

mmap

61

内存管理

sys_mmap()

munmap

3

内存管理

sys_munmap()

mprotect

20

内存管理

sys_mprotect()

mlock

2

内存管理

sys_mlock()

brk

3

内存管理

sys_brk()

rt_sigaction

21

信号

sys_rt_sigaction()

rt_sigprocmask

1

信号

sys_rt_sigprocmask()

sigaltstack

1

信号

sys_sigaltstack()

rt_sigreturn

1

信号

sys_rt_sigreturn()

getpid

8

进程管理

sys_getpid()

prlimit64

5

进程管理

sys_prlimit64()

arch_prctl

2

进程管理

sys_arch_prctl()

sysinfo

2

进程管理

sys_sysinfo()

getuid

2

进程管理

sys_getuid()

uname

1

进程管理

sys_uname()

setpgid

1

进程管理

sys_setpgid()

getrusage

1

进程管理

sys_getrusage()

geteuid

1

进程管理

sys_geteuid()

getppid

1

进程管理

sys_getppid()

sendto

3

网络

sys_sendto()

connect

1

网络

sys_connect()

套接字

1

网络

sys_socket()

clone

1

进程管理

sys_clone()

set_tid_address

1

进程管理

sys_set_tid_address()

wait4

2

时间

sys_wait4()

alarm

1

时间

sys_alarm()

set_robust_list

1

Futex

sys_set_robust_list()

跟踪 paxtest kiddie 工作负载

运行以下命令来跟踪 paxtest kiddie 工作负载

strace -c paxtest kiddie

工作负载发起的系统调用

下表显示了工作负载调用的系统调用、每个系统调用被调用的次数以及对应的 Linux 子系统。

系统调用

调用次数

Linux 子系统

系统调用 (API)

read

3

文件系统

sys_read()

write

11

文件系统

sys_write()

close

41

文件系统

sys_close()

stat

24

文件系统

sys_stat()

fstat

2

文件系统

sys_fstat()

pread64

6

文件系统

sys_pread64()

access

1

文件系统

sys_access()

pipe

1

文件系统

sys_pipe()

dup2

24

文件系统

sys_dup2()

execve

1

文件系统

sys_execve()

fcntl

26

文件系统

sys_fcntl()

openat

14

文件系统

sys_openat()

rt_sigaction

7

信号

sys_rt_sigaction()

rt_sigreturn

38

信号

sys_rt_sigreturn()

clone

38

进程管理

sys_clone()

wait4

44

时间

sys_wait4()

mmap

7

内存管理

sys_mmap()

mprotect

3

内存管理

sys_mprotect()

munmap

1

内存管理

sys_munmap()

brk

3

内存管理

sys_brk()

getpid

1

进程管理

sys_getpid()

getuid

1

进程管理

sys_getuid()

getgid

1

进程管理

sys_getgid()

geteuid

2

进程管理

sys_geteuid()

getegid

1

进程管理

sys_getegid()

getppid

1

进程管理

sys_getppid()

arch_prctl

2

进程管理

sys_arch_prctl()

总结

本文档旨在用作如何使用 strace 收集有关工作负载所用资源的细粒度信息的指南。

参考资料