在 Linux 上用 strace 来理解系统调用( 二 )


回顾一下上面对 execve 的分析,你能说一下这个系统调用的作用吗?
你无需记住所有系统调用或它们所做的事情,因为你可以在需要时参考文档 。手册页可以解救你!在运行 man 命令之前,请确保已安装以下软件包:
请记住,你需要在 man 命令和系统调用名称之间添加 2 。如果使用 man man 阅读 man 命令的手册页,你会看到第 2 节是为系统调用保留的 。同样,如果你需要有关库函数的信息,则需要在 man 和库函数名称之间添加一个 3 。
以下是手册的章节编号及其包含的页面类型:

  • 1:可执行的程序或 shell 命令
  • 2:系统调用(由内核提供的函数)
  • 3:库调用(在程序的库内的函数)
  • 4:特殊文件(通常出现在 /dev)
使用系统调用名称运行以下 man 命令以查看该系统调用的文档:
按照 execve 手册页,这将执行在参数中传递的程序(在本例中为 ls) 。可以为 ls 提供其他参数,例如本例中的 testdir 。因此,此系统调用仅以 testdir 作为参数运行 ls:
下一个系统调用,名为 stat,它使用 testdir 参数:
使用 man 2 stat 访问该文档 。stat 是获取文件状态的系统调用,请记住,Linux 中的一切都是文件,包括目录 。
接下来,openat 系统调用将打开 testdir 。密切注意返回的 3 。这是一个文件描述符,将在以后的系统调用中使用:
到现在为止一切都挺好 。现在,打开 trace.log 文件,并转到 openat 系统调用之后的行 。你会看到 getdents 系统调用被调用,该调用完成了执行 ls testdir 命令所需的大部分操作 。现在,从 trace.log 文件中用 grep 获取 getdents:
getdents 的手册页将其描述为 “获取目录项”,这就是你要执行的操作 。注意,getdents 的参数是 3,这是来自上面 openat 系统调用的文件描述符 。
现在有了目录列表,你需要一种在终端中显示它的方法 。因此,在日志中用 grep 搜索另一个用于写入终端的系统调用 write:
在这些参数中,你可以看到将要显示的文件名:file1 和 file2 。关于第一个参数(1),请记住在 Linux 中,当运行任何进程时,默认情况下会为其打开三个文件描述符 。以下是默认的文件描述符:
  • 0:标准输入
  • 1:标准输出
  • 2:标准错误
因此,write 系统调用将在标准显示(就是这个终端,由 1 所标识的)上显示 file1 和 file2 。
现在你知道哪个系统调用完成了 ls testdir/ 命令的大部分工作 。但是在 trace.log 文件中其它的 100 多个系统调用呢?操作系统必须做很多内务处理才能运行一个进程,因此,你在该日志文件中看到的很多内容都是进程初始化和清理 。阅读整个 trace.log 文件,并尝试了解 ls 命令是怎么工作起来的 。
既然你知道了如何分析给定命令的系统调用,那么就可以将该知识用于其他命令来了解正在执行哪些系统调用 。strace 提供了许多有用的命令行标志,使你更容易使用,下面将对其中一些进行描述 。
默认情况下,strace 并不包含所有系统调用信息 。但是,它有一个方便的 -v 冗余选项,可以在每个系统调用中提供附加信息:
在运行 strace 命令时始终使用 -f 选项是一种好的作法 。它允许 strace 对当前正在跟踪的进程创建的任何子进程进行跟踪:
假设你只需要系统调用的名称、运行的次数以及每个系统调用花费的时间百分比 。你可以使用 -c 标志来获取这些统计信息:
假设你想专注于特定的系统调用,例如专注于 open 系统调用,而忽略其余部分 。你可以使用-e 标志跟上系统调用的名称:
如果你想关注多个系统调用怎么办?不用担心,你同样可以使用 -e 命令行标志,并用逗号分隔开两个系统调用的名称 。例如,要查看 write 和 getdents 系统调用:
到目前为止,这些示例是明确地运行的命令进行了跟踪 。但是,要跟踪已经运行并正在执行的命令又怎么办呢?例如,如果要跟踪用来长时间运行进程的守护程序,该怎么办?为此,strace 提供了一个特殊的 -p 标志,你可以向其提供进程 ID 。
我们的示例不在守护程序上运行 strace,而是以 cat 命令为例,如果你将文件名作为参数,通常 cat 会显示文件的内容 。如果没有给出参数,cat 命令会在终端上等待用户输入文本 。输入文本后,它将重复给定的文本,直到用户按下 Ctrl + C 退出为止 。
从一个终端运行 cat 命令;它会向你显示一个提示,并等待在那里(记住 cat 仍在运行且尚未退出):
在另一个终端上,使用 ps 命令找到进程标识符(PID):


推荐阅读