在Linux中,
strace
实用程序允许您查看程序所调用的系统调用。因此,以这样的程序为例:
int main(){
printf("x");
return 0;
}
假设您将其编译为
printx
,那么
strace printx
会给出以下结果:
execve("./printx", ["./printx"], [/* 49 vars */]) = 0
brk(0) = 0xb66000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (找不到文件或目录)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0e5000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (找不到文件或目录)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=119796, ...}) = 0
mmap(NULL, 119796, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa6dc0c7000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (找不到文件或目录)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1811128, ...}) = 0
mmap(NULL, 3925208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa6dbb06000
mprotect(0x7fa6dbcbb000, 2093056, PROT_NONE) = 0
mmap(0x7fa6dbeba000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b4000) = 0x7fa6dbeba000
mmap(0x7fa6dbec0000, 17624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa6dbec0000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0c6000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0c5000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0c4000
arch_prctl(ARCH_SET_FS, 0x7fa6dc0c5700) = 0
mprotect(0x7fa6dbeba000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7fa6dc0e7000, 4096, PROT_READ) = 0
munmap(0x7fa6dc0c7000, 119796) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa6dc0e4000
write(1, "x", 1x) = 1
exit_group(0) = ?
在跟踪的倒数第二个调用中,由
write(1,"x",1x)
处理(有所不同,请参见下文),这时控制权从用户空间的
printx
传递到了Linux内核,由内核处理其余部分。大多数系统调用都以这种方式进行封装。正如其名称所示,封装函数只���一个薄薄的代码层,它将参数放置在正确的寄存器中,然后执行软件中断0x80。内核捕获中断,剩下的就成为历史了。或者至少过去是这样的。显然,中断捕获的开销相当高,正如早期的一篇帖子指出的那样,现代CPU架构引入了
sysenter
汇编指令,以便以更快的速度实现相同的结果。这个页面
System Calls非常好地总结了系统调用的工作原理。
我感觉你可能会对这个答案有点失望,就像我一样。很明显,在某种意义上,这是一个错误的底部,因为在调用
write()
并使字母“x”出现在屏幕上的图形卡帧缓冲区实际上被修改之间仍然有很多事情要发生。如果深入研究内核,聚焦联系点(保持“橡胶和道路”比喻),这肯定是一次值得学习的而且也需要耗费时间的努力。我猜你必须要穿过几层抽象,如缓冲输出流、字符设备等。如果你决定跟进此问题,请务必发布结果 :)