x86 的 TSS 任务切换机制
轉自:http://blog.chinaunix.net/uid-587665-id-2732907.html
【0】寫在前面
segment descriptors 構建保護模式下的最基本、最根本的執行環境。system descriptors 則構建保護模式下的核心組件:
1、TSS descriptor 提供硬件級的進程切換機制
- 2、LDT descriptor 供進程使用多個 descriptor
- 3、Gate descriptor 提供 processor 權限級別的切換機制。
5.7.1、 TSS 提供的進程切換機制
- TSS 是一段內存區域,存放進程相關的執行環境信息。初始化的 TSS 是由用戶提供,進程切換時的保存信息由 processor 執行。
5.7.1.1、 三個元素構成 TSS 環境:
1、 TSS descriptor:這個 descriptor 屬于 system descriptor 類型,它的 S (system)位是 0。
下面列出 TSS descriptors 的類型值:
0001: 16-bit TSS 0011: busy 16-bit TSS 1001: 32-bit TSS 1011: busy 32-bit TSS以上是 x86 下的 TSS descriptor 類型,分為 16 和 32 位 TSS,x64 的 long mode 下 32 位的 TSS descriptor 將變為 64 位 TSS descriptor。
情景提示:
關于 TSS 的 busy 與 available 狀態:
- 1、 TSS descriptor 的類型指明是 busy 與 available 狀態。
- 2、 TSS descriptor 的 busy 與 available 狀態由 processor 去控制。即:由 processor 置為 busy 或 avaibable。 除了初始的 TSS descriptor 外。
- TSS 的 busy 狀態主要用來支持任務的嵌套。TSS descriptor 為 busy 狀態時是不可進入執行的。同時防止 TSS 進程切換機制出現遞歸現象。
2、 TSS selector 以及 TR(Task Register)寄存器
TR 寄存器的結構與 segment registers 是完全一致的,即:由軟件可見的 selector 部分與 processor 可見的隱藏部分(信息部分)構成。
TR.selector 與 CS.selector 中的 selector 意義是完全一樣的。其 descriptor 的加載也是一樣的。
即: TR.selector 在 GDT / LDT 中索引查找到 TSS descriptor 后,該 TSS descriptor 將被加載到 TR 寄存的隱藏部分。當然在加載到 TR 寄存器之前,要進行檢查通過了才可加載到 TR 寄存器。若加載 non-TSS descriptor 進入 TR 則會產生 #GP 異常。
3、 TSS 塊(Task Status Segment)
像 code segment 或 data segments 一樣,最終的 TSS segment 由 TSS descriptor 來決定。 TSS descriptor 指出 TSS segment 的 base、limit 及 DPL 等信息。
TSS segment 存放 eflags 寄存器、GPRs 寄存器及相關的權限級別的 stack pointer (ss & sp)、CR3 等等信息。
5.7.1.2、 TSS 機制的建立
對于多任務 OS 來說,TSS segment 是必不可少的,系統至少需要一個 TSS segment,但是現在的 OS 系統不使用 TSS 機制來進行任務的切換。
情景提示:
- TSS 存在的唯一理由是:需要提供 0 ~ 2 權限級別的 stack pointer,當發生 stack 切換時,必須使用 TSS 提供的相應的 stack pointer。
- 但是:若提供空的 TSS segment,或者可以考慮以直接傳遞 stack pointer 的方式實現 stack 切換,即便是這樣設計 processor 要讀取 TSS segment 這一工作是必不可少的。
下面的指令用來建立初始的 TSS segment:
LTR word ptr [TSS_Selector] /* 在 [TSS_selector] 提供 TSS selector */
或:LTR ax /* 在 ax 寄存器里提供 TSS selector */ltr 指令使用提供的 selector 在 GDT / LDT 里索引查找到 TSS descriptor 后,加載到 TR 寄存器里。初始的 TSS descriptor 必須設為 available 狀態,否則不能加載到 TR。processor 加載 TSS descriptor 后,將 TSS descriptor 置為 busy 狀態。
5.7.1.3、 TSS 進程切換的過程
當前進程要切換另一個進程時,可以使用 2 種 selector 進行:使用 TSS selector 以及 Task gate selector(任務門符)。
call 0x2b:0x00000000 /* 假設 0x2b 為 TSS selector */ call 0x3b:0x00000000 /* 假設 0x3b 為 Task-gate selector */TSS 提供的硬件級進程切換機制較為復雜,大多數 OS 不使用 TSS 機制,是因為執行的效能太差了。上面的兩條指令的 TSS 進程切換的過程如下:
1、使用 TSS selector
call 0x2b:0x00000000 /* 0x2b 為 TSS selector */
這里使用 jmp 指令與 call 指令會有些差別,call 允許 TSS 進程切換的嵌套,jmp 不允許嵌套。(1)processor 使用 TSS selector (0x2b) 在 GDT 索引查找第 5 個 descriptor
(2)processor 檢查找到的 descriptor 類型是否是 TSS descriptor,不是的話將產生 #GP 異常。是否為 available TSS,若目標 TSS descriptor 是 busy 的話,同樣將產生 #GP 異常。
(3)processor 進行另一項檢查工作:權限的檢查,CPL <= DPL 并且 selector.RPL <= DPL 即為通過,否則產生 #GP 異常。
(4)當前進程的執行環境被保存在當前進程的 TSS segment 中。
情景提示:
在這一步里,此時還沒發生 TSS selector 切換,processor 把當前進程的環境信息保存在當前的 TSS segment 中。(5)這里發生了 TSS selector 切換。新的 TSS selector 被加載到 TR.selector,而新的 TSS descriptor 也被加載到 TR 寄存的隱藏部分。
情景提示:
- (1)這里,processor 還要將舊的 TSS selector 保存在當前的 TSS segment(新加載的 TSS)中的 link 域。這個 TSS segment 中的 link 其實就是 old TSS selector 域,用來進程返回時獲得原來的 TSS selector 。從而實現任務的嵌套機制。
- (2)processor 將當前 eflags 寄存器的 NT(Nest Task)標志位置為 1,表明當前的進程是嵌套內層。
(6)processor 從當前的 TSS segment 取出新進程的執行環境,包括:各個 selector registers(segment registers)、GPRs、stack pointer (ss & sp)、CR3 寄存器以及 eflags 寄存器等。
在這一步,在加載 selectors 進入 segment registers 之前,還必須經過相關的 selector & descriptor 的常規檢查以及權限檢查。通過之后才真正加載。否則同樣產生 #GP 異常。
情景提示:
- processor 還要做另一項工作,就是:將新進程的 TSS descriptor 置為 busy 狀態。使得新進程不能重入。
(7)processor 從當前的 CS:RIP 繼續往下執行,完成這個 TSS 進程的切換。這個 CS: RIP 就是新加載的新進程的 cs : rip
- 從上面的過程可以看出,使用 TSS 進程切換機制異常復雜,導致進程切換的效能太差了。比使用 call gate 以及 trap gate 慢上好多。若當中發生權限的改變,還要發生 stack 切換。
- 進程的返回同樣復雜。同樣需要這么多步驟,總結來說,主要的時間消耗發生在新舊進程的信息保存方面。
2、 使用 task gate selector
另一種情況是使用 task gate selector 進行 TSS 進程切換,使用 task gate selector 除了可以 call/jmp 外,還可用在中斷機制上,下面的兩種情況:
call 0x3b:0x00000000 /* 0x3b 為 task gate selector */
或:int 0x3e /* 假設 0x3e 是 task gate 的向量 */這兩種情形差不多,除了一些細微的差別外,不過是觸發的機制不同而已。
processor 在 GDT 索引查找到的是一個 task gate descriptor,這個 task gate descriptor 中指出了目標的 TSS selector 。processor 從 task gate descriptor 里加載 TSS selector,剩下的工作和使用 TSS selector 進行切換進程是一致。
processor 訪問 task gate descriptor 僅需對 task gate descriptor 作出權限檢查:CPL <= DPL 并且 RPL <= DPL。在這里不需要作出對 TSS descriptor 的 DPL 權限進行檢查。
gate descriptor 機制提供了一層間接的訪問層,主要用來控制權限的切換。
實際上:
- 對于 call 0x2b:0x00000000 這條指令,processor 并不知道這個 selector 是 TSS selector 還是 task gate selector,在查找到 descriptor 后,processor 查看這個 descriptor 的 type 才能確定是 TSS selector 還是 task gate selector。
最后需注意:
- (1)使用 jmp 指令進行轉移,processor 不會將 eflags 中 NT 標志位置 1
- (2)使用中斷機制下的 TSS 進程切換,processor 將不作任務的權限檢查動作。
5.7.1.4、 TSS 進程的返回
從 TSS 機制切換的進程在執行完后使用 iret 指令返回原進程進,同樣會發生新舊 TSS segment 的更新動作。
進程通過使用 ret 返回時,processor 將不會從嵌套內層返回到的嵌套外層進程,也就是不會返回原進程。processor 對 ret 指令的處理,只會從 stack pointer(ss : esp) 取返回地址。
- 對于使用進程使用了 iret 中斷返回指令時:
- (1)processor 將會檢查當前的 eflags.NT 標志位是否為 1,也就是檢查當前進程是否處于嵌套的內層。
- (2)eflags.NT = 1,processor 將從當前 TSS segment 中的 link(old TSS selector)域中取出原來進程的 TSS selector。
- (3)processor 將不作任何的權限檢查,TSS selector 被加載到 TR.selector,TSS descriptor 同時被加載到 TR 的隱藏部分。
- (4)processor 將清除當前的 eflags.NT 為 0。若是使用 ret 指令返回的,processor 是不會清 eflags.NT 為 0
- (5)從 TSS segment 中加載新的進程執行環境,從新的 CS:EIP 處繼續執行。 將原來的 TSS descriptor 重新置為 available 狀態,使得可以再次進入。
- 由上可得,processor 遇到 ret 指令時,是不會對 eflags.NT 進行檢查的。而使用 iret 指令,processor 將對 eflags.NT 進行檢查。
總結
以上是生活随笔為你收集整理的x86 的 TSS 任务切换机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从备案到开机要多久(从备案到开机)
- 下一篇: 关于一致/非一致代码段与TSS 关系的个