自制操作系統Antz(4)——進入保護模式 (下) 實現內核並從硬盤載入


Antz系統更新地址: https://www.cnblogs.com/LexMoon/category/1262287.html

Linux內核源碼分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html

  目前已經完成了MBR的雛形,並且直接操作顯卡完成了屏幕的內容顯示。接下來我們要改造之前的MBR,做一個大的改進,使MBR可以讀取硬盤,因為我們的MBR受限制於512字節大小,在這么小的空間里沒法為內核准備好環境,更不要說加載內核到內存中並運行了,所以我們需要在另一個程序中完成初始化環境與加載內核的任務,這個程序我們叫做loader。loader這個程序放在哪里呢?如何去執行呢?這就是這次MBR改進的任務了,我們需要從硬盤上去把loader加載到內存中,並把執行權的接力棒交給它。

  在第一天講過了,MBR是在硬盤的第0扇區,第一扇區是空閑的,但是離的太近總是感覺不安全,所以我們將loader放到第二扇區。MBR從第二扇區中把它都出來,然后放到哪里呢?

  原則上空閑位置都是可以的。圖中7E00~9FBFF和500~7BFF這兩段可用區域都可以。隨着功能的添加,內核必然會越來越大,所以我們盡量把loader放在低處,所以我選擇為了0x500處。

  說完了本次的基本任務和流程,接下來就是關鍵的問題了,如何操作硬盤?

 


 

0. 關於硬盤

  關於硬盤的種類歷史此處不做介紹。

    如有興趣可參考:硬盤

  我們先來講講它的工作原理,如圖是機械硬盤的示意圖。

    

  主軸上面有兩個盤片,其實不止兩個,這里只是示意性畫了兩個。盤片固定在主軸上隨主軸高速轉動,每個盤片分為上下兩面,每個面都存儲有數據,每個盤面都各有一個磁頭來讀取數據,故一個盤片對應兩個磁頭(注意盤片和盤面,不要看混)。由於盤面和磁頭是一一對應的關系,故用磁頭號來標識盤面號,磁頭0對應盤面0,磁頭1對應盤面1,從0開始計數,盤面0就是第一個盤面。磁頭不會自己在盤片上移動,它需要被固定在磁頭臂上,在磁頭臂的帶動下,沿着盤片的邊緣向圓心的方向來回擺動,注意擺動的軌跡是個弧,並不是絕對徑向地直來直去。一方面是因為磁頭臂是步進電機驅動的,磁頭臂一段時步進電機主軸,另一端的磁頭,電機每次都會轉動一個角度,所以帶動磁頭臂在“畫圓”,而磁頭位於磁頭臂的另一端,所以也跟着呈鍾擺運動,軌跡時弧線,並不是直線。另一方面,磁頭讀取數據也不需要做直來直去的運動,能否找到數據,只跟它最終落點有關,和中間路徑形狀無關,所以一方面盤面自轉,另一方面磁頭擺動,使得磁頭可以盤面任意位置的數據。

  說完了運動,在說存儲邏輯,盤片表面時用於存儲數據的磁性介質,為了更有效的管理磁盤,這些磁性介質也被“格式化”成易於管理的格局,即將盤面划分成了多個同心環,以同心環畫扇形,扇形與每個同心環相交的弧狀區域作為最基本的數據存儲單元。這個同心環就稱為磁道,而同心環上弧狀的扇形部分就稱為扇區,它作為我們硬盤存儲數據的最基本單位,大小是512字節。我們寫入數據最終是寫入了扇形的扇區中。注意,磁道是一個環,不是線,線上可無法存儲數據。磁頭臂帶動磁頭在盤片上方移動,就是在找磁道的位置,盤片高速自轉,就是在磁道內定位扇區。

   磁道的編號和磁頭的編號也是從0開始,相同編號的磁道組成的管狀區域就稱為柱面。柱面有什么用呢? 機械硬盤大的尋道時間是整個硬盤的瓶頸,為了減少尋道時間,就盡量在存儲上下功夫。尋道,簡而言之就是在磁頭在磁道間跳轉,跳轉所需要的時間就是尋道時間。柱面就可以減少尋道的時間。至於原理,可以這樣理解,當我們要存儲的數據少於一個磁道的存儲量時,我們可以直接存儲在一個磁道里面,而不需要跳轉到其他磁道(不需要尋道)。當要存儲量大於一個磁道時,需要多次尋道,而尋道會浪費大量時間,如果我們使用柱面,存滿一個磁道后,將剩下的數據存儲在其他盤面的相同磁道號處,就可以避免尋道了,反過來,讀取數據也是這樣,以此,盤面越多,硬盤越快

  扇區的編號與磁道磁頭不同,它是從1開始編號的,而且一個扇區只對當前磁道有效,所以各個磁道間的扇區編號都相同,至於一個磁道中的扇區數量多少與廠商有關,一般都是63個扇區。磁頭如何找到所需的扇區呢? 每個扇區其實都是有自己的頭部的,頭部之后才是512字節的存儲區,頭部包含了扇區自身的信息,哪些信息可以唯一定位一個扇區呢? 當然是磁頭號,磁道號和扇區號了。

 

