《Linux内核分析》第四周笔记 扒开系统调用的三层皮(上)

扒开系统调用的三层皮(上)

一、用户态、内核态和中断

  库函数将系统调用封装起来。

  1、什么是用户态和内核态

    一般现代CPU都有几种不同的指令执行级别。

    在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态。

    而在相应的低级别执行状态下(用户态),代码的掌控范围会受到限制。只能在对应级别允许的范围内活动。系统容易崩溃。

    在intel X86CPU有四种不同的执行级别0,1,2,3,linux只使用了0级和3级分别来表示内核态和用户态。

  2、在linux内核代码中区分用户态和内核态

    用cs(代码段选择寄存器)和eip(偏移量寄存器)进行区分。

    cs寄存器的最低两位表明了当前代码的特权级。

    内核态下访问:cs和eip的值可以是任意的地址。(0xc0000000以上的地址空间)地址空间:逻辑地址。

    用户态下访问:0xc0000000-0xbfffffff的地址空间。

  3、中断处理是从用户态进入内核态的主要方式

    系统调用只是一种特殊的中断。

    a.寄存器上下文:

      从用户态切换到内核态时:

          必须保存用户态的寄存器上下文

          将内核态的寄存器上下文保存到当前CPU中

    b.中断/int指令会在堆栈上保存一些寄存器的值:

      用户态栈顶地址、当时的状态字、当时的es:eip的值(中断处理程序的入口)。

      同时将内核态堆栈的栈顶地址、当时的状态字、指向中断处理程序的入口,对于系统调用来讲指向system call函数。

    c.中断发生后第一件事情就是保存现场

      保护现场就是:就进入了中断服务程序  保存  需要用到的寄存器的数据,

      恢复现场就是:退出中断程序,  恢复  保存寄存器的数据。

      iret指令与中断信号(包括int指令)发生时的cpu做的动作正好相反。

    d.中断处理的完整过程

      

/*系统调用*/

    /*保存了cs:eip的值  保存了当前的堆栈段寄存器 当前的栈顶  还有标志寄存器 到内核堆栈中*/

    /*同时将当前的中断信号(系统调用)相关联的中断服务例程的入口加载到cs:eip中*/

    /*同时把当前的堆栈段和esp指向内核堆栈的信息也加载到CPU中*/

    完成上述过程之后,当前cpu在执行下一条指令的时候,就已经在执行中断处理程序的入口了,对堆栈的操作由原来的用户态转到内核态。

    在完成中断服务的过程中可能发生进程调度。

    如果不发生进程调度,则执行:

    返回原来的状态。

    如果发生进程调度:

    当前的上述状态将保存在系统中,当下一次发生进程调度在切换回当前进程的时候再执行完上图过程。

二、系统调用概述

  1、系统调用的意义

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

  • 把用户从底层的硬件编程中解放出来(操作系统为我们管理硬件,防止用户态进程直接与硬件设备接触造成系统崩溃)
  • 极大的提高了系统的安全性
  • 使用户程序具有可移植性(用户程序与具体的硬件解耦和,被抽象的接口替代,不会与具体的硬件有紧密关系)

  2、API和系统调用

  a.应用编程接口(application program interface, API) 和系统调用是不同的。

  • API只是一个函数定义
  • 系统调用通过软中断向内核发出一个明确的请求(例如sysrem enter指令)

  b.Libc库定义的一些API引用了封装例程(wrapper routine,唯一目的就是发布系统调用让程序员在写代码的时候不需要用汇编指令来出发一个系统调用,而是直接调用一个函数)

  • 一般每个系统调用对应一个封装例程
  • 库再用这些封装例程定义出给用户的API

