Linux系统调用查询与跟踪

本文来源于大佬的一道面试题:如何查询Linux接口调用次数

虽然最终发现对该面试题的理解有着微妙的偏差,不过如果把Linux接口调用次数理解为程序对Linux的系统调用次数,这其实是一个很有意思的问题。


系统调用数量的意义

所谓的系统调用是指程序进入内核执行任务的方式。程序利用系统调用进行一系列操作,如:进程管理、网络交互、文件读写等等。绝大部分时候应用程序指只需include相关的头文件,即可像调用一个API一样进行系统调用,虽然如此。Linux内核的系统调用基础架构相当复杂,也存在着各有优缺点其他调用方式。

常见的程序的调用可以分为两大类,一类是系统调,用另一类自身函数的调用。因此系统调用是程序流程的重要组成部分;除此之外,系统调用的数量还与系统中断数量等息息相关。因此,获取程序系统调用的情况对于排查及定位程序的异常或者对程序进行性能的分析都有着重要意义。

如何跟踪进程的系统调用

Linux是通过进行系统调用来跟踪其他系统调用的,这个系统调用就是ptrace,它可以暂停被跟踪进程,检查和设置寄存器和内存,查看和修改被追踪进程的内存和寄存器。

大部分的linux系统调用都设计得非常简洁,ptrace也是如此,其所具备的功能相对简单。当需求比较独特时,我们可以自己编写程序调用ptrace来跟踪其他程序的系统调用。但这无疑是一个非常麻烦的事情,因此绝大部分时候我们会使用一些基于ptrace的工具,比如GDB,strace等,本文将会对strace进行简要的介绍。

strace 简介

strace是一个大部分linux发行版自带的系统工具。通常用来跟踪进程执行时的系统调用和所接收的信号。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。

相关参数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
-c 统计每一系统调用的所执行的时间,次数和出错的次数等.
-d 输出strace关于标准错误的调试信息.
-f 跟踪由fork调用所产生的子进程.
-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,,每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息,微秒级.
-ttt 微秒级输出,以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
-V 输出strace的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column 设置返回值的输出位置.默认 为40.
-e expr 指定一个表达式,用来控制如何跟踪.格式如下:
[qualifier=][!]value1[,value2]...
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的qualifier是trace.感叹号是否定符号.例如:-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none. 注意有些shell使用!来执行历史记录里的命令,所以要使用\\.
-e trace=set
只跟踪指定的系统 调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.
-e trace=file
只跟踪有关文件操作的系统调用.
-e trace=process
只跟踪有关进程控制的系统调用.
-e trace=network
跟踪与网络有关的所有系统调用.
-e strace=signal
跟踪所有与系统信号有关的 系统调用
-e trace=ipc
跟踪所有与进程通讯有关的系统调用
-e abbrev=set
设定 strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.
-e raw=set
将指 定的系统调用的参数以十六进制显示.
-e signal=set
指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.
-e read=set
输出从指定文件中读出 的数据.例如:
-e read=3,5
-e write=set
输出写入到指定文件中的数据.
-o filename
将strace的输出写入文件filename
-p pid
跟踪指定的进程pid.
-s strsize
指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
-u username
以username 的UID和GID执行被跟踪的命令
6.3. 命令实例
跟踪可执行程序
strace -f -F -o ~/straceout.txt myserver

下面举几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 例1:启动并跟踪一个程序
strace -f -F -o ~/straceout myserver

# 例2:跟踪已经启动的进程(这里选取了一个uwsgi进程)
strace -p 38128
Process 38128 attached - interrupt to quit
wait4(-1, 0x7ffcf5eb2b5c, WNOHANG, NULL) = 0
epoll_wait(30, {}, 1, 1000) = 0
lseek(2, 0, SEEK_CUR) = 108272503
getsockopt(6, SOL_TCP, TCP_INFO, "\n\0\0\0\0\0\0\0@B\17\0\0\0\0\0\30\2\0\0\0\0\0\0\0\0\0\0d\0\0\0"..., [104]) = 0
wait4(-1, 0x7ffcf5eb2b5c, WNOHANG, NULL) = 0
epoll_wait(30, {}, 1, 1000) = 0
lseek(2, 0, SEEK_CUR) = 108272503
getsockopt(6, SOL_TCP, TCP_INFO, "\n\0\0\0\0\0\0\0@B\17\0\0\0\0\0\30\2\0\0\0\0\0\0\0\0\0\0d\0\0\0"..., [104]) = 0

# 例3:追踪进程和线程
$ strace -fp [pid]

# 例4:追踪进程和限定字符
$ strace -s 80 -fp [pid]

# 例5:统计进程的调用情况
$strace -c -f -p [pid] -o [file_name]

% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
60.47 19.181735 7429 2582 1258 futex
23.42 7.429387 212268 35 35 restart_syscall
11.14 3.533856 983 3596 epoll_wait
1.50 0.475396 51 9368 setsockopt
0.79 0.250978 54 4684 fcntl
0.72 0.227007 48 4684 2342 epoll_ctl
0.42 0.132211 56 2342 2342 connect
0.40 0.127517 54 2342 getsockopt
0.39 0.123448 53 2342 close
0.38 0.119249 51 2342 dup2
0.37 0.117744 50 2342 socket
0.00 0.000432 15 29 recvfrom
0.00 0.000210 7 29 poll
0.00 0.000000 0 10 write
0.00 0.000000 0 29 sendto
------ ----------- ----------- --------- --------- ----------------
100.00 31.719170 36756 5977 total

小结

回到文章开头的问题,我们可以给出答案:通过strace这一工具,我们不仅可以轻易的获取指定进程的系统调用,也有方便的方法并对一段时间内的状态进行统计。剩下的就只是监控范围的问题了。如果需要长时间的监控系统调用并形成日志,也可以考虑使用Linux的用户空间审计工具audit来实现类似的效果。