1. 控制硬盤之前

  之前在直接操作顯存中說過,CPU不會直接與這些設備聯系,而是與IO接口通信,再由IO接口向下傳達信息,CPU與硬盤的聯系就是通過硬盤控制器。

  硬盤控制器與硬盤的關系就好像顯卡與顯示器。關於硬盤的接口,你可能聽說過PATA和SATA,ATA是一種全球化的標准,PATA是並行ATA,SATA是后來的串行ATA。以前的主機一般至支持4個並行PATA,在串行SATA出現之后,支持幾塊硬盤完全取決於主板能力。

  兩種類型線纜完全不一樣,PATA接口的線纜也稱為IDE線,一個IDE線上可以掛兩塊硬盤,一個是主盤,一個是從盤。主盤從盤分工很明顯,很多工作都要靠主盤來進行,比如系統就要裝在主盤上。隨着時代發展,兼容性的提升,主盤從盤已經沒有了區別。之前說一個主板支持四塊PATA硬盤,那么就是兩個IDE線接口。這兩個接口也是以0開始編號的,分別是IDE0,IDE1。不過按照ATA的說法,這兩個插槽接口叫做通道,IDE0就是Primary通道,IDE1就是Secondary通道。SATA硬盤也是兼容PATA的編程接口。(這里不要把主盤從盤和通道弄混了)。

  硬盤是一個很復雜的結構,我們暫時只需要知道一部分端口就可以了。

  端口可以分為兩組,Command Block registers和Control Block registers。 Command Block registers用於向硬盤啟動器寫入命令字或者從硬盤控制器獲取硬盤狀態,Control Block registers用於控制硬盤工作狀態。

  端口是按照通道給出的,所以不要認為端口是直接針對某塊硬盤的,一個通道的主從硬盤都是使用這些端口號的,要想操作某通道上的某塊硬盤,需要單獨指定。看上面的表格,有一個叫做Device的寄存器,這就是驅動器設備,也就是和硬盤相關的。不過此寄存器是八位的,一個通道上就兩塊硬盤,指定哪塊硬盤只用一位就可以了,至於其他位當然也有用處,很多設置都會集中在此寄存器,其中的第四位便是指定通道上的主或從硬盤,0是主盤,1是從盤。端口用途在讀寫時是有區別的,比如Primary通道上的0x1F1端口來說,讀操作時,如果讀取失敗,里面存儲的是失敗狀態信息,所以稱為error寄存器,並且此時會在0x1F2端口中存儲未讀的扇區數。寫操作時就變成Features寄存器,此寄存器用於寫命令參數。至於為什么要把一個寄存器分為兩種狀態,可能時在早期多加寄存器有很大代價吧。

  接下來介紹一下表中各個寄存器的功能。

  data寄存器顧名思義就是管理數據的,數據的讀寫當然是越快越好,所以data寄存器比其他寄存器寬一些,16位。在讀硬盤時,硬盤准備好數據后,硬盤控制器將其放在內部的緩存區中,不斷讀此寄存器便是讀出緩存器中的全部數據。在寫硬盤時,我們要把數據不斷寫入此寄存器中,然后數據便會被送入緩存區,硬盤控制器發現這個緩存區中有數據了,便將此處數據寫入相應扇區中。

  讀硬盤時0x171或0x1F1的寄存器叫做Error寄存器,只在讀取失敗時才有用,里面有記錄失敗的信息,尚未讀取的扇區數在Sector count寄存器中。在寫硬盤時,該寄存器叫做Feature寄存器,里面是一些命令需要指定的額外參數。Error和Feature是同一個寄存器,只是在不同情況有不同的名稱,它是八位寄存器。

  Sector count寄存器用來指定帶讀取或者帶寫入的扇區數。硬盤每完成一個扇區,此寄存器中的值就會減一,這是一個八位寄存器,最大值為255,若指定為0,則表示需要操作256個扇區。

  LBA寄存器有LBA low,LBA mid,LBA high三個,它們三個都是8位,LBA  low寄存器用來存儲28位地址的第0~7位,LBA mid用來存儲28位的第8~15位,LBA high寄存器用來存儲28位的第16~23位。那么剩下的四位呢? 這就是device寄存器的任務了。

  device寄存器是個雜項,它的寬度是八位,第四位是存儲LBA的第24位~27位。結合上面的三個LBA寄存器,第四位用來指定通道上的主盤或從盤,0代表從,1代表主。第六位用來存儲是否啟用LBA方式,1代表LBA模式,0代表CHS模式。另外兩位第五位和第七位是固定為1的,稱為MBS位,可以不用注意。

  讀硬盤時,端口0x1F7或0x177的寄存器叫Status,它是8位寬度的寄存器,用來給出硬盤的狀態信息。第0位是ERR位,如果此位為1,表示命令出錯了,具體原因可見Error寄存器。第三位data request位,如果此位為1,表示數據已經准備好了。第6位為DRDY,表示硬盤就緒。第七位是BSY位,表示硬盤是否繁忙。

  寫硬盤時,端口0x1F7或0x177的寄存器叫Command,它和Status是同一個,此寄存器用來存儲讓硬盤執行的命令,把命令寫入此寄存器,只要把命令寫入此寄存器,硬盤就開始工作了。主要是以下三個命令:

    1)identify : 0xEC    硬盤識別

    2)read sector : 0x20   即讀扇區

    3)  write sector : 0x30   即寫扇區

 

 

