《Linux内核分析》第四周:扒开系统调用的三层皮
杨舒雯 原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
一、 用户态、内核态和中断处理过程
1.用户态、内核态区别
-
在高级别的状态下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态。
-
在相应的低级别执行状态下,代码的掌控范围会受到限制。
2.中断处理
-
中断时从用户态转换为内核态的主要方式。
-
中断发生的两种情况:1.可能是硬件中断,中断服务进程;2.用户态程序调用了系统调用(其中系统调用是一种特殊的中断)。
-
当从用户态切换到内核态时,必须要保存用户态的寄存器上下文,中断指令会在寄存器上保存一些寄存器的值放入内核堆栈,比如:用户态栈顶地址(ss:esp),标志寄存器(eflags),cs:eip(为了返回的时候popl弹出保存的返回地址)。同时,将相关联的中端服务历程的入口加载到cs:eip,把当前的堆栈段esp也加载到CPU里面。
-
中断发生之后第一件事就是保存现场;同样,中断处理结束前的最后一件事情就是恢复现场。也就是说,SAVE ALL之后就是内核态了;restore all之后再返回用户态。
-
iret指令与中断信号(包括int指令)发生时的CPU所做的动作恰好相反。
二、系统调用概述和系统调用的三层皮
1.系统调用概述
- 系统调用的意义:
- 操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用。
①把用户从底层的硬件编程中解放出来
②极大的提高了系统的安全
③使用户程序具有可移植性
- 操作系统提供的API和系统调用的关系
①应用编程接口(API)和系统调用是不同的。使用API是为了让用户从底层硬件编程中解放出来。
②API只是一个被封装好的函数定义
③系统调用通过软中断向内核发出一个明确的请求
④Libc库定义的一些API引用了封装例程(wrapper routine),唯一目的就是发布系统调用。libc库定义的API使得程序员不用去以汇编代码进行系统调用而是直接以函数调用的形式。
⑤一般每个系统调用对应一个封装例程,库再用这些封装例程定义出给用户的API
⑥API与系统调用不是单一的一对一的关系,也存在多对多的关系。但也有特例,例如一些数学函数没有用到系统调用
⑦大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
⑧返回值-1在多数情况下表示内核不能满足进程的请求,Libc中定义的errno变量包含特定的出错码
2.系统调用的三层皮
①系统调用的三层皮:xyz,system_call,sys_xyz。也就是:API,中断向量,服务程序。
②详细过程:首先,xyz()函数是系统调用对应的API,这个应用程序编程接口里面封装了一个系统调用,这个系统调用会触发一个int0x80的中断,产生向量为128的编译异常,0x80这个中断向量对应着system_call这个内核代码的起点,这个内核代码里面会有SAVE_ALL,然后执行到sys_xyz()中断服务程序,进入程序里面处理,在中断服务程序执行完之后会ret_from_sys_call,在return的过程中可能会发生进程调度,如果没有进程调度,就会iret,回到用户态接着执行。
3.系统调用的参数传递方法
①内核实现了很多不同的系统调用, 进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数
②system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号。
三、 实验
1.实验要求
①选择一个系统调用(13号系统调用time除外),系统调用列表参见http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl
②参考视频中的方式使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
2.库函数API进行24号系统调用
当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。 在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常。内核实现了很多不同的系统调用,进程指明需要哪个系统调用,把系统调用号作参数传入eax寄存器。下面以linux系统调用getuid为例简单分析一下系统调用过程。
代码:
getuid.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *agrv[])
{
uid_t uid;
uid=getuid();
printf("The current user ID:%d\n",uid);
return 0;
}
执行结果:
3.C代码中嵌入汇编代码进行20号系统调用
代码:
执行结果:
四、 总结
我认为系统调用就是内核将常用的用户需要使用底层硬件或特权级操作的相关代码,封装成服务例程,给每个例程一个编号(系统调用号),当用户需要执行相应功能时,进程产生软中断int 0X80,装系统调用号作为参数传入eax寄存器。完成相应的功能。