任务是处理器可以分派、执行和挂起的工作单元,用于执行程序、任务或进程、操作系统服务实用程序、中断或异常处理程序、内核或执行实用程序。
IA-32架构提供了一种机制,用于保存任务的状态,调度任务执行以及任务切换。在保护模式下,所有处理器的执行都在任务内进行。即使是简单的系统也必须定义至少一个任务。更复杂的系统可以使用处理器的任务管理功能来支持多任务应用程序。
80x86
架构提供了多核处理器、多线程支持、分段与分页机制、任务状态段(TSS)、任务门、中断描述符表(IDT)、任务切换指令、本地APIC、调度优先级等硬件特性,为操作系统实现任务管理和多任务处理提供基本支持。
任务是一个正在运行的程序,或者是一个等待准备运行的程序。通过中断、异常、跳转或调用,我们可以执行一个任务。当这些控制转移形式之一和某个描述符表中指定项的内容一起使用时,那么这个描述符就是一类导致新任务开始执行的描述符。
描述符表中与任务相关的描述符有两类:任务状态段描述符和任务门。当执行权传给这任何一类描述符时,都会造成任务切换。
任务切换与过程调用相似,但存在一些区别:
任务由两部分组成:任务执行空间和任务状态段TSS
如果使用了分页机制,则当一个任务被加载进处理器中执行时,任务使用的页目录的基址也将加载到控制寄存器CR3中。
当前执行任务的状态由处理器所有以下内容组成:
在调度任务之前,除了任务寄存器的状态外,所有这些项都包含在任务的TSS中。但是,LDTR寄存器的完整内容并不包含在TSS中,只包含LDT的段选择子。
这些状态信息是任务的运行时环境,记录了任务执行的位置、状态和上下文信息。在进行任务切换时,需要保存当前任务的这些状态信息,以便稍后恢复到相同的执行点并继续执行。任务切换发生时,操作系统会将当前任务的状态信息保存到TSS中或其他数据结构中,然后切换到新任务的状态信息,确保任务的切换和执行的连续性。
软件或处理器可以通过下列方式之一调度任务执行:
CALL
指令对任务的显式调用JMP
指令显式地跳转到任务(Linux内核使用的方式)IRET
指令返回(EFLAGS寄存器中的NT标志为1)所有这些调度任务执行的方法都会使用一个指向任务门或任务TSS段的选择子来确定一个任务。
CALL
或JMP
指令调度一个任务时,指令中的选择子
当任务被调度执行时,会在当前运行的任务和被调度的任务之间发生任务切换。
如果当前执行的任务(调用任务)调用正在分派的任务(被调用任务),则调用任务的TSS段选择器存储在被调用任务的TSS中,以提供回调用任务的链接。
对于所有IA-32处理器,任务都不是递归的,即任务不能调用或跳转到自己。
中断和异常可以通过任务切换到处理程序任务来处理。在这里,处理器执行任务切换来处理中断或异常,并在从中断处理程序任务或异常处理程序任务返回时自动切换回被中断的任务。该机制还可以处理在中断任务期间发生的中断。
作为任务切换的一部分,处理器还可以切换到另一个LDT,允许每个任务对基于LDT的段具有不同的逻辑到物理地址映射。页目录基寄存器(CR3)也在切换时被重新加载,从而允许每个任务拥有自己的一组页表,有助于隔离任务防止相互干扰。
使用任务管理工具来处理多任务应用程序是可选的,可以使用软件来实现多任务,使得每
个软件定义的任务在一个IA-32架构的任务上下文中执行。
处理器定义了五种数据结构来处理与任务相关的活动:
在保护模式下运行时,必须为至少一个任务创建TSS和TSS描述符,并且必须将TSS的段选择器加载到任务寄存器中(使用
LTR
指令)。
恢复任务所需的处理器状态信息保存在称为任务状态段(TSS)的系统段中。TSS的字段分为两大类:动态字段和静态字段。
IRET
指令将任务切换回前一个任务)。请注意,对于特定任务,这些字段中的值是静态的;然而,如果任务内部发生堆栈交换,则寄存器SS和ESP内容将发生变化。
当存在时,这些映射存储在TSS较高的地址。I/O映射基址指向I/O权限位映射的开始和中断重定向位映射的结束。
如果使用分页机制,需要注意:
TSS(任务状态段),像所有其他段一样,由段描述符定义。
CALL
和JMP
期间引发通用保护异常(#GP),在IRET
期间引发无效TSS异常(#TS)。TSS描述符格式与其他段描述符相似:
Type字段中的忙碌标志(B)指示任务是否处于忙碌状态。
任务是不可以递归执行的,因此处理器使用忙碌标志来检测对已中断任务执行调用的尝试。
基地址、段限长、描述符特权级DPL、颗粒度G和存在位P具有与数据段描述符中相应字段同样的功能。
67H
的值,即比TSS的最小大小少一个字节。试图切换到TSS描述符的限长小于67H
的任务会引发无效TSS异常(#TS)。处理器在任务切换时不会检查限长是否大于
67H
;然而,在访问I/O权限位图或中断重定向位图时会进行检查。
使用调用或跳转指令,任何可以访问TSS描述符(即其CPL数值等于或小于TSS描述符的DPL)的程序都能够通过调用或跳转来进行任务切换。
在大多数系统中,TSS描述符的DPL被设置为小于3的值,以便只有特权软件可以执行任务切换。然而,在多任务应用中,对于一些TSS描述符,DPL可能被设置为3,以允许在应用程序(或用户)特权级别进行任务切换。
任务寄存器保存了16位段选择子和当前任务TSS的整个段描述符。在处理器上电或复位时,段选择符和基地址被设置为默认值0;限长被设置为FFFFH
。
任务寄存器有一个可见部分(可以被软件读取和更改)和一个不可见部分(由处理器维护,软件无法访问)。
LTR
(加载任务寄存器)和STR
(存储任务寄存器)指令加载和读取任务寄存器的可见部分:
LTR
指令将一个段选择符(源操作数)加载到任务寄存器中,该段选择符指向GDT中的TSS描述符。然后,它将任务寄存器的不可见部分加载为来自TSS描述符的信息。
LTR
是一个特权指令,只能在CPL为0时执行。它在系统初始化期间用于将初始值放入任务寄存器。随后,在任务切换发生时,任务寄存器的内容会在隐式情况下被更改。
STR
指令将任务寄存器的可见部分存储在通用寄存器或内存中。这个指令可以由任何特权级别的运行代码执行,以标识当前运行的任务。通常只由操作系统软件使用。
任务门描述符提供对任务的间接受保护引用。它可以放置在GDT、LDT或IDT中。任务门描述符中的TSS段选择符字段指向GDT中的TSS描述符。该段选择符中的RPL字段不被使用,当使用任务门时,目标TSS描述符的DPL不会被使用。
LDT中的任务门、GDT中的任务门和IDT中的任务门都可以指向同一个任务。
任务门描述符的DPL控制了在任务切换期间对TSS描述符的访问。当程序或进程通过任务门对任务进行调用或跳转时,指向任务门的门选择符的CPL和RPL字段必须小于或等于任务门描述符的DPL。
任务可以通过任务门描述符或TSS描述符进行访问。这两种结构满足以下需求:
处理器在以下四种情况下将执行任务切换:
JMP
或CALL
指令以访问GDT中的TSS描述符。JMP
或CALL
指令以访问GDT或当前LDT中的任务门描述符。IRET
指令。JMP
、CALL
和IRET
指令,以及中断和异常,都是用于重定向程序的机制。通过引用TSS描述符或任务门(在调用或跳转到任务时),或者IRET
指令的执行时EFLAGS寄存器的NT标志的状态,决定是否进行任务切换。
处理器在切换到新任务时执行以下操作:
通过JMP
或CALL
指令的操作数、任务门或先前任务的链接字段(对于由IRET
指令引发的任务切换)获得新任务的TSS段选择符。
特权级检查,检查当前(旧的)任务是否允许切换到新任务。
JMP
和CALL
指令。当前任务的CPL和新任务的段选择符的RPL必须小于或等于被引用的TSS描述符或任务门的DPL。INT n
指令生成的中断)和IRET
指令可以不考虑目标任务门或TSS描述符的DPL而进行任务切换。INT n
指令生成的中断,会检查DPL。存在有效性检查,检查新任务的TSS描述符是否标记为存在且具有有效的限长(大于或等于67H
)。
可用性检查,检查新任务是可用(调用、跳转、异常或中断)还是忙碌(IRET
返回)。
内存检查,检查当前(旧的)TSS、新TSS以及在任务切换中使用的所有段描述符是否已经分页到系统内存中。
忙碌标志B清除判断
JMP
或IRET
指令引发的,处理器会清除当前(旧的)任务的TSS描述符中的忙碌(B)标志;CALL
指令、异常或中断引发的,则保持忙碌(B)标志。NT标志清除判断
IRET
指令引发的,处理器会清除暂存的EFLAGS寄存器图像中的NT标志;CALL
或JMP
指令、异常或中断引发的,则保持暂存的EFLAGS图像中的NT标志不变。保护现场,将当前(旧的)任务的状态保存到当前任务的TSS中。处理器通过在任务寄存器中查找当前TSS的基地址,然后将以下寄存器的状态复制到当前TSS中来完成此操作:
NT标志设置判断
CALL
指令、异常或中断引发的,处理器将在从新任务加载的EFLAGS中设置NT标志。IRET
指令或JMP
指令引发的,则NT标志将反映新任务加载的EFLAGS中NT的状态。忙碌标志B设置判断
CALL
指令、JMP
指令、异常或中断引发的,处理器将在新任务的TSS描述符中设置忙碌(B)标志;IRET
指令引发的,则保持忙碌(B)标志。使用新任务TSS的段选择符和描述符加载任务寄存器。
将TSS状态加载到处理器,在加载TSS状态期间发生故障可能会损坏系统架构,尝试在之前的执行环境中处理该错误
加载并验证与段选择符相关联的描述符。与此加载和验证相关的任何错误都发生在新任务的上下文中,并可能损坏系统架构,在开始执行新任务之前生成适当的异常。
开始执行新任务。(对于异常处理程序而言,新任务的第一条指令尚未执行。)
如果所有的检查和保存都成功完成,处理器会执行任务切换。如果在步骤1到11中发生了无法恢复的错误,处理器不会完成任务切换,并确保将处理器返回到执行引发任务切换的指令之前的状态。
在切换任务时,新任务的特权级别不会继承自暂停的任务,新任务的特权级与原任务的特权级没有任何关系。新任务会从CS寄存器的CPL字段指定的特权级别开始执行,该特权级别从TSS中加载。由于任务通过其单独的地址空间和TSS进行隔离,并且特权级规则控制对TSS的访问,因此软件无需在任务切换时执行显式的特权级别检查。
每次任务切换都会设置控制寄存器CR0中的任务切换标志TS。当产生浮点异常时,系统软件可用TS标志来协调处理器和浮点协处理器之间的操作。TS标志表明协处理器中的上下文可能与当前任务不一致。
在IA-32架构中,中断或异常向量指向IDT表中的中断门或陷阱门时,通常不会发生任务切换。中断门和陷阱门是用于处理中断和异常的两种机制,它们允许在特定的中断或异常发生时转移控制流到指定的处理程序,而不会导致特权级别的改变或任务切换。这些门的设计目的是为了提供一种可靠的处理中断和异常的方式,并不提供直接的任务切换功能。
如果想在中断或异常时进行任务切换,可以在中断或异常处理程序中使用任务门来改变特权级别,从而实现任务切换。
TSS的前一个任务链接字段和EFLAGS寄存器中的NT标志用于将执行返回到前一个任务。
EFLAGS.NT = 1表示当前正在执行的任务嵌套在另一个任务的执行内部
对于不同情形下的任务切换,发生任务嵌套的情况也不相同:
CALL
指令、中断或异常导致任务切换时,处理器将当前TSS的段选择符复制到新任务的TSS的前一个任务链接字段,然后在EFLAGS中设置NT标志为1。指明TSS的前一任务链接字段中存放有保存的TSS段选择符IRET
指令来挂起新任务,处理器会检查NT标志是否为1,然后使用前一个任务链接字段中的值返回到前一个任务。JMP
指令导致任务切换时,新任务不会嵌套。前一个任务链接字段不会被使用,并且NT标志为0。当不需要嵌套时,可以使用JMP
指令来分派新任务。NT标志可以由在任何特权级别下执行的软件修改。程序可以设置NT标志并执行IRET
指令。这可能会随机调用当前任务的TSS中前链接字段中指定的任务。为防止这种意外的任务切换成功,操作系统应该在创建的每个TSS中将前一个任务链接字段初始化为0。
注意,运行于任何特权级上的程序都可以修改NT标志并执行
IRET
指令。这种做法会让处理器去执行当前任务TSS的前一任务链接字段指定的任务。为了避免这种伪造的任务切换执行成功,操作系统应该把每个TSS的该字段初始化为0。
任务切换会修改忙标志B、 NT标志、前一任务链字段和TS标志等,不同情形下的任务嵌套对应不同的标志状态。
在任务嵌套的情况下,一般使用IRET
指令恢复前一个任务的状态,并从前一个任务的入口点继续执行,需要以下步骤:
IRET
指令:执行IRET
指令,该指令会将控制流返回到前一个任务,并从前一个任务的TSS中加载相应的寄存器状态,完成任务切换。在单处理器系统中,如果需要将任务从链接任务链中移除,可以使用以下过程来移除任务:
在多处理器系统中,必须向该过程添加额外的同步和序列化操作,以确保在前一个任务链接字段更改和忙标志清除时,TSS及其段描述符都被锁定,避免并发冲突。
任务地址空间是指操作系统或程序为每个任务分配的内存地址范围,是一种逻辑抽象,其中包括该任务可以访问和操作的内存区域。这个地址空间用于存储任务的代码、数据、堆栈以及其他可能需要的信息。
一个任务的地址空间包括该任务可以访问的段。这些段包括TSS中引用的代码、数据、堆栈和系统段,以及任务代码访问的其他段。这些段被映射到处理器的线性地址空间,而线性地址空间又被映射到处理器的物理地址空间(可以是直接映射或通过分页)。
如果启用了分页,TSS中的CR3寄存器(PDBR)字段允许每个任务拥有其自己的一组页表,用于将线性地址映射到物理地址。或者,多个任务可以共享相同的页表集。
任务可以以以下两种方式映射到线性地址空间和物理地址空间中:
不同任务的线性地址空间可以映射到完全不同的物理地址。如果不同页目录的条目指向不同的页表,而页表指向物理内存的不同页面,那么这些任务就不共享物理地址。
无论采用哪种映射任务线性地址空间的方法,
- 所有任务的TSS必须位于物理空间的共享区域,所有任务都可以访问这个共享区域。这种映射是必需的,以便在处理器在任务切换期间读取和更新TSS时,TSS地址的映射不会发生变化。
- GDT所映射的线性地址空间也应该映射到物理空间的共享区域,否则将违背GDT的目的。
任务逻辑地址空间指的是每个任务在操作系统中分配的虚拟地址空间,这个虚拟地址空间包含了任务能够使用的所有地址。每个任务感知到自己拥有独立的地址空间,该地址空间是虚拟的,不直接对应物理硬件上的实际内存地址。
在多任务操作系统中,每个任务都有自己的逻辑地址空间,这个空间是由操作系统管理和分配的。逻辑地址空间允许任务在其中进行操作,包括读取、写入、执行代码等。任务可以在逻辑地址空间中创建自己的数据结构、变量、代码段等,而不会影响其他任务的运行。
任务之间数据在一定程度上可以共享,为了允许任务之间共享数据,可以使用以下方法为数据段建立共享的逻辑到物理地址空间的映射: