Linux内核分析(五)系统调用过程解析

来源:互联网 发布:张北云计算产业园在哪 编辑:程序博客网 时间:2024/05/17 23:38

禹晓博+ 原创作品转载请注明出处 + 欢迎加入《Linux内核分析》MOOC网易云课堂学习

一、系统调用流程分析

        系统调用系统调用就是用户空间应用程序和内核提供的服务之间的一个接口。由于服务是在内核中提供的,因此无法执行直接调用;相反,我们必须使用一个进程来跨越用户空间与内核之间的界限,这实际上就是系统调用的过程。我们首先来开一个系统调用流程的示意图。


        Linux中的系统调用的实现会根据不同的架构而有所变化,而且即使在某种给定的体系结构上也会有所不同,我们看到上述是一个简单的系统调用的例子。每一个个系统调用都是通过一个单一的入口点多路传入内核的。eax 寄存器用来标识应当调用的某个系统调用,这在 C 库中做了指定(来自用户空间应用程序的每个调用)。当加载了系统的 C 库调用索引和参数时,就会调用一个软件中断(0x80 中断),它将执行 system_call 函数(通过中断处理程序),这个函数会按照 eax 内容中的标识处理所有的系统调用。在经过几个简单测试之后,使用 system_call_table 和 eax 中包含的索引来执行真正的系统调用了。从系统调用中返回后,最终执行 syscall_exit,并调用 resume_userspace 返回用户空间。然后继续在 C 库中执行,它将返回到用户应用程序中。

        上一篇文章实际上我们经讲过了关于system_call_table相关的内容了,我们实际上可以简单的理解为一个跳转列表一样的东西,那么系统是如何通过这些列表来找到具体的服务程序的呢?这里面实际上非常类似于中断处理的过程,因为系统调用过程的本身就是引发一次中断的过程。这里面有一个非常好的词就是trap。实际上这是一次主动引起的异常(大体上可以这么理解)。那么在分析之前我们首先先看这个system_call_table是如何参与系统调用过程的呢?我们来看这样一张图。


        首先我们分析一下这个过程,首先在用户空间中,我们编写的程序中有一个系统调用的过程,这个时候首先他会进入到系统调用的控制程序中去,在这个过程中首先要做的就是保存现场,然后就是要定位到这个系统调用列表中去。我们看到这个过程中首先程序会找到sys_call_table的基地址然后在进行相应的运算之后得出一个偏移地址,这里面是采用eax*4来得到这样一个地址的。入口地址实际上就是系统服务例程的基地址,我们具体需要什么样的功能实现都在这个服务程序中。


        上面分析我们知道实际上eax就是提供一个索引来确定要调用sys_call_table中那个表项对用的系统调用。上图中就是一些具体的例子。其中的offset就是eax提供的索引编译当然是计算过的。然后是对应的symbol和表项,最后是系统中他们所在的位置。

        那么接下来问题就来了,上面我们仅仅是知道系统调用过程中的上面部分的过程情况,那么在系统调用的执行过程中,具体代码的执行过程是什么样子的呢?这里面我们结合第四章讲义System Call(可以到网易云课堂中孟宁老师的Linux内核分析课程中下载)最后给出的系统调用过程的汇编伪代码以及实际的运行过程来分析一下system_call具体的执行过程。首先我们还是来看一张流程图(为什么这么多图呢吐舌头,因为这样可以少写点字,省得麻烦呀,图的形式更为清晰也容易理解)


       我们看到这里面进程的调度时机发生在返回之前,有可能会有一些其他的外部请求来引起这样的进程调度过程,这里面我们实际上是结合讲义中的伪代码以及一些扩充的细节来完成的这个过程图。就像视频中所讲的那样实际上在系统从用户态陷入内核态的时候(trap~)就是开始调用的时机,然后系统就会按照上述的过程(实际上第一章图片是一个总体的描述,大家可以和流程图结合起来看)进行,然后再返回到用户态下。上面的图画的实在辛苦哦应该省去大家不少阅读时间吧,如果有什么错误欢迎指正出来啊。

二、实验过程描述

       下面我们来一起看一下实验过程,很不幸的是系统调用过程的内部跟踪在gdb下是不能完全跟踪的,所以我们接下来的过程就是叙述一下在gdb下进行内核跟踪的过程。

        首先,我们先删除一下之前我们的menuOS的文件,因为这次我们更新了其中的命令。用于跟进系统调用过程,加入了time这样一个选项,这个地方我们可以发起一个断点然后跟踪系统调用过程。


       上面设个图我们就是删除之前的系统文件的过程然后从github上更新这次实验要用的新的menuOS镜像。然后我们开始制作根文件系统。


        之后系统就成功启动了,我们就会开到熟悉的界面。如果是要跟踪调试系统的话我们可以用这个命令:

:~/$qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S  


       同样的我们再打开一个窗口然后进入gdb调试模式,然后我们首先是要加载它的符号表,命令是 file linux3.18.6/vmlinux 然后在连接到他,命令是:target remote:1234这样我们就可以开始调试他了。


        然后我们添加了一个断点在sys_time上,然后我们按c继续执行我们开到系统停在了相应的位置上。


        这个时候我们可以输入list进行查看对应部分的代码。这里面有一个系统调用额定SYSCALL_DEFINE里面的time就是我们写的那个time,当然这里面也可以换成其他的比如上周我们自己写内容,只要修改test.c里面的内容就可以了。


        这个时候我们再看一下我们的menuOS界面的情况,如下图所示:这个时候我们输入time他就会停在那个地方了以为设置过断点,然后我们可以用s进行单步调试。那个再之后就是我们上面所说的系统调用过程了。


三、总结

        经过了两个实验的过程我们已经大体上对系统调用的过程有所了解,实际上这个过程还比我们所说的更为复杂,之后我会有时间慢慢扩充它。我们知道操作系统为在用户态运行的进程与硬件设备进行交互提供了一组几口。在应用程序与硬件之间设置一个额外的层实际上是有很多优点的——所谓的系统调用。首先这使得编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来。其次,着极大的提高了系统的安全性,因为内核在试图满足某个请求之前在接口级别上就可以检查这种请求是否是正确的合法的。最后更为重要的是,这些接口是的程序更具有可以执行,因为只要内核所提供的一组接口相同,那么在任意一个内核之上就可以正确的编译和执行我们编写的程序了。

        Linux系统就是通过内核发出的系统调用(system call)实现了用户态进程和硬件设备之间的大部分接口。最后我们还是以一张图来从很抽象的层次说明这件事情。


0 0
原创粉丝点击