【发布时间】:2017-03-31 21:52:36
【问题描述】:
当激活(即 TR 寄存器指向的那个)TSS 的字段改变时会发生什么?特别是,对 ESP0/RSP0 字段的更改是否会立即生效?或者处理器是否像使用段选择器一样保留 TSS 缓存,因此需要 LTR 指令来强制处理器重新加载 TSS 字段?
【问题讨论】:
当激活(即 TR 寄存器指向的那个)TSS 的字段改变时会发生什么?特别是,对 ESP0/RSP0 字段的更改是否会立即生效?或者处理器是否像使用段选择器一样保留 TSS 缓存,因此需要 LTR 指令来强制处理器重新加载 TSS 字段?
【问题讨论】:
处理器使用 TSS 存储当前上下文并加载下一个要调度的上下文在任务切换期间。
在 CPU 切换到此类 TSS 之前,更改 TSS 结构不会影响任何上下文。
CPU 执行任务切换时
软件或处理器可以通过以下方式之一分派任务以执行:
• 使用 CALL 指令显式调用任务。
• 使用 JMP 指令显式跳转到任务。
• (由处理器)对中断处理程序任务的隐式调用。
• 对异常处理程序任务的隐式调用。
• 当EFLAGS 寄存器中的NT 标志被设置时返回(以IRET 指令开始)。
您可以在 Intel 手册 3 的第 7 章阅读 TSS。
ltr 不执行切换,来自英特尔手册 2:
段选择器加载到任务寄存器后,处理器使用段选择器定位 全局描述符表 (GDT) 中 TSS 的段描述符。
然后它加载段限制和基数 从段描述符到任务寄存器的 TSS 地址。
任务寄存器指向的任务是 标记为忙,但不会切换到任务。
编辑:我实际测试过 CPU 是否缓存了 TSS 中的静态值。
测试包含一个引导程序(附件),
tr。 在我的 Haswell 和 Bochs 上,结果为 2,这意味着 CPU 在需要时从内存(层次结构)中读取 TSS。
虽然对模型的测试不能推广到 ISA,但情况并非如此。
BITS 16
xor ax, ax ;Most EFI CPS need the first instruction to be this
;But I like to have my offset to be close to 0, not 7c00h
jmp 7c0h : WORD __START__
__START__:
cli
;Set up the segments to 7c0h
mov ax, cs
mov ss, ax
xor sp, sp
mov ds, ax
;Switch to PM
lgdt [GDT]
mov eax, cr0
or ax, 1
mov cr0, eax
;Set CS
jmp CS_DPL0 : WORD __PM__ + 7c00h
__PM__:
BITS 32
;Set segments
mov ax, DS_DPL0
mov ss, ax
mov ds, ax
mov es, ax
mov esp, ESP_VALUE0
;Make a minimal TSS BEFORE loading TR
mov eax, DS_DPL0
mov DWORD [TSS_BASE + TSS_SS0], eax
mov DWORD [TSS_BASE + TSS_ESP0], ESP_VALUE1
;Load TSS in TR
mov ax, TSS_SEL
ltr ax
;Go to CPL = 3
push DWORD DS_DPL3 | RPL_3
push DWORD ESP_VALUE0
push DWORD CS_DPL3 | RPL_3
push DWORD __PMCPL3__ + 7c00h
retf
__PMCPL3__:
;UPDATE ESP IN TSS
mov ax, DS_DPL3 | RPL_3
mov ds, ax
mov DWORD [TSS_BASE + TSS_ESP0], ESP_VALUE2
;SWITCH STACK
call CALL_GATE : 0
jmp $
__PMCG__:
mov eax, esp
mov bx, 0900h | '1'
cmp eax, ESP_VALUE1 - 10h
je __write
mov bl, '2'
cmp eax, ESP_VALUE2 - 10h
je __write
mov bl, '0'
__write:
mov WORD [0b8000h + 80*5*2], bx
cli
hlt
GDT dw 37h
dd GDT + 7c00h ;GDT symbol is relative to 0 for the assembler
;We translate it to linear
dw 0
;Index 1 (Selector 08h)
;TSS starting at 8000h and with length = 64KiB
dw 0ffffh
dw TSS_BASE
dd 0000e900h
;Index 2 (Selector 10h)
;Code segment with DPL=3
dd 0000ffffh, 00cffa00h
;Index 3 (Selector 18h)
;Data segment with DPL=0
dd 0000ffffh, 00cff200h
;Index 4 (Selector 20h)
;Code segment with DPL=0
dd 0000ffffh, 00cf9a00h
;Index 5 (Selector 28h)
;Data segment with DPL=0
dd 0000ffffh, 00cf9200h
;Index 6 (Selector 30h)
;Call gate with DPL = 3 for SEL=20
dw __PMCG__ + 7c00h
dw CS_DPL0
dd 0000ec00h
;Fake partition table entry
TIMES 446-($-$$) db 0
db 80h, 0,0,0, 07h
TIMES 510-($-$$) db 0
dw 0aa55h
TSS_BASE EQU 8000h
TSS_ESP0 EQU 4
TSS_SS0 EQU 8
ESP_VALUE0 EQU 7c00h
ESP_VALUE1 EQU 6000h
ESP_VALUE2 EQU 7000h
CS_DPL0 EQU 20h
CS_DPL3 EQU 10h
DS_DPL0 EQU 28h
DS_DPL3 EQU 18h
TSS_SEL EQU 08h
CALL_GATE EQU 30h
RPL_3 EQU 03h
【讨论】:
call 指令描述和手册中的调用门描述似乎暗示了这一点。不过,这很容易测试,也许我可以调查一下。
仅在必要时读取 TSS,并且没有特殊的 TSS 缓存。 (GDT 中的 TSS 描述符与段描述符一样被缓存,但不是 TSS 本身的内容。TSS 可以像任何其他内存区域一样缓存在普通的 L1/L2/L3 内存缓存中。)
在不同情况下可以读取 TSS 的三个不同区域。在适当的情况发生之前,更改 TSS 中的任何值均无效。它们是:
请注意,在 64 位模式下,仅会出现第 1、3 和 5 种情况,因为 64 位模式不支持虚拟 8086 模式,也不支持任务切换。
除了 GDT 中与给定选择器对应的条目外,LTR 指令不会读取任何内存区域,也没有任何内部 TSS 缓存可供它刷新。
【讨论】: