x86 - CPU架构/寄存器详解 (一)x86、8086、i386、IA-32 是什么?
x86 - CPU架构/寄存器详解 (二) 实模式(8086模式)
x86 - CPU架构/寄存器详解 (三) 保护模式
x86 - 分段与分页详解
x86 - 特权级别 CPL / RPL / DPL / IOPL
x86 - 操作系统:中断、陷阱、异常、故障、终止
x86 - 描述符详解:存储/系统段描述符、门描述符
段描述符是GDT、LDT、IDT表中(关于三个表的介绍-系统地址寄存器)的一个数据结构项,用于向处理器提供有关一个段的位置和大小信息以及访问控制的状态信息。通常由编译器、链接器、加载器、操作系统或执行体创建,但不由应用程序创建。
每个段描述符的长度是8字节,含有3个主要字段:段基地址、段限长和段属性。
Limit
段限长字段Limit(Segment limit field):用于指定段的长度。处理器会把段描述符中两个段限长字段组合成一个20位的值,并根据颗粒度标志G来指定段限长Limit值的实际含义。
• 如果G=0,则段长度Limit范围可从1B~1MB,单位是1B。
• 如果G=1,则段长度Limit范围可从4KB~4GB,单位是4KB。
根据段类型中的段扩展方向标志E,处理器以两种不同方式使用段限长Limit:
• 对于向上扩展的段(expand-up segment,简称上扩段),逻辑地址中的偏移值范围可以从0到段限长值Limit。大于段限长Limit的偏移值将产生一般保护性异常(general-protection exceptions, #GP, SS段寄存器除外)或产生栈错误异常(stack-fault exceptions, #SS)。
• 对于向下扩展的段(expand-down segment,简称下扩段),段限长Limit的含义相反。根据默认栈指针大小标志B的设置,偏移值范围可从段限长Limit+1到0xFFFFFFFF或0xFFFF。而小于等于段限长Limit的偏移值将产生一般保护性异常或栈错误异常。对于下扩段,减小段限长字段中的值会在该段地址空间底部分配新的内存,而不是在顶部分配。IA-32架构的栈总是向下扩展的,因此这种实现方式很适合扩展堆栈。
Base
基地址字段Base(Base address field):该字段定义在4GB 线性地址空间中一个段字节0所处的位置。处理器会把3个分立的基地址字段组合形成一个32位的值。段基地址应该对齐16字节边界。16字节对齐不是必须的,但对齐在16字节边界上使得程序能最大化程序性能。
TYPE
段类型字段TYPE(Type field):指定段或门(Gate)的类型、说明段的访问类型以及段的扩展方向。该字段的解释依赖于描述符类型标志S指明是一个应用(存储段)描述符还是一个系统描述符(System)。TYPE字段的编码对代码、数据或系统描述符都不同。
S
描述符类型标志S(Descriptor type flag):用于指明一个段描述符是系统段描述符(当S=0)还是代码 或数据段 描述符(当S=1)。
DPL
描述符特权级字段DPL(Descriptor privilege level):用于指明描述符的特权级。特权级范围从0到3。0级特权级最高,3级最低。DPL用于控制对段的访问。
P
段存在标志P(Segment present):用于指出一个段是在内存中(P=1)还是不在内存中(P=0)。
当一个段描述符的P标志为0时,那么把指向这个段描述符的选择符加载进段寄存器将导致产生一个段不存在异常(segment-not-present exception,#NP)。内存管理软件可以使用这个标志来控制在某一给定时间实际需要把那个段加载进内存中。这个功能为虚拟存储提供了除分页机制以外的控制。当P标志为0时,操作系统可以自由使用格式中标注为可用(Available)的字段位置来保存自己的数据,例如有关不存在段实际在什么地方的信息。
D/B标志
D/B标志(默认操作大小/默认栈指针大小和/或上界限,Default operation size/default stack pointer size and/or upper bound):根据段描述符描述的是一个可执行代码段、下扩数据段还是一个堆栈段,这个标志具有不同的功能。(对于32位代码和数据段,这个标志应该总是设置为1;对于16位代码和数据段,这个标志被设置为0。)
• 可执行代码段。此时这个标志称为D标志并用于指出该段中的指令引用有效地址和操作数的默认长度。如果该标志置位,则默认值是32位地址和32位或8位的操作数;如果该标志为0,则默认值是16位地址和16位或8位的操作数。指令前缀0x66可以用来选择非默认值的操作数大小;前缀0x67可用来选择非默认值的地址大小。
• 栈段(由SS寄存器指向的数据段)。此时该标志称为B(Big)标志,用于指明隐含堆栈操作(如PUSH、POP或CALL)时的栈指针大小。如果该标志置位,则使用32位栈指针并存放在ESP寄存器中;如果该标志为0,则使用16位栈指针并存放在SP寄存器中。如果堆栈段被设置成一个下扩数据段,这个B标志也同时指定了堆栈段的上界限。
• 下扩数据段。此时该标志称为B标志,用于指明堆栈段的上界限。如果设置了该标志,则堆栈段的上界限是0xFFFFFFFF(4GB);如果没有设置该标志,则堆栈段的上界限是0xFFFF(64KB)。
G
粒度标志G(Granularity):该字段用于确定段限长字段Limit值的单位。如果颗粒度标志为0,则段限长值的单位是字节;如果设置了颗粒度标志,则段限长值使用4KB单位。(这个标志不影响段基地址的颗粒度,基地址的颗粒度总是字节单位。)若设置了G标志,那么当使用段限长来检查偏移值时,并不会去检查偏移值的12位最低有效位。例如,当G=1时,段限长为0表明有效偏移值为0~4095。
L
64位代码段标志L(64-bits code segment):在IA-32模式,第二个双字的第21字节指示一个代码的是否包含本地64位代码。L置1表示这个代码段的指令执行在64位模式,置0表示执行在兼容模式。如果L位被设置了,那么D标志一定要置0。当不处于IA-32e模式时,和对于非代码段,这个位被保留并且总是应该被置0。
AVL
可用和保留位AVL(Available and reserved bits):段描述符第2个双字的第20字节可供系统软件使用。
当S=1且TYPE字段的最高位(第2个双字的位11)为0时,表明是一个数据段描述符。
B位
默认栈指针大小和上界限。
对于栈段(由SS寄存器指向的数据段)来说,该位用来指明隐含堆栈操作(如PUSH、POP或CALL)时的栈指针大小:
B=0:使用SP寄存器
B=1:使用ESP寄存器
同时,B的值也决定了栈的上部边界:
B=0:栈段的上部边界(也就是SP寄存器的最大值)为0xFFFF;
B=1:栈段的上部边界(也就是ESP寄存器的最大值)为0xFFFF_FFFF.
A位
已访问:用于表示一个段最近是否被访问过(准确地说是指明从上次操作系统清零该位后一个段是否被访问过)。
当创建描述符的时候,应该把这位清零。之后,每当该段被访问时(准确地说是处理器把这个段的段选择符加载进段寄存器时)它就会将该位置“1”;对该位的清零是由操作系统负责的,通过定期监视该位的状态,就可以统计出该段的使用频率。当内存空间紧张时,可以把不经常使用的段退避到硬盘上,从而实现虚拟内存管理。
W位
可写:指示段的读写属性。
W=0:段不允许写入,否则会引发处理器异常中断;
W=1:允许写入。
E位
扩展方向:
E=0:表示向上扩展的段(简称上扩段),逻辑地址中的偏移值范围可以从0到Limit;
E=1:表示向下扩展的段(简称下扩段,通常是栈段),逻辑地址中的偏移范围可以从Limit到0xFFFF(当B=0时)或者0xFFFF_FFFF(当B=1时)。
当S=1且TYPE字段的最高位(第2个双字的位11)为1时,表明是一个代码段描述符。
D位
默认操作数大小:用于指出该段中的指令引用有效地址和操作数的默认长度。
D=0:默认值是16位的地址和16位或者8位的操作数;
D=1:默认值是32位的地址和32位或者8位的操作数;
说明:指令前缀0x66可以用来选择非默认值的操作数大小,指令前缀0x67可以用来选择非默认值的地址大小。
A位
已访问:与数据段描述符中的A位相同。
R位
可读:
R=0:代码段不可读,只能执行。
R=1:代码段可读,可执行。
这里的R属性并非针对处理器,而是用来限制程序的行为。当常数或者静态数据被放在了一个ROM中时,就可以使用一个可读可执行的代码段,然后通过使用带CS前缀的指令,就可以读取代码段中的数据。
注意:在保护模式下,代码段是不可写的;堆栈段必须是可读可写的数据段。
C位
一致性:
C=0:表示非一致性代码段。这样的代码段可以被同级代码段调用,或者通过门调用;
C=1:表示一致性代码段。可以从低特权级的程序转移到该段执行(但是低特权级的程序仍然保持自身的特权级)。
注意:所有的数据段都是非一致性的,即意味着它们不能被低特权级的程序或过程访问。然而与代码段不同,数据段可以被更高特权级的程序或过程访问,而无需使用特殊的访问门
GDT(全局描述符表)
整个系统只有一个GDT;
通过全局描述符寄存器GDTR进行定位;
LDT(局部描述符表)
每个任务都配有一个LDT,LDT基地址、界限等信息存放在任务对应的TCB中;
通过局部描述符寄存器GDTR进行定位;
将LDT视为一种特殊的内存段,则可为每一个LDT创建一个LDT描述符,将描述符存放到GDT中;
访问LDT时:GDT选择子==>访问GDT==>LDT描述符==>访问LDT(==>加载到LDTR);
TSS(任务状态段)
每个任务都配有一个TSS,TSS基地址、界限等信息可以存放在任务对应的TCB中;
通过任务寄存器TR进行定位;
将TSS视为一种特殊的内存段,则可为每一个TSS创建一个TSS描述符,将描述符存放到GDT中;
访问TSS时:GDT选择子==>访问GDT==>TSS描述符==>访问TSS(==>加载到TR);
TCB(任务控制块)
任务控制块,可以说是系统中用来管理任务的最重要的数据结构了,操作系统用来管理任务的所有信息都可以放在这里,有些书籍上也称之为 PCB(Process Control Block,进程控制块)。
在这个结构中,一些常用的信息包括:
需要注意的是:上面的 LDT、TSS,是 x86 处理器中设计的运行机制,是处理器要求这样的。而 TCB 不是处理器要求的,它是操作系统的实现者自己来构建的,因此可以根据自己的需要来进行设计。
每一个应用程序需要一个 TCP 结构,所有的 TCB 结构就可以构成一个链表,便于操作系统来管理。
比如:在发生任务切换的时候,就可以顺着链表头,一次扫描链表上的每一个 TCB 节点。如果找到了当前正在被执行(即将被中止)的任务,就把这个任务的状态标记为暂停,并移动到链表的末尾,然后把链表头部的第一个处于 ready 状态的任务加载到处理器中去执行。当然,Linux 系统中的处理过程更为复杂,它把每一个任务按照优先级放在不同的等待队列中,然后利用哈希桶算法来查找任务。
LDT描述符存放在GDT中,在GDT中的选择子可以存入相应的TCB中;
段基地址指示LDT在内存中的起始地址;
段界限指示LDT的范围;
S-TYPE固定为’0-0010’,表明为LDT描述符;
TSS描述符存放在GDT中,指向任务的TSS。
TYPE中’B’:忙位,刚创建时应为0,任务开始执行,挂起时为1,由硬件管理,防止切换任务切到自己;
任务不可重入的本质是,执行任务切换时,新任务的状态不能为忙(TYPE中的B不能为1)。
TSS描述符DPL必须为0,只有CPL为0能调用。
门(调用、中断、任务或陷阱)用于跨段转移执行控制,权限级别检查的完成方式取决于所使用的目标类型和指令:
类型 | 区别 |
---|---|
调用门 | 调用门使用 CALL 和 JMP 指令。调用门将控制从较低权限代码转移到与当前特权级相同或较高权限的代码段,门 DPL 用于确定哪些权限级别可以访问门。调用门正在(或可能已经)逐渐被放弃,转而支持更快的 SYSENTER/SYSEXIT 机制。 |
任务门 | 任务门用于硬件多任务支持。硬件任务切换可以自动发生(CALL/JMP 到任务门描述符),或者在设置 NT 标志时通过中断或 IRET。它的工作方式与中断门或陷阱门相同。没有使用任务门是因为内核通常希望在任务切换时完成额外的工作。 |
中断描述符 | 中断、陷阱、任务门一起被称为中断描述符表,存储在IDT中。除了参数从一个特权堆栈到另一个特权堆栈的传输之外,它们的工作方式与调用门相同。一个区别是中断门会清除 EFLAGS 中的 IF 位,而陷阱门则不会,这使它们成为处理硬件中断的理想选择。陷阱广泛用于硬件辅助虚拟化。 |
类型 | 区别 |
---|---|
段描述符 | 用于描述符内存段,比如:数据段、代码段、堆栈段,存储段基址 |
门描述符 | 用于描述可执行代码,比如:一段程序、一个过程(例程、子程序)或者一个任务,存储段选择子 |
短调用
短调用就是我们在汇编常见的CALL指令,调用格式为:CALL 立即数/寄存器/内存。为什么是短调用,我们来看一下执行该指令时堆栈的变化:
调用CALL指令之后,CPU只将当前的EIP压入堆栈后跳转到目标地址,发生改变的寄存器只有ESP和EIP,即所谓的短调用。
长调用
长调用分为两种,一种提权,一种不提权,调用格式为:
CALL CS:EIP,其中EIP是废弃的,CS为指向调用门的段选择子。
但是值得注意的是CS一旦更换,它的EIP和SS要同时更换。在代码执行的时候,一定会用到堆栈,堆栈的段权限必须与CS匹配(代码的特权级别必须和栈的级别一致),这就是为什么SS必须更换;
注意:JMP FAR只能跳转到同级非一致代码段,但CALL FAR可以通过调用门提权,提升CPL的权限
长调用不提权
当段选择子指向的调用门不提权时,发生改变的寄存器有ESP、EIP和CS,比短调用多一个CS。执行情况如下图所示:
当通过门,权限不变的时候,只会PUSH两个值:CS和返回地址,新的CS的值由调用门决定。
长调用提权
当段选择子指向的调用门不提权时。发生改变的寄存器有ESP、EIP、CS和SS。执行情况如下图所示:
当通过门,权限改变的时候,会PUSH四个值:SS、ESP、CS和返回地址,新的CS的值由调用门决定,新的SS和ESP由TSS提供(不同特权等级的代码段需要使用不同的栈,TSS中保存有三个特权级别下的栈指针-ESP/SS)
调用门描述某个子程序的入口,只存在于GDT中;
调用门内的选择子必须指向代码段描述符,调用门内的偏移是对应代码段内的偏移。利用段间调用指令CALL,通过调用门可实现任务内从外层特权级变换到内层特权级;
调用门执行流程如下所示:
• 指令格式:CALL CS:EIP (EIP是废弃的)
• 执行步骤同长调用
• 根据CS的值查GDT表,找到对应的段描述符且该描述符是一个调用门。
• 在调用门描述符中存储另一个代码段的段选择子,将其加载到CS中。
• 选择子指向的段的Base + 偏移地址就是真正要执行的地址。
• 使用REFT返回(当发生权限切换的时候,堆栈会保存 SS ESP CS EIP(返回地址),RETF本质就是将这些堆栈值进行恢复)
通过调用门进行程序的转移控制时,CPU会检查以下这几个字段:
1.当前代码段的CPL;
2.调用门描述符中的DPL;
3.调用门描述符中的RPL;
4.目的代码描述符的DPL;
5.目标代码段描述符中的一致性标志C
阴影部分表示空闲不用;P标志位,为1时表示在内存中;DPL描述优先级别;类型码101表示任务门。
其他三种门相比,在任务门中不需要用段内位移,因为任务门不指向某一个子程序的入口,而是指向GDT中的TSS描述符。此外,任务门中相对于D标志位的位置永远是0。
任务门执行过程:
1. 通过INT N的指令进行触发任务门
2. 查IDT表,找到任务门描述符
3. 通过任务门描述符,查GDT表,找到TSS段描述符
4. 使用TSS段中的值修改TR寄存器
5. IRETD返回
修改TR寄存器途径:
CPL=0时,可以通过LTR指令去修改TR寄存器。
CPL=3时,可以通过CALL FAR或者JMP FAR指令来修改。用JMP去访问一个任务段的时候,如果是TSS段描述符,先修改TR寄存器,再用TR指向的TSS中的值修改当前的寄存器。
CALL 和 JMP 实现任务切换的不同之处:
如果用CALL,它会在 TSS 中的 Previous Task Link填写数值,并将EFLAGS寄存器的NT位改为1。如果这个位被改为1,iret指令会被当做任务返回,从TSS里的取出Previous Task Link返回;反之则为正常的中断返回,从堆栈读值返回。而JMP指令不会做上述事情。
中断与嵌套调用:
当中断发生时,可以执行常规的中断处理过程,也可以进行任务切换。尽管性质不同,但它们都要使用iret 指令返回。前者是返回到同一任务内的不同代码段;后者是返回到被中断的那个任务,处理器需要区分这两种截然不同的返回类型。
EFLAGS有NT位(位14), 意思是嵌套任务标志(Nested Task Flag)。 每个任务的TSS中都有一个任务链接域 Previous Task Link( 指向前一个任务的指针,可以填写为前一个任务的TSS描述符选择子)。如果当前任务EFLAGS寄存器的NT位是“1”,则表示当前正在执行的任务嵌套于其他任务内,并且能够通过TSS任务链接域的指针返回到前一个任务。
可以使用iret 指令从当前任务返回(转换)到前一个任务,前提是当前任务EFLAGS寄存器的NT位必须是“1"。无论任何时候处理器碰到iret指令,它都要检查NT位:
• 如果此位是0,表明是一般的中断过程,按一般的中断返回处理,即,中断返回是任务内的(中断处理过程虽然属于操作系统,但属于任务的全局空间);
• 如果此位是1,则表明当前任务之所以能够正在执行,是因为中断了别的任务,因此应当返回原先被中断的任务继续执行。此时,由处理器固件把当前任务EFLAGS寄存器的NT位改成“0",并把TSS描述符的B位改成“0”(非忙)。在保存了当前任务的状态之后,接着,用新任务(被中断的任务)的TSS恢复现场。
IDT表与GDT表不同,它的第一个元素不是NULL。IDT表包含3种门描述符:任务门描述符、中断门描述符、陷阱门描述符。
中断描述符表 - IDT 是一个系统表,它与中断或异常向量相联系。每一个中断或异常向量在这个系统表中有对应的中断或异常处理程序入口地址。中断描述符的每一项对应一个中断或异常向量,每个向量由8个字节组成。因此,最多需要 256 * 8=2048 字节来存放IDT。
除了调用门外,中断门也可以用来提权。不提权时,INT N 会压栈CS,EFLAG EIP;提权时,会依次压栈 SS ESP EFLAG CS EIP。需要用堆栈保存EFLAG是因为中断门会将EFLAG的IF位置0。
中断门的结构和调用门结构几乎一样,只是调用门用来写参数数目的位被清空不再使用和Type域不一样而已.
进入中断门:
• 指令格式:INT N (N为中断门索引号)
• 执行步骤:
○ 在没有权限切换时,会向堆栈顺次压入EFLAG、CS和EIP;如果有权限切换,会向堆栈顺次压入SS、ESP、EFLAG、CS和EIP。
○ CPU会索引到IDT表,后面的N表示查IDT表项的下标。对比调用门,中断门没有了RPL,故CPU只会校验CPL。
○ 在中断门中,不能通过RETF返回,而应该通过IRET/IRETD指令返回。
调用门和中断门的区别:
1. 调用门通过CALL FAR指令执行,RETF返回。中断门用INT指令执行,IRET或IRETD返回。
2. 调用门查GDT表。中断门查IDT和GDT表
3. CALL CS:EIP中CS是段选择子,由三部分组成。INT x指令中的x只是索引,中断门不检查RPL,只检查CPL。
4. 调用门可以传参数。中断门不能传参数。
陷阱门执行流程与中断门一模一样。与中断门的区别,中断门执行时,将IF位清零,但陷阱门不会(IF允许/关闭中断)。
中断门和陷阱门在使用上的区别不在于中断是外部产生的还是有CPU本身产生的,而在于:
○ 通过中断门进入中断服务程序时CPU会自动将中断关闭(将EFLAGS寄存器中IF标志位置0),以防止嵌套中断产生;
○ 通过陷阱门进入服务程序时则维持IF标志位不变。
这是二者唯一的区别。
处理器用以下四种方法将控制转换到其他任务:
● 当前程序、任务或者过程执行一个将控制转移到GDT内某个TSS描述符的jmp或者call指令;
● 当前程序、任务或者过程执行一个将控制转移到GDT或者当前LDT内某个任务门描述符的jmp或者call指令;
● 一个异常或者中断发生时,中断号指向中断描述表内的任务门;
● 在EFLAGS寄存器的NT位置位的情况下,当前任务执行了一个iret指令。
jmp、call、iret 指令或者异常和中断,是程序重定向的机制,它们所引用的TSS描述符或者任务门,以及EFLAGS寄存器NT标志的状态,决定了任务切换是否,以及如何发生。在任务切换时,处理器执行以下操作: .
① 从JMP或者CALL指令的操作数、任务门或者当前任务的TSS任务链接域取得新任务的TSS描述符选择子。最后一种方法适用于以iret发起的任务切换。
② 检查是否允许从当前任务(旧任务)切换到新任务。数据访问的特权级检查规则适用于jmp和call指令,当前(旧)任务的CPL和新任务段选择子的RPL必须在数值上小于或者等于目标TSS或者任务门的DPL。异常、中断(除了以int n指令引发的中断)和iret指令引起的任务切换忽略目标任务门或者TSS描述符的DPL。对于以int n指令产生的中断,要检查DPL。
③ 检查新任务的TSS描述符是否已经标记为有效(P=1), 并且界限也有效(大于或者等于0x67,即十进制的103)。
④ 检查新任务是否可用,不忙(B=0,对于以CALL、 JMP、 异常或者中断发起的任务切换)或者忙(B=1,对于以iret发起的任务切换)。
⑤ 检查当前任务(旧任务)和新任务的TSS,以及所有在任务切换时用到的段描述符已经安排到系统内存中。
⑥ 如果任务切换是由jmp或者iret 发起的,处理器清除当前(旧)任务的忙(B) 标志;如果是由call指令、异常或者中断发起的,忙(B)标志保持原来的置位状态。
⑦ 如果任务切换是由iret指令发起的,处理器建立EFLAGS寄存器的一个临时副本并清除其NT标志;如果是由call指令、jmp 指令、异常或者中发起的,副本中的NT标志不变。
⑧ 保存当前(旧)任务的状态到它的TSS中。处理器从任务寄存器中找到当前TSS的基地址,然后将以下寄存器的状态复制到当前TSS 中:所有通用寄存器、段寄存器中的段选择子、刚才那个EFLAGS寄存器的副本,以及指令指针寄存器EIP。
⑨ 如果任务切换是由call 指令、异常或者中断发起的,处理器把从新任务加载的EFLAGS寄存器的NT标志置位;如果是由iret或者jmp指令发起的,NT标志位的状态对应着从新任务加载的EFLAGS寄存器的NT位。
⑩ 如果任务切换是由call指令、jmp 指令、异常或者中断发起的,处理器将新任务TSS描述符中的B位置位;如果是由iret指令发起的,B位保持原先的置位状态不变。
⑪ 用新任务的TSS选择子和TSS描述符加载任务寄存器TR。
⑫ 新任务的TSS状态数据被加载到处理器。这包括LDTR寄存器、PDBR (控制寄存器CR3)、EFLAGS寄存器、EIP寄存器、通用寄存器,以及段选择子。载入状态期间只要发生一个故障,架构状态就会被破坏(因为有些寄存器的内容已被改变,而且无法撤销和回退)。所谓架构,是指处理器对外公开的那一部分的规格和构造;所谓架构状态,是指处理器内部的各种构件,在不同的条件下,所建立起来的确定状态。当处理器处于某种状态时,再施加另一种确定的条件,可以进入另一种确定的状态,这应当是严格的、众所周知的、可预见的。否则,就意味着架构状态遭到了破坏。
⑬ 与段选择子相对应的描述符在经过验证后也被加载。与加载和验证新任务环境有关的任何错误都将破坏架构状态。注意,如果所有的检查和保护工作都已经成功实施,处理器提交任务切换。如果在从第1步到第11步的过程中发生了不可恢复性的错误,处理器不能完成任务切换,并确保处理器返回到执行发起任务切换的那条指令前的状态;如果在第12步发生了不可恢复性的错误,架构状态将被破坏;如果在提交点(第13步)之后发生了不可恢复性的错误,处理器完成任务切换并在开始执行新任务之前产生一个相应的异常。
⑭ 开始执行新任务。
在任务切换时,当前任务的状态总要被保存起来。在恢复执行时,处理器从EIP寄存器的保存值所指向的那条指令开始执行,这个寄存器的值是在当初任务被挂起时保存的。任务切换时,新任务的特权级别并不是从那个被挂起的任务继承来的。新任务的特权级别是由其段寄存器CS的低2位决定的,而该寄存器的内容取自新任务的TSS。因为每个任务都有自己独立的地址空间和任务状态段TSS,所以任务之间是彼此隔离的,只需要用特权级规则控制对TSS的访问就行,软件不需要在任务切换时进行显式的特权级检查。任务状态段TSS的任务链接域和EFLAGS寄存器的NT位用于返回前一个任务执行,当前EFLAGS寄存器的NT位是“1”表明当前任务嵌套于其他任务中。无论如何,新任务的TSS描述符的B位都会被置位,旧任务的B位取决于任务切换的方法。表15-1给出了不同条件下,B位、NT位和任务链接域的变化情况。
https://mp.ofweek.com/ai/a756714121207
https://www.cnblogs.com/wingsummer/tag/Win%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/default.html?page=1
https://cloud.tencent.com/developer/article/1681295