在Linux中,strace
实用程序使您可以查看程序进行了哪些系统调用。因此,采用这样的程序
int main(){
printf(“ x”);
返回0;
}
假设您将其编译为printx
,然后strace printx
给出
execve(“ ./ printx”,[“ ./printx”],[/ * 49个变量* /])= 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
关闭(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
write(1,“ x”,1x)= 1
exit_group(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()
与到达该点之间仍然有很多事情要做实际上修改了图形卡帧缓冲区,以使字母“ x”出现在屏幕上。如果要花费大量时间,那么通过深入内核来放大接触点(以与“橡皮筋抵挡道路”类似)是很有教育意义的。我猜想您将不得不经历多个抽象层,例如缓冲的输出流,字符设备等。请确保发布结果,以决定继续进行此操作:)