linux下的系统调用如何从用户态进入内核态?这个问题一直以来都是模模糊糊,最近阅读了《程序员的自我修养》第十二章:系统调用与API,终于对此有了一个算是比较清醒和深刻的认识:总结了一下,画了一个图大致如下(注:没有内核态返回内核态流程):
其中x86有两种方式从用户态陷入内核态:int 0x80系统调用以及新型的sysenter指令。本文暂且基于经典的int 0x80方式。对于用户态,地球人都知道我们写的C程序要调用到glibc库,与我们所理解的库调用并没有多大差异。但是对于glibc库如何陷入内核态,就需要详细的了解一下。
首先,glibc对系统调用做了一系列的封装,典型如fork()函数,比如我们所调用的fork()函数,在glibc中实际上定义的是如下一个宏:
_syscall0(pid_t, fork);
这里有一系列的__syscallN宏定义,其中的N范围从0-6,这是因为x86在linux下支持的最多的参数个数为6个。每个宏对应着相应个数的参数。fork()没有参数,所以是_syscall0。这个宏的参数pid_t是返回值的类型,fork是函数的名字,用于下一步的扩展。其中i386版本的_syscall0宏定义如下:
#define _syscall0(type,name) \
{ long __res; \
__asm__ volatile("int 0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
__syscall_return(type, __res);\
}
这就是采用gcc内联汇编的形式编写的_syscall0宏,通过这个宏,可以很明显的看出来是通过int 0x80方式触发中断,进入内核态中断处理。并指明使用eax来传递参数和返回值。而此处的参数就是系统调用号,所以由此可知,触发中断进入内核时,所有的系统调用号都是通过eax传递,这样在内核态,想知道是什么系统调用,直接查询eax寄存器就可以得到系统调用号了。
进入中断,查询中断号,根据安装的中断处理程序就可以得到这是一个系统调用,于是查询系统调用表,根据eax中传递的系统调用号就可以得到对应的系统调用。
内核处理完成之后,最后通过__syscall_return从用户态返回内核态。当然,返回值还是通过eax传递返回的。
至于用户态和内核态的堆栈的切换,记住其中最重要的一个宏SAVE_ALL,就可以查询到其中的切换了,此处也就不讲述。
阅读(683) | 评论(0) | 转发(0) |