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.这里的地址空间指逻辑地址而非物理地址


(二)中断处理

中断处理是用户态进入内核态主要的方式,系统调用只是一种特殊的中断

  1. 硬件中断,中断服务进程
  2. 用户态执行系统调用,进入内核态

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对应着中断信号恢复指令。


二、系统调用概述和系统调用的三层皮

系统调用

  • 是内核提供的最基本、最重要的服务设施
  • 所有内核服务都通过系统调用的形式提供

(一)系统调用的意义

操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用。

  1. 把用户从底层的硬件编程中解放出来

  2. 极大的提高了系统的安全

  3. 使用户程序具有可移植性

(系统调用减少了系统与硬件之间的耦合,所以极大提高了系统可移植性)

(二)操作系统提供的API和系统调用的关系

  1. 应用编程接口(API)和系统调用是不同的,使用API是为了让用户从底层硬件编程中解放出来。
  • API只是一个被封装好的函数定义
  • 系统调用通过软中断向内核发出一个明确的请求
  1. Libc库定义的一些API引用了封装例程(wrapper routine,唯一目的就是发布系统调用)使程序员在写代码时不用以汇编指令触发系统调用而是直接调用函数。
  • 一般每个系统调用对应一个封装例程
  • 库再用这些封装例程定义出给用户的API
  1. 不是每个API都对应一个特定的系统调用
  • API可能直接提供用户态的服务,例如一些数学函数没有用到系统调用
  • API与系统调用不是单一的一对一的关系
  1. 返回值
  • 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用
  • 返回值-1在多数情况下表示内核不能满足进程的请求
  • Libc中定义的errno变量包含特定的出错码

(三)系统调用的三层皮

一层皮:API

二层皮:中断向量对应的中断服务程序

三层皮:系统调用对应的很多不同种类的服务程序

详细过程:

用户态进程中,xyz()函数是系统调用对应的API,该编程接口里封装了一个系统调用,会触发一个int 0x80的中断,产生向量为128的编程异常。该中断对应着内核态的内核代码入口起点system_call,执行SAVE_ALL,执行到中断服务程序sys_xyz()时,进入程序处理,该中断服务程序执行完后,会ret_from_sys_call,在ret中可能会发生进程调度,如果没发生就会iret,返回用户态,继续执行。

(四)系统调用的参数传递方法

  1. 内核实现了很多不同的系统调用, 进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数

  2. 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;
}

  1. 系统调用返回值使用eax存储,与普通函数一样。

  2. 编译:

     gcc time-asm.c -o time-asm -m32
    
     运行:
    
     . /time-asm
    
     结果与上一个代码一致
  3. 本段代码让我们更清楚地知道用户态对内核态做了什么

四、使用库函数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内核分析第四周学习总结——扒开系统调用的“三层皮”的更多相关文章

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

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

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

    <Linux内核分析> 第五节 扒开系统调用的三层皮(下) 20135307 一.给MenusOS增加time和time-asm命令 给MenuOS增加time和time-asm命令需要 ...

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

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

  4. #Linux第四周学习总结——扒开系统调用的三层皮(上)

    Linux第四周学习总结--扒开系统调用的三层皮(上) 一.用户态.内核态和中断 系统调用通过库函数. 1.用户态和内核态 区分(不同的指令执行级别): 用户态:在相应的低执行状态下,代码的掌控范围受 ...

  5. LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上)【转】

    转自:http://www.cnblogs.com/lalacindy/p/5276874.html 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://moo ...

  6. 20135337朱荟潼 Linux第四周学习总结——扒开系统调用的三层皮(上)

    朱荟潼 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课http://mooc.study.163.com/course/USTC 1000029000 知识点梳理 一.用 ...

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

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

  8. linux内核分析第四周学习笔记

    linux内核分析第四周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...

  9. Linux内核分析第四周学习总结——系统调用的工作机制

    Linux内核分析第四周学习总结--系统调用的工作机制 内核态 执行级别高,可以执行特权指令,访问任意物理地址,在intel X86 CPU的权限分级为0级. 用户态 执行级别低,只能访问0x0000 ...

随机推荐

  1. python第三十二课——队列

    队列:满足特点 --> 先进先出,类似于我们生活中的买票.安检 [注意] 对于队列而言:python中有为其封装特定的函数,在collections模块中的deque函数就可以获取一个队列对象; ...

  2. BZOJ2079:[POI2010]Guilds(乱搞)

    Description Zy皇帝面临一个严峻的问题,两个互相抵触的贸易团体,YYD工会和FSR工会,他们在同一时间请求在王国各个城市开办自己的办事处.这里有n个城市,其中有一些以双向马路相连,这两个工 ...

  3. jQuery做字符串分隔

    var str=new String(); var arr=new Array(); str="ddd,dsd,3,dd,g,k"; //可以用字符或字符串分割 arr=str.s ...

  4. Velocity.js初步

    Js越来越强大了,超乎我的想象,以前JS仅仅只能通过ajax与后台交互,后来又有了Node.js,JS可以用于服务端,然后今天我又发现了JS的动态语言.明天呢?也许不少前端的小伙伴会说,慢些吧,慢些吧 ...

  5. greys java在线诊断工具

    greys是一个开源的github项目,用来分析运行中的java类.方法等信息. greys工具地址: https://github.com/oldmanpushcart/greys-anatomy/ ...

  6. HDU1166

    https://vjudge.net/contest/66989#problem/A C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直 ...

  7. liMarquee – jQuery无缝滚动插件(制作跑马灯效果)

    liMarquee 是一款基于 jQuery 的无缝滚动插件,类似于 HTML 的 marquee 标签,但比 marquee 更强大.它可以应用于任何 Web 元素,包括文字.图像.表格.表单等元素 ...

  8. $\mathcal{Friends' \ \ Links}$友情链接

    \(\mathcal{JuLao \ \& \ \ Dalao}\) \(\_rqy\) \(\_stdcall\) 并(吊)肩(锤)奋(死)斗(我)的\(Oier\) 王旭 苑骏康 张梓淳 ...

  9. Android 混淆打包

    有些时候我们希望我们自己的apk包不能被别人反编译而获取自己的源代码.这就需要我们通过Android提供的混淆打包技术来完成. 一.没有引用外部包的情况: 这种情况下代码混淆的方式相对简单: 1)只需 ...

  10. OO课程学期末总结

    OO课程学期末总结 测试VS正确性论证 OCL vs JSF 对象约束语言(Object Constraint Language), 简称OCL, 是一种指示用户建模系统中的限制方式. 他是UML可选 ...