LINUX内核分析第四周学习总结——扒开系统调用的“三层皮”
LINUX内核分析第四周学习总结——扒开系统调用的“三层皮”
标签(空格分隔): 20135321余佳源
余佳源 原创作品转载请注明出处 《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC 1000029000
扒开系统调用的“三层皮”
一、内核、用户态和中断处理
(一)如何区分用户态、内核态
1. 一般现在的CPU有几种不同的指令执行级别
在高级别的状态下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态,可以执行所有指令。
在相应的低级别执行状态下,代码的掌控范围会受到限制,只能在对应级别允许的范围内活动。
为什么会有权限级别的划分?
答:当所有程序员写的代码都有特权指令时,系统很容易崩溃,没有访问权限划分容易使得系统混乱。
Intel x86 CPU有四种不同的执行级别0—3,Linux只使用了其中的0级和3级来表示内核态和用户态。
2.如何区分用户态、内核态
CPU每条指令的读取都是通过cs:eip(代码段选择寄存器:偏移量寄存器)这两个寄存器,由硬件完成判断。
内核态时,cs与eip的值可以访问任意地址
用户态时,cs与eip只可以访问0x00000000—0xbfffffff的地址空间
P.S.这里的地址空间指逻辑地址而非物理地址
(二)中断处理
中断处理是用户态进入内核态主要的方式,系统调用只是一种特殊的中断
- 硬件中断,中断服务进程
- 用户态执行系统调用,进入内核态
FOR EXAMPLE
interrupt(ex:int 0X80)//系统调用
save cs:eip/ss:esp/eflags(current)to kernel stack,then load cs:eip(entry of a specific ISR)and ss:eip(point to kenerl stack)
//保存当前堆栈段寄存器和当前栈顶和标志位寄存器到内核堆栈中,然后加载当前系统调用相关中断服务例程入口到cs:eip中,把当前的堆栈段和栈顶加载到CPU
SAVE_ALL//进入内核态
...//内核代码,完成中断服务,发生进程调度
RESTORE_ALL//恢复现场,只有在进程调度执行完后才会被执行
iret - pop cs:eip/ss:esp/eflags from kernel stack
中断发生之后第一件事就是保存现场;同样,中断处理结束前的最后一件事情就是恢复现场。也就是说,SAVE ALL之后就是内核态了;restore all之后再返回用户态。
iret指令与中断信号(包括int指令)发生时的CPU所做的动作恰好相反。
注意:从用户态切换到内核态时
必须保存用户态的寄存器上下文
中断指令会把内核态相应的寄存器值放在当前CPU中
中断/int指令会在堆栈上保存一些寄存器的值(eg:用户态栈顶地址(ss:esp),标志寄存器(eflags),cs:eip(为了返回的时候popl弹出保存的返回地址)。同时,将相关联的中端服务历程的入口加载到cs:eip,把当前的堆栈段esp也加载到CPU里面)
系统调用需要int触发,int 80要模拟中断,由硬件来处理,80号中断即为系统调用

中断发生后第一件事就是保存现场,进入中断处理程序,保存需要用到的push到寄存器的值。
中断处理结束前最后一件事是恢复现场,就是退出中断程序,恢复用户态的保存寄存器的数据
iret对应着中断信号恢复指令。
二、系统调用概述和系统调用的三层皮
系统调用
- 是内核提供的最基本、最重要的服务设施
- 所有内核服务都通过系统调用的形式提供
(一)系统调用的意义
操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用。
把用户从底层的硬件编程中解放出来
极大的提高了系统的安全
使用户程序具有可移植性
(系统调用减少了系统与硬件之间的耦合,所以极大提高了系统可移植性)
(二)操作系统提供的API和系统调用的关系
- 应用编程接口(API)和系统调用是不同的,使用API是为了让用户从底层硬件编程中解放出来。
- API只是一个被封装好的函数定义
- 系统调用通过软中断向内核发出一个明确的请求
- Libc库定义的一些API引用了封装例程(wrapper routine,唯一目的就是发布系统调用)使程序员在写代码时不用以汇编指令触发系统调用而是直接调用函数。
- 一般每个系统调用对应一个封装例程
- 库再用这些封装例程定义出给用户的API
- 不是每个API都对应一个特定的系统调用
- API可能直接提供用户态的服务,例如一些数学函数没有用到系统调用
- API与系统调用不是单一的一对一的关系
- 返回值
- 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
- 返回值-1在多数情况下表示内核不能满足进程的请求
- Libc中定义的errno变量包含特定的出错码
(三)系统调用的三层皮

