《Linux内核分析》--扒开系统调用的三层皮 20135311傅冬菁
扒开系统调用的三层皮 20135311傅冬菁
一、内容分析
寄存器上下文(从用户态切换到内核态)
中断/int指令会在堆栈上保存一些寄存器的值(用户态栈顶地址、、当时的状态字、当下的cs:eip的值)
系统调用概述
系统调用(操作系统为用户态进程与硬件设备进行交互提供了一组接口)的意义:
1.把用户从底层的硬件编程中解放出来
2.极大的提高了系统的安全性
3.使用户程序具有可移植性
应用编程接口和系统调用的关系:
API只是一个被封装好的函数定义
系统调用通过软中断(trap)向内核发出一个明确的请求
Libc库定义的一些API引用了封装例程(wrapper routine,唯一目的就是发布系统调用)。
ibc库定义的API使得程序员不用去以汇编代码进行系统调用而是直接以函数调用的形式。
一般每个系统调用对应一个封装例程,库再用这些封装例程定义出给用户的API
不是每一个API都对应一个特定的系统调用。
API可能直接提供用户态的服务
一个单独的API可能调用几个系统调用
不同的API可能调用了同一个系统调用
关于返回值
大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
返回值-1在多数情况下表示内核不能满足进程的请求,Libc中定义的errno变量包含特定的出错码
系统调用的三层皮:xyz,system_call,sys_xyz。即:API,中断向量,服务程序。
xyz()函数是系统调用对应的API,这个应用程序编程接口里面封装了一个系统调用,这个系统调用会触发一个int0x80的中断,产生向量为128的编译异常,0x80这个中断向量对应着system_call这个内核代码的起点,这个内核代码里面会有SAVE_ALL,然后执行到sys_xyz()中断服务程序,进入程序里面处理,在中断服务程序执行完之后会ret_from_sys_call,在return的过程中可能会发生进程调度,如果没有进程调度,就会iret,回到用户态接着执行。
内核实现了很多不同的系统调用, 进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数
system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号。具体过程如下:
1.一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把eax寄存器的值置为2(即__NR_fork)。
2.这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号
3.进入sys_call之后,立即将eax的值压入内核
堆栈
寄存器传递参数也有相应的限制:
1.每个参数长度不能超过寄存器的长度(32位)
2.在eax外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp),超过6个就使用指针调用某一个块的内存。
二、实验过程
这是我选择系统调用号为20的例子,getpid()函数有返回值。
该例子中,getpid()函数的参数为零,所以ebp寄存器中压入的是0这个数;
int $80 是中断指令,调用前一条指令中eax寄存器中所保留的$14(即编号为20的系统调用getpid);
最后再将getpid函数的返回值放在寄存器eax中,以便最后的输出。
除此之外,我还用了exit()这个编号为1的系统调用(该函数无返回值),可是在嵌入式汇编程序中没有编译成功。求各位大神能帮忙指点迷津!!!
三、学习总结
通过自己动手做实验,虽然一直失败,但是至少让我明白了系统调用从用户态到内核态的过程。在调用某个封装好的API时,系统从用户态切换到内核态,并保持当前的进程的上下文,将状态栈顶指针,cs:eip等信息压入栈。之后再调用中断向量/指令,类似于异常中断的过程,进入到内核态,通过系统调用号调用函数,之后再保存当前的返回值,并回到原来的进程当中,完成一次系统调用。