《Linux内核分析》第四周学习笔记
《Linux内核分析》第四周学习笔记 扒开系统调用的三层皮(上)
郭垚 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、用户态、内核态和中断
用户态、内核态和中断处理过程
1. 通过库函数完成系统调用:库函数将系统调用封装起来。
2. 用户态与内核态
- 内核态:一般现代CPU有几种指令执行级别。在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别对应着内核态
- 用户态:在相应的低级别执行状态下,代码的掌控范围有限,只能在对应级别允许的范围内活动
- 如intel x86 CPU有四种不同的执行级别0-3,Linux只使用0级表示内核态,3级表示用户态
- 权限级别的划分使系统更稳定
3. 区分用户态与内核态的方法
(主要是通过代码段选择寄存器cs和偏移量寄存器eip)
- cs寄存器的最低两位表明了当前代码特权级
- CPU每条指令的读取都是通过cs:eip这两个寄存器
(上述两个判断由硬件完成)
- 一般在Linux中,(逻辑)地址空间是显著标志:0xc0000000以上的地址空间只能在内核态下访问,0x00000000-0xbfffffff的地址空间在两种状态下都能访问
注:逻辑地址是进程的地址空间中的。
4. 中断处理
- 中断处理是从用户态进入内核态的主要方式
- 系统调用只是一种特殊的中断
- 寄存器上下文,从用户态切换到内核态时:必须保存用户态的寄存器上下文,同时将内核态的寄存器相应的值放入当前CPU
- 中断/int指令会在堆栈上保存一些寄存器的值:如用户态栈顶地址、当前的状态字、当时cs:eip的值(当前中断程序的入口)
5. 保护现场与恢复现场
保护现场:进入中断程序,保存需要用到的寄存器的数据(中断发生后的第一件事)
#define SAVE_ALL //将其他寄存器的值push到内核堆栈中
恢复现场:退出中断程序,恢复保存寄存器的数据(中断处理结束前最后一件事)
#RESTORE_ALL //将用户态保存的寄存器pop到当前CPU中
iret指令:iret指令与中断信号(包括int指令)发生时的CPU的动作相反
6. 中断处理的完整过程
第一步
interrupt(ex:int 0x80)-save //int 0x80指系统调用
cs:eip/ss:esp/eflags(current)to kernel stack,then //中断将cs:eip、ss:esp(当前堆栈段栈顶)、eflags(当前标志寄存器)保存到内核堆栈中
load cs:eip(entry of a specific ISR)and //将当前中断信号相关联的中断服务入口加载到cs:eip
ss:esp(point to kernel stack). //同时将当前指向内核信息的的堆栈段和esp也加载到CPU中
第二步
SAVE_ALL
-... //内核代码,完成中断服务,(完成中断服务后可能)发生进程调度
//如果发生了进程调度,则当前的状态都会暂时保存在系统中。当其他进程调度切换回当前进程时,则接着执行RESTORE_ALL
第三步
RESTORE_ALL
第四步
iret -pop cs:eip/ss:esp/eflags from kernel stack
二、系统调用概述
系统调用概述和系统调用的三层皮
1. 系统调用概述
系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口。
- 把用户从底层的硬件编程中解放出来
- 极大的提高了系统的安全性
- 使用户程序具有可移植性(用户程序与具体硬件被抽象的接口替代,没有非常紧密的关系)
2. API和系统调用
- 应用程序接口(API)与系统调用不同
- API只是一个函数定义
- 系统调用通过软件中断trap向内核发出一个明确的请求
- Libc库定义的一些API引用了封装例程(唯一目的是发布系统调用,直接调用函数就可以出发系统调用)
- 一般每个系统调用对应一个封装例程
- 库再用这些封装例程定义出给用户的API
- 不是每个API都对应一个特定的系统调用
- API可能直接提供用户态的服务,如一些数学函数
- 一个单独的API可能调用几个系统调用
- 不同的API可能调用了同一个系统调用
- 返回值
- 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
- -1在多数情况下表示内核不能满足进程的请求
- Libc中定义的errno变量包含特定的出错码

