Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🐧 Linux Kernel Comments
    • Chapter 2 - 微型计算机组成结构

      • Chapter 2 - 微型计算机组成结构
    • Chapter 3 - 内核编程语言和环境

      • Chapter 3 - 内核编程语言和环境
    • Chapter 4 - 80X86 保护模式及其编程

      • Chapter 4.1-4.2 - 80X86 系统寄存器和系统指令 & 保护模式内存管理
      • Chapter 4.3 - 分段机制
      • Chapter 4.4 - 分页机制
      • Chapter 4.5 - 保护
      • Chapter 4.6 - 中断和异常处理
      • Chapter 4.7 - 任务管理
      • Chapter 4.8 - 保护模式编程初始化
      • Chapter 4.9 - 一个简单的多任务内核实例
    • Chapter 5 - Linux 内核体系结构

      • Chapter 5.1-5.2 - Linux 内核模式 & 体系结构
      • Chapter 5.3 - Linux 内核对内存的管理和使用
      • Chapter 5.4-5.6 - 中断机制 & 系统调用 & 系统时间和定时
      • Chapter 5.7-5.9 - Linux 进程控制 & 堆栈使用 & 文件系统
    • Chapter 6 - 引导启动程序 (boot)

      • Chapter 6 - 引导启动程序 (boot)
    • Chapter 7 - 初始化程序 (init)

      • Chapter 7 - 初始化程序 (init)
    • Chapter 8 - 内核代码

      • Chapter 8.1 - 内核代码总体功能
      • Chapter 8.2 - asm.s 程序
      • Chapter 8.3 - traps.c 程序
      • Chapter 8.4 - sys_call.s 程序
      • Chapter 8.5 - mktime.c 程序
      • Chapter 8.6 - sched.c 程序
      • Chapter 8.7 - signal.c 程序
      • Chapter 8.8 - exit.c 程序
      • Chapter 8.9 - fork.c 程序
      • Chapter 8.10 - sys.c 程序
    • Chapter 9 - 块设备驱动程序

      • Chapter 9.1 - 块设备驱动程序 总体功能
      • Chapter 9.2 - blk.h 文件
      • Chapter 9.3 - hd.c 程序
      • Chapter 9.4 - ll_rw_blk.c 程序
      • Chapter 9.5 - ramdisk.c 程序
    • Chapter 10 - 字符设备驱动程序

      • Chapter 10.1 - 字符设备驱动程序 总体功能
      • Chapter 10.2 - keyboard.S 程序
      • Chapter 10.3 - console.c 程序
      • Chapter 10.4 - serial.c 程序
      • Chapter 10.5 - rs_io.s 程序
      • Chapter 10.6 - tty_io.c 程序
      • Chapter 10.7 - tty_ioctl.c 程序
    • Chapter 12 - 文件系统

      • Chapter 12.1 - 文件系统 总体功能
      • Chapter 12.2 - buffer.c 程序
      • Chapter 12.3 - bitmap.c 程序
      • Chapter 12.4 - truncate.c 程序
      • Chapter 12.5 - inode.c 程序
      • Chapter 12.6 - super.c 程序
      • Chapter 12.7 - namei.c 程序
      • Chapter 12.9 - block_dev.c 程序
      • Chapter 12.10 - file_dev.c 程序
      • Chapter 12.11 - pipe.c 程序
      • Chapter 12.12 - char_dev.c 程序
      • Chapter 12.13 - read_write.c 程序
      • Chapter 12.14 - open.c 程序
      • Chapter 12.15 - exec.c 程序
      • Chapter 12.16 - stat.c 程序
      • Chapter 12.17 - fcntl.c 程序
      • Chapter 12.18 - ioctl.c 程序
      • Chapter 12.19 - select.c 程序

Chapter 4.5 - 保护

Created by : Mr Dk.

2019 / 07 / 26 14:11

Nanjing, Jiangsu, China


4.5 保护

用于保护各个任务免受相互之间的干扰。当程序对错误的内存空间执行了一次非期望的引用,保护机制可以阻止这种操作并报告此类事件。


4.5.1 段级保护

在使用保护机制时,每次内存引用都将受到检查。检查操作与地址变换并行操作,不会影响到 CPU 的性能。保护检查可分为:

  • 段界限检查
  • 段类型检查
  • 特权级检查
  • 可寻址范围限制
  • 过程入口点限制
  • 指令集限制

所有违反保护的操作都将引起异常。

4.5.1.1 段限长 Limit 检查

用于防止寻址到段外内存位置。段限长的有效值依赖于颗粒度标志 G,以及段描述符中的 20-bit Limit:

  • 1B 颗粒度下 - 0x00000-0xfffff
  • 4KB 颗粒度下 - 0x00000fff-0xffffffff

这只是段长度限制,因此实际上段内偏移 0x000-0xfff 都是有效的、有效 Limit 值应当是段中允许被访问的最后一个地址,比段长度少 1。超出有效地址范围的访问将导致一个 一般保护异常。

下扩数据段?

