第 14 章 任务和特权级保护
为何要使用特权级保护
在保护模式下,通过将内存分成大小不等的段,并用描述符对每个段的用途、类型和长度进行指定,就可以在程序运行时由处理器硬件施加访问保护。
比如,当程序试图让处理器去写一个可执行的代码段时, 处理器就会阻止这种企图;当程序试图让处理器访问超过段界限的内存区域时,处理器也会引发异常中断。
段保护是处理器提供的基本保护功能,但是还是不够。当一个程序老老实实地访问只属于它自己的段时,基本的段保护机制是很有效的。一个失控的程序,或者一个恶意的程序,依然可以通过追踪和修改描述符表来达到它们访问任何内存位置的目的。
比如说,如果用户程序知道 GDT 的位置,它可以通过向段寄存器加载操作系统的数据段描述符,或者在 GDT 中增加一个指向操作系统数据区的描述符,来修改只属于操作系统的私有数据。
其次,32 位处理器是为多任务系统而设计的。多任务系统,对任务之间的隔离和保护,以及任务和操作系统之间的隔离和保护都提出了要求,这可以看做对段保护机制的进一步强化。 同时,在多任务系统中,操作系统居于核心软件的位置,为各个任务服务,负责任务的加载、创建和执行环境的管理,并执行任务之间的调度,对操作系统的保护显得尤为重要。事实上,对于这种要求,基本的段保护机制已经无能为力了。
为何要使用特权级保护?
- 防止恶意代码访问内核。
- 多任务系统要求对任务和操作系统进行隔离和保护。
任务、任务的 LDT 和 TSS
任务
程序(Program)是记录在载体上的指令和数据,为了完成某个特定的工作,其正在执行中的一个副本,叫做任务
(Task)。这句话的意思是说,如果一个程序有多个副本正在内存中运行,那么它对应着多个任务,每一个副本都是一个任务。在上一章里,用户程序就是任务,而内核程序就是操作系统的缩影。
为了有效地在任务之间实施隔离,处理器建议每个任务都应当具有自己的描述符表,称为局部描述符表LDT
(Local Descriptor Table),并且把专属于自己的那些段放到 LDT 中。
和 GDT 一样,LDT 也是用来存放描述符的。不同之处在于,LDT 只属于某个任务。或者说,每个任务都有自己的 LDT,每个任务私有的段,都应当在 LDT 中进行描述。
LDT 的第 1 个描述符,也就是 0 号槽位,也是有效的。
局部描述符表寄存器(LDT)
全局描述符表(GDT)是全局性的,为所有任务服务,所以只需要一个全局描述符表(GDT)和一个 GDTR 寄存器就够了。
局部描述符表(LDT)的数量则不止一个,具体有多少,视任务的多少而定。为了追踪和访问这些 LDT,处理器使用了局部描述符表寄存器
(LDT Register:LDTR)。
在一个多任务的系统中,会有很多任务在轮流执行,正在执行中的那个任务,称为当前任务
(Current Task)。因为 LDTR 寄存器只有一个,所以它只用于指向当前任务的 LDT。每当发生任务切换时,LDTR 的内容被更新,指向新任务的 LDT。
LDTR 的结构和 GDTR 一样,LDTR 包含了 32 位线性基地址字段和 16 位段界限字段,以指示当前 LDT 的位置和大小。
1 | mov cx, 0x005c |
0x005C 的二进制形式为 0000 0000 0101 1100,这很容易看出 TI 位是 1 ,索引号为11(十进制)。
处理器执行以上指令时,必然会访问当前任务的 LDT(该 LDT 在内存中的位置由 LDTR 指定),从它的 11 号槽位取出描述符,并传送到段寄存器 DS 的描述符高速缓存器中去。
任务状态段(TSS)和TR寄存器
在一个多任务的环境中,当任务切换发生时,必须保护旧任务的运行状态,或者说是保护现场,保护的内容包括通用寄存器、段寄存器、 栈指针寄存器 ESP、指令指针寄存器 EIP、状态寄存器 EFLAGS,等等。 否则的话,等下次该任务又恢复执行时,一切都会变得茫然而毫无头绪。
为了保存任务的状态,并在下次重新执行时恢复它们,每个任务都应当用一个额外的内存区域保存相关信息,这叫做任务状态段
(Task State Segment:TSS)。处理器固件能够识别 TSS 中的每个元素,并在任务切换的时候读取其中的信息。
任务状态段 TSS 有固定的格式,最小尺寸是 104 字节,图中所标注的偏移量是十进制的 。
处理器用 TR寄存器
来指向当前任务的 TSS,其内容为 TSS 的基地址和界限。TR 寄存器在处理器中也只有一个。
当任务切换发生的时候,TR 寄存器的内容也会跟着指向新任务的 TSS。这个过程是这样的:
- 首先,处理器将当前任务的现场信息保存到由 TR 寄存器指向的 TSS;
- 然后,再使 TR 寄存器指向新任务的 TSS,并从新任务的 TSS 中恢复现场。
为什么这个寄存器叫 TR,而不是 TSSR。原因很简单,TSS 是一个任务存在的标志,用于区别一个任务和其他任务。所以,这个寄存器叫做
任务寄存器
(Task Register:TR)。
全局空间和局部空间
每个任务实际上包括两个部分:全局部分
和私有部分
。
- 全局部分是所有任务共有的,含有操作系统的软件和库程序,以及可以调用的系统服务和数据;
- 私有部分是每个任务各自的数据和代码,与任务所要解决的具体问题有关,彼此并不相同。
任务实际上是在内存中运行的,所以所谓的全局部分和私有部分,其实是地址空间的划分,即全局地址空间和局部地址空间,简称全局空间
和局部空间
。
全局地址空间是用全局描述符表(GDT)来指定的,而局部地址空间则是由每个任务私有的局部描述符表(LDT)来定义的。
从程序员的角度来看,任务的全局空间包含了操作系统的段,是由别人编写的,但是他可以调用这些段的代码,或者获取这些段中的数据;任务局部空间的内容是由程序员自己创建的。通常任务会在自己的局部空间运行,当它需要操作系统提供的服务时,转入全局空间执行。
特权级概述
描述符特权级 DPL
特权级(Privilege Level),是存在于描述符及其选择子中的一个数值,当这些描述符或者选择子所指向的对象要进行某种操作,或者被别的对象访问时,该数值用于控制它们所能进行的操作, 或者限制它们的可访问性。
这其实对应的就是描述符中的 DPL 字段,可以取值为 00、01、10 和 11,分别对应特权级 0、1、2 和 3。描述符总是指向它所描述的目标对象, 代表着该对象,因此该字段实际上是目标对象的特权级。
对于数据段来说,DPL 决定了访问它们所应当具备的最低特权级别。
如果有一个数据段,其描述符的 DPL 字段为 2,那么,只有特权级为 0、1 和 2 的程序才能访问它。当一个特权级为 3 的程序也试图去读写该段时,将会被处理器阻止,并引发异常中断。
处理器的 4 级环状保护结构:
请求特权级 RPL
段选择子由三部分组成:
描述符的索引号
:用来在描述符表中选择一个段描述符。TI
:描述符表指示器(Table Indicator),TI =0 时,表示描述符在 GDT 中;TI=1 时,描述符在 LDT 中。RPL
:请求者的特权级别(Requestor’s Privilege Level),表示给出当前选择子的那个**程序的特权级别(访问者的特权级)**,正是该程序要求访问这个内存段。每个程序都有特权级别。
我们知道:
- 要将控制从一个代码段转移到另一个代码段,通常是使用 jmp 或者 call 指令,并在指令中提供目标代码的段选择子,以及段内偏移量。
- 为了访问内存中的数据,也必须将段选择子加载到寄存器 DS、ES、FS 或者 GS。
不管是实施控制转移,还是访问数据,这都可以看成是一个请求,请求者提供一个段选择子,请求访问指定的段。从这个意义上来讲,RPL 也就是指请求者的特权级别。
当前特权级 CPL
当处理器正在一个代码段中取指令和执行指令时,那个代码段的特权级叫做当前特权级
(Current Privilege Level,CPL)。正在执行的这个代码段,其选择子位于段寄存器 CS 中,其最低两位就是当前特权级的数值。
应用程序编写时,不需要考虑 GDT、LDT、分段、描述符这些东西,它们是在程序加载时,由操作系统负责创建的,应用程序的编写者只负责具体的功能就可以了。应用程序的加载和开始执行,也是由操作系统所主导的,而操作系统一定会将它放在特权级 3 上。当应用程序开始执行时,当前特权级 CPL 自然就会是 3。
特权指令
计算机系统的脆弱性在于一条指令就能改变它的整体运行状态,比如停机指令 hlt 和对控制寄存器 CR0 的写操作,像这样的指令只能由最高特权级别的程序来做。
因此,那些只有在当前特权级 CPL 为 0 时 才能执行的指令,称为特权指令
(Privileged Instructions)。
典型的特权指令包括加载全局描述符表的指令 lgdt(它在实模式下也可执行)、加载局部描述符表的指令 lldt、加载任务寄存器的指令 ltr、读写控制寄存器的 mov 指令、停机指令 hlt 等十几条。
输入/输出特权级
处理器允许对各个特权级别所能执行的 I/O 操作进行控制。通常这指的是端口访问的许可权,因为对设备的访问都是通过端口进行的。
在处理器的标志寄存器 EFLAGS 中,位 13、位 12 是 IOPL 位,也就是输入/输出特权级(I/O Privilege Level),它代表着当前任务的 I/O 特权级别。
任务是由操作系统加载和创建的,与任务相关的信息都在它自己的任务状态段(TSS)中,其中就包括一个 EFLAGS 寄存器的副本,用于指示与当前任务相关的机器状态。
特权级检查
代码段的特权级检查是很严格的。一般来说,控制转移只允许发生在两个特权级相同的代码段之间。如果当前特权级为 2,那么,它可以转移到另一个 DPL 为 2 的代码段接着执行,但不允许转移到 DPL 为 0、1 和 3 的代码段执行。
为了让特权级低的应用程序可以调用特权级高的操作系统例程(从低特权级到高特权级的代码段转移),有两种方式:
- 依从代码段:将高特权级的代码段定义为依从的
- 使用调用门
依从代码段
描述符的 TYPE 字段中的 C 位:
如果 C=0,这样的代码段只能供同特权级的程序使用;
如果 C=1,这样的代码段称为
依从的代码段
,允许从同特权级和低特权级的程序转移到该段执行。也就是说:当前特权级 CPL 必须低于或者和目标代码段描述符的 DPL 相同时,可以从特权级比它低的程序调用并进入。因为低特权级的数值比较小,在数值上:
CPL >= 目标代码段描述符的DPL
。
Eg:如果一个依从的代码段,其描述符的 DPL 为 1,则只有特权级别为 1、2、3 的程序可以调用,而特权级为 0 的程序则不能。除了从高特权级别的例程(通常是操作系统例程)返回外,在任何时候,都不允许将控制从较高的特权级转移到较低的特权级。
原因:内核的代码已经很牛逼了,它是不会想要调用你的用户程序的代码的,就是这个道理。也可以解释为:操作系统不会引用可靠性比自己低的代码。
代码段不会从高特权级转移到低特权级。但是数据段不一样,高特权级的代码可以访问低特权级的数据段。
为什么叫“依从”
依从的代码段不是在它的 DPL 特权级上运行,而是在调用程序的特权级上运行。
就是说,当控制转移到依从的代码段上执行时,不改变当前特权级 CPL,段寄存器 CS 的 CPL 字段不发生变化,被调用过程的特权 级依从于调用者的特权级,这就是为什么它被称为“依从的”代码段。
简单来说就是:我明明是高特权级别的代码,却可以被低特权级别的代码调用,并且被调用的时候,不使用我的 DPL 作为 CPL,而是使用低特权级别代码的 DPL 作为 CPL。
说文绉绉一点就是:依从代码段转移后的特权级不与自己的特权级(DPL)为主,而是与转移前的低特权级一致,依从转移前的低特权级。
低特权级的代码段可以在不切换进程特权级的情况下执行高特权级的代码,这看似很不安全,但由于访问权限还受制于 RPL,因此低特权级的程序并不能为所欲为。
门描述符
门(Gate)是另一种形式的描述符,称为门描述符
(Gate Descriptor),简称门。它本身只是一个描述符,可以安装在 GDT 或 LDT 中。
可以对比段描述符,段描述符用于描述内存段,门描述符用于描述可执行的代码,比如一段程序、一个过程(例程)或者一个任务。
在保护模式下,中断描述符表(IDT)中的每个表项由 8 个字节组成,其中的每个表项叫做一个门描述符。 “门” 的含义是指当中断发生时必须先访问这些 “门”,能够 “开门”(即:将要进行的处理需通过特权检查,符合设定的权限等约束)后,才能进入相应的处理程序。而门描述符则描述了 “门” 的属性(如特权级、段内偏移量等)。
门的类型:
- 调用门:用于不同特权级之间的过程调用;
- 中断门/陷阱门:作为中断处理过程使用的;
- 任务门:对应着单个的任务,用来执行任务切换。
调用门描述符格式:
门描述符中定义了例程所在代码段的选择子,以及段内偏移等信息。
RPL、CPL 和 DPL
- RPL:访问者的特权级
- CPL:处理器的当前特权级
- DPL:描述符的特权级
在 CPU 中运行的是指令,指令总会属于某个代码段,该代码段的特权级,也就是代码段描述符中的 DPL,便是当前 CPU 所处的特权级,它表示处理器正在执行的代码的特权级。
除依从代码外,转移后的目标代码段的 DPL 是将来处理器的当前特权级 CPL。当前正在执行的代码所在的代码段的 DPL 就是处理器的当前特权级 CPL,当处理器从一个代码段跳到另一个代码段时就有可能发生特权级改变,CPL 也要随之改变。
要判断请求者是谁,最简单的方法是看谁提供的选择子,谁就是请求者。
在绝大多数情况下,请求者都是当前程序自己,因此 CPL=RPL。
1
2mov eax, 0x0008 ; 加载数据段(0-4G)选择子
mov ds, eax当前程序自己拿着段选择子 0x0008 来“请求”代入段寄存器 DS,以便在随后的指令中访问该段中的数据。
应用程序通过调用门调用内核例程,请求者是用户程序,即 RPL=3。但是真正读硬盘的是内核例程,通过调用门会改变当前特权级,进入内核示例后,CPL=0。 此时 RPL 与 CPL 就不相同。
为什么要引入 RPL
想象一下,应用程序的编写者通过钻研,得知了操作系统内核的数据段的选择子,而且希望用这个选择子访问操作系统的数据段。当然,他不可能在应用程序里访问操作系统数据段,因为那个数据段的 DPL 为 0,而应用程序工作时的当前特权级为 3,处理器会很机警地把来访者拒之门外。
但是,他可以借助于调用门。调用门工作在目标代码段的特权级上,一旦处理器的执行流离开应用程序,通过调用门进入操作系统例程时,当前特权级从 3 变为 0。当那个不怀好意的程序将一个指向操作系统数据段的选择子,通过 CX 寄存器作为参数传入调用门时,因为当前特权级已经从 3 变为 0,可以从硬盘读出数据,并且允许向操作系统数据段写入扇区数据,他得逞了!
处理器的智商很低,它不可能知道谁是真正的请求者。
也就是说,对于
mov ds, ax
这条指令来说,AX 寄存器中的选择子可能是操作系统自己提供的,也可能来自于恶意的用户程序,处理器是无法区分出来的。
引入请求特权级(RPL)的原因是处理器在遇到一条将选择子传送到段寄存器的指令时,无法区分真正的请求者是谁。
但是,引入RPL 本身并不能完全解决这个问题,这只是处理器和操作系统之间的一种协议,处理器负责检查请求特权级 RPL,判断它是否有权访问,但前提是提供了正确的 RPL;内核或者操作系统负责鉴别请求者的身份,并有义务保证 RPL 的值和它的请求者身份相符,因为这是处理器无能为力的。
简单来说:在引入 RPL 这件事上,处理器的潜台词是,仅依靠现有的 CPL 和 DPL,无法解决由请求者不同而带来的安全隐患。那么,再增加一道门卫,但前提是操作系统只将通行证发放给正确的人。
操作系统的编写者很清楚段选择子的来源,即,真正的请求者是谁。
- 当它自己读写一个段时,这没有什么好说的;
- 当它提供一个服务例程时,3 特权级别的用户程序给出的选择子在哪里,也是由它定的,它也知道。在这种情况下,它所要做的,就是将该选择子的 RPL 字段设置为请求者的特权级。
剩下的工作就看处理器了。每当处理器执行一个将段选择子传送到段寄存器 (DS、ES、FS、GS)的指令时,会检查以下两个条件是否都能满足:
- 当前特权级 CPL 高于或者和数据段描述符的 DPL 相同。即,在数值上,CPL ≤ 数据段描述符的 DPL;
- 请求特权级 RPL 高于或者和数据段描述符的 DPL 相同。即,在数值上,RPL ≤ 数据段描述符的 DPL。
如果以上两个条件不能同时成立,处理器就会阻止这种操作,并引发异常中断。
所以在上面的例子中,当用户程序想要写内核的数据段,通过调用门调用内核例程后,当前特权级 CPL 与数据段描述符的 DPL 都是 0,满足第一个条件。但是 RPL=3,数据段的 DPL=0,不满足第二个条件,所以处理器引发异常中断。
按照 Intel 公司的说法,引入 RPL 的意图是“确保特权代码不会代替应用程序访问一个段,除非应用程序自己拥有访问那个段的权限”。
基本的特权级检查规则
将控制直接转移到非依从的代码段,要求当前特权级 CPL 和请求特权级 RPL 都等于目标代码段描述符的 DPL。
即,在数值上:
1
2CPL = 目标代码段描述符的DPL
RPL = 目标代码段描述符的DPL一个典型的例子就是使用 jmp 指令进行控制转移:
jmp 0x0012:0x000020000
,因为两个代码段的特权级相同,故转移后当前特权级不变。将控制直接转移到依从的代码段,要求当前特权级 CPL 和请求特权级 RPL 都低于或等于目标代码段描述符的 DPL。
即,在数值上:
1
2CPL >= 目标代码段描述符的DPL
RPL >= 目标代码段描述符的DPL控制转移后,当前特权级保持不变。
高特权级别的程序可以访问低特权级别的数据段,但低特权级别的程序不能访问高特权级别的数据段。
访问数据段之前,肯定要对段寄存器 DS、ES、FS 和 GS 进行修改,比如
mov fs, ax
,在这个时候,要求当前特权级 CPL 和请求特权级 RPL 都必须高于或等于目标数据段描述符的 DPL。即,在数值上:
1
2CPL <= 目标代码段描述符的DPL
RPL <= 目标代码段描述符的DPL最后,处理器要求,在任何时候,栈段的特权级别必须和当前特权级 CPL 相同。因此随着程序的执行,要对段寄存器 SS 的内容进行修改时:
mov ss, ax
,必须进行特权级检查,要求当前特权级 CPL 和请求特权级 RPL 必须等于目标栈段描述符的 DPL。即,在数值上:
1
2CPL = 目标代码段描述符的DPL
RPL = 目标代码段描述符的DPL要求栈段的特权级别必须和当前特权级 CPL 相同,主要是为了防止因栈空间不足而产生不可预料的问题, 同时也是为了防止栈数据的交叉引用。
小结
- 程序员在写程序时,不需要指定特权级别。当程序运行时,操作系统将程序创建为任务局部空间的内容,并赋予较低特权级别,比如 3, 操作系统对应着任务全局空间的内容。如果有多个任务,则操作系统属于所有任务的公共部分。
- 当任务运行在局部空间时,可以在各个段之间转移控制,并访问私有数据,因为它们具有相同的特权级别,但不允许直接将控制转移到高特权级别的全局空间的段,除非通过调用门,或者目标段是依从的代码段。
- 当通过调用门进入全局空间执行时,操作系统可以在全局空间内的各个段之间转移控制并访问数据,因为它们也具有相同的特权级别。 同时,操作系统还可以访问任务局部空间的数据,即低特权级别的数据段。但除了调用门返回外,不允许将控制转移到低特权级别的局部空间内的代码段。
- 任何时候,当前栈的特权级别必须和 CPL 是一样的。进入不同特权级别的段执行时,要切换栈。
内核程序的初始化
保护模式微型核心程序
这是前一章内核程序的修改版本,使用了任务、LDT、TSS 和特权级等最新的处理器特性和工作机制。
1 | ;代码清单14-1 |
加载内核程序
加载的是内核程序,而内核应当工作在 0 特权级,所以主引导程序在初始化内核时,所创建的描述符,其目标特权级 DPL 都为 0,
这些描述符所指向的段,有的是代码段,有的是数据段。
- 对于数据段,只有内核自己才能访问,因为其描述符的 DPL 是 0;
- 对于代码段,通常只有 0 特权级的程序才能将控制转移到该段,也就是说,只能从内核其他正在执行的部分转移到该段执行,因为它们的特权级别相同。
调用门
例程是由内核提供的,它们的特权级通常就是内核的特权级。在上一章里,内核赋予用户程序的特权级别是 0,所以用户程序是在 0 特权级上运行的。也正是因为如此,当用户程序通过 U-SALT 表中的符号地址直接调用内核例程时,才会通过特权级检查。
在本章里,将用户程序的特权级定为 3,为了让用户程序也能顺利调用例程,需要安装调用门
。
调用门(Call-Gate)用于在不同特权级的程序之间进行控制转移。 本质上,它只是一个描述符,可以安装在 GDT 或者 LDT 中。
为什么门描述符存储的是选择子而不是线性地址
对比一下段描述符,里面储存了段基地址和段界限:
但是门描述符中储存的却是例程所在代码段的选择子,以及段内偏移等信息:
原因:
目的决定结构。段描述符用于描述内存段,门描述符用于描述可执行的代码。
- 因为门描述符存储了例程所在代码段的选择子,所以可以在通过调用门进行控制转移时,进行代码段描述符有效性、段界限和特权级的检查。
- 同时因为门描述符里面已经存储了选择子和段内偏移,在通过调用门调用例程时,不需要指令中给出的偏移量。方便使用。
门描述符的属性位
- TYPE 字段用于标识门的类型,共 4 比特,值 1100 表示调用门。
- P 位是有效位,通常应该是 1。当它为 0 时,调用这样的门会导致处理器产生异常中断。
通过调用门进行控制转移
要想通过调用门进行控制转移,可以使用 jmp far 或者 call far 指令,并把调用门描述符的选择子作为操作数。
- 使用 jmp far 指令,可以将控制通过门转移到比当前特权级高的代码段,但不改变当前特权级别。
- 使用 call far 指令,则当前特权级会提升到目标代码段的特权级别。也就是说,处理器是在目标代码段的特权级上执行的。
为什么 call for 指令会提升特权级别?
答:因为栈段的特权级必须同当前特权级保持一致,因此需要从低特权级的栈切换到高特权级的栈。
为了切换栈,每个任务除了自己固有的栈之外,还必须额外定义几套栈,具体数量取决于任务的特权级别。
- 0 特权级任务不需要额外的栈, 它自己固有的栈就足够使用,因为除了调用返回外,不可能将控制转移到低特权级的段;
- 1 特权级的任务需要额外定义 1 个描述符特权级 DPL 为 0 的栈,以便将控制转移到 0 特权级时使用;
- 2 特权级的任务需要额外定义 2 个栈,描述符特权级 DPL 分别是 0 和 1,在控制转移到 0 特权级和 1 特权级时使用;
- 3 特权级的任务最多额外定义 3 个栈,描述符特权级 分别是 0、1 和 2,在控制转移到 0、1 和 2 特权级时使用。
这些额外的栈,也会由操作系统加载程序时自动创建。这些栈的描述符位于任务自己的 LDT 中。同时,还要在任务的 TSS 中登记,原因是,栈切换是由处理器固件自动完成的,处理器需要根据 TSS 中的信息来完成这一过程。
回顾 TSS 的结构:
- 从偏移 4~24 处登记有特权级 0 到 2 的栈段选择子,以及相应的 ESP 初始值。
- 任务自己固有的栈信息则位于偏移量为 56(ESP)和 80(SS) 的地方。
在切换栈时,处理器可以用 TR 找到当前任务的 TSS,并从 TSS 中获取新栈的信息:
- 栈切换前,段寄存器 SS 指向的是旧栈,ESP 指向旧栈的栈顶,即最后一个被压入的过程参数;
- 栈切换后,处理器自动替换 SS 和 ESP 寄存器的内容,使它们分别为新栈的选择子和新栈的栈顶。
这一切,对程序的编写者来说是透明的。就是说,程序员不用关心栈的切换和参数的复制,他即使不知道还有栈切换这回事,也不会影响程序编写工作。
哪些程序可以访问门
调用门描述符中的 DPL 和目标代码(就是门描述符指向的代码)段描述符的 DPL 用于决定哪些特权级的程序可以访问此门:
当前特权级 CPL 和 请求特权级 RPL 都要高于或等于调用门描述符特权级的 DPL。
1
2CPL <= 调用门描述符的DPL
PRL <= 调用门描述符的DPL当前特权级 CPL 低于或等于目标代码段描述符特权级的 DPL。
1
CPL >= 目标代码段描述符的DPL
调用门的安装和测试
调用门描述符可以在 GDT 中创建的,4 个调用门安装之后,GDT 的布局如图:
加载用户程序并创建任务
任务控制块和 TCB 链
在内核初始化完成后,接下来的工作就是加载和重定位用户程序( 应用程序),并移交控制权。
一个程序成为“任务”,并且能够参与任务切换和调度,必须要有 LDT 和 TSS。
有点吃力,沉淀一下再看。
To be continue…
https://github.com/liracle/codeOfAssembly/tree/master/booktool