mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-25 23:11:02 +08:00
Merge pull request #16184 from wxy/20191025-Understanding-system-calls-on-Linux-with-strace
PRF&PUB:20191025 Understanding system calls on Linux with strace
This commit is contained in:
commit
131208ab11
@ -1,8 +1,8 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (wxy)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-11545-1.html)
|
||||
[#]: subject: (Understanding system calls on Linux with strace)
|
||||
[#]: via: (https://opensource.com/article/19/10/strace)
|
||||
[#]: author: (Gaurav Kamathe https://opensource.com/users/gkamathe)
|
||||
@ -10,13 +10,13 @@
|
||||
在 Linux 上用 strace 来理解系统调用
|
||||
======
|
||||
|
||||
> 使用 strace 跟踪用户进程和 Linux 内核之间的薄层。
|
||||
> 使用 strace 跟踪用户进程和 Linux 内核之间的交互。
|
||||
|
||||
![Hand putting a Linux file folder into a drawer][1]
|
||||
|
||||
<ruby>系统调用<rt>system call</rt></ruby>是程序从内核请求服务的一种编程方式,而 `strace` 是一个功能强大的工具,可让你跟踪用户进程与 Linux 内核之间的薄层。
|
||||
<ruby>系统调用<rt>system call</rt></ruby>是程序从内核请求服务的一种编程方式,而 `strace` 是一个功能强大的工具,可让你跟踪用户进程与 Linux 内核之间的交互。
|
||||
|
||||
要了解操作系统的工作原理,首先需要了解系统调用的工作原理。操作系统的主要功能之一是为用户程序提供抽象。
|
||||
要了解操作系统的工作原理,首先需要了解系统调用的工作原理。操作系统的主要功能之一是为用户程序提供抽象机制。
|
||||
|
||||
操作系统可以大致分为两种模式:
|
||||
|
||||
@ -25,9 +25,9 @@
|
||||
|
||||
用户大多使用命令行实用程序和图形用户界面(GUI)来执行日常任务。系统调用在后台静默运行,与内核交互以完成工作。
|
||||
|
||||
系统调用与函数调用非常相似,这意味着它们接受并处理参数然后返回值。唯一的区别是系统调用进入内核,而函数调用不进入。从用户空间切换到内核空间是使用特殊的 [trap][2] 机制完成的。
|
||||
系统调用与函数调用非常相似,这意味着它们都接受并处理参数然后返回值。唯一的区别是系统调用进入内核,而函数调用不进入。从用户空间切换到内核空间是使用特殊的 [trap][2] 机制完成的。
|
||||
|
||||
通过使用系统库(在 Linux 系统上又称为 glibc),系统调用大部分对用户隐藏了。尽管系统调用本质上是通用的,但是发出系统调用的机制在很大程度上取决于机器。
|
||||
通过使用系统库(在 Linux 系统上又称为 glibc),大部分系统调用对用户隐藏了。尽管系统调用本质上是通用的,但是发出系统调用的机制在很大程度上取决于机器(架构)。
|
||||
|
||||
本文通过使用一些常规命令并使用 `strace` 分析每个命令进行的系统调用来探索一些实际示例。这些示例使用 Red Hat Enterprise Linux,但是这些命令运行在其他 Linux 发行版上应该也是相同的:
|
||||
|
||||
@ -71,7 +71,7 @@ yum install strace
|
||||
|
||||
(我使用 `/tmp` 目录是因为每个人都可以访问它,但是你可以根据需要选择另一个目录。)
|
||||
|
||||
在 `testdir` 目录下使用 `ls` 命令验证文件已经创建:
|
||||
在 `testdir` 目录下使用 `ls` 命令验证该文件已经创建:
|
||||
|
||||
```
|
||||
[root@sandbox tmp]# ls testdir/
|
||||
@ -79,13 +79,11 @@ file1 file2
|
||||
[root@sandbox tmp]#
|
||||
```
|
||||
|
||||
你可能每天都使用`ls`命令,而没有意识到系统调用在其下面发生的作用。这里有抽象作用。该命令的工作方式如下:
|
||||
你可能每天都在使用 `ls` 命令,而没有意识到系统调用在其下面发挥的作用。抽象地来说,该命令的工作方式如下:
|
||||
|
||||
```
|
||||
Command-line utility -> Invokes functions from system libraries (glibc) -> Invokes system calls
|
||||
```
|
||||
> 命令行工具 -> 从系统库(glibc)调用函数 -> 调用系统调用
|
||||
|
||||
`ls` 命令在 Linux 上从系统库(即 glibc)内部调用函数。这些库调用完成大部分工作的系统调用。
|
||||
`ls` 命令内部从 Linux 上的系统库(即 glibc)调用函数。这些库去调用完成大部分工作的系统调用。
|
||||
|
||||
如果你想知道从 glibc 库中调用了哪些函数,请使用 `ltrace` 命令,然后跟上常规的 `ls testdir/`命令:
|
||||
|
||||
@ -99,8 +97,7 @@ ltrace ls testdir/
|
||||
yum install ltrace
|
||||
```
|
||||
|
||||
一堆输出会被显示到屏幕上;不必担心,只需继续就行。`ltrace` 命令输出中与该示例有关的一些重要库函数包括:
|
||||
|
||||
大量的输出会被堆到屏幕上;不必担心,只需继续就行。`ltrace` 命令输出中与该示例有关的一些重要库函数包括:
|
||||
|
||||
```
|
||||
opendir("testdir/") = { 3 }
|
||||
@ -116,11 +113,11 @@ readdir({ 3 }) = nil
|
||||
closedir({ 3 })
|
||||
```
|
||||
|
||||
通过查看上面的输出,你或许可以了解正在发生的事情。`opendir` 库函数打开一个名为 `testdir` 的目录,然后调用 `readdir` 函数,该函数读取目录的内容。最后,有一个对 `closedir` 函数的调用,该函数将关闭先前打开的目录。现在先忽略其他 `strlen` 和 `memcpy` 功能。
|
||||
通过查看上面的输出,你或许可以了解正在发生的事情。`opendir` 库函数打开一个名为 `testdir` 的目录,然后调用 `readdir` 函数,该函数读取目录的内容。最后,有一个对 `closedir` 函数的调用,该函数将关闭先前打开的目录。现在请先忽略其他 `strlen` 和 `memcpy` 功能。
|
||||
|
||||
你可以看到正在调用哪些库函数,但是本文将重点介绍由系统库函数调用的系统调用。
|
||||
|
||||
与上述类似,要了解调用了哪些系统调用,只需将 `strace` 放在 `ls testdir` 命令之前,如下所示。 再次,将一堆乱码丢到了你的屏幕上,你可以按照以下步骤进行操作:
|
||||
与上述类似,要了解调用了哪些系统调用,只需将 `strace` 放在 `ls testdir` 命令之前,如下所示。 再次,一堆乱码丢到了你的屏幕上,你可以按照以下步骤进行操作:
|
||||
|
||||
```
|
||||
[root@sandbox tmp]# strace ls testdir/
|
||||
@ -137,14 +134,14 @@ exit_group(0) = ?
|
||||
[root@sandbox tmp]#
|
||||
```
|
||||
|
||||
运行 `strace` 命令后屏幕上的输出只是运行 `ls` 命令的系统调用。每个系统调用都为操作系统提供特定的用途,可以将它们大致分为以下几个部分:
|
||||
运行 `strace` 命令后屏幕上的输出就是运行 `ls` 命令的系统调用。每个系统调用都为操作系统提供了特定的用途,可以将它们大致分为以下几个部分:
|
||||
|
||||
* 进程管理系统调用
|
||||
* 文件管理系统调用
|
||||
* 目录和文件系统管理系统调用
|
||||
* 其他系统调用
|
||||
|
||||
分析显示到屏幕上的信息的一种更简单的方法是使用 `strace` 方便使用的 `-o` 标志将输出记录到文件中。在 `-o` 标志后添加一个合适的文件名,然后再次运行命令:
|
||||
分析显示到屏幕上的信息的一种更简单的方法是使用 `strace` 方便的 `-o` 标志将输出记录到文件中。在 `-o` 标志后添加一个合适的文件名,然后再次运行命令:
|
||||
|
||||
```
|
||||
[root@sandbox tmp]# strace -o trace.log ls testdir/
|
||||
@ -173,7 +170,7 @@ execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
|
||||
* 括号内的文本是提供给该系统调用的参数。
|
||||
* 符号 `=` 后的数字(在这种情况下为 `0`)是 `execve` 系统调用的返回值。
|
||||
|
||||
现在的输出似乎还不太吓人,不是吗?你可以应用相同的逻辑来理解其他行。
|
||||
现在的输出似乎还不太吓人,对吧。你可以应用相同的逻辑来理解其他行。
|
||||
|
||||
现在,将关注点集中在你调用的单个命令上,即 `ls testdir`。你知道命令 `ls` 使用的目录名称,那么为什么不在 `trace.log` 文件中使用 `grep` 查找 `testdir` 并查看得到的结果呢?让我们详细查看一下结果的每一行:
|
||||
|
||||
@ -262,19 +259,19 @@ write(1, "file1 file2\n", 13) = 13
|
||||
* `1`:标准输出
|
||||
* `2`:标准错误
|
||||
|
||||
因此,`write` 系统调用将在标准显示(这就是终端,由 `1` 所标识的)上显示 `file1` 和 `file2`。
|
||||
因此,`write` 系统调用将在标准显示(就是这个终端,由 `1` 所标识的)上显示 `file1` 和 `file2`。
|
||||
|
||||
现在你知道哪个系统调用完成了 `ls testdir/` 命令的大部分工作。但是在 `trace.log` 文件中其它的 100 多个系统调用呢?操作系统必须做很多内务处理才能运行一个进程,因此,你在该日志文件中看到的很多内容都是进程初始化和清理。阅读整个 `trace.log` 文件,并尝试了解什么使 `ls` 命令可以工作。
|
||||
现在你知道哪个系统调用完成了 `ls testdir/` 命令的大部分工作。但是在 `trace.log` 文件中其它的 100 多个系统调用呢?操作系统必须做很多内务处理才能运行一个进程,因此,你在该日志文件中看到的很多内容都是进程初始化和清理。阅读整个 `trace.log` 文件,并尝试了解 `ls` 命令是怎么工作起来的。
|
||||
|
||||
既然你知道了如何分析给定命令的系统调用,那么就可以将该知识用于其他命令来了解正在执行哪些系统调用。`strace` 提供了许多有用的命令行标志,使你更容易使用,下面将对其中一些进行描述。
|
||||
|
||||
默认情况下,`strace` 并不包含所有系统调用信息。但是,它有一个方便的 `-v verbose` 选项,可以在每个系统调用中提供附加信息:
|
||||
默认情况下,`strace` 并不包含所有系统调用信息。但是,它有一个方便的 `-v` 冗余选项,可以在每个系统调用中提供附加信息:
|
||||
|
||||
```
|
||||
strace -v ls testdir
|
||||
```
|
||||
|
||||
在运行 `strace` 命令时始终使用 `-f` 选项是一种好的作法。它允许 `strace` 跟踪由当前正在跟踪的进程创建的任何子进程:
|
||||
在运行 `strace` 命令时始终使用 `-f` 选项是一种好的作法。它允许 `strace` 对当前正在跟踪的进程创建的任何子进程进行跟踪:
|
||||
|
||||
```
|
||||
strace -f ls testdir
|
||||
@ -286,7 +283,7 @@ strace -f ls testdir
|
||||
strace -c ls testdir/
|
||||
```
|
||||
|
||||
假设你想专注于特定的系统调用,例如专注于 `open` 系统调用,而忽略其余部分。你可以使用`-e`标志跟上系统调用的名称:
|
||||
假设你想专注于特定的系统调用,例如专注于 `open` 系统调用,而忽略其余部分。你可以使用`-e` 标志跟上系统调用的名称:
|
||||
|
||||
```
|
||||
[root@sandbox tmp]# strace -e open ls testdir
|
||||
@ -305,7 +302,7 @@ file1 file2
|
||||
[root@sandbox tmp]#
|
||||
```
|
||||
|
||||
如果你想关注多个系统调用怎么办?不用担心,你同样可以使用 `-e` 命令行标志,并用逗号分隔开两个系统调用。例如,要查看 `write` 和 `getdents` 系统调用:
|
||||
如果你想关注多个系统调用怎么办?不用担心,你同样可以使用 `-e` 命令行标志,并用逗号分隔开两个系统调用的名称。例如,要查看 `write` 和 `getdents` 系统调用:
|
||||
|
||||
```
|
||||
[root@sandbox tmp]# strace -e write,getdents ls testdir
|
||||
@ -317,11 +314,11 @@ write(1, "file1 file2\n", 13file1 file2
|
||||
[root@sandbox tmp]#
|
||||
```
|
||||
|
||||
到目前为止,这些示例已明确跟踪了运行的命令。但是,要跟踪已经运行并正在执行的命令又怎么办呢?例如,如果要跟踪只是长时间运行的进程的守护程序,该怎么办?为此,`strace` 提供了一个特殊的 `-p` 标志,你可以向其提供进程 ID。
|
||||
到目前为止,这些示例是明确地运行的命令进行了跟踪。但是,要跟踪已经运行并正在执行的命令又怎么办呢?例如,如果要跟踪用来长时间运行进程的守护程序,该怎么办?为此,`strace` 提供了一个特殊的 `-p` 标志,你可以向其提供进程 ID。
|
||||
|
||||
不用在守护程序上运行 `strace`,而是以 `cat` 命令为例,如果你将文件名作为参数,通常会显示文件的内容。如果没有给出参数,`cat` 命令会在终端上等待用户输入文本。输入文本后,它将重复给定的文本,直到用户按下 `Ctrl + C` 退出为止。
|
||||
我们的示例不在守护程序上运行 `strace`,而是以 `cat` 命令为例,如果你将文件名作为参数,通常 `cat` 会显示文件的内容。如果没有给出参数,`cat` 命令会在终端上等待用户输入文本。输入文本后,它将重复给定的文本,直到用户按下 `Ctrl + C` 退出为止。
|
||||
|
||||
从一个终端运行 `cat` 命令;它会向你显示一个提示,而等待在那里(记住 `cat` 仍在运行且尚未退出):
|
||||
从一个终端运行 `cat` 命令;它会向你显示一个提示,并等待在那里(记住 `cat` 仍在运行且尚未退出):
|
||||
|
||||
```
|
||||
[root@sandbox tmp]# cat
|
||||
@ -344,7 +341,7 @@ strace: Process 22443 attached
|
||||
read(0,
|
||||
```
|
||||
|
||||
现在,返回到你使 `cat` 命令运行的终端,并输入一些文本。我出于演示目的输入了 `x0x0`。注意 `cat` 是如何简单地重复我输入的内容。因此,`x0x0` 出现了两次。我输入了第一个,第二个是 `cat` 命令重复的输出:
|
||||
现在,返回到你运行 `cat` 命令的终端,并输入一些文本。我出于演示目的输入了 `x0x0`。注意 `cat` 是如何简单地重复我输入的内容的。因此,`x0x0` 出现了两次。我输入了第一个,第二个是 `cat` 命令重复的输出:
|
||||
|
||||
```
|
||||
[root@sandbox tmp]# cat
|
||||
@ -352,7 +349,7 @@ x0x0
|
||||
x0x0
|
||||
```
|
||||
|
||||
返回到将 `strace` 接驳到 `cat` 进程的终端。现在你会看到两个额外的系统调用:较早的 `read` 系统调用,现在在终端中读取 `x0x0`,另一个为 `write`,将 `x0x0` 写回到终端,然后是再一个新的 `read`,正在等待从终端读取。请注意,标准输入(`0`)和标准输出(`1`)都在同一终端中:
|
||||
返回到将 `strace` 接驳到 `cat` 进程的终端。现在你会看到两个额外的系统调用:较早的 `read` 系统调用,现在在终端中读取 `x0x0`,另一个为 `write`,它将 `x0x0` 写回到终端,然后是再一个新的 `read`,正在等待从终端读取。请注意,标准输入(`0`)和标准输出(`1`)都在同一终端中:
|
||||
|
||||
```
|
||||
[root@sandbox ~]# strace -p 22443
|
||||
@ -399,7 +396,7 @@ via: https://opensource.com/article/19/10/strace
|
||||
作者:[Gaurav Kamathe][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[wxy](https://github.com/wxy)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
Loading…
Reference in New Issue
Block a user