一层皮:API
二层皮:中断向量对应的中断服务程序
三层皮:系统调用对应的很多不同种类的服务程序
详细过程:
用户态进程中,xyz()函数是系统调用对应的API,该编程接口里封装了一个系统调用,会触发一个int 0x80的中断,产生向量为128的编程异常。该中断对应着内核态的内核代码入口起点system_call,执行SAVE_ALL,执行到中断服务程序sys_xyz()时,进入程序处理,该中断服务程序执行完后,会ret_from_sys_call,在ret中可能会发生进程调度,如果没发生就会iret,返回用户态,继续执行。
(四)系统调用的参数传递方法
内核实现了很多不同的系统调用, 进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数
system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号。具体过程如下:
1.一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把eax寄存器的值置为2(即__NR_fork)。
2.这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号
3.进入sys_call之后,立即将eax的值压入内核堆栈

超过6个,就将某一个寄存器作为指针,指向内存,进入内核态后可以访问所有地址空间,即通过内存传递数据
三、使用库函数API和C代码中嵌入汇编代码触发系统调用
(一)使用库函数API获取当前系统时间


编译:
gcc time.c -o time -m32
之后,再输入
./time
结果:
打印出的就是系统时间下的 年:月:日:时:分:秒
PS:year由于个人喜好 +1960 ,所以现在是2076,正常应该是 +1900,月份应该是3月,即 mon+1
(二)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"//传递系统调用号13(13的16进制即为d)
"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;
}

系统调用返回值使用eax存储,与普通函数一样。
编译:
gcc time-asm.c -o time-asm -m32 运行: . /time-asm 结果与上一个代码一致
本段代码让我们更清楚地知道用户态对内核态做了什么
四、使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
我的选择是!!!
第20号系统调用,getpid
1.使用库函数API:
/* getpid.c */
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
pid = getpid();
printf("pid = %d \n",pid);
return 0;
}
getpid函数用来取得目前进程的进程ID,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题。
运行结果如下:

2.嵌入汇编:
/* asm_getpid.c */
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
pid = getpid();
asm volatile(
"mov $0x14,%%eax\n\t" /* 将系统调用号20放入eax中。 */
"int $0x80\n\t" /* 中断向量号0x80,即128。int 128 执行系统调用。 */
"mov %%eax,%0\n\t" /* 返回值保存在eax中,将它赋值给pid */
: "=m" (pid)
);
printf("pid = %d \n",pid);
return 0;
}
运行结果:

