在 Linux 中,strace
实用程序允许您查看程序进行了哪些系统调用。所以,采取这样的程序
诠释主要(){
printf("x");
返回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 (没有这样的文件或目录)
打开(“/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
关闭(3)= 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (没有那个文件或目录)
打开(“/lib/x86_64-linux-gnu/libc.so.6”,O_RDONLY|O_CLOEXEC)= 3
读(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
关闭(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
写(1,“x”,1x)= 1
退出组(0)=?
橡胶在跟踪的倒数第二个调用中遇到道路(分类,见下文)write(1,"x",1x)
:。此时,控制权从用户态传递printx
到处理其余部分的 Linux 内核。write()
是在中声明的包装函数unistd.h
extern ssize_t write (int __fd, __const void *__buf, size_t __n) __wur;
大多数系统调用都是以这种方式包装的。包装函数,顾名思义,只不过是一个薄代码层,它将参数放在正确的寄存器中,然后执行软件中断 0x80。内核捕获中断,剩下的就是历史了。或者至少这是它过去的工作方式。显然,中断捕获的开销相当高,正如之前的一篇文章所指出的,现代 CPU 架构引入了sysenter
汇编指令,它可以快速完成相同的结果。这个页面系统调用对系统调用的工作方式进行了很好的总结。
write()
我觉得你可能会对这个答案感到有点失望,就像我一样。显然,从某种意义上说,这是一个错误的底部,因为在调用 to和点之间还有很多事情要做显卡帧缓冲区实际上已修改为使字母“x”出现在您的屏幕上。如果费时费力,通过深入内核来放大接触点(以保持“橡胶对道路”的类比)肯定是有教育意义的。我猜你将不得不经历几个抽象层,比如缓冲输出流、字符设备等。如果你决定跟进,请务必发布结果:)