CPU 还会检查描述符表的长度,GDTR、IDTR、LDTR 寄存器中包含 16-bit 的限长。防止程序访问描述符表之外的描述符。

4.5.1.2 段类型 TYPE 检查

段描述符的 S 标志和 TYPE 字段含有类型信息:

  • S 指出描述符是系统描述符还是应用描述符
  • TYPE 定义代码、数据、系统等描述符的具体类型

当操作段选择符和段描述符时,CPU 随时检查类型信息。段寄存器只能存放特定类型的描述符,指令只能使用某些预定义的方法来访问某些段。

4.5.1.3 特权级

CPU 可以识别 4 个特权级,0-3,数值越大,特权越小。利用特权级可以防止较低特权级的程序访问较高特权级的段,除非是在受控的条件下。当 CPU 检测到违反特权级的操作时,产生一个一般保护性异常。CPU 可以识别三种类型的特权级:

  • 当前特权级 CPL,存放在 CS 或 SS 寄存器中,通常 CPL 就是当前代码段的特权级

  • 描述符特权级 DPL,是一个段或门的特权级,存放在描述符的 DPL 字段中,根据访问的段或门的类型不同,DPL 也有不同的含义

  • 请求特权级 RPL,是一种赋予段选择符的超越特权级,存放在段选择符中

    • CPU 会同时检查 RPL 和 CPL,以确定是否允许访问一个段
    • 始终使用 RPL 和 CPL 中的较低特权与 DPL 进行比较

    RPL 确保高特权级的代码不会代表应用程序去访问一个段。除非应用程序自己具有访问这个段的权限。

4.5.2 访问数据段时的特权级检查

为访问数据段中的操作数,段选择符需要被加载进数据段寄存器或堆栈段寄存器。把段选择符加载进段寄存器之前,CPU 会进行特权级检查,只有 DPL ≥ (CPL & RPL),段选择符才会被加载,否则产生一个一般保护性异常。

使用堆栈段寄存器也会进行特权级检查,此时,CPL、RPL、DPL 都必须相同,否则产生一个一般保护性异常。

4.5.3 代码段之间转移控制时的特权级检查

将程序控制权从一个代码段转移到另一个代码段,代码段的段选择符需要加载进 CS 中。控制转移使用指令 JMP、RET、INT 和 IRET 实现,JMP 或 CALL 可以利用以下四种方法之一来引用另一个代码段:

  • 目标操作数含有目标代码段的段选择符
  • 目标操作数指向一个调用门描述符,该门描述符中含有目标代码段的选择符
  • 目标操作数指向一个 TSS,TSS 中含有目标代码段的选择符
  • 目标操作数指向一个任务门,该门指向一个 TSS,TSS 含有目标代码段的选择符

4.5.3.1 直接调用或跳转到代码段

  • 近转移:在当前代码段中执行程序控制转移,不会执行特权级检查
  • 远转移:将控制转移到另一个代码段中,CPU 一定会进行特权级检查

进行特权级检查时,验证 4 种特权级信息:

  • CPL
  • 被调用过程目的代码段段描述符中的 DPL
  • 目的代码段的段选择符中的 RPL
  • 目的代码段段描述符中的一致性标志 C

访问非一致代码段时:

  • CPL 必须等于 DPL,否则引发一个一般保护异常
  • RPL 在数值上必须 ≤ CPL
  • 当非一致代码段的选择符被加载进 CS 寄存器时,CPL 不会改变

访问一致代码段时:

  • 只有 CPL < DPL 时,才会产生一般保护异常
  • 忽略对 RPL 的检查
  • CPL 不改变

大多数代码段都是非一致代码段,程序的控制权只能转移到具有相同特权级的代码段中,除非通过调用门。

4.5.3.2 门描述符

对具有不同特权级的代码段提供受控的访问,共有四种类型:

  • 调用门 - Call Gate
  • 陷阱门 - Trap Gate
  • 中断门 - Interrupt Gate
  • 任务门 - Task Gate

其中,调用门用于在不同特权级之间实现受控的程序控制转移。调用门描述符可以存放在 GDT 或 LDT 中,不能放在 IDT 中。调用门的功能:

  • 段选择符 - 指定要访问的代码段
  • 偏移值 - 指向对应代码段中程序的入口处
  • DPL - 指定调用门的特权级,从而指定了通过调用门访问的程序所要具备的特权级
  • P - 指定调用门描述符是否有效
  • 参数个数 - 指明发生堆栈切换时,从调用者堆栈复制到新堆栈中的参数个数

Linux 内核中没有用到调用门。。。。。。

4.5.3.3 通过调用门访问代码段

为了访问调用门,需要为 CALL 或 JMP 提供一个远指针:

  • 段选择符用于指定调用门
  • 偏移值可以是任意的,因为 CPU 并不会使用

段选择符定位了调用门描述符。调用门描述符中的段选择符定位到了目的代码段的段描述符,调用门描述符中的偏移值是目的代码段中的程序入口偏移,从而形成了目的代码段中指定程序入口处的线性地址。通过调用门进行控制转移时,CPU 对 4 种特权级进行检查:

  • CPL
  • 调用门选择符中的 RPL
  • 调用门描述符中的 DPL
  • 目标代码段描述符中的 DPL
  • 目标代码段描述符中的一致性标志 C