?  c.不是每个API都对应一个特定的系统调用。 (可能一对多或多对一)?

  • API可能直接提供用户态的服务 ?如,一些数学函数 ?
  • 一个单独的API可能调用几个系统调用 ?
  • 不同的API可能调用了同一个系统调用 ?

  d.返回值 ?

  • 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用 ?
  • -1在多数情况下表示内核不能满足进程的请求 ?
  • Libc中定义的errno变量包含特定的出错码

            用户态                                                                                                内核态

 

     函数xyz()      API中封装了一个系统调用,               0x80中断向量对应着system call(内核代码入口起点)

                                   执行对应的中断服务程序sys_xyz()之后return from sys call

                                   过程中可能发生进程调度,未发生则返回iret

                系统调用会触发一个0x80的中断

    系统调用对应的API

系统调用的三层皮:xyz、system_call、sys_xyz(API、中断向量对应的中断服务程序、系统调用中的多种服务程序)

3.系统调用程序及服务例程 
  a.当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。 (中断向量0x80与system_call绑定起来,系统调用将xyz与sys_xyz联系起来)

  • 在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常
  • Intel Pentium II中引入了sysenter指令(快速系统调用),2.6已经支持(本课程不考虑这个)
    ?

  b.传参:    
  内核实现了很多不同的系统调用,
  进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数。
?  使用eax寄存器传递
      系统调用的参数传递方法:

  1)系统调用也需要输入输出参数,例如

  • 实际的值。
  • 用户态进程地址空间的变量的地址。
  • 甚至是包含指向用户态函数的指针的数据结构的地址。

?  2)system_call是linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号

  • 一个应用程序调用fork()封装例程,那么在执行int $0x80之前就把eax寄存器的值置为2(即__NR_fork)。
  • 这个寄存器的设置是libc库中的封装例程进行的,因此用户一般不关心系统调用号。
  • 进入sys_call之后,立即将eax的值压入内核堆栈。

? 3)寄存器传递参数具有如下限制:
  • 每个参数的长度不能超过寄存器的长度,即32位 。
  • 在系统调用号(eax)之外,参数的个数不能超过6个(ebx, ecx,edx,esi,edi,ebp) 。
  • 超过6个怎么办? 将某一寄存器作为一个指针,指向一块内存,进入内核态之后可以访问所有的地址空间,通过那块内存来传递数据。

三、使用库函数API和C代码中嵌入汇编代码触发同一个系统调用

1.系统调用实验:例:使用库函数API来获取系统当前时间(time)

/*声明一个time_tt变量*/

/*声明了一个tm为了输出时变为可读*/

/*使用time系统调用,返回tt*/

/*将tt变为t格式的*/

/*输出*/

获得当前的系统时间:

实验实现使用库函数API来取得真实的用户识别码(getuid):

编译getuid.c取得当前真实的用户识别码:

使用C代码中嵌入汇编代码触发系统调用获取系统当前真实的用户识别码:

ebx归零

使用eax传递系统调用号 24

系统调用的返回值使用eax存储

结果与之前一致。

四、小结

  系统调用是用户程序与内核的接口。通过系统调用进程,可由用户态转入内核谈态,在内核态下完成相应的服务;之后,再返回到用户态。这种实现方式必然跨越我们刚才提及的两个模式:内核态与用户态。系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口,其意义是把用户从底层的硬件编程中解放出来,极大的提高了系统的安全性及使用户程序具有可移植性。在实验中分别使用使用库函数API和C代码中嵌入汇编代码触发同一个系统调用,在这个实验中我使用库函数取得真实的用户识别码(getuid),以及使用C代码中嵌入汇编代码触发系统调用获取系统当前真实的用户识别码,过程看似容易,实际上花费了很长时间,开热点做实验什么的,希望为下一次的学习打好基础吧。

时间: 2024-11-29 04:30:20

《Linux内核分析》第四周笔记 扒开系统调用的三层皮(上)的相关文章

linux内核分析 第五周 扒开系统调用的三层皮(下)

rm menu -rf 强制删除原menu文件 git clone http://github.com/mengning/menu.git 从github中克隆 cd menu 在test.c中增加上周编写的两个函数:Getpid()和GetpidAsm(),修改test.c中的main函数,添加两行代码MenuConfig make rootfs这一步之后会打开menu,输入help后可以看到当前的系统调用: 分别执行刚加进去的两个系统调用: 插入断点进行调试: 从system_call开始到

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

