前言
我不是专业的内核研读者,只因自己的好奇心,想一探(IO多路复用)select系统调用的实现原理,于是一路追踪到其内核的底层调用。特此记录这一段学习过程。
本机系统:ubuntu15.04
内核版本:3.19.0-29-generic
sys_select 哪去了?
当用户态使用系统调用的时候,操作系统会根据该函数的系统调用号找到其对应的底层函数(前缀 sys_ )去执行。比如当用户调用下面的函数的时候:
系统调用 | 内核实际调用 |
---|---|
open() | sys_open() |
select() | sys_select() |
poll() | sys_poll() |
… | sys_… |
于是我在 /include/sys/select.h 库文件中找到了select() 函数的声明:
在下载的本系统的内核源代码中找到了 select() 函数的源文件 —- /fs/select.c
但是,在这个文件中并没有找到其对应的底层函数 sys_select() , 只找到了这个(下图),看起来是select()的实现源码
难道sys_select 调用没了? SYSCALL_DEFINE5 这个宏是什么东西?
SYSCALL_DEFINEx 宏
于是经过网上查阅,我找到了这个文件 — /include/linux/syscalls.h
瞬间顿悟了。。。下来根据文件内容,我们将这个宏一步步展开
1、SYSCALL_DEFINE5 变成了SYSCALL_DEFINEx 的形式,## 是连接符,比如 ##select 就变成了 _select , __VA_ARGS_ 表示可变参数,对应函数中的多个参数。
2、SYSCALL_DEFINEx转变为 __SYSCALL_DEFINEx 的形式
3、__SYSCALL_DEFINEx 转变为 asmlinkage long sys##name 的形式,
sys##name 即 sys_select , (惊喜!) 看来 sys_ 这个底层调用还是依然存在的。
下来分析括号里面的 __MAP、__SC_LONG ,只要继续找到这两个宏定义就行了。
4、这一步就可以把上面括号中的数字5 去除了,转变成这样的形式:
asmlinkage long sys_select(MAP5(__SC_DECL,__VA_ARGS))
5、结合上一步的MAP宏,括号中转变为 SC_DECL(int,__nfds) ,__MAP4(__SC_DECL,__VA_ARGS)
根据下图,进一步转变可以分离出select()的第一个参数:
asmlinkage long sys_select(int nfds, __MAP4(__SC_DECL,__VA_ARGS))
6、第4的图可以看出,MAP宏是一个递归展开的过程,所以继续展开下去,最终 select()调用会变为如下形式:
asmlinkage long sys_select(int n,fd_set __user* inp, fd_set __user* outp,fd_set __user* exp,struct timeval __user* tvp)
据查,2.6.28及其之后的内核源码里,系统调用的过程都是通过以上形式的宏定义实现的。
http://blog.chinaunix.net/uid-23069658-id-4106015.html
sys_底层调用
接下来就是底层常见的调用流程:
如何找到sys_调用
1、
当用户态在执行一个系统调用时,会首先确定对应的系统调用号。对应关系在下面的文件中:
/usr/include/asm/unistd_64.h
可知,select的系统调用号是23
2、
根据系统调用号在系统调用表中找到对应的内核调用,系统调用表在如下文件中:(需要下载内核源码)
linux-source-3.19.0/arch/x86/syscalls/syscall_64.tbl
所以,select的入口是sys_select,即前面那个宏展开后的函数名。