一、80386任務切換介紹
前面提到過,80386是一款對多任務操作系統提供良好支持的CPU。多道程序功能使得在某一耗時任務執行時(例如大數據的I/O),允許其它短耗時任務並發的執行(例如接受輸入的控制台命令) ,極大的提高了用戶的體驗。
80386作為早期的32位CPU,是單核處理器。因此80386支持的多任務系統,指的是能夠在同一時間內並發運行多個互相之間獨立的程序,運行中的多個程序能夠在操作系統的任務管理程序下交替、相互穿插的執行。並發的多個任務或主動的讓出CPU,或被動的被強制剝奪CPU的使用權,從而達到在單位時間內,不同任務都能夠得到CPU資源的目的。
由於相互獨立的任務之間很難彼此信任,相互間協調的工作(例如協程,在合適的時候主動的讓出CPU);同時也為了減少應用程序開發的負擔,主流的操作系統一般通過時鍾中斷這樣的周期性事件,剝奪運行中任務的CPU控制權。任務之間的切換、調度完全由操作系統負責,應用程序在開發時一般不需要考慮任務調度相關的問題。
應用程序無感知的任務調度能夠在任意時刻終止當前任務A的執行,並切換至另一個任務B;在接下來的某一時刻還能切換回任務A並接着運行。要做到這一點,就必須確定任務運行時的關鍵上下文數據,在任務換出時將當時的上下文信息像快照一樣保存起來(保護現場);同時在任務被重新調度回來時,通過之前保留的上下文信息,恢復現場。
那么什么是80386中的任務上下文?80386又是如何做到在任務切換時保護現場和恢復現場的?
二、80386任務狀態段
為了保證任務切換后,能夠被正確的恢復,80386設計了一個專用數據結構用於保存恢復任務所必需的信息,這一特殊的數據結構被稱為任務狀態段TSS(Task-State Segement),大小至少為104字節,其基本內容結構圖如下圖所示。
任務狀態段TSS中的內容,按照類型大致可以分為以下幾個部分:
寄存器保存區域
位於TSS中間位置的寄存器保存區域,用於保存切換任務時,32位的通用寄存器(EAX、EBX等)、16位的段寄存器(CS、DS、ES等)、32位的標志寄存器EFLAGS、指令指針寄存器EIP的快照記錄。
高特權級堆棧指針區域
在關於數據段特權級時提到過,為了避免高特權級的程序由於棧空間不足而崩潰以及不同特權級間棧數據的交叉引用。處理器在特權級發生變化時,當前任務的堆棧也必須發生變化。
具體來說就是一個任務在每個不同的特權級下,都應該擁有一個獨立的堆棧。特權級一共有4個等級,同時由於特權級的限制,高特權級的任務一般無法將控制轉移至低特權級的段。
特權級為0的內核任務不需要額外的棧,使用自己固有的內核棧即可。特權級為1的任務需要定義一個額外的棧,用於將控制轉移至特權級0時使用。依此類推,特權級為2的任務需要定義兩個額外的棧,特權級為3的應用程序應該准備3個額外的堆棧以便在不同的特權級間進行切換。
這在TSS中有所體現,任務切換時也必須把這些額外的棧選擇子/棧頂指針記錄下來,其中SS0、SS1、SS2分別是任務在特權級0、1、2時的棧段選擇子,ESP0、ESP1、ESP2分別是任務在特權級0、1、2時的棧頂指針寄存器的值。至於當前任務固有的棧,則位於上述的寄存器保存區域中的SS、ESP中。
不同特權級切換時的額外的棧一般是由操作系統加載任務時創建的,對應用程序是透明的。
地址映射寄存器區域
80386在開啟了保護模式后,內存尋址的方式發生了改變。
每個任務一般都存在一個屬於任務自己的段表LDT,同時如果當前CPU還開啟了分頁機制的話,則還需要通過一個頁表來做進一步的映射,將內存的線性地址轉換為最終的物理地址。所以在TSS中,存有指向當前任務LDT首地址的LDT段選擇子字段,以及在分頁模式下工作的CR3寄存器(PDBR Page Directory Base Register 頁表基址寄存器)的值。
LDT段選擇子加能夠幫助CPU定位當前任務的段表;保存的CR3能夠在開啟分頁機制時,指向一級頁表的首地址。關於80386內存分頁機制的原理,將在后續的博客中展開介紹。
任務鏈接字段
80386允許在任務之間進行嵌套。在中斷等情況下,可以暫時打斷當前任務(如應用程序)並切換至更緊急的中斷處理程序(如操作系統內核內存缺頁中斷處理程序)。
為了能夠在任務切換時,新任務執行完成后再切換回之前的老任務。TSS中引入了任務鏈接字段,用於在嵌套任務切換時,記錄當前任務的前一個任務的TSS段選擇子。嵌套任務的切換工作原理,將在下文進行詳細介紹。
I/O映射位圖基地址
前面的博客在I/O特權級保護中提到了I/O映射位圖。和LDT一樣,每個任務都擁有自己的I/O映射位圖,TSS中的I/O映射位圖基地址用於指向當前任務的I/O映射位圖,其中的值代表着I/O許可位圖相對於當前TSS內存段起始位置的偏移量。這也是為什么TSS段最小是104字節的原因,因為在104字節的基礎結構之后,還允許拓展的存放一個I/O映射位圖數據。
在尋找TSS時,如果發現該偏移量已經超過了TSS段描述符中的段界限,CPU不會報錯,而是認為當前任務不存在I/O映射位圖。這種情況下,如果任務的當前特權級CPL低於IOPL的話,將無權訪問任何外設端口。
任務寄存器TR
TSS和LDT一樣,都被認為是一種系統內存段(S位為0),對應的段描述符必須預先加載進全局描述符表GDT中。這么設計的主要目的還是為了將TSS通過特權級的機制加以保護。
80386提供了LDTR寄存器用於指向當前任務的LDT段。同理,80386也提供了另一個專用的寄存器指向當前任務的TSS段,這個專用的寄存器被稱為任務寄存器(Task Resigter TR)。TR寄存器是16位的,其中裝載的是所指向的TSS段的段選擇子。任務切換時,將TR中的段選擇子指向新任務的TSS段即可。
TSS段描述符
TSS作為段描述符的一種,和LDT段描述符,數據段/代碼段描述符的格式差不多,最大的區別在於TYPE字段。TSS的TYPE字段為10B1,其中高2位,和最低位是固定不變的,而B位是忙(Busy)位的意思,B位為0時代表任務不處於忙狀態,B位為1時代表任務處於忙狀態。
當任務剛剛被初始化時,B位應該被操作系統設置為0。當任務處於執行狀態時,或者因為中斷等原因被暫時切換為掛起狀態時,B位會被CPU固件設置為1。
三、80386任務切換的方式
80386允許在4種情況下進行任務切換:
1、當前任務執行了一條引用TSS描述符的JMP或者CALL指令
使用jmp far或者call far時,如果給出的段選擇子指向的是普通的代碼段,那么CPU認為是普通的跳轉或是過程調用,依然是同一個任務,不會發生不同任務上下文的切換。
使用jmp far或者call far給出的段選擇子指向的是一個TSS系統段時,CPU將進行任務切換。
2、當前任務執行了一條引用任務門的JMP或者CALL指令
使用jmp far或者call far時,如果給出的段描述符指向的是一個任務門時,CPU將進行任務切換。
3、引用中斷描述符表(IDT)中的任務門中斷或異常
一般的中斷處理可以使用中斷門或者陷阱門進行處理,此時CPU認為這是在同一個任務內的控制轉移。但是如果中斷號對應的是任務門,則CPU認為這是一次任務切換。
4、當嵌套任務標志NT置位時,當前任務執行了一條IRET指令
中斷發生時,可以是同一個任務內進行常規的中斷處理,也可以進行任務切換,這兩者都可以通過IRET中斷返回指令進行返回。同一任務內的中斷返回會跳轉回中斷觸發時的代碼段,當前任務繼續。使用任務切換進行的任務處理將會在返回時,切換回中斷發生前的任務。
IRET指令返回時,用於區分上述兩種類型的方式是判斷標志寄存器EFLAGS的NT位(第14位)的值,NT的意思是Nested Task,NT位是嵌套位置標識。NT位=1時,代表當前執行的任務是嵌套於另一個任務中的,並且能夠在任務返回時通過TSS中的任務鏈接字段返回到前一個任務。
當因中斷而發生任務切換的瞬間,存在兩個概念:舊任務(當前被打斷執行的任務)和新任務。
對於舊任務,需要保護其上下文,將包括EFLAGS標志寄存器內的各種必要的數據快照存入舊任務的TSS段中保存,此時的舊任務TSS段TYPE字段的B位一定是為1的,因為當前任務是處於執行狀態被中斷被迫打斷執行的。
對於新任務,需要將舊任務的TSS段選擇子存入自己的TSS段中的任務鏈接字段處,用於在新任務IRET返回時能夠切換回舊任務。此時的新任務TSS段TYPE字段的B位設置為1,並且切換時的新任務EFLAGS寄存器中的NT位也
必須設置為1。
CPU在執行IRET中斷返回指令時,會首先檢查EFLAGS寄存器的NT位。
如果NT位為0,說明當前任務沒有處於其它任務中嵌套的執行,進行普通的中斷過程返回,不發生任務切換。
如果NT位為1,說明當前任務的是嵌套於其它被打斷的任務中執行的,需要讓被打斷的任務恢復運行。此時,將當前需要返回的任務的NT位設置為0,同時當前任務對應的TSS段描述符的B位也設置為0,因為中斷返回的當前任務已經返回,變得不忙(Busy)了也不嵌套於其它任務中。隨后,進行當前任務的現場保護,使用保存在當前任務TSS中的任務鏈接字段指向的被中斷任務的TSS進行現場恢復。
用CALL指令發起的任務切換,其任務新舊任務狀態是嵌套的。可以使用IRET指令返回前一個任務,此時的舊任務TSS段的B位以及EFLAGS寄存器的NT位都設置為0,即不忙,非嵌套狀態。
用JMP指令發起的任務切換,不會產生新舊任務的嵌套狀態,此時舊任務的B位設置為0,但EFLAGS寄存器的NT位保持不變。新任務的B位設置為1,NT位和新任務恢復現場時的TSS中的快照記錄保持一致。
任務的不可重入性
任務是不可以重入的,即新任務進行切換時,其本身的狀態不能為忙(TSS段TYPE字段的B位不能為1)。
在嵌套的任務鏈中,新任務TSS段的任務鏈接字段指向了上一個被打斷執行的任務,構成了一個單向的鏈表。伴隨着一個個新任務的終止並返回,嵌套的處理邏輯總會執行完。但前提是嵌套鏈表中不能出現環路。如果出現了環路,即新任務還能夠嵌套進已經在當前鏈條中的舊任務中,那么這個嵌套鏈條便成為了一個永遠無法終止的死循環了。
由於當前任務被中斷嵌套時,其TSS段的B位始終都是為1的,因此CPU在處理任務切換時會首先校驗新任務TSS段的B位,如果為1則表示新任務也是處於嵌套鏈條中的,CPU會產生異常以阻止這樣的任務切換。
總結
80386的任務這一概念,與操作系統中的進程、線程這些概念有着緊密關系。對任務和任務切換概念的理解,有助於更好的學習操作系統。
在這任務切換方式的介紹中,多次提到了任務門、中斷門等概念。限於篇幅,關於門描述符和中斷相關的內容將在后續的博客中進行詳細介紹。