20135327郭皓--Linux内核分析第四周 扒开系统调用的三层皮(上)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20135327郭皓--Linux内核分析第四周 扒开系统调用的三层皮(上)相关的知识,希望对你有一定的参考价值。
Linux内核分析第四周 扒开系统调用的三层皮(上)
郭皓 原创作品转载请注明出处 《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000
一、用户态、内核态和中断
用户态:当进程在执行用户自己的代码时,则称其处于用户态,即此时处理器在特权级最低的(3级)用户代码中运行。
内核态:当一个进程执行系统调用而陷入内核代码中执行时,我们就称进程处于内核态,此时处理器处于特权级最高的(0级)内核代码中执行。
PS:CPU指令执行级别
中断:
用户态和内核态的转换:
中断处理的完整过程
进入中断程序,保存寄存器数据 SAVE_ALL -..//内核代码,完成中断服务,发生进程调度 RESTORE_ALL//退出中断程序,恢复寄存器数据 iret(pop cs:eip/ss:esp/eflags from kernel stack)//对应着中断信号或int指令,与发生时CPU动作相反
二、系统调用概述
系统调用:
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。
系统调用的意义:
- 把用户从底层的硬件编程中解放出来
- 极大的提高了系统的安全性
- 使用户程序具有可移植性
API和系统调用:
应用编程接口(application program interface,API)和系统调用是不同的
- API只是一个函数定义
- 系统调用通过软中断向内核发出一个明确求
Libc库定义的一些API引用了封装例程(wrapper routine,唯一目的就是发布系调用)
- 一般每个系统调用对应一个封装例程
- 库再用这些封装例程定义出给用户的API
不是每个API都对应一个特定的系统调用。
- API可能直接提供用户态的服务
- 如,一些数学函数
- 一个单独的API可能调用几个系统调用
- 不同的API可能调用了同一个系统调用
返回值:
- 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
- -1在多数情况下表示内核不能满足进程的请求
- Libc中定义的errno变量包含特定的出错码
应用程序、封装例程、系统调用处理程序及系统调用服务例程之间的关系
xyz():API 其中封装了一个系统调用Intel 0x80触发一个中断
中断向量0x80对应内核代码系统调用起点system_call
中断服务程序(sys_xyz)
系统调用三层皮:
- API(xyz)
- 中断向量(system_call)
- 中断服务程序(sys_xyz)
当用户态进程调用一个系统调用时, CPU切换到内核态并开始执行一个内核函数。
- 在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常
- Intel Pentium II中引入了sysenter指令(快速系统调用), 2.6已经支持(本课程不考虑这个)
传参:
内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数
- 使用eax寄存器
系统调用也需要输入输出参数,例如
- 实际的值
- 用户态进程地址空间的变量的地址
- 甚至是包含指向用户态函数的指针的数据结构的地址
system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号
- 一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把eax寄存器的值置为2(即__NR_fork)。
- 这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号
- 进入sys_call之后,立即将eax的值压入内核堆栈
• 寄存器传递参数具有如下限制:
• 1)每个参数的长度不能超过寄存器的长度,即32位
• 2)在系统调用号( eax)之外,参数的个数不能超过6个( ebx,ecx, edx, esi, edi, ebp)
三、实验:使用库函数API和c代码中嵌入汇编代码触发同一系统调用
1.使用库函数API获取系统当前进程pid值
在这里我使用的是获取进程pid的库函数getpid()
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 int main(int argc, const char *argv[]) 6 { 7 pid_t tt; 8 tt = getpid(); 9 printf("%u\n", tt); 10 return 0; 11 }
2.使用c代码中嵌入汇编代码
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 int main(int argc, const char *argv[]) 6 { 7 pid_t tt; 8 asm volatile( 9 "mov $0x14, %%eax\n\t"//系统调用号14放在eax中 10 "int $0x80\n\t" //系统调用中断 11 "mov %%eax, %0\n\t"//取出eax中返回的值 12 :"=m" (tt) 13 ); 14 printf("%u\n", tt); 15 return 0; 16 }
总结:
通过本周的学习,更加熟悉了系统调用的本质,以及系统调用和中断的关联。系统调用是用户态和内核态的桥梁,而具体的措施就是中断。
以上是关于20135327郭皓--Linux内核分析第四周 扒开系统调用的三层皮(上)的主要内容,如果未能解决你的问题,请参考以下文章
20135327郭皓--Linux内核分析第六周 进程的描述和进程的创建