系统调用(通常称为syscalls)是Linux内核与上层应用程序进行交互通信的唯一接口。用户程序通过直接或间接(通过库函数)使用系统调用,即可使用内核资源,包括系统硬件资源。
POSIX标准和linux系统调用
POSIX(Portable Operating System Interface,可移植操作系统接口)是由IEEE和ISO/IEC开发的一簇标准。该标准是基于现有的UNIX实践和经验,描述了操作系统的调用服务接口,用于保证编制的应用程序可以在源代码一级上在多种操作系统上移植运行。
0.12内核共有87个系统调用功能。这些功能号定义在文件include/unistd.h
中,格式为__NR_xxx
。这些功能号实际上对应于include/linux/sys.h
中定义的系统调用处理程序指针数组表sys_call_table[]
的索引值。
通过printf由浅入深看系统调用的实现
- 相关文件:
kernel/sys_call.s
、include/unistd.h
、include/linux/sys.h
、lib/write.c
当应用程序经过库函数向内核发出一个中断调用int 0x80
时,就开始执行一个系统调用,其中寄存器eax中存放着系统调用号来区分是哪个系统调用请求。携带的参数则会依次存放在寄存器ebx、ecx和edx中。
例如,用户态下write
函数的操作,实际会由操作系统利用int 0x80
中断进入内核态,执行对应的sys_write
来完成。
例如,在include/unistd.h
中,write
系统调用功能号为
#define __NR_write 4
对应include/linux/sys.h
中sys_call_table[]
的第4项
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, ...};
-
在
init/main.c
文件中,可以看到printf
实际上使用了write
这个系统调用。int printf(const char *fmt, ...) { va_list args; int i; va_start(args, fmt); write(1, printbuf, i = vsprintf(printbuf, fmt, args)); va_end(args); return i; }
-
在
lib/write.c
中,我们可以看到write
系统调用的实现,_syscall3(int, write, int, fd, const char *, buf, off_t, count)
可以在
include/unistd.h
中,看出_syscall3
是一个宏定义,我们根据这个宏定义将write
展开,从而可以得到这样一个含有内嵌汇编的函数,int write(int fd, const char * buf, off_t count) { long __res; __asm__ volatile ("int $0x80" : "=a" (__res) : "0" (__NR_write), "b" ((long)(fd)), "c" ((long)(buf)), "d" ((long)(count))); if (__res >= 0) return (int) __res; errno = -__res; return -1; }
-
可以看出
write
函数利用了int $0x80
中断,并将__NR_write
功能号和参数传了进去。中断0x80
即为系统调用,在sched_init
中设置了对应的中断处理函数地址,void sched_init(void) { ... set_system_gate(0x80,&system_call); }
-
所以,接下来会去执行
system_call
程序(在kernel/sys_call.s
中),其中关键的一句代码如下(eax即为__NR_write,其中的4指的是每个函数指针大小为4),这句代码意思就是去执行sys_call_table+%eax*4
处的函数,即sys_write
,system_call: ... call *sys_call_table(,%eax,4) ...
-
在内核态下,执行系统调用处理程序
sys_write
。