操作原理:getpid系统调用是第20号,所以首先要将这个系统调用号放入eax寄存器中,然后使用int 128指令执行系统调用,这时就会执行eax中的第20号系统调用。返回值是保存在eax寄存器中,所以把它赋值给0号也就是pid。
五、小结
- 即便是最简单的程序,在进行输入输出等操作时也会需要调用操作系统所提供的服务,也就是系统调用。
- Linux下的系统调用是通过中断(int 0x80)来实现的。
- 在start_kernel中的trap_init将(系统调用的)中断向量和汇编代码的入口(system_call)绑定,一旦执行int指令(如int0x80),cpu就会自动跳转到绑定的汇编代码入口处执行中断服务程序,此时cpu进入内核态,在服务处理结束返回用户态之前,可能会发生进程调度,调度完成后,cpu才会返回用户态。
- Linux 采用的是 C 语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,而第一个参数则最后入栈。
LINUX内核分析第四周学习总结——扒开系统调用的“三层皮”的更多相关文章
- 《Linux内核分析》 第四节 扒开系统调用的三层皮(上)
<Linux内核分析> 第四节 扒开系统调用的三层皮(上) 张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com ...
- 《Linux内核分析》 第五节 扒开系统调用的三层皮(下)
<Linux内核分析> 第五节 扒开系统调用的三层皮(下) 20135307 一.给MenusOS增加time和time-asm命令 给MenuOS增加time和time-asm命令需要 ...
- 《Linux内核分析》第五周 扒开系统调用的三层皮(下)
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FIVE( ...
- #Linux第四周学习总结——扒开系统调用的三层皮(上)
Linux第四周学习总结--扒开系统调用的三层皮(上) 一.用户态.内核态和中断 系统调用通过库函数. 1.用户态和内核态 区分(不同的指令执行级别): 用户态:在相应的低执行状态下,代码的掌控范围受 ...
- LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上)【转】
转自:http://www.cnblogs.com/lalacindy/p/5276874.html 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://moo ...
- 20135337朱荟潼 Linux第四周学习总结——扒开系统调用的三层皮(上)
朱荟潼 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课http://mooc.study.163.com/course/USTC 1000029000 知识点梳理 一.用 ...
- LINUX内核分析第五周学习总结——扒开系统调用的“三层皮”(下)
LINUX内核分析第五周学习总结--扒开系统调用的"三层皮"(下) 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>M ...
- linux内核分析第四周学习笔记
linux内核分析第四周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...
- Linux内核分析第四周学习总结——系统调用的工作机制
Linux内核分析第四周学习总结--系统调用的工作机制 内核态 执行级别高,可以执行特权指令,访问任意物理地址,在intel X86 CPU的权限分级为0级. 用户态 执行级别低,只能访问0x0000 ...
随机推荐
- 2-4 R语言基础 列表
#列表list > l1 <- list("a",2,10L,3+4i,TRUE) #每个元素没有名字> l1[[1]][1] "a" [[2 ...
- linux 的常用命令---------第九阶段
Centos 7 系统启动及相关配置文件(面试题) 1. BIOS 初始化,开始post开机自检(主要检查磁盘.cpu.内存) 2. 加载 MBR 到内存 3. GRUB 阶段(可不说) 4. 加载内 ...
- 有crontab中的脚本不执行,需要在脚本里面export各种环境变量
[oracle@sta ~]$ vi .bash_profile # .bash_profile # Get the aliases and functionsif [ -f ~/.bashrc ]; ...
- Qt Creator无法debug,报错:The selected debugger may be inappropriate for the inferior. Examining symbols and setting breakpoints by file name and line number may fail. The inferior is in the Portable ...
看到这个报错我是绝望的 解决:下载windows sdk win10 sdk 只安装Debugging Tools for Windows 打开 工具-选项-Kits 安装sdk成功后我们可以看到 ...
- jmeter接口测试4-使用数据库mysql构造参数
jmeter测试中,测试数据一般和测试用例分离 测试数据一般可以使用csv构造,进行参数化 但也可以使用mysql等数据库构造 方案一:一个线程循环调用mysql数据,不是并发,不适用于性能测试,更适 ...
- Underscore.js 入门-常用方法介绍
Underscore.js是一个很精干的库,压缩后只有4KB.它提供了几十种函数式编程的方法,弥补了标准库的不足,大大方便了JavaScript的编程.MVC框架Backbone.js就将这个库作为自 ...
- BT5R3蛋疼的metasploit升级
刚装了BT5R3,急着想把metasploit升级,原版本是4.5.0,试了网上的各种方法,终于试到了个能成功的,再次记录一下. 系统环境:BT5 R3 1.apt-get update 2.apt- ...
- Java 中long类型转换成为int类型时可能会出错的地方
那计算两个日期之间间隔的天数为例来说明这个问题. 下面是计算日期间隔天数的简单算法(主要出错的地方为红色标注的地方): public int getDay(String startDate, Stri ...
- memset()初始化为1的那些事
问题代码: #include <stdio.h> #include <string.h> int main() { ]; int a; while(~scanf("% ...
- P2962 [USACO09NOV]灯Lights
贝希和她的闺密们在她们的牛棚中玩游戏.但是天不从人愿,突然,牛棚的电源跳闸了,所有的灯都被关闭了.贝希是一个很胆小的女生,在伸手不见拇指的无尽的黑暗中,她感到惊恐,痛苦与绝望.她希望您能够帮帮她,把所 ...