苦李  

  操作系统存在的目的之一就是资源管理,这里面就包括安全隔离的内容。要想服众,必须得拥有至高无上的权力,因此,被管理的程序不能具有太高的特权,必须要比操作系统的权力低,通常这个被管理的对象就是用户进程。我们把用户程序的特权级降级到 3 级,这样操作系统才能高枕无忧 。

 

 为什么要有任务状态段TSS

  CPU 执行任务时,需要把任务运行所需要的数据加载到寄存器、栈和内存中,因为 CPU 只能直接处理这些资源中的数据。任务的数据和指令是 CPU 的处理对象,任务的执行要占用一套存储资源,如寄存器和内存,这些存储资源中装的是任务的数据和指令,但是CPU不喜欢使用内存这个低速的容器,更喜欢寄存器这种速度和CPU匹配的容器。所以任何时候,寄存器中的内容才是任务的最新状态。采取轮流使用 CPU 的方式运行多任务,当前任务在被换下 CPU 时,任务的最新状态,也就是寄存器中的内容应该找个地方保存起来,以便下次重新将此任务调度到 CPU 上时可以恢复此任务的最新状态, 这样任务才能继续执行,否则就出错了。

  所以Intel的建议就是给每个任务关联一个任务状态段,就是TSS,用来表示任务。任务切换的实质就是TR寄存器指向不同的TSS段。但是Linux并未采用这种方式。

  TSS描述符同样是在GDT中保存的。结构如下:

 

 

 现代操作系统采用的任务切换方式

  TSS 是 x86CPU的特定结构,被用来定义“任务”,它是内置到处理器原生支持的多任务的一种形式。但是几乎所有的x86CPU都未采用这种方式。这是为什么?

  1. 每一次任务切换过程中, CPU 除了做特权级检查外,还要在 TSS 的加载、保存、设置 B 位,以及设置标志寄存器 eflags 的 NT 位诸多方面消耗很多精力,这导致此种切换方式效能很低。
  2. 一个任务需要单独关联一个 TSS, TSS 需要在 GDT 中注册, GDT 中最多支持 8192 个描述符, 为了支持更多的任务,随着任务的增减,要及时修改 GDT,在其中增减 TSS 描述符,修改过后还要重新加载 GDT。这种频繁修改描述符表的操作还是很消耗 CPU 资源的。

 

  但是有一个地方我们必须使用到TSS。这就是 CPU 向更高特权级转移时所使用的栈地址,需要提前在 TSS 中写入。

  导致转移到更高特权级的一种情况是在用户模式下发生中断, CPU 会由低特权级进入高特权级,这 会发生堆栈的切换。当一个中断发生在用户模式(特权级 3),处理器从当前 TSS 的 ss0 和 esp0 成员中获取用于处理中断的堆栈。因此,我们必须创建一个 TSS,并且至少初始化 TSS 中的这些字段 。

  所以,我们使用 TSS 唯一的理由是为 0 特权级的任务提供栈。

 

  Linux任务切换方式

  Linux为每个CPU创建一个TSS,在各个CPU上的所有任务共享同一 个 TSS,各 CPU 的 TR 寄存器保存各 CPU 上的 TSS,在用 ltr指令加载 TSS 后,该 TR 寄存器永远指向同一个 TSS,之后再也不会重新加载 TSS。在进程切换时,只需要把 TSS 中 ss0 及 esp0 更新为新任务的内核栈的段地址及栈指针。

  那任务的状态信息保存在哪里呢?当 CPU 由低特权级进入高特权级时, CPU 会“自动”从 TSS 中获取对应高特权级的栈指针。我们具体说一下,Linux 只 用到了特权 3 级和特权 0 级,因此 CPU 从 3 特权级的用户态进入 0 特权级的内核态时(比如从用户进程进入中断), CPU自动从当前任务的TSS中获取ss0和esp0字段的值作为0特权级的栈,然后Linux“手动”执行一系列的push指令将任务的状态的保存在0特权级栈中,也就是 TSS 中 ss0和 esp0所指向的栈。

  此方案相对于CPU原生方案效率大大提高。

 

posted on 2022-02-13 15:46  苦李  阅读(134)  评论(0)    收藏  举报