实验内容: 1.执行rm menu -rf命令,强制删除原有的menu 2.使用git命令 git clone https://github.com/mengning/menu.git 克隆新的menu 3.在test.c中,在main函数中增加两个MenuConfig 4.增加对应的GetPid函数和GetPidAsm函数 5.通过脚本 make rootfs,编译并运行Menu 6.设置断点使用gdb跟增系统调用内核函数sys_time 系统调用是一种中断: 1. 保存现场 在系统调用时,我

第四周 扒开系统调用的三层皮(上)

用户态,内核态和中断 和系统调用打交道的方式:通过库函数,把系统调用给封装起来 用户态vs内核态: 一般现代CPU都有几种不同的指令执行级别 在高级别的状态下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别对应着内核态 在相应的低级别执行状态下,代码的掌控范围会受到限制,只能在对应级别允许的范围内活动 为什么有权限级别的划分:为了防止系统崩溃以及恶意代码的入侵,通过划分权限级别来让系统更稳定 举例:Intel x86 CPU有四种不同的执行级别0-3,Linux只使用了其中的0级和

20135327郭皓--Linux内核分析第四周 扒开系统调用的三层皮(上)

Linux内核分析第四周 扒开系统调用的三层皮(上) 郭皓 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一.用户态.内核态和中断 用户态:当进程在执行用户自己的代码时,则称其处于用户态,即此时处理器在特权级最低的(3级)用户代码中运行. 内核态:当一个进程执行系统调用而陷入内核代码中执行时,我们就称进程处于内核态,此时处理器处于特权级最高的(0级)内核代码中执行. PS:CPU指令

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

[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK FOUR(3.14——3.20)扒开系统调用的“三层皮” SECTION 1 用户态.内核态和中断处理过程 1.用户态.内核态区别 在高级别的状态下,代码可以执行特权指令,访问任意的物理地址: 在相应的低级别执行状态下,代码的掌控范围会受到限制. 为什么会有这种级别划分? 没有访问权限划分容易使得系统混乱(毕竟普通程序

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

LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上) 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 (一)用户态.内核态和中断处理过程 (二)系统调用概述 系统调用概述和系统调用的三层皮 (三)使用库函数API和C代码中嵌入汇编代码触发同一个系统调用 使用库函数API获取系统当前时间 C代码中嵌入汇编代码的方法(复习) 使用C代码中嵌入汇编代码触发系统调

第四周—扒开系统调用的“三层皮”

[洪韶武 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ]  第四周 扒开系统调用的“三层皮” 一.本周学习内容总结   1.系统调用的“三层皮” xyz函数—API systemcall—中断向量 sysxyz—中断服务程序 2.系统调用的意义及API与系统调用的关系 (1)系统调用的意义: 把用户从底层的硬件编程中解放出来 极大提高了系统安全性 使用户程序具有可移植性 (2)API

实验五:扒开系统调用的三层皮(下)

实验五:扒开系统调用的三层皮(下) 王朝宪20135114 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.给MenuOS增加time和time-asm命令 1. 通过内核的方式(跟踪调试系统调用)来理解并使用系统调用. rm menu -rf //强制删除当前menu git clone http://github.com/mengning/menu.git //重新克隆新版本的m

20135201李辰希 《Linux内核分析》第四周 扒开系统调用的“三层皮”

李辰希无转载 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.用户态.内核态和中断处理过程 1.我们与系统调用打交道是通过库函数的方式 2.一般现代CPU都有几种不同的指令执行级别 因为如果所有程序员写的代码都可以有特权指令的话,系统就会很容易崩溃. 3.区别: 在高级别的状态下,代码可以执行特权指令,访问任意的物理地址. 在相应的低级别执行状态下,代码的掌控范围会受到限制. Intel x86 CPU有四