用户态<->内核态
3. 系统调用的三层皮
系统调用的三层皮:xyz(API)、system_ call(中断向量)、sys_xyz(中断向量对应的中断服务程序)
4. 系统调用程序及服务例程
- 当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数
- 在Linux中是通过执行int $0x80来执行系统调用的, 这条汇编指令产生向量为128的编程异常
- Intel Pentium II中引入了sysenter指令(快速系统调用),2.6已经支持
- (系统调用号将xyz和sys_xyz关联起来)
- 传参:内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数
- 使用eax寄存器
5. 参数传递
- 系统调用也需要输入输出参数,例如
- 实际的值
- 用户态进程地址空间的变量的地址
- 甚至是包含指向用户态函数的指针的数据结构的地址
- system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号
- 一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把eax寄存器的值置为2(即_NRfork)。
- 这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号
- 进入sys_call之后,立即将eax的值压入内核堆栈
- 寄存器传递参数具有如下限制:
- 每个参数的长度不能超过寄存器的长度,即32位
- 在系统调用号(eax)之外,参数的个数不能超过6个(ebx,ecx,edx,esi,edi,ebp)
- 超过6个则将某一个寄存器作为一个指针指向一块内存,进入内存态后可以访问所有地址空间,可以通过那块内存传递数据
三、使用库函数API和C代码中嵌入汇编代码触发同一个系统调用
3.1 使用库函数API获取系统当前时间
C代码time.c :
#include <stdio.h>
#include <time.h>
int main()
{
time_t tt;//int型数值
struct tm *t; //便于输出值可读
tt = time(NULL);
t = localtime(&tt);//将tt转换成之前声明的t类型,便于可读
printf("time:%d:%d:%d:%d:%d:%d:\n",t->tm_year+1960,t->tm_mon,t->tm_mda,t->tm_hour,t->tm_min,t->tm_sec);
return 0;
}
- 编译: gcc time.c -o time -m32
- 运行: ./time
- 结果:输出系统时间的年:月:日:时:分:秒
3.2 C代码中嵌入汇编代码的写法
详细语法见http://www.cnblogs.com/20135228guoyao/p/5243214.html
3.3 使用C代码中嵌入汇编代码触发系统调用获取系统当前时间
嵌入汇编代码time_asm.c :
#include <stdio.h>
#include <time.h>
int main()
{
time_t tt;//int型数值
struct tm *t;
asm volatile(
"mov $0,%%ebx\n\t"//系统调用传递第一个参数使用ebx,这里是null
"mov $0xd,%%eax\n\t"//使用%eax传递系统调用号13,用16进制表示为0xd
"int $0x80\n\t" //执行系统调用
"mov %%eax,%0\n\t"//通过eax这个寄存器返回系统调用值,和普通函数一样
:"=m"(tt)
);
t = localtime(&tt);
printf("time:%d:%d:%d:%d:%d:%d:\n",t->tm_year+1960,t->tm_mon,t->tm_mda,t->tm_hour,t->tm_min,t->tm_sec);
return 0;
}
- 编译: gcc time-asm.c -o time-asm -m32
- 运行: ./time-asm
- 结果:输出系统时间的年:月:日:时:分:秒(执行结果与C代码一样)
四、实验
使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用。本次实验中我使用第20号系统调用getpid()函数,用于取得进程识别码。
C代码(getpid.c):
#include <unistd.h>
#include <stdio.h> int main()
{
pid_t pid;
pid = getpid();
printf("pid = %d \n",pid);
return 0;
}
- 编译及运行结果见下图:

嵌入汇编代码getpid_asm.c:
#include <unistd.h>
#include <stdio.h> int main()
{
pid_t pid;
pid = getpid();
asm volatile(
"mov $0,%%ebx\n\t" //系统调用传递第一个参数使用ebx,因为这里没有传入参数所以是null
"mov $0x14,%%eax\n\t" //使用%eax传递系统调用号20,用16进制表示为0x14
"int $0x80\n\t" //执行系统调用
"mov %%eax,%0\n\t" //将%0(即pid的返回值)放到%eax寄存器中
: "=m" (pid)
);
printf("pid = %d \n",pid);
return 0;
}
- 编译及运行结果见下图:

