系统调用是程序向内核请求服务的一种编程方式。要想了解操作系统如何工作,首先要了解系统调用如何工作的。操作系统主要的功能是对应用程序提供抽象。操作系统粗略的分为:操作系统内核使用的内核模式和应用程序运行的用户模式。
系统调用和函数调用很类似,输入参数项并返回值。唯一不同之处是系统调用进入内核而函数调用做不到。从用户空间切换到内核空间通过一种特殊的trap机制(陷阱)。
备注:trap机制的来历是这样的:内核态的操作系统对所有硬件有完全访问权限,可以运行任何指令;而用户态软件只能使用少数指令,并不具备直接访问硬件的权限。软件要访问硬件或者要调用内核函数该怎么办呢?陷阱指令可以使执行流程从用户态陷入内核态,并把控制权转移给操作系统,用户程序就可以访问硬件和调用内核函数了。
系统调用对于大多数使用者,通过系统调用库(Linux下统称为glibc)来实现的。尽管系统调用的本质是一样的,但发出系统调用的机制很大程度上取决于机器。比如strace sleep 60s在不同机器上系统调用是不同的。
你可能每天都在使用ls命令,但并没有意识到系统调用如何实现的,将其抽象如下:
Command-line utility命令行应用 ---> Invokes functions from system libraries (glibc)系统lib库 ---> Invokes system calls。
ls命令调用Linux上系统lib库的函数。这些lib又触发系统调用完成整个任务。
ltrace:know the functions were called from the glibc library
[root@sandbox ~]# yum install ltrace
[root@sandbox ~]# ltrace ls testdir/
opendir("testdir/") = { 3 }
readdir({ 3 }) = { 101879119, "." }
readdir({ 3 }) = { 134, ".." }
readdir({ 3 }) = { 101879120, "file1" }
strlen("file1") = 5
memcpy(0x1665be0, "file1\0", 6) = 0x1665be0
readdir({ 3 }) = { 101879122, "file2" }
strlen("file2") = 5
memcpy(0x166dcb0, "file2\0", 6) = 0x166dcb0
readdir({ 3 }) = nil
closedir({ 3 })
一个testdir目录通过opendir lib库函数打开,紧接着是readdir函数,读取目录内容。最后是closedir函数,关掉目录。其他函数先忽略。
strace: trace system calls and signals
strace运行时能截取并记录进程的系统调用和进程接收的信号。每个系统调用的名称,及其参数和返回值打印到标准错误或使用-o指定文件输出。
strace是一个非常有用的诊断,指导和调试工具。 系统管理员,诊断人员和疑难解决人员将发现,对于不容易获取源代码的程序而言,这是无价的,因为它们无需重新编译即可跟踪它们。 由于系统调用和信号是在用户/内核接口上发生的事件(system calls and signals are events that happen at the user/kernel interface),因此仔细检查此边界对于错误隔离,健全性检查和尝试捕获竞争状(bug isolation, sanity checking and attempting to capture race conditions)非常有用。
1) system call系统调用举例
"cat /dev/null" is:
open("/dev/null", O_RDONLY) = 3
Errors (通常返回值-1) have the errno symbol and error string appended.
open("/foo/bar", O_RDONLY) = -1 ENOENT (No such file or directory)
2) Signals信号举例
信号打印为信号符号和已解码的siginfo。 跟踪和中断“ sleep 666”命令的摘录为:
sigsuspend([]
--- SIGINT {si_signo=SIGINT, si_code= SI_USER, si_pid=...} ---
+++ killed by SIGINT +++
假如一个系统调用执行的同时有另外一个正在调用的线程或进程,trace尝试按照先后事件的顺序,将系统调用标记为未完成。调用返回后被标记为恢复。
[pid 28772] select(4, [3], NULL, NULL, NULL
[pid 28779] clock_gettime(CLOCK_REALTIME, {1130322148, 939977000}) = 0
[pid 28772] <... select resumed> ) = 1 (in [3])
trace安装
[root@sandbox ~]# yum install strace
[root@sandbox ~]# rpm -qa | grep -i strace
strace-4.12-9.el7.x86_64
[root@sandbox ~]# strace -V
strace -- version 4.12
系统调用的分类和strace实战
系统调用粗略的分为
1) 进程管理的系统调用
2) 文件管理的系统调用
3) 目录和文件系统管理的系统调用
4) 其他系统调用
[root@sandbox tmp]# strace ls testdir/
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
brk(NULL) = 0x1f12000
<<< truncated strace output >>>
write(1, "file1 file2\n", 13file1 file2) = 13
close(1) = 0
munmap(0x7fd002c8d000, 4096) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
你不用记住每个系统调用函数,通过man来查即可。下面是man相应章节数及其内容:
1. Executable programs or shell commands
2. System calls (functions provided by the kernel)
3. Library calls (functions within program libraries)
4. Special files (usually found in /dev)
比如man 2 execve:execve() executes the program pointed to by filename'
使用场景:
strace ls testdir/ 把调用打印在屏幕上
strace -o trace.log ls testdir 把系统调用输出到文件
strace -v ls testdir 系统调用额外的信息
strace -f ls testdir 进程相关子进程的跟踪
strace -c ls testdir/ 统计每次系统调用花费时长和总时长的占比
strace -e open ls testdir 或 strace -e write, getdents ls testdir指定系统调用函数跟踪
strace -p 22443 指定进程id跟踪
strace -t ls testdir/ 显示系统调用的时间戳
参考
Understanding system calls on Linux with strace
https://www.man7.org/linux/man-pages/man1/strace.1.html