《Intel开发手册卷3》读书笔记3

1、中断和异常的概述

        中断和异常是强制性的执行流的转移,从当前正在执行的程序或任务转移到一个特殊的称作句柄的例程或任务。当硬件发出信号时,便产生中断,中断的产生同正在执行的程序是异步的,即中断的产生是随机的。其用于处理处理器的外部事件,比如为外设服务的请求。使用INTn指令,软件也可以产生中断异常是在处理器执行指令的过程中发现错误而产生的,比如除数为零。处理器可以检测出多种不同的错误,包括保护异常,页错误,内部机器错误。

        当处理器收到中断信号或检测到异常时,便挂起当前正在运行的进程或任务,而转去执行中断或异常处理例程。中断或异常处理例程执行完之后,处理器继续被中断的进程或任务。被中断的进程或任务继续执行,就像从未被打断过一样,只有两种情况例外:无法从发生的异常恢复,中断使当前的程序终止。

        处理器接收到的中断有两个来源:外部(硬件产生的)中断和软件产生的中断。

        外部中断是通过处理器的引脚接收的,也可以通过局部APIC串行总线接收。P6家族或Pentium处理器上主要的中断引脚是连接到局部APIC的LINT[1:0]两个引脚,当局部APIC被关闭时,这两个引脚被分别配置成INTR和NMI引脚。当信号由INTR引脚传递给处理器时,便发生了一个外部中断,处理器从系统总线读取由外部中断控制器发来的中断向量号。若信号从NMI引脚传递进来,则发生的是一个不可屏蔽中断(NMI),其向量号为2任何通过INTR引脚或局部APIC传递到处理器的外部中断都被称作可屏蔽硬件中断。通过INTR引脚传递的可屏蔽硬件中断可使用所有Intel架构定义的中断向量(0~255);而通过局部APIC传递的部分只能使用16~255号向量。

        将中断向量号作为INT指令的操作数即可通过INT指令在程序中产生中断。比如,指令INT35即可调用第35号中断处理例程。虽然INTn指令可以在软件中用来模拟某个异常,但有一点要注意。若该指令中的n指向Intel定义的某个异常,处理器便会产生一个指向相应的中断,接着就是调用相应的处理例程。这其实就相当于一个中断,处理器并不将出错码压入堆栈。于是,即便该异常原本有一个出错码,这时也被略去了。但对于那些带有出错码的异常处理例程,它们退出时会试图去弹出一个并不存在的出错码。此时,处理例程将EIP认为是错误码而弹出,而将一个无关的值弹出给EIP,于是程序返回到一个错误的地方去了

        处理器为每个异常和中断分配了一个识别码,称作向量。向量号从0到31被分配给异常和NMI中断使用。但目前的处理器还未使用完全部的这32个向量。未使用的向量号保留给将来使用。不要将其移做它用。32到255之间的向量号提供给用户使用。这些中断不在Intel的保留部分之列,一般被分配给外部I/O设备,允许它们通过某个外部硬件中断机制向处理器传递信号。

2、异常分类

        异常分为错误,陷阱和终止三种情况:

        1)错误是一种通常能够被修正的异常,一旦修正,程序能够不失连续性地接着执行。当报告错误发生时,处理器将机器状态恢复到执行错误之前的状态。错误处理例程的返回地址(CS和EIP的存储值)指向产生错误的指令,而不是产生错误指令之后的那条指令。注意:只有少数几个异常被报告为错误,它们是不可恢复的,且处理器的上下文中的内容也会有部分丢失

        2)陷阱是一种异常,当引起陷阱的指令发生时,马上产生该异常。陷阱允许程序不失连续性的继续执行。陷阱处理例程的返回地址指向引起陷阱指令的下一条指令

        3)终止是另一种异常,它并不总是报告产生异常的指令的确切位置,也不允许引起终止的进程或任务重新执行。终止被用来报告严重错误,比如硬件错误,不一致或非法系统表值。

        为了使“从中断或异常处理例程返回的”被中断的程序或任务能继续执行,除“终止”之外的所有异常均严格地在前一条指令结束而下一条指令未开始执行时被报告,中断也是在该时刻被检测的。对于错误类的异常,返回地址指向产生错误的指令。所以当从错误处理例程返回时,产生错误的指令将重新被执行

        对陷阱类异常来说,返回地址指针指向的是产生陷阱指令的下一条指令。当一条转移指令执行过程中检测到陷阱时,返回地址指针则反映了执行转向的情况。从陷阱处理例程返回时,进程或任务从INTO指令的下一条指令处开始执行。

        如果在转移执行的指令期间检测到陷阱,则返回指令指针反映该转移。例如,如果在执行 JMP 指令时检测到陷阱,则返回指令指针将指向 JMP 指令的目的地,而不是指向 JMP 指令之后的下一个地址。

        终止类异常不支持进程或任务的继续执行。终止处理例程的作用是:当有终止异常发生时,收集处理器的各种相关诊断信息,并关闭进程或系统

        中断则绝对保证了在不失连续性的条件下,使被中断的进程和任务能继续执行。返回地址指针指向发生中断时的下一条指令。对于带重复前缀的指令,中断发生在两次循环之间。当 NMI 中断处理程序正在执行时,处理器会阻止后续 NMI 的传送,直到下一次执行 IRET 指令。这种 NMI 阻塞可防止 NMI 处理程序的嵌套执行。

        当通过 INTR 引脚生成异常向量中断时,处理器不会将错误代码推送到堆栈上,因此异常处理程序可能无法正常运行。

