Linux内核分析4:扒开系统调用三层皮

实验:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

席金玉+《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

一、系统调用的相关知识

系统调用:系统调用只是一个特殊的中断。我们通过库函数和系统调用打交道,库函数把系统调用封装起来。

1、储备知识——内核态和用户态

内核态:在高执行级别下,代码可以执行特权指令,访问任意的物理内存,这种CPU执行级别就对应着内核态。

用户态:在用户态级别下,代码的掌控范围会受到限制,只能在对应级别允许的范围内活动。

注:Intel x86CPU有四种不同的执行级别0-3,Linux只使用了其中的0级和3级分别来表示内核态和用户态。

2、为什么有权限级别的划分?

若没有用户态和内核态的划分,用户写的不健壮的程序就可以执行特权指令时,就很容易是系统崩溃。操作系统发展过程中划分了用户态和内核态,是系统更稳定的机制。

3、寄存器在系统调用中的作用

Cs寄存器的最低两位表明了当前代码的特权级。CPU每条指令的读取都是通过CS:EIP这两个寄存器:其中CS是代码段选择寄存器,EIP是偏移量寄存器。

4、内存地址空间
一般在Linux中,地址空间是一个显著的标志:0xc0000000以上的地址空间只能在内核态下访问,0x0000000--0xbfffffff的地址空间在两种状态下都可以访问。

注:产生中断是从用户态进入内核态的主要方式。

5、寄存器上下文:
5.1从用户态切换到内核态时:

  1. 必须保存用户态的寄存器上下文;
  2. 同时把内核态的寄存器的值放到寄存器中。

5.2中断/int指令会在堆栈上保存一些寄存器的值:

  1. 如用户态栈顶地址;
  2. 当前的状态字;
  3. 当时的CS:EIP的值。
5.3中断发生后的第一件事就是保存现场:

保存现场:就是进入中断程序,保存需要用到的寄存器的数据。

5.4中断处理结束前最后一件事就是恢复现场:

恢复现场:就是退出中断程序恢复保存寄存器的数据。

注:Iret指令和中断信号(包括int指令)发生时的CPU做的动作整好相反。


二、中断处理的完整过程

如下图所示

                          

  1. 中断指令interrupt(ex:int 0x80)开始进行系统调用;
  2. 保存当前CS:EIP,SS:ESP,eflags的值到内核堆栈,同时加载了中断服务程序的地址到CS:EIP以及内核堆栈栈顶指针到SS:ESP中。Int指令完成上述操作过程。
  3. 内核代码,完成中断服务:
    • 发生进程调度,则保存调度时的现场,进行调度,完成调度后再恢复现场;
    • 不发生进程调度,则恢复之前的保存现场:iret - pop cs:EIP/SS:ESP/eflags from kernel stack.

三、系统调用的意义

1、操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用。
  • 把用户从底层的硬件编程中解放出来;
  • 极大的提高了系统的安全性;
  • 使用户程序具有可移植性。
2、操作系统提供的API和系统调用的关系。
  • 应用编程接口(Application program interface,API)和系统调用时不同的;
  • API只是一个函数定义;
  • 系统调用通过软中断向内核发出一个明确的请求。

Libc库定义的一些API引用了封装例程(wrapper routine,唯一的目的就是发布系统调用):一般每个系统调用对应一个封装例程。库再用这些封装例程定义给出用户的API。

3、不是每个API都对应一个特定的系统调用:
  • API可能直接提供用户态的服务;
  • 一个单独的API可能调用几个系统调用;
  • 不同的API可能调用了同一个系统调用。
4、返回值
  • 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用;
  • -1在多数情况下表示内核不能满足进程的请求;
  • Libc中定义的errno变量包含特定的出错码。

注:系统调用的三层皮:API,中断向量,中断服务程序

5、系统调用的服务例程:
5.1 当用户态进程调用一个系统调用时,CPU切换内核态并开始执行一个内核函数。
  • 在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常;
  • Inter Pentium II中引入了sysenter指令(快速系统调用),2,6已经支持。
5.2 传参:
  • 内核实现了很多不同的系统调用;
  • 进程必须指明需要哪个系统调用,这需要使用EAX寄存器传递一个名为系统调用号的参数
5.3 系统调用也需要输入输出参数,例如:
  • 实际的值;
  • 用户态进程地址空间的变量的地址;
  • 甚至包含指向用户态函数的指针的数据结构的地址。

System call是Linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax

5.3 传递的系统调用号;

系统调用号将xyz和sys_xyz关联起来;用eax寄存器来传递参数;

  • 一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把EAX寄存器的值置为2(即 NR fork);
  • 这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号;
  • 进入sys,call之后,立即将EAX的值压入内核堆栈;
5.4 寄存器参数具有如下限制:
  • 每个参数的长度不能超过寄存器的长度,即32位;
  • 在系统调用号(EAX)之外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp)。

四、实验过程
  1. 打开实验楼虚拟机,进入实验楼环境;
  2. 新建一个名为 time.c的源文件,并输入代码,如下图:

编译并运行该文件,输出如下结果:


该程序实现用库函数方式触发系统调用获取系统当前时间,时间为:2016年3月16日14:27:5

3. 新建一个名为 time-asm.c的源文件,并输入代码,如下图


编译并运行该文件,输出结果如下:


该程序是用汇编方式触发系统调用获取系统当前时间,时间为:2016年3月16日14:28:30

4. 完成了对time.c和time-asm.c的编译和运行,下面将这两个程序保存到实验楼代码库中:



如图所示,已经成功将time.c和time-asm.c文件保存到实验课代码库中。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值