Linux内核设计第四周——扒开系统调用三层皮
Linux内核设计第四周
——扒开系统调用三层皮
一、知识点总结
(一)、系统调用基础知识
1、用户态和内核态
内核态:在高级别的状态下,代码可以执行特权指令,访问任意的物理地址;
用户态:在相应的低级别执行状态下,代码的掌控范围会受到限制。
区分:
CPU每条指令的读取都是通过cs:eip,cs寄存器最低两位表明了当前代码的特权级。内核态下可访问所有地址空间。
- 0xc0000000(逻辑地址)以上的空间只能在内核态下访问
- 0x00000000 ~ 0xbfffffff 内核态和用户态均可访问
2、Intel x86 CPU有四种不同的执行级别0——3,Linux只是用了其中的0和3来表示内核态和用户态。
3、中断处理是是从用户态进入内核态的主要方式。系统调用只是一种特殊的中断。
4、用户态进入内核态需保存寄存器上下文:
- 用户态寄存器上下文保存
- 恢复内核态相应的寄存器值到CPU中
5、中断/int指令会在堆栈上保存一些寄存器的值。
- 如:用户态栈顶地址、当时的状态字、当时的cs:eip的值。
6、中断发生后的第一件事就是保存现场,结束前最后一件事是恢复现场。
- 保护现场就是进入中断程序 保存需要用到的寄存器的数据。
- 恢复现场就是推出中断程序 恢复保存寄存器的数据。
7、中断处理的完整过程
interrupt(ex:int 0x80)-save
- 保存了cs:eip、ss:esp、eflags到内核堆栈中
- 加载了cs:eip、ss:esp到CPU中
SAVE_ALL
- 完成中断服务,发生进程调度
RESTORE_ALL
iret-pop cs:eip/ss:esp/eflags from kernel stack
(二)、系统调用概述
1、系统调用:操作系统为用户态进程与硬件设备进行交互提供了一组接口
2、系统调用的意义:
- 把用户从底层的硬件编程中解放出来
- 极大的提高了系统的安全性
- 使用户程序具有可移植性
3、API:应用编程接口
4、操作系统提供的API和系统调用的关系
- API:应用程序编程接口,是一个函数定义。
- 系统调用:通过软中断向内核发出一个明确的请求。
5、Lib库定义的一些API引用了封装例程——唯一的目的是发布系统调用
- 一般每个系统调用对应一个封装例程
- 库再用这些封装例程定义出给用户的API。
特点:
- API与系统调用不是一一对应的
- API可以直接提供用户态服务(如:数学函数)
- 一个单独的API可能调用几个系统调用
- 不同的API可能调用了同一个系统调用
返回值:
- 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
- -1表示内核不能满足进程请求
- Libc中定义errno变量,包含特定出错码
6、系统调用三层皮
- API
- 中断向量
- 中断服务程序
7、系统调用的参数传递:
(1)寄存器传递参数具有如下限制:
- 每个参数的长度不能超过寄存器的长度,即32位
- 在系统调用号(eax)之外,参数的个数不能超过6个(ebx, ecx,edx,esi,edi,ebp)
- 超过6个怎么办?把某一个寄存器作为一个指针,指向某一块内存。
(2)参数的种类
系统调用也需要输入输出参数 - 实际的值 - 用户态进程地址空间的变量的地址 - 甚至是包含指向用户态函数的指针的数据结构的地址
二、实验过程
我选择了调用39号系统调用mkdir,调用了系统函数sys_mkdir()
(一)直接进行系统调用的源代码如下图:
执行结果如下图:
(二)使用嵌入式汇编进行系统调用的源代码如下图:
执行结果如下图:
三、分析汇编代码调用系统调用的工作过程(参数的传递的方式)
如上的图片所示: mkdir系统调用
- (1)需要传递两个参数,第一个是要新建文件夹的路径,第二个是新建文件夹的权限。
- (2)有一个返回值,表示是否创建成功
我们在使用汇编代码调用系统函数sys_mkdir()时,需要利用寄存器传递参数,这里我选择,ebx、ecx寄存器传递两个参数、用eax寄存器传递返回值,只需要将输入的参数赋值给相应的寄存器即可,通过int指令,进入系统调用。
四、总结
阐明自己对“系统调用的工作机制”的理解。
- (1)系统调用,就是用户提供系统调用号和相关函数参数,就可以执行相应的功能。
- (2)而具体怎么实现是内核态在执行,用户是不需要考虑相关的堆栈变化,这样用户就可以轻松调用系统函数。