3、中断优先级

        如果在指令边界有多个异常或中断发生,处理器将以预定的顺序来为它们提供服务。

4、中断描述符表IDT

        中断描述符表(IDT)为每一个异常或中断向量对应的例程或任务分配了一个门描述符。同GDT和诸多LDT一样,IDT也是由一系列由8个字节组成的描述符组成的(在保护模式下)。和GDT不同的是,IDT中的第一个元不是NULL描述符。异常或中断向量号乘上8即可得到IDT中的描述符的索引(即门描述符包含的字节数)。由于只有256个中断或异常向量,所以IDT不必包含多于256个描述符。并且可以包含不足256个的描述符,因为只有那些确实发生的异常或中断才需要一个描述符。所有IDT中的空描述符须将存在位设为0。IDT 的基地址应在 8 字节边界上对齐,以最大限度地提高缓存行填充的性能。IDT 可以驻留在线性地址空间中的任何位置。处理器使用 IDTR 寄存器来定位 IDT。该寄存器保存 IDT 的 32 位基地址和 16 位限制

        LIDT(加载IDT寄存器)和SIDT(存储IDT寄存器)指令分别加载和存储IDTR寄存器的内容。 LIDT 指令将内存操作数中保存的基地址和限制加载到 IDTR 寄存器中。该指令只有当CPL为0时才可以执行。通常由操作系统的初始化代码在创建IDT时使用。操作系统也可以使用它从一个 IDT 更改为另一个 IDT。 SIDT 指令将 IDTR 中存储的基值和极限值复制到内存。该指令可以在任何权限级别执行。

        IDT 可以包含三种门描述符中的任意一种:

                • 任务门描述符

                • 中断门描述符

                 • 陷阱门描述符

        

         IDT 中使用的任务门的格式与 GDT 或 LDT 中使用的任务门的格式相同任务门包含用于异常和/或中断处理程序任务的 TSS 的段选择器。中断门和陷阱门与调用门非常相似(,它们包含一个远指针(段选择器和偏移量),处理器使用该远指针将程序执行转移到异常或中断处理程序代码段中的处理程序过程。这些门的不同之处在于处理器处理 EFLAGS 寄存器中 IF 标志的方式。

        处理器处理对异常和中断处理程序的调用的方式与处理使用 CALL 指令对过程或任务的调用的方式类似。当响应异常或中断时,处理器使用异常或中断向量作为 IDT 中描述符的索引。如果索引指向中断门或陷阱门,处理器将以类似于调用调用门的 CALL 的方式调用异常或中断处理程序。如果索引指向任务门,处理器将以类似于调用任务门的方式执行到异常或中断处理程序任务的任务切换中断门或陷阱门引用在当前执行任务的上下文中运行的异常或中断处理程序过程,门的段选择器指向 GDT 或当前 LDT 中可执行代码段的段描述符。门描述符的偏移字段指向异常或中断处理过程的开始。

        当处理器执行对异常或中断处理程序的调用时:

                1)如果处理程序要在数值较低的特权级别上执行,则会发生堆栈切换。当发生堆栈切换时:

                        1.a)从当前执行任务的 TSS 中获取处理程序要使用的堆栈的段选择器和堆栈指针。在这个新堆栈上,处理器推送被中断过程的堆栈段选择器和堆栈指针

                        1.b) 然后,处理器将 EFLAGS、CS 和 EIP 寄存器的当前状态保存在新堆栈上

                        1.c))如果异常导致错误代码被保存,则会将其推送到新堆栈上的 EIP 值之后

                2)如果处理程序过程将以与被中断过程相同的权限级别执行:

                       2.a)处理器将 EFLAGS、CS 和 EIP 寄存器的当前状态保存在当前堆栈上

                       2.b)如果异常导致错误代码被保存,则会将其推送到当前堆栈中的 EIP 值之后

        

