IA-32 Intel手册学习笔记(三)任务切换
一个任务由两部分组成
- 任务执行空间,由代码段,栈段,和一个或多个数据段组成
- 任务状态段(TSS)
如果操作系统使用处理器特权级机制,那么任务执行空间会对每个特权级提供不同的栈(实际上是对0, 1, 2三个特权级分别提供不同的栈)
注,用户程序的特权级是3,不是上述三个栈中的任何一个,而是SS : ESP表示的栈
一个正在被执行的任务状态包括
- 任务当前执行的空间,由各种段选择子决定(CS, DS, SS, ES, FS和GS)。在线性地址空间中,是一个段空间
- 通用寄存器状态,包括EAX, ECX, EDX, EBX, ESP, ESI和EDI的值
- EIP寄存器的状态,即EIP的值
- CR3控制寄存器的值
- 任务寄存器(TR)的状态
- LDTR寄存器的状态
- I/O内存映射的基地址(包含在TSS中),(共享内存?)
- 三个栈指针,分别指向特权级0, 1, 2的栈地址
- 上一个被执行的任务的link(地址)
任务状态中”上一个被执行的任务的link”指的是
当任务A使用CALL指令调用任务B时,在任务B的任务状态中会包含任务A的地址,以便于当B执行完成后可以返回到任务A接着执行。很像汇编中CALL指令调用函数时将下一条语句的地址入栈。
软件或者处理器可以使用以下几种方法调度一个任务
- 显式使用CALL指令调用某个任务
- 显式使用JUMP指令跳转到某个任务
- 由处理器隐式调用中断处理任务
- 隐式调用异常处理任务(也是由处理器?)
- 如果EFLAGS寄存器的NT标志被设置,那么可以使用IRET指令在返回时隐式调转到某个任务
上述的所有方法都是通过一个指向任务段描述符的选择子或者指向任务门描述符的选择子实现的。
在任务切换过程中,假设由于某种原因(通常是属于某个任务的时间片用光),处理器将从当前正在执行的任务A切换到另一个任务B。此时,处理器需要记录任务A的所有状态以便当切换回任务A时就好像没离开过一样,所以,处理器会先将任务A的状态(上面列出的任务状态)存储在任务A的TSS中(任务状态段)。然后将任务A挂起,并将任务B的状态加载到处理器上(从B的TSS中),以此完成任务切换。
虽然听起来任务切换比较简单,就是保存A,执行B。但是在这个看似简单的过程中,处理器做了非常多的事情,不过再次之前,先看一下都在什么时候处理器会执行任务切换
- 当前任务A执行了JMP或CALL指令显示调用任务B。JMP和CALL的操作数为段选择子,指向GDT中任务B的TSS段描述符
- 当前任务A执行了JMP或CALL指令显示调用任务B。JMP和CALL的操作数为段选择子,指向GDT或任务B的LDT中的任务门描述符
- 当前任务A出现一个指向IDT中任务门描述符的中断/异常向量
- 当前任务A执行了IRET指令且EFLAGS寄存器中的NT标志位被设置
当发生任务切换时,处理器依次执行如下操作
- 从任务门或上一个任务的link中获取任务B的TSS段选择子作为JMP或CALL的操作数
- 核查任务A是否可以切换到任务B。这里需要应用特权级规则,即任务A的CPL和任务B的TSS段选择子的RPL必须小于任务B的TSS段描述符或任务门的DPL。另外对于中断/异常(不包括INT n中断)以及IRET指令则不需要进行核查
- 核查TSS段描述符是否有一个有效的段界限
- 核查任务B是处于空闲状态(available)还是忙碌状态(busy),检查段描述符中B标志位,这步骤用于防止递归调用任务
- 核查任务A的TSS,任务B的TSS和切换过程中被使用的所有段描述符是否都已经被映射到物理内存中(分页机制可能会导致某些数据被存放到外存中)
- 对于JMP和IRET指令,因为不需要返回当前任务A,所以将任务A的段描述符的B标签置0,表示任务A处于空闲状态;对于CALL指令,因为需要返回当前任务A,所以将B标签置1,表示任务A处于忙碌状态,防止递归调用任务A
- 如果任务切换由IRET指令引发(此时NT标志位是1),那么在转移过程中会将NT标志置0。如果由其他因素引发(CALL, JMP,中断,异常),则保持NT标志位不变
- 保存当前任务A的任务状态,这个过程中会通过TR获取任务A的TSS地址,然后将任务A的所有运行状态(主要是各个寄存器的值)保存在任务A的TSS中
- 如果任务切换由CALL,中断或异常引发,那么处理器将会将任务B的NT标志位置1。如果由IRET或JMP指令引发,那么处理器会从任务B的TSS中读取NT的值
- 如果任务切换由CALL,JMP,中断或异常引发,那么处理器会设置任务B的段描述符的B标签。如果由IRET引发,则保持不变
- 将任务B的段选择子和段描述符加载到TR寄存器中
- 将任务B的TSS加载到处理器中,本质是设置各种寄存器的值
- 加载有关的段描述符
- 开始执行任务B
任务门描述符提供对TSS的间接引用,可以位于GDT,LDT以及IDT中,描述符中保存着段选择子,通过段选择子,可以在GDT中找到TSS段描述符以获取TSS的基地址
当程序使用CALL或JMP指令尝试条用任务门时,CALL和JMP的操作数实际上是任务门的选择子,通过选择子可以找到在GDT,LDT或IDT中的任务门描述符。不过,任务门选择子的CPL和RPL必须小于等于门描述符的DPL
任务门描述符的结果如下
TSS(任务状态段)的结果如下
可以看出TSS中主要就是各种寄存器的值,这主要是为了当任务切换时,把当前正在执行的任务的各种状态保存下来,这样,当又执行这个任务时,就可以从TSS中获取刚离开时这个任务的状态,从而像从没有离开过一样。
每个任务都有属于自己的TSS,而TR寄存器保存着当前正在执行的任务的TSS的段选择子和基地址以及偏移量。
在执行任务A时,TR寄存器保存着任务A的TSS的段选择子和基地址。当处理器从任务A切换到任务B时,处理器执行如下操作
- 从TR中获取任务A的TSS段基地址
- 将任务A的各种状态存在任务A的TSS中
- 通过任务B的选择子或任务门获得任务B的TSS段选择子和基地址
- 将任务B的TSS段选择子和基地址存在TR寄存器中
- 将任务B的TSS段中的各种信息赋值给相应寄存器
TR寄存器结果如下