MIT 6.828 JOS學習筆記12 Exercise 1.9


Lab 1中Exercise 9的解答報告

Exercise 1.9:

  判斷一下操作系統內核是從哪條指令開始初始化它的堆棧空間的,以及這個堆棧坐落在內存的哪個地方?內核是如何給它的堆棧保留一塊內存空間的?堆棧指針又是指向這塊被保留的區域的哪一端的呢?

答:

  1. 先需要判斷操作系統內核是從哪條指令開始初始化它的堆棧空間的。

  前面已經分析過boot.S和main.c文件的運行過程,這個文件中的代碼是PC啟動后,BIOS運行完成后,首先執行的兩部分代碼。但是它們並不屬於操作系統的內核。當main.c文件中的bootmain函數運行到最后時,它執行的最后一條指令就是跳轉到entry.S文件中的entry地址處。此時控制權已經被轉交給了entry.S。

  在跳轉到entry之前,並沒有對%esp,%ebp寄存器的內容進行修改,可見在bootmain中並沒有初始化堆棧空間的語句。

  下面進入entry.S,在entry.S中我們可以看到它最后一條指令是要調用i386_init()子程序。這個子程序位於init.c文件之中。在這個程序中已經開始對操作系統進行一些初始化工作,並且自重進入mointor函數。可見到i386_init子程序時,內核的堆棧應該已經設置好了。所以設置內核堆棧的指令就應該是entry.S中位於 call i386_init 指令之前的兩條語句:

  movl    $0x0,%ebp            # nuke frame pointer
  movl    $(bootstacktop),%esp

  這兩條指令修改了%ebp,%esp兩個寄存器的值,而這兩個寄存器的值是和堆棧息息相關的。

  

  2. 這個堆棧坐落在內存的什么地方?

  解答這個問題就需要我們好好分析一下entry.S文件了。最好的方法自然是閱讀其源碼,並且也要配合反匯編文件。

  首先我們配置好一個qemu,gdb調試環境,現在我們首先在gdb中設置一個斷點(指令: b *0x7d63),就設置到馬上進入entry之前,然后一步步進行調試。

  

  當我們准備運行entry.S中第一條指令 movx $0x1234, 0x472 時,指令地址是0x10000C,如上圖所示。這個比較好理解,因為在bootmain里面,我們已經把操作系統的內核文件全部加載到物理內存0x100000處了。所以0x10000C是系統內核的第一條指令所在的物理地址處。

  而當我們繼續運行運行到jmp *%eax之后,后面的指令地址就都變化了,變換為:

  

  圖中的地址是0xf010002f,很明顯這是一個虛擬地址,它的真實地址應該是0x0010002f,因為所有的內核代碼都實際存放在這個內存區域中。之所以現在要把指令地址設置為0xf010002f,即把操作系統的代碼的虛擬地址設置為從0xf0100000開始。目的就是能夠讓程序員在編程時,能夠利用虛擬地址空間的低地址空間。如果它編寫的程序調用了操作系統的代碼,則操作系統代碼的虛擬地址一定位於高地址空間0xf0100000處。這樣非常有利於程序員寫程序。

  所以必須有一種機制能夠實現,即便程序員在程序中指定的操作系統的代碼的虛擬地址在0xf0100000高地址空間,但是我們這個機制也能夠把這個高地址轉換為這個代碼真實的在內存中的位置。比如上圖中,我們想訪問0xf010002f處的指令,這是個虛擬地址,當實際運行時,會有一種機制把這個地址轉換為真實地址0x0010002f。

  在entry.S文件中,下面的這些代碼是來實現這個機制的:

1   movl    $(RELOC(entry_pgdir)), %eax
2   movl    %eax, %cr3
3   movl    %cr0, %eax
4   orl    $(CR0_PE|CR0_PG|CR0_WP), %eax
5   movl    %eax, %cr0

  這個機制的實現方式是通過寫了一個c語言的頁表,entry_pgdir,這個手寫的頁表可以自動的把[0xf0000000-0xf0400000]這4MB的虛擬地址空間映射為[0x00000000-0x00400000]的物理地址空間。可見這個頁表的映射能力還是比較有限的,只能映射一個區域。對於當前執行的這些指令,這個映射空間就已經足夠了。因為當前運行的是內核程序,他們的虛擬空間地址范圍在[0xf0000000-0xf0400000]之內。但是當操作系統真正正常的運行起來的時候,這個映射就不夠用了。必須采用更全面的,也就是在lab 2中要介紹的頁表機制。所以當操作系統真正正常運行起來時,entry_pgdir這個頁表將不會再使用。

  現在依次分析上述語句,首先看第1句,它的功能是把entry_pgdir這個頁表的起始物理地址送給%eax,這里RELOC宏的功能是計算輸入參數的物理地址。

  第2句,把entry_pgdir這個頁表的起始地址傳送給寄存器%cr3。

  控制寄存器cr2和cr3都是和分頁機制相關的寄存器。其中cr3寄存器存放頁表的物理起始地址。

  第3~5句,修改cr0寄存器的值,把cr0的PE位,PG位, WP位都置位1。其中PE位是啟用保護標識位,如果被置1代表將會運行在保護模式下。PG位是分頁標識位,如果這一位被置1,則代表開啟了分頁機制。WP位是寫保護標識,如果被置位為1,則處理器會禁止超級用戶程序向用戶級只讀頁面執行寫操作。

  這條指令過后,就開始工作在具有分頁機制的模式之下了。接下來的指令就可以指定[0xf0000000-0xf0400000]范圍的指令了。

 

    然后下面兩條指令就把當前運行程序的地址空間提高到[0xf0000000-0xf0400000]范圍內。

1     mov    $relocated, %eax
2     jmp    *%eax

  其中在實際調試時,這兩條指令如下:

  

  可見relocated的值為0xf010002f。此時分頁系統會把這個虛擬地址,轉換為真實的物理地址。

  

  接下來就到了最關鍵的兩句指令:

  

1     movl    $0x0,%ebp            # nuke frame pointer
2     movl    $(bootstacktop),%esp
3     call    i386_init

  這兩個指令分別設置了%ebp,%esp兩個寄存器的值。其中%ebp被修改為0。%esp則被修改為bootstacktop的值。這個值為0xf0110000。另外在entry.S的末尾還定義了一個值,bootstack。注意,在數據段中定義棧頂bootstacktop之前,首先分配了KSTKSIZE這么多的存儲空間,專門用於堆棧,這個KSTKSIZE = 8 * PGSIZE  = 8 * 4096 = 32KB。所以用於堆棧的地址空間為 0xf0108000-0xf0110000,其中棧頂指針指向0xf0110000. 那么這個堆棧實際坐落在內存的 0x00108000-0x00110000物理地址空間中。

   3. 核是如何給它的堆棧保留一塊內存空間的?

   其實就是通過剛剛分析的,在entry.S中的數據段里面聲明一塊大小為32Kb的空間作為堆棧使用。從而為內核保留了一塊空間。

 

  4. 堆棧指針又是指向這塊被保留的區域的哪一端的呢?

  堆棧由於是向下生長的,所以堆棧指針自然要指向最高地址了。最高地址就是我們之前看到的bootstacktop的值。所以將會把這個值賦給堆棧指針寄存器。

   

  以上就是對Exercise 1.9的分析,歡迎大家提出問題或建議~

    zzqwf12345@163.com

  

 

 

 

 

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM