操做系統--特權級切換

有4個層次的特權級,從高到低依次是:0級、1級、2級、3級。切換特權級是指從0級轉移到1級、或從1級轉移到3級。總之,是指從一個特權級轉移到了另一個不一樣的特權級。函數

學習特權級切換,關鍵知識點是:學習

  1. 兩個指令calliret
  2. 一個CPU特性:特權級變換時會將一個特權級的堆棧複製到另一個特權級堆棧。

從低到高

只有使用調用門才能從低特權級轉移到高特權級,更具體地說,是使用語句call 門選擇子操作系統

門描述符

門選擇子的結構和段選擇子一致,只不過它指向的是門描述符而不是段描述符。code

門描述符和段描述符佔用的內存空間相同,都是8個字節,64個bit。可後者包含的元素是:進程

  1. 目標代碼段選擇子。
  2. 在目標代碼段中的偏移量。
  3. ParamCount。這是什麼?後面再解釋。
  4. 門描述符的屬性。

代碼

使用調用門從低特權級轉移到高特權級的代碼是:ip

push	ax
push	bx
;SelectorGate 指向一個高特權級的目標代碼段
call	SelectorGate:0

ParamCount

入棧了2個元素,2就是上文提到的ParamCount。能夠把它理解成函數的參數。在門描述符中,ParamCount佔用5個bit位,能表示的最大值是2的5次方-1,即31。這意味着,使用一個調用門,最多能入棧31個元素。內存

堆棧複製

假定,上面的代碼的能成功從低特權級代碼段L轉移到高特權級代碼段H。L和H是不一樣的代碼段,各自的堆棧也不一樣。這不是必須的。L和H共用一個堆棧,也不是不行。這樣的話,在H中操做堆棧可能會破壞L中的堆棧(總之,存在這種可能)。因此,代碼段擁有獨立的堆棧更好。工作流

那麼,問題來了。入棧操做發生在L中,被入棧元素存在於L的堆棧LS,在H中怎麼從LS中獲取數據呢?也許存在方法,但必定很繁瑣。現實中,CPU會自動把LS中的元素複製到H的堆棧中。並不是複製LS中的所有元素,而是從LS的棧頂開始,複製ParamCount個元素。這個複製操做發生在call執行的時候。it

示意圖

短調用是段內部的調用,長調用是段之間的調用。長調用才能從低特權級轉移到高特權級。table

短調用和長調用示意圖之間的差異,僅僅在於後者把cs入棧了。cs是調用者的代碼段選擇子。

示意圖中的堆棧,是H的堆棧HS。

短調用
call執行前----> 高地址
ss
esp <-----------堆棧
參數二(ax)
參數一(bx)
call執行後----> eip
長調用
call執行前----> 高地址
ss
esp <-----------堆棧
參數二(ax)
參數一(bx)
cs
call執行後----> eip

CPU工做流程--調用門

調用門的運行過程,涉及LS和HS兩個堆棧。上面的示意圖只畫出了HS的狀態,不足以闡述調用門的整個流程,本小節再用文字詳細說明調用門的運行過程。

TSS

先介紹一個新東西,TSS,任務狀態寄存器,一個寄存器。CPU在切換任務的時候,能用TSS給切換下來的任務創建一個快照,這個快照包含一個任務的全部寄存器數據。不過,大部分操做系統嫌棄這種切換方式太消耗時間,並無徹底按照CPU廠商的意圖使用TSS。Linux系統也是如此。

在TSS中,包含ss0、esp0ss一、esp1ss二、esp2三組數據,正好對應0特權級、1特權級、2特權級三個層次的特權級。

特權級不是有四個層次嗎?爲何沒有對應3特權級的那一組數據呢?TSS的做用是爲低特權級向高特權級轉移時提供高特權級的堆棧。3特權級是最低特權級,沒有更低的特權級向它轉移。

假如,從3特權級向0特權級轉移,CPU會從TSS中選擇ss0、esp0做爲0特權級代碼段的堆棧。

流程

使用call SelectorGate:0實現低特權級向高特權級轉移的流程以下(不敘述CPL等特權級檢查流程,假設知足這些條件):

  1. 執行call語句時。
  2. 把當前代碼段的堆棧LS的ss_old、esp_old臨時保存起來。
  3. 從門選擇子指向的目標代碼段中獲取DPL,根據DPL的值,在TSS中選擇ss、esp,將堆棧指向的新堆棧。例如,DPL的值是0,選擇ss0、esp0
  4. ss_old、esp_old中的入棧到新堆棧中。
  5. 把LS中的棧元素複製到新堆棧中。複製規則是:從LS的棧頂開始,複製ParamCount個元素。
  6. 依次入棧cs、eip

小結

從低特權級轉移到高特權級的方法是,使用調用門,具體語句是call SelectorGate:0

不能使用jmpjmp只能在實現短調用,在同一個特權級轉移。由於jmp不會將下一條指令的地址存儲到新特權級的堆棧中,這意味着,jmp是一個有去無回的指令。從低特權級轉移到高特權級的場景,通常是用戶進程求助操做系統完成某種功能,須要再次返回用戶進程。

從高到低

電腦開機後,CPU的特權級是0,這是從BIOS那裏寄存下來的。這種知識彷佛無用,懶得多說。

前文講了從低特權級切換到高特權級的方法,可CPU從開始工做的那一刻起,一直都是在0特權級。這樣說來,若是要動手實現從低特權級轉移到高特權級,應該先實現從高特權級轉移到高特權級。

怎麼實現?

前文已經埋下了伏筆,call指令會將調用者(低特權級)的cs(選擇子)eip(偏移量)入棧到被調用者(高特權級)的堆棧中。從堆棧中獲取調用者(低特權級)的cs(選擇子)eip(偏移量),就能從高特權級轉移到低特權級。完成這項工做,只需一個指令而已,iretf

代碼

;特權級是3
push ax
push bx
call SelectorGate:0

mov ax, 5

call SelectorGate:0調用下面的代碼。

;特權級是0
;調用門選擇子指向的目標代碼段,高特權級代碼段
;一些操做,示範,沒必要理會具體功能
mov al, 'A'
mov ah, 0Fh
mov [gs:(80*20+20)*2], ax

iretf

iretf執行後,CPU會繼續執行mov ax, 5mov ax, 5就是被調用者堆棧中的cs:eip指向的指令。

示意圖

仍是畫兩個和上面call指令相似的堆棧圖吧。

短調用返回

ret執行後 高地址
調用者ss
調用者esp
參數一
參數二
ret執行前 調用者eip

長調用返回

retf執行後 高地址
調用者ss
調用者esp
參數一
參數二
調用者cs
retf執行前 調用者eip

工做流程--iretf

長調用返回使用iretf指令將上面的堆棧S中的元素出棧。具體流程以下:

  1. 從S中獲取調用者cs、調用eip,並加載到當前cs、eip中。
  2. iretf含有參數,esp增長參數個數跳過這些參數。
  3. 繼續出棧,把調用者esp、調用者ss加載到當前espss中。此時會切換到調用者堆棧。
  4. iretf含有參數,增長esp的值以跳過參數(在call前,參數也壓入了調用者堆棧中)。
  5. 最後執行cs:eip代碼。

iretf

  1. RET:多是近返回,也多是遠返回。
  2. RETN:近返回指令。
  3. RETF:遠返回指令。
  4. RET6:子程序返回後,(SP)←(SP) + 6。

沒有找到iretf的權威資料,上面的資料也沒有驗證。先擱置吧。

疑問

不使用調用門能不能轉移特權級?

使用jmp只能在同特權級跳轉。

一致代碼段,call只能轉移到比當前特權級高或相等的特權級。

非一致代碼段,call只能在相同特權級跳轉。

何時檢查特權級

沒有弄明白。好像不重要。

相關文章
相關標籤/搜索