首页 理论教育 系统调用参数传递方法

系统调用参数传递方法

时间:2023-10-21 理论教育 版权反馈
【摘要】:如果是中断,则调用DispatchInterrupt函数,该函数进一步会调用SyscallHandler函数,而SyscallHandler函数会根据系统调用功能号,调用真正的功能例程:注意,SyscallHandler的lpEsp参数,就是GeneralIntHandler函数传递过去的,依然指向堆栈框架的EBP处。SyscallHandler根据系统调用编号,调用对应的系统调用服务例程,对于有返回值的系统调用服务例程,则把返回值强制转换为LPVOID,并存储在系统调用参数块对象中。这样在系统调用的代理函数中,就会把返回值返回给用户程序。

系统调用参数传递方法

前面讲过,系统调用涉及几个概念:代理函数、存根代码(可能包含多个函数)和服务例程,其中代理函数与用户应用程序链接在一起,形成一个可执行的二进制模块。而存根代码和服务例程则是由操作系统核心实现的。代理函数是用户态的系统调用服务函数,该函数只建立合适的堆栈框架,然后发起一个软件中断,从而使得当前执行环境切换到内核中。系统调用存根代码,则是核心态的系统调用功能支撑代码,这些代码提取相关的函数参数,并根据系统调用功能编号,调用对应的服务例程。而服务例程则是运行在内核中的实际功能函数,用于完成实际的系统功能,然后把结果返回给发起系统调用的应用程序。

以CreateKernelThread函数为例,该函数的系统调用代理函数代码如下(为便于解释,代码做了修改,主要是展开了宏定义):

978-7-111-41444-5-Chapter06-6.jpg

978-7-111-41444-5-Chapter06-7.jpg

上述汇编代码中,int 0x80处是一个分界点。前面部分代码,是把函数的参数压入堆栈,同时压入一个空的双字,以存储函数的返回值,然后压入系统调用编号,以指示内核调用哪个服务例程。这些汇编指令执行完毕,堆栈框架如图6-2所示。

在压入系统调用编号前,压入一个0,以预留返回值空间。在内核完成系统服务函数的执行后,会把服务函数的返回值存储在这个地方。因此在int 0x80指令后的两条pop指令中,就把该返回值存入了EAX寄存器,从而可以直接返回给调用程序。

在int 0x80汇编指令执行后,对应的堆栈框架如图6-3所示。

978-7-111-41444-5-Chapter06-8.jpg

图6-2 堆栈框架

978-7-111-41444-5-Chapter06-9.jpg

图6-3 int 0x80指令执行后的堆栈框架

这就是一个典型的中断发生后的堆栈框架。在中断处理程序(gl_syscall标号处的汇编代码,参见前一节内容)入口处,汇编语言入口函数又把通用寄存器保存在了堆栈里面,这样在调用通用中断处理函数(GeneralIntHandler,该函数被写入汇编标号gl_general_int_handler处)时,堆栈框架如图6-4所示。

而通用中断处理函数GeneralIntHandler的原型如下:

978-7-111-41444-5-Chapter06-10.jpg(www.xing528.com)

于是lpEsp参数就指向了上述堆栈框架的箭头处(注意,ESP寄存器的值是通过先存入EAX,再压入堆栈的。这样在堆栈内的ESP值,实际上是指向EBP处的)。要理解这个框架,需要读者非常清楚每条指令执行后引起的堆栈指针的变化。如果在这个地方感到迷茫,也不要紧,可参考本书第8章,或者参考Intel的CPU指令用户手册。或者只要记住lpEsp参数指向图6-4中的箭头处就可以了,至于是否理解指向这个位置的原因,并不影响后续阅读。

978-7-111-41444-5-Chapter06-11.jpg

图6-4 调用通用中断处理函数时的堆栈框架

GeneralIntHandler根据中断号码,判断是异常还是中断。如果是中断,则调用DispatchInterrupt函数,该函数进一步会调用SyscallHandler函数,而SyscallHandler函数会根据系统调用功能号,调用真正的功能例程:

978-7-111-41444-5-Chapter06-12.jpg

978-7-111-41444-5-Chapter06-13.jpg

注意,SyscallHandler的lpEsp参数,就是GeneralIntHandler函数传递过去的,依然指向堆栈框架的EBP处。

__SYSCALL_PARAM_BLOCK是预先定义的一个结构体,如下:

978-7-111-41444-5-Chapter06-14.jpg

可以看出,__SYSCALL_PARAM_BLOCK实际上就是反映了系统调用过程中建立起来的堆栈框架结构。通过这个结构,系统调用存根代码可以访问到任何系统调用相关的信息,包括功能号、相关参数等。对于功能例程的返回值,也是填写到这个结构体中(实际上就是填写到了堆栈内,在系统调用代理函数返回时,可直接返回给用户程序)。

SyscallHandler根据系统调用编号,调用对应的系统调用服务例程,对于有返回值的系统调用服务例程,则把返回值强制转换为LPVOID,并存储在系统调用参数块对象中。这实际上是存储在了堆栈中的返回值位置处。这样在系统调用的代理函数中,就会把返回值返回给用户程序。需要注意的是,在__SYSCALL_PARAM_BLOCK的定义中,只定义了一个用户参数——lpParams[1],实际上这是一个可扩展数组,可以通过增加索引(比如lpParam[2])的方式,来访问更多的用户参数。

系统调用实现后,基于Hello China的应用程序开发就非常简单了。只需在源文件中包含一个系统调用头文件,而无需把所有Hello China操作系统的核心代码都包含进来。这样可大大减小软件工程源代码规模,且使操作系统和应用程序完全独立,有利于以模块化方式实现大型软件系统的开发。

系统调用的实现机制讲完了,希望读者能够对系统调用的实现有一个清晰的理解。通用操作系统,比如Windows和Linux的系统调用实现与此类似,不同的是这些通用操作系统需要处理用户态堆栈和核心态堆栈之间的数据转换,这实际上就是一些内存复制操作,比较简单。如果读者对这部分内容仍然存有疑问,可到网络上搜索相关资料做进一步了解。据作者了解,在互联网上,系统调用实现机制相关的文章非常多,而且大多数都讲得比较深入浅出

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