其中,CPL 和 RPL 应当 ≤ 调用门描述符的 DPL。DPL 指明了调用程序能够访问调用门的最低特权级。如果调用者与调用门之间的特权级检查通过,CPU 将 CPL 与目的代码段描述符的 DPL 进行检查:

  • CALL 指令可以通过调用门将控制转移到 特权级更高 的 非一致性代码段 中 (DPL < CPL)
  • JMP 指令只能把控制转移到 DPL == CPL 的 非一致性代码段 中
  • CALL 和 JMP 都可以把控制转移到 特权级更高 的 一致性代码段 中 (DPL ≤ CPL)

如果控制转移到了更高特权级的非一致代码段中,CPL 被设置为目的代码段的 DPL,引发堆栈切换。而如果控制转移到了更高特权级的一致性代码段上,CPL 不会改变,因此不会引起堆栈切换。调用门可以让一个代码段中的过程被不同特权级的程序访问,比如一个代码段中的 OS 代码可以被自身或应用程序执行。

4.5.3.4 堆栈切换

当程序控制被转移到 更高特权级 的 非一致性代码 中,CPU 会自动切换到目的代码段特权级的堆栈中:

  • 防止高特权级程序由于栈空间不足而崩溃
  • 防止低特权级程序通过共享的堆栈有意或无意地干扰高特权级程序

每个任务定义最多 4 个栈,分别对应一个特权级。每个栈都位于不同的段中,使用段选择符和段内偏移指定。OS 负责为所用到特权级建立堆栈和堆栈段描述符,并在 TSS 中设置初始指针值。当通过调用门造成特权级改变时,CPU 切换堆栈,并开始在新的特权级上执行被调用过程。切换堆栈的过程如下:

  • 根据目的代码段的 DPL (即新的 CPL) 从 TSS 中选择新栈的指针
  • 检查新栈指针的段描述符特权级和类型是否有效,否贼产生一个无效 TSS 异常
  • 临时保存当前 SS、ESP,将新栈的段选择符加载到 SS、ESP,将临时保存的 SS、ESP 压入新栈
  • 把调用门描述符中指定参数个数个参数从调用栈复制到新栈中 (最大 31 个)
  • 把返回指令指针 (CS + EIP) 压入新栈,把新代码段选择符加载到 CS 中,把调用门描述符中的偏移量加载到 EIP 中,开始执行被调用过程

4.5.3.5 从被调用过程返回

指令 RET 用于执行近返回、同特权级远返回、不同特权级远返回

  • 近返回仅在当前代码段中转移控制权,CPU 仅进行段界限检查
  • 同特权级远返回,CPU 从堆栈中弹出返回指令指针,CPU 还是会进行特权级检查,应付一些问题
  • 特权级改变的远返回仅允许返回到低特权级程序中

CPU 会执行以下步骤:

  1. 检查 CS 寄存器中 RPL 的值,确定返回时特权级是否需要改变
  2. 弹出堆栈上保存的 CS 和 EIP,并加载,同时执行代码段描述符和选择符的特权级和类型检查
  3. 如果返回会改变特权级,那么在 ESP 寄存器上加上参数个数,丢弃栈上的参数,此时 ESP 指向原调用者的 SS 和 ESP
  4. 把保存的 SS 和 ESP 加载到 SS 和 ESP 中,切换回调用者堆栈;此时被盗用着堆栈的 SS 和 ESP 被抛弃
  5. 检查段寄存器,如果有 DPL < 新 CPL 的段,那么 CPU 用空选择符来加载该段寄存器

4.5.4 页级保护

分页机制只识别两级权限:

  • 0、1、2 被归类为超级用户级
  • 3 被作为普通用户级

在超级用户级执行的程序对任何页面都具有可读、可写、可执行的权限 (包括用户级的页面)。首先,所有段级保护被检查和测试;如果通过检查,再进行页级保护检查。分段和分页保护机制类似串行电路,只要有一个不通过,就不会通过检查。页表中标志作用于单个页面,页目录项中的标志作用于所映射的所有页面。组合保护属性由两者属性相“与”构成。

4.5.4.1 修改页表项的软件问题

OS 软件修改页表项内容所需遵守的规则:页表项被缓存在 TLB 中,处理器不维护 TLB 与内存中页表的一致性,需要 OS 来确保。OS 必须在改动过页表后刷新 TLB,以保证一致性 - 通过重新加载 CR3 寄存器实现。

特殊情况:当不存在页面的表项被修改时,不需要重新刷新 TLB,因为无效的表项不会被存入 TLB。

4.5.5 组合页级和段级保护

CPU 首先执行段级保护,再执行页级保护。若在任何一级检测到一个保护违规错误,则会放弃内存访问,并产生一个异常。页级保护不能替代或忽略段级保护,而是被用于增强段级保护。

Edit this page on GitHub
Prev
Chapter 4.4 - 分页机制
Next
Chapter 4.6 - 中断和异常处理