Linux系统调用过程分析

本文以write()来简要分析一下Linux系统调用过程:

write系统调用:
函数定义如下:

Linux系统调用过程分析lib/write.c

其中_syscall是一个宏定义, 内容类似如下:

Linux系统调用过程分析
include/unistd.h

其实就相当于int write(int fd, const char *buf, off_t count) { … }
就是定义了write()这个函数。

可以看到,在执行write函数时,其实就是执行了int 0x80, 使用了0x80中断来实现系统调用,因为write函数调用的时候是在用户态的,而write要做的是把字符串写入到指定文件中,比如写到显存中,写入显存这个操作是需要内核态,也就是0特权级下才能进行的(当然要看显存对应选择子的DPL),也就是说现在在用户态完成不了这个写入显存的操作,怎么办?当然是先切换特权级到内核态,等完成了0特权级完成了操作后再回到用户态即可,int 0x80就完成了用户态向内核态的切换。

Linux中不止一个write调用,还有open, read…等,那int 0x80如何确定是调用哪一个系统函数呢?
int 0x80这个中断也有“参数”, 上面"0" (__NR_name) 部分就是int 0x80的参数,0x80中断会根据__NR_name这个下标来调用与write对应的系统调用,为什么说__NR_name是下标呢,既然说它是下标,那么就会有一个数组。是的,Linux内部维护了一个系统调用的数组,如下:

Linux系统调用过程分析
include/linux/sys.h

这些下标定义如下:

Linux系统调用过程分析
include/unistd.h

可以看到,里面存放了Linux所支持的所有系统调用(内核态函数的指针),而__NR_name就是每个用户态函数在这个数组对应内核态函数的下标。比如write()对应的内核态函数为sys_write,当调用write时,最终会调用到上面这个sys_call_table中的sys_write函数,下面看下是怎么通过int 0x80和他的参数__NR_name来调用对应的内核态函数的。

Linux系统调用过程分析
kernel/sched.c

可以看到,在初始化的时候,int 0x80对应的处理函数为system_call, 当调用int 0x80时,会执行system_call这个函数,如下:

Linux系统调用过程分析
kernel/sys_call.s

看上面99行,就是调用了sys_call_table中相应的项,其中eax中的值就是上面的__NR_name,(数组的下标), 比如write()调用到system_call时,__NR_write = 4, sys_call_table第5荐正好为sys_write, 所以就执行了相应的内核态函数(执行内核态才能执行的操作)。

总结:

Linux实现系统调用首先要有一个表来存放所有与用户态函数对应的系统态的函数(sys_call_table), 调用过程:user_func() --> int 0x80 (user_func对应的系统态函数在表中的下标, 记为index), 此时已经进入内核态 --> system_call(index) --> sys_func();


Linux源码版本: 0.12