五、总结
系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口,也是一种特殊的中断,可使用户态切换到内核态。当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。同时我通过实践掌握了用户态进程切换到内核态的具体过程:使用ebx传递系统调用第一个参数、使用eax传递系统调用号、int $0x80指令执行系统调用、最后将返回值存入eax。
《Linux内核分析》第四周学习笔记的更多相关文章
- linux内核分析第四周学习笔记
linux内核分析第四周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...
- 20135320赵瀚青LINUX内核分析第四周学习笔记
赵瀚青原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 概述 本周的内容主要是讲解系 ...
- Linux内核分析——第四周学习笔记20135308
第四周 扒开系统调用的“三层皮” 一.内核.用户态和中断 (一)如何区分用户态.内核态 1.一般现在的CPU有几种不同的指令执行级别 ①在高级别的状态下,代码可以执行特权指令,访问任意的物理地址,这种 ...
- Linux内核分析——第四周学习笔记
扒开系统调用的三层皮[上] 前言:以下笔记除了一些讲解视频中的概念记录,图示.图示中的补充文字.总结.分析.小结部分均是个人理解.如有错误观点,请多指教! 补充:[系统调用的参数传递方法]视频中讲解简 ...
- LINUX内核分析第四周学习总结——扒开系统调用的“三层皮”
LINUX内核分析第四周学习总结--扒开系统调用的"三层皮" 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>MOOC ...
- Linux内核分析第二周学习笔记
linux内核分析第二周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...
- linux内核分析第一周学习笔记
linux内核分析第一周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...
- Linux内核分析第四周学习总结——系统调用的工作机制
Linux内核分析第四周学习总结--系统调用的工作机制 内核态 执行级别高,可以执行特权指令,访问任意物理地址,在intel X86 CPU的权限分级为0级. 用户态 执行级别低,只能访问0x0000 ...
- LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上)【转】
转自:http://www.cnblogs.com/lalacindy/p/5276874.html 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://moo ...
- Linux内核分析第四周学习总结
朱国庆+原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 扒开系统调用的三层皮 ...
随机推荐
- 团队作业——Alpha冲刺 3/12
团队作业--Alpha冲刺 冲刺任务安排 杨光海天 今日任务:完成Android开发环境的搭建,学习基础开发知识 明日任务:继续学习Android开发知识,与其他成员协商,了解自己需要完成的开发任务, ...
- 团队作业——Alpha冲刺 12/12
团队作业--Alpha冲刺 冲刺任务安排 杨光海天 今日任务:自定义保存界面布局以及交互接口函数的实现 明日任务:总结项目中的问题,为什么没能按照预期推进项目 郭剑南 今日任务:继续解决Python编 ...
- facebook api & oauth protocal
http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-10.5 http://stackoverflow.com/questions/14 ...
- 邮局加强版:四边形不等式优化DP
题目描述 一些村庄建在一条笔直的高速公路边上,我们用一条坐标轴来描述这条公路,每个村庄的坐标都是整数,没有两个村庄的坐标相同.两个村庄的距离定义为坐标之差的绝对值.我们需要在某些村庄建立邮局.使每个村 ...
- 关于Maven配置的一些标签含义(后续逐渐补充)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...
- Python2.7-stat
stat模块,用于解释 os.stat(),os.lstat(),os.fstat() 返回的结果,定义了许多表示文件或路径的各个状态的常数和测试各个状态的函数具体参考 官方文档 和 http://w ...
- Underscore.js 入门-常用方法介绍
Underscore.js是一个很精干的库,压缩后只有4KB.它提供了几十种函数式编程的方法,弥补了标准库的不足,大大方便了JavaScript的编程.MVC框架Backbone.js就将这个库作为自 ...
- Codeforces round 1103
Div1 534 我可能还太菜了.jpg 果然我只是Div 2 选手 A (这题是Div1吗... 直接构造:竖着放的在第一行和第二行,然后横着放的时候直接放在第三行就行. #include < ...
- 学会查看Linux手册页(man文档)
区段1:用户指令区段2:系统调用区段3:程序库调用区段4:设备区段5:文件格式区段6:游戏区段7:杂项区段8:系统指令区段9:内核内部指令区段n:Tcl或Tk指令 如果记不清楚工具或者函数的完整名字, ...
- W25Q128---读写
占坑! 总结:通信方式是SPI,读数据可以从任何地方读,写数据和擦出数据需要按照页或者扇区或者簇为单位进行. 写数据:一次最多写一页,如果超出一页数据长度,则分几次完成.例如本芯片一个扇区为4096个 ...