一、原理总结

  本周老师讲的内容主要包括三个方面,用户态、内核态和中断,系统调用概述,以及使用库函数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内核分析之扒开系统调用的三层皮(上)的更多相关文章

  1. 《Linux内核分析》--扒开系统调用的三层皮 20135311傅冬菁

    扒开系统调用的三层皮           20135311傅冬菁 一.内容分析 寄存器上下文(从用户态切换到内核态) 中断/int指令会在堆栈上保存一些寄存器的值(用户态栈顶地址..当时的状态字.当下 ...

  2. linux 内核 第四周 扒开系统调用的三层皮 上

    姬梦馨 原创作品 http://mooc.study.163.com/course/USTC-1000029000 一.用户态.内核态和中断处理过程 用户通过库函数与系统调用联系起来:库函数帮我们把系 ...

  3. Linux内核分析之扒开系统调用的三层皮(下)

    一.实验内容 1. 通过内核的方式使用系统调用 需要使用的命令 rm menu -rf //强制删除当前menugit clone http://github.com/mengning/menu.gi ...

  4. 《Linux内核分析》-- 扒开系统调用的三层皮(下)之system_call中断处理过程 20135311傅冬菁

    20135311傅冬菁  原创作品 <Linux内核分析>MOOC课程 分析system_call中断处理过程 内容分析与总结: 系统调用在内核代码中的工作机制和初始化 系统调用在用户态中 ...

  5. 《Linux内核分析》第四周 扒开系统调用的“三层皮”

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FOUR( ...

  6. 《Linux内核分析》第五周 扒开系统调用的三层皮(下)

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FIVE( ...

  7. LINUX内核分析第五周学习总结——扒开系统调用的“三层皮”(下)

    LINUX内核分析第五周学习总结--扒开系统调用的"三层皮"(下) 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>M ...

  8. 《Linux内核分析》第五周学习总结 扒开系统调用的三层皮(下)

    扒开系统调用的三层皮(下) 郝智宇 无转载 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.给Men ...

  9. 《Linux内核分析》 第四节 扒开系统调用的三层皮(上)

    <Linux内核分析> 第四节 扒开系统调用的三层皮(上) 张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com ...

随机推荐

  1. math汇总

    **** 1/(1^2) + 1/(2^2) + … + 1/(n^2)会收敛到pi^2/6,当n的数位大于6位数字时,最后的结果就是pi^2/6 ****** &的大作用 1.先看看这个n= ...

  2. outlook找不到文件Outlook.pst 如何启动

    首先注明:这种情况在控制面板-邮件  无法打开的情况下可以使用以下命令打开设置界面 解决方法: 1. 开始 –> 运行 –> cmd 2. 在DOS下,用CD 切换到 Outlook.ex ...

  3. [转载]:经纬度与WGS84坐标转换

    本代码实现在WGS84系统的大地坐标(BLH)和空间直角坐标(XYZ)的互相转换,符合标准语法,可直接使用 如下代码,输出为: WGS84:  -2175790.73969891    4461032 ...

  4. 【转】linux下skype的安装使用

    http://hi.baidu.com/24_jason/item/f85725306c7dbcf5df2221ca Fedora 18/17, CentOS/RHEL/SL 6.3 安装 Skype ...

  5. Scrum 项目——1

    广商检索页面 1) N (Need 需求) 这个页面会按一定的规律来集合广商的一些资源,包括微信公众号.教务系统登录处.宿舍报修等,是为了方便我们整个广商的学生和老师来运用.因为现在虽然有很多微信公众 ...

  6. 易语言软件加VMProtect壳的正确方法

    VMP是一款很强大的加密壳,代码虚拟化技术可以很好的保护程序不被恶意修改破J但是很多人不知道怎么给自己的程序加壳,今天给大家晋级下加壳的正确方法 相信很多新手都以为只要把软件直接拖到VMP里重新编译一 ...

  7. Redis 的Lua Script脚本功能

    从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求值 Redis2.6内置的Lua Script支持,可以在Redis的Server端一次 ...

  8. C/C++操作MySQL数据库——增、删、改、查

    1.数据库链接 int cppDatebase::DatabaseConnect(sBit8 *uName,sBit8 *pWord,sBit8 *dbName) { dbHandle = mysql ...

  9. mysql事件定时

    DELIMITER $$ MONTH STARTS '2013-01-07 11:20:00' ON COMPLETION PRESERVE ENABLE DO BEGIN CALL ps(); EN ...

  10. JSTL和EL的区别

    JSTL(JSP Standard Tag Library,JSP标准标签库)是一个不断完善的开放源代码的JSP标签库,是由apache的jakarta小组来维护的.JSTL只能运行在支持JSP1.2 ...