2. 控制硬盤步驟

  有的指令直接往command寄存器中寫就可以了,有的還需要feature寄存器中寫入參數。最主要的就是command寄存器一定要最后寫,因為一旦command寄存器被寫入后,硬盤就開始干活了。關於操作步驟如下:

  1)先選擇通道,往該通道的sector count寄存器中寫入帶操作的扇區數。

  2)往該通道上的三個LBA寄存器寫入扇區起始地址的低24位。

  3)往device寄存器中寫入LBA地址的24~27位,並置第六位為1,使其為LBA模式,設置第4位,選擇操作的硬盤(主從)。

  4)往該通道上的額command寄存器中寫入操作命令。

  5)讀取該通道上的status寄存器,判斷硬盤工作是否完成

  6)如果以上步驟是讀硬盤,進入下一個步驟。否則,完工

  7)將硬盤數據讀出

3. 使用硬盤

  MBR即將改造成可以讀取硬盤,那么我們的內核加載就有了方法。所以我們要學會從另一個程序中完成初始化環境並加載內核,這個程序叫做loader,loader放在第二個扇區,地址之前已經講過了0x500~0x7BFF區域中。

  1 %include "boot.inc"
  2 SECTION MBR vstart=0x7c00
  3   mov ax,cs
  4   mov ds,ax
  5   mov es,ax
  6   mov ss,ax
  7   mov fs,ax
  8   mov sp,0x7c00
  9   mov ax,0xb800
 10   mov gs,ax
 11 
 12   mov ax,0x600
 13   mov bx,0x700
 14   mov cx,0
 15   mov dx,0x1010
 16   int 0x10
 17 
 18   mov byte [gs:0x00],'A'
 19   mov byte [gs:0x01],0xA4
 20 
 21   mov byte [gs:0x02],'n'
 22   mov byte [gs:0x03],0x13
 23 
 24   mov byte [gs:0x04],'t'
 25   mov byte [gs:0x05],0x52
 26 
 27   mov byte [gs:0x06],'z'
 28   mov byte [gs:0x07],0xB1
 29 
 30   mov byte [gs:0x08],' '
 31   mov byte [gs:0x09],0xCC
 32 
 33   mov byte [gs:0x0A],'U'
 34   mov byte [gs:0x0B],0x2B
 35 
 36   mov byte [gs:0x0C],'h'
 37   mov byte [gs:0x0D],0x6D
 38 
 39   mov byte [gs:0x0E],'l'
 40   mov byte [gs:0x0F],0x7E
 41 
 42   mov byte [gs:0x10],' '
 43   mov byte [gs:0x11],0x49
 44 
 45   mov byte [gs:0x12],'K'
 46   mov byte [gs:0x13],0xE5
 47 
 48   mov byte [gs:0x14],'o'
 49   mov byte [gs:0x15],0x8A
 50 
 51   mov byte [gs:0x16],'n'
 52   mov byte [gs:0x17],0x96
 53 
 54   mov byte [gs:0x18],'e'
 55   mov byte [gs:0x19],0x68
 56 
 57   mov eax,LOADER_START_SECTOR
 58   mov bx,LOADER_BASE_ADDR
 59   mov cx,1
 60   call rd_disk_m_16
 61 
 62   jmp LOADER_BASE_ADDR
 63 
 64 rd_disk_m_16:
 65 
 66   mov esi,eax
 67   mov di,cx
 68   mov dx,0x1f2
 69   mov al,cl
 70   out dx,al
 71 
 72   mov eax,esi
 73 
 74   mov dx,0x1f3
 75   out dx,al
 76 
 77   mov cl,8
 78   shr eax,cl
 79   mov dx,0x1f4
 80   out dx,al
 81 
 82   shr eax,cl
 83   mov dx,0x1f5
 84   out dx,al
 85 
 86   shr eax,cl
 87   and al,0x0f
 88   or al,0xe0
 89   mov dx,0x1f6
 90   out dx,al
 91 
 92   mov dx,0x1f7
 93   mov al,0x20
 94   out dx,al
 95 
 96 not_ready:
 97   nop
 98   in al,dx
 99   and al,0x88