要从异常或中断处理程序过程返回,处理程序必须使用 IRET(或 IRETD)指令。IRET 指令与 RET 指令类似,只是它将保存的标志恢复到 EFLAGS 寄存器中。仅当 CPL 为 0 时,EFLAGS 寄存器的 IOPL 字段才会恢复。仅当 CPL 小于或等于 IOPL 时,IF 标志才会更改。如果在调用处理程序过程时发生堆栈切换,IRET 指令将在返回时切换回被中断过程的堆栈

        中断门和陷阱门之间的唯一区别是处理器处理 EFLAGS 寄存器中 IF 标志的方式当通过中断门访问异常或中断处理过程时,处理器会清除 IF 标志,以防止其他中断干扰当前中断处理程序。随后的 IRET 指令将 IF 标志恢复为其堆栈上 EFLAGS 寄存器保存的内容中的值。通过陷阱门访问处理程序不会影响 IF 标志

        当通过 IDT 中的任务门访问异常或中断处理程序时,会导致任务切换。使用单独的任务处理异常或中断有几个优点:

                • 被中断的程序或任务的整个上下文会自动保存。

                • 新的TSS 允许处理程序在处理异常或中断时使用新的特权级0 堆栈。如果当前特权级 0 堆栈损坏时发生异常或中断,则通过任务门访问处理程序可以通过为处理程序提供新的特权级 0 堆栈来防止系统崩溃。

                • 通过为处理程序提供单独的地址空间,可以进一步将其与其他任务隔离。这是通过给它一个单独的 LDT 来完成的。

        使用单独任务处理中断的缺点是任务切换时必须保存的机器状态量使其比使用中断门慢,从而导致中断延迟增加

