有4個層次的特權級,從高到低依次是:0級、1級、2級、3級。切換特權級是指從0級轉移到1級、或從1級轉移到3級。總之,是指從一個特權級轉移到了另一個不一樣的特權級。函數
學習特權級切換,關鍵知識點是:學習
call
和iret
。只有使用調用門才能從低特權級轉移到高特權級,更具體地說,是使用語句call 門選擇子
。操作系統
門選擇子的結構和段選擇子一致,只不過它指向的是門描述符而不是段描述符。code
門描述符和段描述符佔用的內存空間相同,都是8個字節,64個bit。可後者包含的元素是:進程
使用調用門從低特權級轉移到高特權級的代碼是:ip
push ax push bx ;SelectorGate 指向一個高特權級的目標代碼段 call SelectorGate:0
入棧了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 | |
調用門的運行過程,涉及LS和HS兩個堆棧。上面的示意圖只畫出了HS的狀態,不足以闡述調用門的整個流程,本小節再用文字詳細說明調用門的運行過程。
先介紹一個新東西,TSS,任務狀態寄存器,一個寄存器。CPU在切換任務的時候,能用TSS給切換下來的任務創建一個快照,這個快照包含一個任務的全部寄存器數據。不過,大部分操做系統嫌棄這種切換方式太消耗時間,並無徹底按照CPU廠商的意圖使用TSS。Linux系統也是如此。
在TSS中,包含ss0、esp0
、ss一、esp1
、ss二、esp2
三組數據,正好對應0特權級、1特權級、2特權級三個層次的特權級。
特權級不是有四個層次嗎?爲何沒有對應3特權級的那一組數據呢?TSS的做用是爲低特權級向高特權級轉移時提供高特權級的堆棧。3特權級是最低特權級,沒有更低的特權級向它轉移。
假如,從3特權級向0特權級轉移,CPU會從TSS中選擇ss0、esp0
做爲0特權級代碼段的堆棧。
使用call SelectorGate:0
實現低特權級向高特權級轉移的流程以下(不敘述CPL等特權級檢查流程,假設知足這些條件):
call
語句時。ss_old、esp_old
臨時保存起來。ss、esp
,將堆棧指向的新堆棧。例如,DPL的值是0,選擇ss0、esp0
。ss_old、esp_old
中的入棧到新堆棧中。ParamCount
個元素。cs、eip
。從低特權級轉移到高特權級的方法是,使用調用門,具體語句是call SelectorGate:0
。
不能使用jmp
。jmp
只能在實現短調用,在同一個特權級轉移。由於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, 5
。mov ax, 5
就是被調用者堆棧中的cs:eip
指向的指令。
仍是畫兩個和上面call
指令相似的堆棧圖吧。
ret執行後 | 高地址 |
---|---|
調用者ss | |
調用者esp | |
參數一 | |
參數二 | |
ret執行前 | 調用者eip |
retf執行後 | 高地址 |
---|---|
調用者ss | |
調用者esp | |
參數一 | |
參數二 | |
調用者cs | |
retf執行前 | 調用者eip |
長調用返回使用iretf
指令將上面的堆棧S中的元素出棧。具體流程以下:
iretf
含有參數,esp增長參數個數跳過這些參數。esp
、ss
中。此時會切換到調用者堆棧。iretf
含有參數,增長esp的值以跳過參數(在call
前,參數也壓入了調用者堆棧中)。cs:eip
代碼。沒有找到iretf
的權威資料,上面的資料也沒有驗證。先擱置吧。
不使用調用門能不能轉移特權級?
使用jmp
只能在同特權級跳轉。
一致代碼段,call
只能轉移到比當前特權級高或相等的特權級。
非一致代碼段,call
只能在相同特權級跳轉。
沒有弄明白。好像不重要。