MIT 6.828 JOS學習筆記3. Exercise 1.2


這篇博文是對Lab 1中的Exercise 2的解答~

Lab 1 Exercise 2:

  使用GDB的'si'命令,去追蹤ROM BIOS幾條指令,並且試圖去猜測,它是在做什么。但是不需要把每個細節都弄清楚。

答:

  在這里我們將盡可能的去分析每一條指令,由於題目中說我們只需要知道BIOS的幾條指令在做什么就夠了,所以我們也會盡可能的去分析,由於能力有限,這里面有很多問題還沒有解決,希望大家諒解。以后有機會會盡可能的把沒分析的命令去補全。

  首先注意這里是緊接着Lab 1 Part 1.2那篇博文中建立了qemu和gdb調試環境之后進行了,至於如何建立調試環境,大家可以再回到那篇博文中具體查看~

  在Lab 1 Part 1.2中我們建立了調試環境后,在運行gdb的窗口中會將顯示出PC機啟動后運行BIOS的第一條命令,如下:

1. 0xffff0:  ljmp $0xf000, $0xe05b    

 

  這是運行的第一條指令,是一條跳轉指令,跳轉到0xfe05b地址處。至於要知道這個地址是怎么通過指令中的值計算出來的,我們需要先知道,當PC機啟動時,CPU運行在實模式(real mode)下,而當進入操作系統內核后,將會運行在保護模式下(protected mode)。實模式是早期CPU,比如8088處理器的工作模式,這類處理器由於只有20根地址線,所以它們只能訪問1MB的內存空間。但是CPU也在不斷的發展,之后的80286/80386已經具備32位地址總線,能夠訪問4GB內存空間,為了能夠很好的管理這么大的內存空間,保護模式被研發出來。所以現代處理器都是工作在保護模式下的。但是為了實現向后兼容性,即原來運行在8088處理器上的軟件仍舊能在現代處理器上運行,所以現代的CPU都是在啟動時運行於實模式,啟動完成后運行於保護模式。BIOS就是PC剛啟動時運行的軟件,所以它必然工作在實模式。

  至於這兩個模式下的運行原理,可以看這個鏈接:http://blog.csdn.net/zdwzzu2006/article/details/4030948

  這里先簡單介紹一下地址的計算方法,在實模式下,指令中出現的地址都是采用

    (段基址:段內偏移)

  的形式的。其中這兩個字段的值,通常是存放寄存器中的。其中段基址必須放在段寄存器中,包括CS(代碼段), DS(數據段), SS(堆棧段), ES(擴展段)。不同的段寄存器存放的是你程序不同的段的起始地址。

  但是由於8088CPU中寄存器都是16位,而CPU地址總線是20位的,我們怎么通過16位的寄存器去拼接20位的地址呢?

  所以我們需要采用下面的方法:把段寄存器中的值左移4位,形成20位段基址,然后和16位段內偏移相加,就得到了真實地址。比如上面的指令中段寄存器的內容為0xf000,所以真實地址為 0xf000<<4+0xe05b = 0xfe05b。

 

2. 0xfe05b: cmpl $0x0, $cs:0x6ac8

  下一條指令,把0x0這個立即數和$cs:0x6ac8所代表的內存地址處的值比較,至於為什么這樣比較,現在還不是很清楚。其中$cs:0x6ac8就是我們剛剛介紹的在實模式下地址形成的格式,其中$cs就代表CS段寄存器的值。

 

3. 0xfe062:  jne  0xfd2e1

   jne指令:如果ZF標志位為0的時候跳轉,即上一條指令cmpl的結果不是0時跳轉,也就是$cs:0x6ac8地址處的值不是0x0時跳轉。

 

4. 0xfe066:  xor  %dx, %dx

  下一條指令地址是0xfe066,可見上面的跳轉指令並沒有跳轉。這條指令的功能是把dx寄存器清零。

 

5. 0xfe068:  mov  %dx %ss
6. 0xfe06a:  mov  $0x7000, %esp
7. 0xfe070:  mov  $0xf34d2, %edx
8. 0xfe076:  jmp  0xfd15c
9. 0xfd15c:  mov  %eax, %ecx

  接下來的這些指令就是設置一些寄存器的值,具體含義現在不明白..

  這里要注意第8條指令,進行了絕對跳轉。

 