·        IDT 中的任务门引用 GDT 中的 TSS 描述符。切换到处理程序任务的处理方式与普通任务切换相同。返回到中断任务的链接存储在处理程序任务的 TSS 的前一个任务链接字段中。如果异常导致生成错误代码,则该错误代码将复制到新任务的堆栈中

        由于IA-32体系结构任务不可重入,因此中断处理程序任务必须在完成处理时和执行IRET指令之间禁用中断。此操作可防止在中断任务的TSS仍标记为忙碌时发生另一个中断,这将导致通用保护(#GP)异常。

5、任务管理

        任务是处理器可以分派、执行和挂起的工作单元。它可用于执行程序、任务或进程、操作系统服务实用程序、中断或异常处理程序、或者内核或执行实用程序。任务由两部分组成:任务执行空间和任务状态段(TSS)。任务执行空间由代码段、堆栈段和一个或多个数据段组成。如果操作系统或执行程序使用处理器的特权级保护机制,则任务执行空间还为每个特权级提供单独的堆栈TSS指定了组成任务执行空间的段,并为任务状态信息提供存储位置。在多任务系统中,TSS还提供了一种链接任务的机制。任务由其 TSS 的段选择器来标识。当任务加载到处理器中执行时,TSS 的段选择器、基地址、限制和段描述符属性将加载到任务寄存器TR中。如果任务实现了分页,则任务使用的页目录的基地址被加载到控制寄存器CR3中

        

当前正在执行的任务的状态b包含以下内容:

        • 任务的当前执行空间,由段寄存器中的段选择器(CS、DS、SS、ES、FS 和 GS)定义。

        • 通用寄存器的状态。

        • EFLAGS 寄存器的状态。

        • EIP 寄存器的状态。

        • 控制寄存器CR3 的状态。

        • 任务寄存器的状态。

        • LDTR 寄存器的状态。

        • I/O 映射基址和I/O 映射(包含在TSS 中)。

        • 指向特权0、1 和2 堆栈(包含在TSS 中)的堆栈指针。

        • 链接到先前执行的任务(包含在TSS 中)。

        • 影子堆栈指针(SSP) 的状态。

         在调度任务之前,除任务寄存器的状态外,上述所有项都包含在任务的TS中。此外,LDTR寄存器的完整内容不包含在TSS中,仅包含LDT的段选择器

         软件或处理器可通过以下方式之一调度任务的执行: 1) 使用 CALL 指令明确调用任务;2) 使用 JMP 指令明确跳转到任务;3)隐式调用(由处理器)中断处理程序任务;4)隐式调用异常处理任务;5)当 EFLAGS 寄存器中的 NT 标志被设置时的返回(由 IRET 指令启动)。所有这些调度任务的方法都通过指向任务门或任务 TSS 的段选择器来确定要调度的任务。使用 CALL 或 JMP 指令调度任务时,指令中的选择器可以直接选择 TSS,也可以选择持有 TSS 选择器的任务门当通过调度任务来处理中断或异常时,中断或异常的 IDT 条目必须包含一个任务门,该任务门持有 中断或异常处理程序 TSS 的选择器

        当任务被调度执行时,当前运行的任务和调度的任务之间会发生任务切换。在任务切换期间,当前正在执行的任务的执行环境(称为任务状态或上下文)会保存在其 TSS 中,任务的执行也会暂停。然后,被调度任务的上下文被加载到处理器中,该任务的执行从新加载的 EIP 寄存器所指向的指令开始。如果任务在系统上次初始化后尚未运行,EIP 将指向任务代码的第一条指令;否则,它将指向任务上次激活时执行的最后一条指令后的下一条指令。如果当前执行的任务(调用任务)调用了被调度的任务(被调用任务),则调用任务的 TSS 段选择器将存储在被调用任务的 TSS 中,以提供返回调用任务的链接。对于所有 IA-32 处理器,任务都不是递归的。任务不能调用或跳转到自身

        可以通过任务切换到处理程序任务来处理中断和异常。在这里,处理器执行任务切换以处理中断或异常,并在从中断处理程序任务或异常处理程序任务返回时自动切换回被中断的任务。这种机制还能处理中断任务期间发生的中断。作为任务切换的一部分,处理器还可以切换到另一个 LDT,从而使每个任务都能为基于 LDT 的网段提供不同的逻辑到物理地址映射。页面目录基础寄存器 (CR3) 也会在任务切换时重新加载,从而使每个任务都能拥有自己的页面表。这些保护机制有助于隔离任务,防止它们相互干扰。如果不使用保护机制,处理器就不会在任务之间提供保护。即使是使用多权限级别保护的操作系统也是如此。

        处理器定义了五种数据结构,用于处理与任务相关的活动: 1)任务状态段(TSS);2)任务门描述符;3)TSS 描述符;4)任务寄存器TR; 5)EFLAGS 寄存器中的 NT 标志。在保护模式下运行时,至少需要为一个任务创建 TSS 和 TSS 描述符,并将 TSS 的段选择器装入任务寄存器(使用 LTR 指令)。

        还原任务所需的处理器状态信息保存在称为任务状态段(TSS)的系统段中,TSS 的字段主要分为两类:动态字段和静态字段。在任务切换期间暂停任务时,处理器会更新动态字段。以下是动态字段:

                1)通用寄存器字段 - 任务切换前 EAX、ECX、EDX、EBX、ESP、EBP、ESI 和 EDI 寄存器的状态。

               2)段选择器字段 - 任务切换前存储在 ES、CS、SS、DS、FS 和 GS 寄存器中的段选择器。- EFLAGS 寄存器字段 - 任务切换前 EFLAGS 寄存器的状态。

                3)EIP(指令指针)字段 - 任务切换前 EIP 寄存器的状态。

                4)上一任务链接字段 - 包含上一任务 TSS 的段选择器(由调用、中断或异常启动的任务切换时更新)。该字段(有时也称为后置链接字段)允许使用 IRET 指令将任务切换回前一任务。

        处理器会读取静态字段,但通常不会更改它们。这些字段是在创建任务时设置的。以下是静态字段:

                1) LDT 段选择器字段 - 包含任务 LDT 的段选择器。

                2)CR3 控制寄存器字段 - 包含任务使用的页面目录的基本物理地址。控制寄存器 CR3 也称为页面目录基寄存器(PDBR)。

                3)0、1 和 2 级权限堆栈指针字段 - 这些堆栈指针由堆栈段的段选择器(SS0、SS1 和 SS2)和堆栈偏移量(ESP0、ESP1 和 ESP2)组成的逻辑地址构成。需要注意的是,这些字段中的值对于特定任务来说是静态的;而如果任务内发生堆栈切换,SS 和 ESP 值将发生变化。

                4) T(调试陷阱)标志(字节 100,位 0)- 设置 T 标志后,当发生任务切换到该任务时,处理器将引发调试异常(请参阅第 18.3.1.5 节 "任务切换异常条件")。

                5) I/O 映射基地址字段 - 包含从 I/O 映射基地址开始的 16 位偏移。

        如果使用分页: 1)与前一个任务的 TSS、当前任务的 TSS 以及每个任务的描述符表项相对应的页面都应标记为可读/可写。2) 如果在启动任务切换之前,内存中已存在包含这些结构的页面,则任务切换的速度会更快。

6、TSS段

        与所有其他段一样,TSS 由段描述符定义。  TSS描述符只能放在GDT中;它们不能放置在 LDT 或 IDT 中。尝试使用设置了 TI 标志(指示当前 LDT)的段选择器访问 TSS 会导致在 CALL 和 JMP 期间生成一般保护异常 (#GP);它会在 IRET 期间导致无效的 TSS 异常 (#TS)。如果尝试将 TSS 的段选择器加载到段寄存器中,也会生成一般保护异常。类型字段中的繁忙标志(B)指示任务是否繁忙。繁忙的任务当前正在运行或暂停。值为1001B的类型字段表示非活动任务;值 1011B 表示任务繁忙。任务不是递归的。处理器使用繁忙标志来检测调用已中断执行的任务的尝试。为了确保只有一个忙碌标志与一项任务相关联,每个 TSS 应该只有一个指向它的 TSS 描述符。

        

        任何能够访问 TSS 描述符的程序或过程(即,其 CPL 在数值上等于或小于 TSS 描述符的 DPL)都可以通过调用或跳转来切换任务。在大多数系统中,TSS描述符的DPL被设置为小于3的值,因此只有特权软件才能执行任务切换。然而,在多任务应用程序中,某些 TSS 描述符的 DPL 可能设置为 3,以允许在应用程序(或用户)权限级别进行任务切换。在 64 位模式下,TSS 描述符扩展为 16 个字节。此扩展也适用于 64 位模式的 LDT 描述符。

        任务寄存器保存当前任务的 TSS 的 16 位段选择器和整个段描述符(32 位基地址(IA-32e 模式下为 64 位)、16 位段限制和描述符属性)。该信息是从当前任务的 GDT 中的 TSS 描述符复制的。任务寄存器有可见部分(可以由软件读取和更改)和不可见部分(由处理器维护,软件无法访问)。可见部分的段选择器指向GDT中的一个TSS描述符。处理器使用任务寄存器的不可见部分来缓存 TSS 的段描述符。将这些值缓存在寄存器中可以提高任务的执行效率。

         LTR(加载任务寄存器)和 STR(存储任务寄存器)指令加载并读取任务寄存器的可见部分: LTR 指令将段选择器(源操作数)加载到指向 GDT 中 TSS 描述符的任务寄存器中,然后,它使用 TSS 描述符中的信息加载任务寄存器的不可见部分。 LTR 是一条特权指令,只有当 CPL 为 0 时才可以执行。它在系统初始化期间用于将初始值放入任务寄存器中。此后,当任务切换发生时,任务寄存器的内容会隐式更改。STR(存储任务寄存器)指令将任务寄存器的可见部分存储在通用寄存器或存储器中。该指令可以由在任何权限级别运行的代码执行,以便识别当前正在运行的任务。但是,它通常仅由操作系统软件使用。 (如果 CR4.UMIP = 1,则仅当 CPL = 0 时才能执行 STR。)

7、任务门描述符

        任务门描述符提供对任务的间接、受保护的引用。它可以放置在 GDT、LDT 或 IDT 中。任务门描述符中的 TSS 段选择器字段指向 GDT 中的 TSS 描述符。该段选择器中的 RPL 未使用。 任务门描述符的 DPL 控制任务切换期间对 TSS 描述符的访问。当程序或过程通过任务门调用或跳转到任务时,指向任务门的门选择器的 CPL 和 RPL 字段必须小于或等于任务门描述符的 DPL。请注意,当使用任务门时,不使用目标 TSS 描述符的 DPL。

        可以通过任务门描述符或 TSS 描述符来访问任务。这两种结构都满足以下需求:

                 • 任务需要仅具有一个繁忙标志:由于任务的繁忙标志存储在 TSS 描述符中,因此每个任务应仅具有一个 TSS 描述符。然而,可能有多个任务门引用相同的 TSS 描述符。

                • 需要提供对任务的选择性访问:任务门满足了这一需求,因为它们可以驻留在LDT 中,并且可以具有与TSS 描述符的DPL 不同的DPL。没有足够权限访问 GDT 中任务的 TSS 描述符(通常 DPL 为 0)的程序或过程可以通过具有更高 DPL 的任务门来访问该任务。任务门为操作系统提供了更大的自由度来限制对特定任务的访问。

                • 需要由独立任务处理中断或异常:任务门也可以驻留在IDT 中,这允许由处理程序任务处理中断和异常。当中断或异常向量指向任务门时,处理器会切换到指定的任务。

8、任务切换

        处理器在以下四种情况之一中将执行转移到另一个任务:

                 • 当前程序、任务或过程执行 JMP 或 CALL 指令,指向 GDT 中的 TSS 描述符。

                 • 当前程序、任务或过程执行 JMP 或 CALL 指令,指向 GDT 或当前 LDT 中的任务门描述符。

                • 中断或异常向量指向 IDT 中的任务门描述符。

                • 当 EFLAGS 寄存器中的 NT 标志被设置时,当前任务执行 IRET。

        JMP、CALL 和 IRET 指令以及中断和异常都是重定向程序的机制。TSS 描述符或任务门的引用(调用或跳转到任务时)或 NT 标志的状态(执行 IRET 指令时)决定是否发生任务切换。切换到新任务时,处理器执行以下操作:

                1. 从任务门或前一个任务链接字段(对于使用 IRET 指令启动的任务切换)获取新任务的 TSS 段选择器作为 JMP 或 CALL 指令的操作数。

                2. 检查当前(旧)任务是否允许切换到新任务。数据访问权限规则适用于 JMP 和 CALL 指令。当前(旧)任务的 CPL 和新任务的段选择器的 RPL 必须小于或等于所引用的 TSS 描述符或任务门的 DPL。无论目标任务门或 TSS 描述符的 DPL 如何,异常、中断(下一句中标识的中断除外)以及 IRET 和 INT1 指令都可以切换任务。对于由 INT n、INT3 和 INTO 指令生成的中断,将检查 DPL,如果 DPL 小于 CPL,则会产生一般保护异常 (#GP)

                3. 检查新任务的 TSS 描述符是否标记为存在且具有有效限制(大于或等于 67H)。如果任务切换由 IRET 发起,并且影子堆栈在当前 CPL 下启用,则 SSP 必须对齐到 8 个字节,否则会生成 #TS(当前任务 TSS)故障。如果 CR4.CET 为 1,则 TSS 必须是 32 位 TSS,并且新任务的 TSS 限制必须大于或等于 107 个字节,否则会生成 #TS(新任务 TSS)故障。

                4. 检查新任务是否可用(调用、跳转、异常或中断)或忙(IRET 返回)。

                 5. 检查当前(旧)TSS、新 TSS 以及任务切换中使用的所有段描述符是否已分页到系统内存中。

                6. 将当前(旧)任务的状态保存在当前任务的 TSS 中。处理器在任务寄存器TR中找到当前 TSS 的基址,然后将以下寄存器的状态复制到当前 TSS 中:所有通用寄存器、来自段寄存器的段选择器、EFLAGS 寄存器的临时保存映像以及指令指针寄存器 (EIP)

                 7. 将新任务的 TSS 的段选择器和描述符加载到任务寄存器中。

                8. 影子堆栈相关操作。

                9. TSS 状态被加载到处理器中。这包括 LDTR 寄存器、PDBR(控制寄存器 CR3)、EFLAGS 寄存器、EIP 寄存器、通用寄存器和段选择器。加载此状态期间的故障可能会破坏架构状态。(如果未启用分页,则会从新任务的 TSS 读取 PDBR 值,但不会将其加载到 CR3 中)。

                10. 如果任务切换是通过 JMP 或 IRET 指令发起的,则处理器会清除当前(旧)任务的 TSS 描述符中的忙(B)标志;如果是通过 CALL 指令、异常或中断发起的:忙(B)标志保持设置状态。

                11. 如果任务切换是通过 IRET 指令发起的,则处理器会清除 EFLAGS 寄存器的临时保存映像中的 NT 标志;如果任务切换由 CALL 或 JMP 指令、异常或中断发起,则保存的 EFLAGS 映像中的 NT 标志保持不变

                12. 如果任务切换是由 CALL 指令、异常或中断发起的,则处理器将在从新任务加载的 EFLAGS 中设置 NT 标志。如果由 IRET 指令或 JMP 指令发起,则 NT 标志将反映从新任务加载的 EFLAGS 中的 NT 状态。 

                13. 如果任务切换是由 CALL 指令、JMP 指令、异常或中断发起的,则处理器将在新任务的 TSS 描述符中设置忙 (B) 标志;如果由 IRET 指令发起,则忙 (B) 标志保持设置状态

                 14. 与段选择器关联的描述符已加载并限定。与此加载和限定相关的任何错误都发生在新任务的上下文中,并可能破坏架构状态。

                15. 影子堆栈一些相关操作。

                16. 开始执行新任务。

        如果所有检查和保存均已成功完成,则处理器将进行任务切换。如果在步骤 1 至步骤 8 中发生不可恢复的错误,则处理器不会完成任务切换,并确保处理器返回到执行启动任务切换的指令之前的状态。如果在步骤 9 中发生不可恢复的错误,则架构状态可能会损坏,但会尝试在先前的执行环境中处理该错误。如果在提交点之后(在步骤 13 中)发生不可恢复的错误,则处理器完成任务切换(不执行额外的访问和段可用性检查)并在开始执行新任务之前生成适当的异常。如果异常发生在提交点之后,异常处理程序必须先完成任务切换,然后才允许处理器开始执行新任务。

        任务切换成功时,始终会保存当前正在执行任务的状态。如果任务恢复,则从保存的 EIP 值指向的指令开始执行,寄存器将恢复为任务暂停时的值。 切换任务时,新任务的特权级别不会从暂停的任务继承其特权级别。新任务开始在 CS 寄存器的 CPL 字段中指定的特权级别执行,该字段从 TSS 加载。由于任务由其单独的地址空间和 TSS 隔离,并且特权规则控制对 TSS 的访问,因此软件不需要在任务切换时执行显式特权检查。 每次发生任务切换时,控制寄存器 CR0 中的 TS(任务切换)标志都会被设置。系统软件使用 TS 标志来协调浮点单元在与处理器其余部分生成浮点异常时的操作。

        TSS 的上一个任务链接字段(有时称为“反向链接”)和 EFLAGS 寄存器中的 NT 标志用于将执行返回到上一个任务EFLAGS.NT = 1 表示当前执行的任务嵌套在另一个任务的执行中。 当 CALL 指令、中断或异常导致任务切换时:处理器将当前 TSS 的段选择器复制到新任务的 TSS 的上一个任务链接字段;然后设置 EFLAGS.NT = 1。 如果软件使用 IRET 指令暂停新任务,处理器将检查 EFLAGS.NT = 1;然后使用上一个任务链接字段中的值返回到上一个任务。

        NT 标志可由在任何特权级别执行的软件修改。程序可以设置 NT 标志并执行 IRET 指令。这可能会随机调用当前任务的 TSS 的上一个链接字段中指定的任务。为了防止此类虚假任务切换成功,操作系统应将其创建的每个 TSS 中的上一个任务链接字段初始化为 0。

8.1、使用忙碌标志避免地任务递归切换

        TSS 只允许为一个任务保存一个上下文;因此,一旦调用了任务,对该任务的递归(或可重入)调用将导致任务的当前状态丢失。TSS 段描述符中的忙标志用于防止可重入任务切换以及随后的任务状态信息丢失。处理器按如下方式管理忙标志:

                1. 调度任务时,处理器设置新任务的忙标志。

                2. 如果在任务切换期间,当前任务处于嵌套链中(任务切换由 CALL 指令、中断或异常生成),则当前任务的忙标志保持设置状态。

                3. 切换到新任务(由 CALL 指令、中断或异常启动)时,如果新任务的忙标志已设置,则处理器生成通用保护异常 (#GP)。如果任务切换由 IRET 指令启动,则不会引发异常,因为处理器期望设置忙标志

                4. 当通过跳转到新任务(使用任务代码中的 JMP 指令启动)或通过任务代码中的 IRET 指令终止任务时,处理器将清除忙标志,使任务返回“不忙”状态 处理器通过阻止任务切换到自身或嵌套任务链中的任何任务来防止递归任务切换由于多次调用、中断或异常,嵌套挂起任务链可能会增长到任意长度。忙标志会阻止调用此链中的任务。 忙标志可用于多处理器配置,因为处理器在设置或清除忙标志时遵循 LOCK 协议(在总线上或缓存中)。此锁定可防止两个处理器同时调用同一任务。

        在单处理器系统中,如果需要从链接任务链中删除某个任务,需要执行以下过程:

                1. 禁用中断。

                2. 更改抢占任务(暂停要删除的任务的任务)的 TSS 中的前一个任务链接字段。假设抢占任务是要删除的任务链中的下一个任务(较新的任务),将前一个任务链接字段更改为指向链中下一个最旧任务的 TSS 或指向链中更旧的任务。

                3. 清除要从链中删除的任务的 TSS 段描述符中的忙 (B) 标志。如果要从链中删除多个任务,则必须清除要删除的每个任务的忙标志。

                 4. 启用中断。 在多处理系统中,必须将额外的同步和序列化操作添加到此过程,以确保在更改前一个任务链接字段和清除忙标志时,TSS 及其段描述符都处于锁定状态。

        任务的地址空间由任务可以访问的段组成。这些段包括 TSS 中引用的代码、数据、堆栈和系统段以及任务代码访问的任何其他段。这些段被映射到处理器的线性地址空间,而后者又被映射到处理器的物理地址空间(直接或通过分页)。 TSS 中的 LDT 段字段可用于为每个任务提供自己的 LDT。为任务提供自己的 LDT 允许将与任务相关的所有段的段描述符放置在任务的 LDT 中,从而将任务地址空间与其他任务隔离。 多个任务也可以使用相同的 LDT。这是一种内存高效的方式,允许特定任务相互通信或控制,而不会破坏整个系统的保护屏障。由于所有任务都可以访问 GDT,因此也可以创建通过此表中的段描述符访问的共享段。如果启用了分页,TSS 中的 CR3 寄存器 (PDBR) 字段允许每个任务拥有自己的一组页表,用于将线性地址映射到物理地址。或者,多个任务可以共享同一组页表。

        任务可以通过以下两种方式之一映射到线性地址空间和物理地址空间:

                • 所有任务共享一个线性到物理地址空间映射。 — 当未启用分页时,这是唯一的选择。如果没有分页,所有线性地址都会映射到相同的物理地址。启用分页后,这种形式的线性到物理地址空间映射是通过对所有任务使用一个页面目录来获得的。如果支持按需分页虚拟内存,线性地址空间可能会超出可用的物理空间。

                • 每个任务都有自己的线性地址空间,该空间映射到物理地址空间。 — 这种映射形式是通过为每个任务使用不同的页面目录来实现的。由于 PDBR(控制寄存器 CR3)是在任务切换时加载的,因此每个任务可能具有不同的页面目录。 不同任务的线性地址空间可能映射到完全不同的物理地址。如果不同页面目录的条目指向不同的页表,并且页表指向物理内存的不同页面,则任务不共享物理地址。

        无论使用哪种映射任务线性地址空间的方法,所有任务的 TSS 都必须位于物理空间的共享区域中,该区域可供所有任务访问。这种映射是必需的,以便处理器在任务切换期间读取和更新 TSS 时,TSS 地址的映射不会发生变化。GDT 映射的线性地址空间也应映射到物理空间的共享区域;否则,GDT 的目的就落空了

        为了允许任务间共享数据,请使用以下技术为数据段创建共享的逻辑到物理地址空间映射:

                 • 通过 GDT 中的段描述符 — 所有任务都必须能够访问 GDT 中的段描述符。如果 GDT 中的某些段描述符指向线性地址空间中的段,而这些段被映射到所有任务共用的物理地址空间区域,则所有任务都可以共享这些段中的数据和代码。

                 • 通过共享 LDT — 如果两个或多个任务的 TSS 中的 LDT 字段指向同一个 LDT,则它们可以使用同一个 LDT。如果共享 LDT 中的某些段描述符指向映射到物理地址空间公共区域的段,则这些段中的数据和代码可以在共享 LDT 的任务之间共享。这种共享方法比通过 GDT 共享更具选择性,因为共享可以限制在特定任务中。系统中的其他任务可能具有不同的 LDT,这些 LDT 不允许它们访问共享段。

                • 通过不同 LDT 中的段描述符(这些段描述符被映射到线性地址空间中的公共地址)— 如果线性地址空间的这个公共区域被映射到每个任务的物理地址空间的相同区域,这些段描述符允许任务共享段。此类段描述符通常称为别名。这种共享方法比上面列出的方法更具选择性,因为 LDT 中的其他段描述符可能指向不共享的独立线性地址。

        在 64 位模式下,任务结构和任务状态与保护模式下的类似。但是,保护模式下可用的任务切换机制在 64 位模式下不受支持。任务管理和切换必须由软件执行。如果在 64 位模式下尝试执行以下操作,处理器将发出一般保护异常 (#GP)

                • 使用 JMP、CALL、INT n、INT3、INTO、INT1 或中断将控制权转移到 TSS 或任务门。

                 • 将 EFLAGS.NT(嵌套任务)设置为 1 的 IRET。

        虽然 64 位模式不支持硬件任务切换,但必须存在 64 位任务状态段 (TSS)。 TSS 保存对 64 位模式很重要且与任务切换机制无直接关系的信息。这些信息包括:

                • RSPn — 特权级别 0-2 的堆栈指针 (RSP) 的完整 64 位规范形式

                • ISTn — 中断堆栈表 (IST) 指针的完整 64 位规范形式。

                • I/O 映射基址 — 从 64 位 TSS 基址到 I/O 权限位图的 16 位偏移量。 激活 IA-32e 模式后,操作系统必须创建至少一个 64 位 TSS。它必须执行 LTR 指令(在 64 位模式下)以将指向 64 位 TSS 的指针加载到 TR 寄存器中,该 TSS 负责 64 位模式程序和兼容模式程序。

9、多处理器管理

        32 位 IA-32 处理器支持对系统内存中的位置执行锁定的原子操作。这些操作通常用于管理共享数据结构(例如信号量、段描述符、系统段或页表),其中两个或多个处理器可能会同时尝试修改同一字段或标志。处理器使用三种相互依赖的机制来执行锁定的原子操作:

               • 保证原子操作。

                 • 总线锁定,使用 LOCK# 信号和 LOCK 指令前缀。

                • 缓存一致性协议,确保可以对缓存数据结构(缓存锁)执行原子操作; Pentium 4、Intel Xeon 和 P6 系列处理器中存在此机制。

        Intel 64 和 IA-32 处理器提供 LOCK# 信号,该信号在某些关键内存操作期间自动置位,以锁定系统总线或等效链路。此信号的置位称为总线锁定。当此输出信号置位时,来自其他处理器或总线代理的总线控制请求将被阻止。软件可以通过在指令前添加 LOCK 前缀来指定其他需要遵循 LOCK 语义的情况。  

         锁定指令保证只锁定目标操作数定义的内存区域,但系统可能会将其解释为对更大内存区域的锁定。 软件应使用相同的地址和操作数长度访问信号量(用于在多个处理器之间发送信号的共享内存)。例如,如果一个处理器使用字访问来访问信号量,则其他处理器不应使用字节访问来访问该信号量。

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值