基於JOS 80x86 的堆棧切換簡要分析

基於JOS 80x86 的堆棧切換簡要分析


這個問題一直困擾很久,發現還是有點粗心,源頭--堆棧初始化沒怎麼搞明白.


這裏首先強調,一定一定要搞清楚分段和分頁保護的機制.

現有分段,後有分頁,分頁可有可無,看寄存器cr0是否開啓PE位(page enable. 在JOS系統的boot.S裏就已經開啓了)


文章從三個方面對棧進行分析

0. GDT 全局段尋址描述表

1. 棧的初始化.

2.用戶棧到內核棧的切換

3.內核棧到用戶棧的切換



0. GDT 全局段尋址描述表


你能看見第0個段這個時候是不允許訪問的,GD_KT右移三位變成 (0x8 >> 3 == 1),第一個段是內核的代碼段.可讀可執行.第二個是GD_KD 右移三位 (0x10 >> 3 ==  2)第二段.是內核數據段.

第三個GD_UT右移三位(0x18 >> 3 == 3) 第三個段是用戶代碼段.

第四個GD_UD 右移三位(0x20 >> 3 == 4) 第四個段是用戶數據段.

後面的TSS段和我們的主題關係不大,只是任務切換的時候有用.

看過這裏之後,嘛嘛再也不用擔心別人裝逼的時候說"代碼和數據是分離的",我聽不懂了.

紙老虎!



1.棧的初始化.

首先開機系統開始運行的時候,在boot.S階段,還沒有開啓之前,就立馬設置好了棧.怎麼做的呢?


首先,把ax寄存器異或置0.然後把ax寄存器的值賦值給ds es ss寄存器.


初始的時候,數據段,額外段,堆棧段,都指向第0個段.這時候還沒有什麼分頁機制

段尋址 address == segment : offset == (segment << 4 bits ) + offset 就直接得到物理地址了

而這裏選擇的是第0個段啊!同志啊,...在這個"原始的荒野",你用的地址都是物理地址

接着立馬就開啓了分頁機制,

lgdt指令馬上加載我們之前介紹的GDT全局段描述表.

開啓分頁機制,我們也就進入了保護模式.




接着在bootloader階段各種段 ds cs都指向$ PROT_MODE_DSEG 0x10指向的內核數據段



重要的事情說三遍,

JOS中堆棧段和數據段指向同一個段,

JOS中堆棧段和數據段指向同一個段,

JOS中堆棧段和數據段指向同一個段,

: )


到後來初始化CPU的時候,也是把ss指向 GD_KD


OK ,到這裏棧的初始化就算講明白了(至少我自我感覺非常良好哈哈哈)



2. 用戶棧切換到內核棧.

這裏有各種方式可以切換,我們集中分析一種Trap Gate觸發的切換就好了(其餘的還有Call Gate, Interrupt Gate,Task Gate)可以去看趙炯的0.11 Linux源代碼分析那本書,對於80x86的介紹非常的詳細,也可以讀Intel的手冊...

重點放在*(int *)0xDeadBeef = 0就好,其他的可以無視,和我們這一小節的主題無關,我們關注的是棧的切換.


由於這裏嘗試對一個非法地址寫入,那麼直接page fault,有米有! 

由於觸發的異常,那麼CPU會幫我們直接把堆棧段進行切換(注意,很多其他寄存器不會自動切換,但是cs ss會!)

口說無憑,我們來測試

下面是剛好在這句坑爹的指令執行之前,各種寄存器的狀態


常規寄存器都不需要怎麼關注,集中看 cs ss ds es fs gd eflags就好


下面我們看對比圖.觸發page fault前後的對比.




你會發現 cs ss 變了其他的 ds es fs gs都沒變,而且這時候 eflags的IF標識沒啦,中斷這個時候是被屏蔽的.

結論: 觸發異常的時候,CPU是會自動切換 代碼段和堆棧段寄存器的,而數據段沒有自動切換,以至於我們需要手動的在彙編代碼中切換 ds .當ds都完成切換的時候,就完成了所謂的從用戶態到內核態切換.


3. 內核態到用戶態的切換(這裏不討論用戶異常棧的情況).

真正切換的地方在這裏.從內核棧切換到普通用戶棧.實質上是前面用戶態到內核態的一個逆向過程.

寄存器pop的順序都是完全相反的...


這裏把tf指針指向的 struct Trapframe設置成棧頂指針,很巧妙的把各種恢復各種寄存器的值.

直到最後 iret由於返回地址不再內核代碼段內,發生堆棧切換.


這是切換前後的寄存器對比圖:




切換之後,eflags立馬有了 IF,允許了中斷調用.









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章