10. 0xfd15f:  cli

   關閉中斷指令。這個比較好理解,啟動時的操作是比較關鍵的,所以肯定是不能被中斷的。這個關中斷指令用於關閉那些可以屏蔽的中斷。比如大部分硬件中斷。

 

11. 0xfd160:  cld

   設置方向標識位為0,表示后續的串操作比如MOVS操作,內存地址的變化方向,如果為0代表從低地址值變為高地址。

   具體什么是串操作,可以看這個鏈接:http://www.tyut.edu.cn/kecheng1/2008/site04/courseware/chapter3/3-3-4.html

 

12. 0xfd161:  mov  $0x8f, %eax
13. 0xfd167:  out  %al, $0x70
14. 0xfd169:  in  $0x71, %al

   這三個操作中涉及到兩個新的指令out, in。這兩個操作是用於操作IO端口的。這種IO端口的操作我們后面會經常接觸到,這里大致說下。

   CPU與外部設備通訊時,通常是通過訪問,修改設備控制器中的寄存器來實現的。那么這些位於設備控制器當中的寄存器也叫做IO端口。為了方便管理,80x86CPU采用IO端口單獨編址的方式,即所有設備的端口都被命名到一個IO端口地址空間中。這個空間是獨立於內存地址空間的。所以必須采用和訪問內存的指令不一樣的指令來訪問端口。

   所以這里引入in,out操作:

       in %al, PortAddress    向端口地址為PortAddress的端口寫入值,值為al寄存器中的值

     out PortAddres,%al    把端口地址為PortAddress的端口中的值讀入寄存器al中

   標准規定端口操作必須要用al寄存器作為緩沖。

   那么這三條命令就是要操作端口0x70,0x71,它們對應的是什么設備呢?根據下面的鏈接中所提供的清單(這個連接之后會經常用到,建議大家收藏):http://bochs.sourceforge.net/techspec/PORTS.LST

   我們知道了,0x70端口和0x71端口是用於控制系統中一個叫做CMOS的設備,這個設備是一個低功耗的存儲設備,它可以用於在計算機關閉時存儲一些信息,它是由獨立的電池供電的。這個鏈接有詳細介紹http://wiki.osdev.org/CMOS

   這個CMOS中可以控制跟PC相關的多個功能,其中最重要的就是時鍾設備(Real Time Clock)的 ,它還可以控制是否響應不可屏蔽中斷NMI(Non-Maskable Interrupt)

   操作CMOS存儲器中的內容需要兩個端口,一個是0x70另一個就是0x71。其中0x70可以叫做索引寄存器,這個8位寄存器的最高位是不可屏蔽中斷(NMI)使能位。如果你把這個位置1,則NMI不會被響應。低7位用於指定CMOS存儲器中的存儲單元地址所以如果你想訪問第1號存儲單元,並且在訪問時,我要使能NMI,那么你就應該向端口0x70里面送入0b10000001 = 0x81。

   即mov $0x81, %al

        out %al, 0x70

    然后對於這個地址單元的操作,比如讀或者寫就可以由0x71端口完成,比如你現在想從1號存儲單元里面讀出它的值,在完成上面的兩條指令后,就可以輸入這條指令

   in $0x71, %al 

  再回到我們的系統,這三條指令可以看出,它首先關閉了NMI中斷,並且要訪問存儲單元0xF的值,並且把值讀到al中,但是在后面我們發現這個值並沒有被利用,所以可以認為這三條指令是用來關閉NMI中斷的。

  

15. 0xfd16b:  in  $0x92, %al
16. 0xfd16d:  or  $0x2, %al
17. 0xfd16f:  out  %al, $0x92

 

  這三步操作又是在控制端口,此時被控制的端口號為0x92,通過上面那個鏈接 http://bochs.sourceforge.net/techspec/PORTS.LST

  我們可以查看到,它控制的是 PS/2系統控制端口A,而第16,17步的操作明顯是在把這個端口的1號bit置為1。這個端口的bit1的功能是

    bit 1= 1 indicates A20 active

   即A20位,即第21個地址線被使能,了解實模式和保護模式的同學肯定清楚,如果A20地址線被激活,那么系統工作在保護模式下。但是在之后的boot loader程序中,計算機首先要工作在實模式下啊。所以這里的這個操作,根據網上 http://kernelx.weebly.com/a20-address-line.html 所說應該是去測試可用內存空間。在boot loader之前,它肯定還會轉換回實模式。

 

