2.7 使用 trap gate trap gate 在结构上与 interrupt gate 是完全一样的,参见节 2.6 的那图,trap gate 与 interrupt gate 不同的一点是:使用 trap gate 的,processor 进入 interrupt handler 前并不改变 eflags.IF 标志,这意味着在 interrupt handler 里将允许可屏蔽中断的响应。
*(--esp) = eflags; /* push eflags */
if (gate_descriptor.type == TRAP_GATE) {
/* skip: do nothing */
} else if (gate_descriptor.type == INTERRUPT_GATE){
eflags.IF = 0; /* clear eflags.IF */
} else if (gate_descriptor.type == TASK_GATE) {
... ...
}
| 2.8 使用 task gate 在使用 task gate 的情形下变得异常复杂,你需要为 new task 准备一个 task 信息的 TSS,然而你必须事先要设置好当前的 TSS 块,也就是说,系统中应该有两个 TSS 块:
- current TSS
- TSS of new task
当前的 TSS 是系统初始化设置好的,这个 TSS 的作用是:当发生 task 切换时保存当前 processor 的状态信(当前进程的 context 环境),新任务的 TSS 是通过 task gete 进行切换时使用的 TSS 块,这个 TSS 是存放新任务的入口信息。
tss_desc dw 0x67 ; seletor.SI = 3
dw TSS
dd 0x00008900
tss_gate_desc dw 0x67 ; selector.SI = 4
dw TSS_TASKGATE
dd 0x00008900
| 在上面的示例代码中,设置了两个 TSS descriptor,一个供系统初始化使用(tss_desc),另一个是为新任务而设置(tss_task_gate),代码中必须设置两个 TSS 块:
TSS 块的内容是什么在这个示例中无关紧要,然而 TSS_TASKGATE 块中应该设置新任务的入口信息,其中包括:eip 和 cs 值,以后必要的 DS 与 SS 寄存器值,还有 eflags 和 GPRs 值,下面的代码正是做这项工作:
; set TSS for task-gate
mov dword [TSS_TASKGATE+0x20], BP_handler32 ; tss.EIP
mov dword [TSS_TASKGATE+0x4C], code32_sel ; cs
mov dword [TSS_TASKGATE+0x50], data32_sel ; ss
mov dword [TSS_TASKGATE+0x54], data32_sel ; ds
mov dword [TSS_TASKGATE+0x38], esp ; esp
pushf
pop eax
mov dword [TSS_TASKGATE+0x24], eax ; eflags | 我将新任务的入口点设为 BP_handler32(),这个是 #BP 断点异常处理程序,保存当前的 eflags 值作为新任务的 eflags 值。
我们必须为 task gate 设置相应的 IDT 表项,正如下面的示例代码:
; set IDT vector: It's a #BP handler
mov word [IDT+3*8+2], tss_taskgate_sel ; tss selector
mov dword [IDT+3*8+4], 0xe500 ; type = task gate
| 示例代码中,我为 vector 3(#BP handler)设置为 task-gate descirptor,当发生 #BP 异常时,就会通过 task-gate 进行任务切换到我们的新任务(BP_handler32)。
; load IDT into IDTR
lidt [IDT_POINTER]
; load TSS
mov ax, tss_sel
ltr ax
| 当然我们应该先设置好 IDT 表和加载当前的 TSS 块,这个 TSS 块就是我们所定义的第1个 TSS descirptor (tss_desc),这个 TSS 块里什么内容都没有,设置它的目的是为切换到新任务时,保存当前任务的 context 环境,以便执行完新任务后切换回到原来的任务。
db 0xcc ; throw BreakPoint | 现在我们就可以测试我们的 BP_handler32(),通过 INT3 指令引发 #BP 异常,这个异常通过 task-gate 进行切换。
我们的 BP_handler32 代码是这样的:
;-----------------------------------------------------
; INT3 BreakPoint handler for 32-bit interrupt gate
;-----------------------------------------------------
BP_handler32:
jmp do_BP_handler32
BP_msg32 db 'I am a 32-bit breakpoint handler with task-gate on 32-bit proected mode',0
do_BP_handler32:
mov edi, 10
mov esi, BP_msg32
call printmsg
clts ; clear CR0.TS flag
iret
| 它只是简单的显示一条信息,在这个 BP_handler32 中,我们应该要清 CR0.TS 标志位,这个标志位是通过 TSS 进行任务切换时,processor 自动设置的,然而 processsor 不会清 CR0.TS 标志位,需要代码中清除。
2.8.1 任务切换的情形在本例中,我们来看看当进行任务切换时发生了什么,processor 会设置一些标志位:
- 置 CR0.TS = 1
- 置 eflags.NT = 1
设置 CR0.TS 标志位表示当前发生过任务切换,processor 只会置位,而不会清位,事实上,你应该使用 clts 指令进行清位工作。设置 eflags.NT 标志位表示当前任务是在嵌套层内,它指示当进行中断返回时,需切换回原来的任务,因此,请注意:
当执行 iret 指令时,processor 会检查 eflags.NT 标志是否置位 | 当 eflags.NT 被置位时,processor 执行另一个任务切换工作,从 TSS 块的 link 域中取出原来的 TSS selector 从而切换回原来的任务。这不像 ret 指令,它不会检查 eflags.NT 标志位。
processor 也会对 TSS descriptor 做一些设置标志,当进入新任务时,processor 会设置 new task 的 TSS descriptor 为 busy,当切换回原任务时,会置回这个任务的 TSS descriptor 为 available,同时 processor 会检查 TSS 中的 link 域的 TSS selector(原任务的 TSS)是否为 busy,如果不为 busy 则会抛出 #TS 异常。
当然发生切换时 processor 会保存当前的 context 到 current TSS 块中,因此:
- 切换到目标任务时,processor 会保存当前的任务 context 到 TSS,读取目标任务的 TSS,加载相应的信息,然后进入目标任务
- 目标任务执行完后,切换回原任务时,processor 会保存目标任务的 context 到目标任务的 TSS 中,从目标任务的 TSS 块读取 link(原任务的 TSS selector),加载相应的信息,返回到原任务
当从目标任务返回时,processor 会清目标任务的 eflags.NT = 0,如前所述目标任务的 TSS descriptor 也会被置为 available |