Linux内核分析之扒开系统调用的三层皮(上)
一、原理总结
本周老师讲的内容主要包括三个方面,用户态、内核态和中断,系统调用概述,以及使用库函数API获取系统当前时间。系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口,也是一种特殊的中断,可使用户态切换到内核态。当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。
1.用户态、内核态和中断
内核态:一般现代CPU有几种指令执行级别。在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别对应着内核态
用户态:在相应的低级别执行状态下,代码的掌控范围有限,只能在对应级别允许的范围内活动。如intel x86 CPU有四种不同的执行级别0-3,Linux只使用0级表示内核态,3级表示用户态。权限级别的划分使系统更稳定
区分用户态与内核态主要通过代码段选择寄存器cs和偏移量寄存器eip,cs寄存器的最低两位表明了当前代码特权级,CPU每条指令的读取都是通过cs:eip这两个寄存器一般在Linux中,(逻辑)地址空间是显著标志:0xc0000000以上的地址空间只能在内核态下访问,0x00000000-0xbfffffff的地址空间在两种状态下都能访问
中断处理是从用户态进入内核态的主要方式。系统调用只是一种特殊的中断。从用户态切换到内核态时:必须保存用户态的寄存器上下文,同时将内核态的寄存器相应的值放入当前CPU。中断/int指令会在堆栈上保存一些寄存器的值:如用户态栈顶地址、当前的状态字、当时cs:eip的值(当前中断程序的入口)
保护现场:进入中断程序,保存需要用到的寄存器的数据(中断发生后的第一件事)
#define SAVE_ALL //将其他寄存器的值push到内核堆栈中
恢复现场:退出中断程序,恢复保存寄存器的数据(中断处理结束前最后一件事)
#RESTORE_ALL //将用户态保存的寄存器pop到当前CPU中
iret指令与中断信号(包括int指令)发生时的CPU的动作相反
2.系统调用概述
系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口。把用户从底层的硬件编程中解放出来,极大的提高了系统的安全性,使用户程序具有可移植性。应用程序接口(API)与系统调用不同,API只是一个函数定义,系统调用通过软件中断trap向内核发出一个明确的请求,Libc库定义的一些API引用了封装例程,库再用这些封装例程定义出给用户的API。不是每个API都对应一个特定的系统调用,一个单独的API可能调用几个系统调用,不同的API可能调用了同一个系统调用
系统调用的三层皮:xyz(API)、system_ call(中断向量)、sys_xyz(中断向量对应的中断服务程序)。当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数,在Linux中是通过执行int $0x80来执行系统调用的, 这条汇编指令产生向量为128的编程异常。
3.使用库函数API和C代码中嵌入汇编代码触发同一个系统调用
使用库函数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+,t->tm_mon,t->tm_mda,t->tm_hour,t->tm_min,t->tm_sec);
return ;
}
编译: gcc time.c -o time -m32
运行: ./time
结果:输出系统时间的年:月:日:时:分:秒
使用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+,t->tm_mon,t->tm_mda,t->tm_hour,t->tm_min,t->tm_sec);
return ;
}
编译: gcc time-asm.c -o time-asm -m32
运行: ./time-asm
结果:输出系统时间的年:月:日:时:分:秒
该嵌入汇编代码的执行结果和C代码一样。
二、实验内容
使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用,在这里选出系统调用号为64的系统调用sys_getppid(),该系统调用用于返回当前进程的父进程进程号数。
下面是直接使用库函数API使用系统调用。getppid.c代码中,函数getppid()是glibc对系统调用sys_getppid的封装,用于获取当前进程的父进程的进程号。sys_getppid系统调用号为64,在用户态时候,如果用户调用了getppid(),系统会产生一中断,进入到了内核态执行sys_getppid。getppid()的功能是返回当前进程的父进程的ID,它本身是不能完成的,必须请求操作系统服务即sys_getppid,让操作系统把当前进程的ID告诉给getppid()。
#include <stdio.h>
#include<unistd.h> int main()
{
pid_t pid;
pid=getppid();
printf("The number of parent process is: %d\n",pid);
return ;
}
下面再使用C语言内嵌汇编代码的方式实现同一个系统调用。getpid_asm.c代码
#include <stdio.h>
#include<unistd.h> int main()
{
pid_t pid;
asm volatile(
"mov $0,%%ebx\n\t" /*ebx用来传递参数,getppid(void)的参数是void所以设置为零*/
"mov $0x40,%%eax\n\t" /*eax用来传递系统调用号,getppid的系统调用号是64,所以是0x40*/
"int $0x80\n\t" /* 软中断汇编指令,系统进入内核态 */
"mov %%eax,%0\n\t" /*eax保留返回值,把返回值放到输出参数中,即pid变量中*/
:"=m" (pid) /*输出参数是pid*/
);
printf("The number of parent process is: %d\n",pid);
return ;
}
由实验截图可知,两种实现方式得到的父进程进程号数一致

下面分析嵌入式汇编代码及参数传递:
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这个寄存器返回系统调用值,和普通函数一样
三、总结
本实验的关键是系统调用。在Linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用。进程不会提及系统调用的名称。 系统调用号相当关键,一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。Linux有一个“未实现”系统调用sys_ni_syscall(),它除了返回一ENOSYS外不做任何其他工作,这个错误号就是专门针对无效的系统调用而设的。因为所有的系统调用陷入内核的方式都一样,所以仅仅是陷入内核空间是不够的。因此必须把系统调用号一并传给内核。在x86上,系统调用号是通过eax寄存器传递给内核的。在陷人内核之前,用户空间就把相应系统调用所对应的号放入eax中了。这样系统调用处理程序一旦运行,就可以从eax中得到数据。其他体系结构上的实现也都类似。
除了系统调用号以外,大部分系统调用都还需要一些外部的参数输人。所以,在发生异常的时候,应该把这些参数从用户空间传给内核。最简单的办法就是像传递系统调用号一样把这些参数也存放在寄存器里。
刘帅
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
Linux内核分析之扒开系统调用的三层皮(上)的更多相关文章
- 《Linux内核分析》--扒开系统调用的三层皮 20135311傅冬菁
扒开系统调用的三层皮 20135311傅冬菁 一.内容分析 寄存器上下文(从用户态切换到内核态) 中断/int指令会在堆栈上保存一些寄存器的值(用户态栈顶地址..当时的状态字.当下 ...
- linux 内核 第四周 扒开系统调用的三层皮 上
姬梦馨 原创作品 http://mooc.study.163.com/course/USTC-1000029000 一.用户态.内核态和中断处理过程 用户通过库函数与系统调用联系起来:库函数帮我们把系 ...
- Linux内核分析之扒开系统调用的三层皮(下)
一.实验内容 1. 通过内核的方式使用系统调用 需要使用的命令 rm menu -rf //强制删除当前menugit clone http://github.com/mengning/menu.gi ...
- 《Linux内核分析》-- 扒开系统调用的三层皮(下)之system_call中断处理过程 20135311傅冬菁
20135311傅冬菁 原创作品 <Linux内核分析>MOOC课程 分析system_call中断处理过程 内容分析与总结: 系统调用在内核代码中的工作机制和初始化 系统调用在用户态中 ...
- 《Linux内核分析》第四周 扒开系统调用的“三层皮”
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FOUR( ...
- 《Linux内核分析》第五周 扒开系统调用的三层皮(下)
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FIVE( ...
- LINUX内核分析第五周学习总结——扒开系统调用的“三层皮”(下)
LINUX内核分析第五周学习总结--扒开系统调用的"三层皮"(下) 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>M ...
- 《Linux内核分析》第五周学习总结 扒开系统调用的三层皮(下)
扒开系统调用的三层皮(下) 郝智宇 无转载 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.给Men ...
- 《Linux内核分析》 第四节 扒开系统调用的三层皮(上)
<Linux内核分析> 第四节 扒开系统调用的三层皮(上) 张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com ...
随机推荐
- Ibatis 测试出SQL
String sql = Brg.Global.Map.BaseBatis.GetRuntimeSql("select_T_JewelleryProductType", _Mode ...
- SQLite 命令
在shell下直接敲 sqlite3 进入sqlite命令行模式下(CLP的shell模式,CLP是sqlite3的命令行程序) sqlite3 -help (注意有空格)显示命令行模式下,sqli ...
- VMware 中windows server 之DHCP 搭建与测试
感悟: 由于打算将windows server 的服务器搭建维护从头重新学习总结一下,遇到搭建dhcp服务的时候,在虚拟机中一直测试不成功,耽误我好几星期了,一点也不夸张,心情和积极性也大大受到打击. ...
- imp导入oracle的dmp备份数据
imp system/oracle fromuser=lc0029999 touser=lc0029999 rows=y commit=y buffer=65536 feedback=10000 ig ...
- php关闭错误提示
今天调试phalcon的一个接口时候碰到如下提示: Deprecated: mongogo::mongogo(): The Mongo class is deprecated, please use ...
- 不使用容器构建Registry
安装必要的软件 $ sudo apt-get install build-essential python-dev libevent-dev python-pip liblzma-dev 配置 doc ...
- JavaScript valueOf() 函数详解
valueOf()函数用于返回指定对象的原始值. 该方法属于Object对象,由于所有的对象都"继承"了Object的对象实例,因此几乎所有的实例对象都可以使用该方法. 所有主流浏 ...
- MS sql server 基础知识回顾(二)-表连接和子查询
五.表连接 当数据表中存在许多重复的冗余信息时,就要考虑将这些信息建在另一张新表中,在新表中为原表设置好外键,在进行数据查询的时候,就要使用到连接了,表连接就好像两根线,线的两端分别连接两张表的不同字 ...
- Python 单例
方法1: 1 class Singleton(object): def __new__(cls, *args, **kwargs): if '_inst' not in vars(cls): cls. ...
- meta标签总结
1."format-detection" format-detection翻译成中文的意思是“格式检测”,顾名思义,它是用来检测html里的一些格式的, 那关于meta的forma ...