100 
101   cmp al,0x08
102   jnz not_ready
103 
104   mov ax,di
105   mov dx,256
106   mul dx
107   mov cx,ax
108 
109   mov dx,0x1f0
110 
111 go_on_read:
112   in ax,dx
113   mov [bx],ax
114   add bx,2
115   loop go_on_read
116   ret
117 
118   times 510-($-$$) db 0
119   db 0x55,0xaa

  boot.inc文件內容如下:

1 ;-------------     loader和kernel   ----------
2 LOADER_BASE_ADDR equ 0x900 
3 LOADER_START_SECTOR equ 0x2

  

  LOADER_BASE_ADDR就是loader在內存中的位置,LOADER_START_SECTOR說明了loader放在了第二個扇區。

  內核加載器如下:

 1 %include "boot.inc"
 2 section loader vstart=LOADER_BASE_ADDR
 3 
 4 ; 輸出背景色綠色,前景色紅色,並且跳動的字符串"1 MBR"
 5 mov byte [gs:0x00],'2'
 6 mov byte [gs:0x01],0xA4     ; A表示綠色背景閃爍,4表示前景色為紅色
 7 
 8 mov byte [gs:0x02],' '
 9 mov byte [gs:0x03],0xA4
10 
11 mov byte [gs:0x04],'L'
12 mov byte [gs:0x05],0xA4
13 
14 mov byte [gs:0x06],'O'
15 mov byte [gs:0x07],0xA4
16 
17 mov byte [gs:0x08],'A'
18 mov byte [gs:0x09],0xA4
19 
20 mov byte [gs:0x0a],'D'
21 mov byte [gs:0x0b],0xA4
22 
23 mov byte [gs:0x0c],'E'
24 mov byte [gs:0x0d],0xA4
25 
26 mov byte [gs:0x0e],'R'
27 mov byte [gs:0x0f],0xA4
28 
29 jmp $               ; 通過死循環使程序懸停在此

  使用dd命令將之前生成的bin寫入第0個扇區,loader生成的bin寫入第2個扇區(個人愛好,也可以是第一個,但boot.inc也要改變)。


免責聲明!

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



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