想学linux c开发的博友或者正在学的博友,当你们的代码里出现系统调用,比如read、write、open等。你怎么看、怎么想?有没有想一探究竟,深究一下系统调用的始终。准备好纸和笔,让我们开始系统调用跟踪之旅:
开始之前呢,先花费2分钟概览一下图(1),在你的头脑中做个快照,便于后面分析的理解,以read调用为例,展开分析。
read函数的声明位于头文件#include
原型为:ssize_t read(int fd, void *buf, size_t count)。
read函数在用户空间的伪代码:
说明:
第4行,寄存器eax保存了read函数的系统调用号,在 文件include/asm-i386/unistd.h里有定义(#define __NR_read 3);第5~7行,将三个参数分别放入三个寄存器(通过寄存器来传递参数)
第8行,执行系统调用,进入内核;
第9行,获取eax寄存器所保存的函数返 回值。
第8行是一个中断,执行完第8行后,已经进入了系统内核,中断向量表中记录0x80号的中断处理程序开始执行,中断向量表的初始化在arch/x86_64/kernel/traps.c中定义:
如红框标注所示。IA32_SYSCALL_VECTOR是系统调用的中断号,在include/asm-x86_64/hw_irq.h中定义:
正好是0x80。而system_call是系统调用的中断处理函数指针,用户执行int $0x80后会执行到这个函数,它在arch/x86_64/kernel/entry.S中定义:
SAVE_ALL是一个宏,在这include/asm-x86_64/calling.h文件里定义:
主要作用就是将各个寄存器压入栈中。
cmpl $(nr_syscalls), %eax比较eax的值是否大于等于nr_syscalls,nr_syscalls是比最大有效系统调用号大1的值,在arch/i386/kernel/entry.S中定义:
syscall_table_size就是系统调用表的大小(单位:字节),syscall_table_size其实是一个数组,数组里存放的是各个系统调用函数的地址,元素类型是long型,除以4刚好是系统调用函数的个数。
如果从eax寄存器传进来的系统调用号有效,那么就执行第12行,在系统调用表里找到相应的系统调用服务程序,sys_call_table在arch/i386/kernel/entry.S中定义:
*sys_call_table(,%eax,4)指的是sys_call_table里偏移量为%eax*4上的那个值指向的函数,这里%eax=3,那么第5行的sys_read()函数就会被调用。sys_read()在/fs/read_write.c中定义:
asmlingage是一个宏,定义为:__attribute__((regparm(0))),作用是让这个函数只从栈上获取参数(因为之前的SAVE_ALL将参数压到了栈里面)。
Fget_light:根据fd指定的索引,从当前进程描述符中取出相应的file对象,如果没有找到指定的file对象,则返回错误,如果找到了指定的file对象,则调用file_pos_read函数取出此次读写文件的当前位置。
然后调用vfs_read执行文件读取操作,而这个函数最终调用file->f_op_read指向的函数[fs/read_write.c文件中],代码如下:
接下来调用file_pos_write()更新文件的当前读写位置,调用fput_light更新文件的引用计数。最后返回读取数据的字节数。
思考:file->f_op_read指向的函数是哪个?鉴于篇幅,我们准备在下一篇博文详解,继而开始真正数据读取之旅。
当执行完中断处理程序后,后面会调用RESTORE_REGS来恢复各个寄存器:
RESTORE_REGS的定义:
RESTORE_INT_REGS的定义:
当你耐着性子看到这里,真诚的说一声谢谢。我的劳作是有意义的。我写这篇博文的本意就是让自己在头脑形成一条清晰的脉络,read系统调用的清晰脉络。在平时分析问题的时候,沿着这条清晰的脉络,将疑难一一排除。对了,写这篇博文时,我参照的系统内核源码版本是:
linux-2.6.0
Good luck for you!