18. 0xfd171:  lidtw  %cs:0x6ab8

  lidt指令:加載中斷向量表寄存器(IDTR)。這個指令會把從地址0xf6ab8起始的后面6個字節的數據讀入到中斷向量表寄存器(IDTR)中。中斷是操作系統中非常重要的一部分,有了中斷操作系統才能真正實現進程。每一種中斷都有自己對應的中斷處理程序,那么這個中斷的處理程序的首地址就叫做這個中斷的中斷向量。中斷向量表自然是存放所有中斷向量的表了。關於中斷向量表的介紹,大家可以戳這個鏈接 http://wiki.osdev.org/Interrupt_Descriptor_Table 

  

19. 0xfd177:  lgdtw  %cs:0x6a74 

   把從0xf6a74為起始地址處的6個字節的值加載到全局描述符表格寄存器中GDTR中。這個表實現保護模式非常重要的一部分,我們在介紹boot loader時會具體介紹它。

 

20. 0xfd17d:  mov  %cr0, %eax
21. 0xfd180:  or  $0x1, %eax
22. 0xfd184:  mov  %eax, %cr0

  計算機中包含CR0~CR3四個控制寄存器,用來控制和確定處理器的操作模式。其中這三個語句的操作明顯是要把CR0寄存器的最低位(0bit)置1。CR0寄存器的0bit是PE位,啟動保護位,當該位被置1,代表開啟了保護模式。但是這里出現了問題,我們剛剛說過BIOS是工作在實模式之下,后面的boot loader開始的時候也是工作在實模式下,所以這里把它切換為保護模式,顯然是自相矛盾。所以只能推測它在檢測是否機器能工作在保護模式下。

 

23. 0xfd187:  ljmpl  $0x8, $0xfd18f
24. 0xfd18f:   mov  $0x10, %eax
25. 0xfd194:  mov  %eax, %ds
26. 0xfd196:  mov  %eax, %es
27. 0xfd198:  mov  %eax, %ss
28. 0xfd19a:  mov  %eax, %fs
29. 0xfd19c:  mov  %eax, %gs

   修改這些寄存器的值。這些寄存器都是段寄存器。大家可以戳這個鏈接看一下具體介紹 http://www.eecg.toronto.edu/~amza/www.mindsec.com/files/x86regs.html

   這里的23~29步之所以這么做是按照規定來的,https://en.wikibooks.org/wiki/X86_Assembly/Global_Descriptor_Table鏈接中指出,如果剛剛加載完GDTR寄存器我們必須要重新加載所有的段寄存器的值,而其中CS段寄存器必須通過長跳轉指令,即23號指令來進行加載。所以這些步驟是在第19步完成后必須要做的。這樣才能是GDTR的值生效。

 

。。。。。

 

  非常抱歉,到目前為止我只能理解到這里,后面的代碼雖然操作上容易,但是它們的目的變得非常難以琢磨,所以我只能把自己比較理解的一部分展示給大家,在之后我會盡力把這部分的知識補全!

  綜上,我們可以看到BIOS的操作就是在控制,初始化,檢測各種底層的設備,比如時鍾,GDTR寄存器。以及設置中斷向量表。這都和Lab 1 Part 1.2最后兩段說的一樣。但是作為PC啟動后運行的第一段程序,它最重要的功能是把操作系統從磁盤中導入內存,然后再把控制權轉交給操作系統。所以BIOS在運行的最后會去檢測可以從當前系統的哪個設備中找到操作系統,通常來說是我們的磁盤。也有可能是U盤等等。當BIOS確定了,操作系統位於磁盤中,那么它就會把這個磁盤的第一個扇區,通常把它叫做啟動區(boot sector)先加載到內存中,這個啟動區中包括一個非常重要的程序--boot loader,它會負責完成整個操作系統從磁盤導入內存的工作,以及一些其他的非常重要的配置工作。最后操作系統才會開始運行。

  可見PC啟動后的運行順序為 BIOS --> boot loader --> 操作系統內核

  

  以上就是對Exercise 2的分析,老規矩

    zzqwf12345@163.com

  歡迎騷擾~

  

 


免責聲明!

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



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