系统调用过程详解
在上一篇博客中,我们介绍了库函数和系统调用的联系和区别。在这篇博客中,我们将通过分析Linux0.11的源码来理解系统调用的实际执行过程。
整个过程如下:首先指令流执行到系统调用函数时,系统调用函数通过int 0x80指令进入系统调用入口程序,并且把系统调用号放入%eax中,如果需要传递参数,则把参数放入%ebx,%ecx和%edx中。进入系统调用入口程序(System_call)后,它首先把相关的寄存器压入内核堆栈(以备将来恢复),这个过程称为保护现场。保护现场的工作完成后,开始检查系统调用号是不是一个有效值,如果不是则退出。接下来根据系统调用号开始调用系统调用处理程序(这是一个正式执行系统调用功能的函数),从系统调用处理程序返回后,就会去检查当前进程是否处于就绪态、进程时间片是否用完,如果不在就绪态或者时间片已用完,那么就会去调用进程调度程序schedule(),转去执行其他进程。如果不执行进程调度程序,那么接下来就会开始执行ret_from_sys_call,顾名思义,这这个程序主要执行一些系统调用的后处理工作。比如它会去检查当前进程是否有需要处理的信号,如果有则去调用do_signal(),然后进行一些恢复现场的工作,返回到原先的进程指令流中。至此整个系统调用的过程就结束了。
注:关于进程调度函数schedule()和信号处理函数do_signal()我们会在后面的博客中具体进行讲解。
好了,知道了系统调用的大致执行过程,下面我们来看看Linux0.11内核是如何实现这一功能的。
我们以Linux0.11内核中init/main.c中的fork()函数为例(fork()是一个系统调用)。
在main.c中有这么几行代码:
这里的_syscall0表示内嵌宏代码,(不明白?没关系),我们只要知道执行fork(),就相当于执行_syscall0(),它的定义在include/Unistd.h中:
这是一段嵌入在C语言的汇编代码(注:在Linux内核中有很多这样的代码,为的是在某些关键的地方使用汇编代码提供运行效率)。这段代码的大致含义是:执行int 0x80,把系统调用号放入%eax。这个时候系统就自动进入了系统调用入口程序_system_call中,它在kernel/System_call.s中:
第81-82行的代码是判断系统调用号是不是有效值。从第83-93就在进行一些入栈的操作,紧接着第94行代码执行call _sys_call_table(,%eax,4),这个_sys_call_table是什么东西呢?它就是系统调用处理函数的指针数组,它里面包括了所有的系统调用。这个指针数组定义在include/linux/Sys.h,截个图给大家看看
看到没?我们要调用的fork()它的处理函数就是sys_fork(),它处在第3个位置上,所以当我们执行call _sys_call_table(,%eax,4)时,它就自动去执行sys_fork()啦!从系统调用处理函数返回后,第96-100的代码会去判断需不需要执行调度函数schedule。如果不需要的话,就开始执行ret_from_sys_call了,它同样在kernel/System_call.s中:
这个函数会去检查是否有信号需要处理,如果有则调用do_signal函数。标号3那个位置的汇编代码就是进行一些恢复现场的工作。
Linux0.11内核的系统调用的整个过程大概就是这样!
更多推荐
所有评论(0)