扒开系统调用的三层皮——系统调用(20135304刘世鹏)
作者:刘世鹏20135304
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、用户态、内核态和中断处理过程
1. 用户态和内核态
CPU指令执行级别
- 高级别:执行特权指令,访问任意的物理地址——内核态。
- 低级别:代码只能在级别允许的特定范围内活动——用户态。在日常操作下,执行系统调用的方式是通过库函数,库函数封装系统调用,为用户提供接口以便直接使用。
-
Intel x86 CPU有四种不同的执行级别0~3,Linux只用其中的0和3来表示内核态和用户态
-
区分内核态和用户态:CPU每条指令的读取都是通过cs:eip,cs寄存器最低两位表明了当前代码的特权级。
- 内核态下可访问所有地址空间
- 0xc0000000(逻辑地址)以上的空间只能在内核态下访问
- 0x00000000 ~ 0xbfffffff 内核态和用户态均可访问
-
用户态转换为内核态的主要方式:中断
2. 中断处理
中断处理:用户态进入内核态的主要方式
-
用户也可以通过系统调用陷入内核,系统调用是特殊的中断。
寄存器上下文
用户切换到内核,必须保存用户态的寄存器上下文
中断int指令会在堆栈上保存:
用户态栈顶地址,当时的状态字,当时的cs:eip值
内核态栈顶地址,当时的状态字,中断处理程序入口。
中断服务程序:
首先保护现场(SAVE_ALL:保存需要用到的寄存器数据)。
退出恢复现场(RESTORE_ALL:退出中断程序,恢复保存寄存器的数据)。
用户态进程和硬件设备的交互:系统调用
- 把用户态从底层的硬件设备编程中解放出来
- 极大地提升了系统安全性
- 有利于程序的可移植性
应用程序接口API :仅是函数定义
系统调用:是进程明确的请求
每一个系统调用对应一个封装例程,封装例程再被API引用。但是API和系统调用没有明确的对应关系,可能一对一,一对多,多对多,多对一。
二、系统调用
1. 意义
系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口
- 把用户从底层的硬件编程中解放出来
- 极大的提高了系统的安全性
- 使用户程序具有可移植性
2. API与系统调用
API:应用程序编程接口,是一个函数定义。
系统调用:通过软中断向内核发出一个明确的请求。
-
一般每个系统调用对应一个封装例程,库再用这些封装例程定义出用户的API,方便用户使用。所以,API与系统调用不是一一对应的
- API可以直接提供用户态服务(如:数学函数) - 一个单独的API可能调用几个系统调用 - 不同的API可能调用了同一个系统调用
-
返回值
- 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用 - -1表示内核不能满足进程请求 - Libc中定义errno变量,包含特定出错码
3. 系统调用“三层皮”
- API
- 中断向量
- 中断服务程序
-
当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。
-
Linux中是通过执行int $0x80来执行系统调用,这条汇编指令产生向量为128的编程异常 —— 即中断向量0x80与System_call绑定起来。
-
系统调用号将函数
xyz()
和中断服务程序sys_xyz
关联起来。
4. 参数传递
-
内核实现了很多不同的系统调用,进程用系统调用号这个参数指明需要哪个系统调用。
-
system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,使用eax寄存器传递系统调用号。
-
寄存器传递参数的限制
- 每个参数的长度不能超过寄存器的长度,即32位 - 在系统调用号(eax)之外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp) - 超过6个的情况下,使用某一个寄存器作为指针,进入内核态之后可以访问所有的地址空间,通过某一片区域传递参数。
实验
实验过程:
1、库函数API使用系统调用
- char sysname[_UTSNAME_SYSNAME_LENGTH]; //当前操作系统名 - char nodename[_UTSNAME_NODENAME_LENGTH]; //网络上的名称 - char release[_UTSNAME_RELEASE_LENGTH]; //当前发布级别 - char version[_UTSNAME_VERSION_LENGTH]; //当前发布版本 - char machine[_UTSNAME_MACHINE_LENGTH]; //当前硬件体系类型
2、C代码中嵌入汇编代码使用系统调用
3、实验结果