【自制操做系統03】讀取硬盤中的數據

經過 【自制操做系統01】硬核講解計算機的啓動過程【自制操做系統02】環境準備與啓動區實現 的講解,咱們已經實現了一個最簡單的操做系統(僅僅一條機器指令)。html

今天咱們要再往前進一步,逐漸將這個最簡單的操做系統完善起來。以前最簡單的操做系統是寫在啓動區的 512 字節裏,這麼小的空間之後確定不能所有用來寫操做系統的代碼,因此它的主要任務就是將硬盤中更多的數據讀取到內存裏,並跳轉到內存的那個位置開始運行。git

這裏不得不回顧一下每節課都說到的四次跳躍:ide

  1. 一跳:按下開機鍵,CPU 將 PC 寄存器的值強制初始化爲 0xffff0,這個位置是 BIOS 程序的入口地址
  2. 二跳:該入口地址處是一個跳轉指令,跳轉到 0xfe05b 位置,開始執行
  3. 三跳:執行了一些硬件檢測工做後,最後一步將啓動區內容加載(複製)到內存 0x7c00,並跳轉到這裏
  4. 四跳:啓動區代碼主要是加載操做系統內核,並跳轉到加載處

其實咱們能夠無限跳躍下去,只要感受某一個環節的任務複雜了,就能夠分紅兩步來走。但也徹底能夠從第三跳開始就不再跳轉了,把全部操做系統須要的指令和數據都從硬盤中加載到內存,而後執行,但這樣顯然很差。oop

1、代碼總覽

先不說別的,先發上來一份本章內容的所有代碼學習

mbr.asm

;----BIOS把啓動區加載到內存的該位置,因此需設置地址偏移量
section mbr vstart=0x7c00

;----設置堆棧地址
mov sp,0x7c00

;----卷屏中斷,目的是清屏
mov ax,0x0600
mov bx,0x0700
mov cx,0
mov dx,0x184f
int 0x10

;----直接往顯存中寫數據
mov ax,0xb800
mov gs,ax
mov byte [gs:0x00],'m'
mov byte [gs:0x02],'b'
mov byte [gs:0x04],'r'

;----讀取硬盤(第2扇區)並加載到內存(0x900)
mov eax,0x02    ;起始扇區lba地址,LBA=(柱面號*磁頭數+磁頭號)*扇區數+扇區編號-1
mov bx,0x900    ;寫入的內存地址,以後用
mov cx,4        ;待讀入的扇區數
call read_disk
jmp 0x900

;----讀硬盤方法,eax爲lba扇區號,bx爲待寫入內存地址,cx爲讀入的扇區數
read_disk:
    mov esi,eax ;備份
    mov di,cx   ;備份
    
;第一步,設置要讀取的扇區數
    mov dx,0x1f2
    mov al,cl
    out dx,al
    mov eax,esi ;恢復
    
;第二步,設置LBA地址
    mov cl,8
    ;0-7位寫入0x1f3
    mov dx,0x1f3
    out dx,al
    ;8-15位寫入0x1f4
    mov dx,0x1f4
    shr eax,cl
    out dx,al
    ;16-23位寫入0x1f5
    mov dx,0x1f5
    shr eax,cl
    out dx,al
    ;24-27位寫入0x1f6
    mov dx,0x1f6
    shr eax,cl
    and al,0x0f ;lba的24-27位
    or al,0xe0  ;另外4位爲1110,表示lba模式
    out dx,al
    
;第三步,寫入讀命令
    mov dx,0x1f7
    mov al,0x20
    out dx,al

;第四步,檢測硬盤狀態
.not_ready:
    nop
    in al,dx
    and al,0x88 ;第4位爲1表示準備好,第7位爲1表示忙
    cmp al,0x08
    jnz .not_ready
    
;第五步,讀數據
    mov ax,di
    mov dx,256
    mul dx
    mov cx,ax
    
    mov dx,0x1f0
    .go_on_read:
        in ax,dx
        mov [bx],ax
        add bx,2
        loop .go_on_read
        ret
    
;----512字節的最後兩字節是啓動區標識
times 510-($-$$) db 0
db 0x55,0xaa

loader.asm

section loader vstart=0x900
mov byte [gs:0xa0],'l'
mov byte [gs:0xa2],'o'
mov byte [gs:0xa4],'a'
mov byte [gs:0xa6],'d'
mov byte [gs:0xa8],'e'
mov byte [gs:0xaa],'r'

Makefile

mbr.bin: mbr.asm
    nasm -I include/ -o out/mbr.bin mbr.asm -l out/mbr.lst
    
loader.bin: loader.asm
    nasm -I include/ -o out/loader.bin loader.asm -l out/loader.lst
    
os.raw: mbr.bin loader.bin
    ../bochs/bin/bximage -hd -mode="flat" -size=60 -q target/os.raw
    dd if=out/mbr.bin of=target/os.raw bs=512 count=1
    dd if=out/loader.bin of=target/os.raw bs=512 count=4 seek=2
    
brun:
    make install
    make only-bochs-run
    
only-bochs-run:
    ../bochs/bin/bochs -f ../bochs/bochsrc.disk -q
    
install:
    make clean
    make -r os.raw
    
clean:
    rm -rf target/*
    rm -rf out/*

2、磁盤

若是你粗略地讀了一下代碼,起碼能夠知道 mbr.asm 中的代碼,前半部分是在屏幕上輸出一個 mbr 字符串,這是上節課爲了作最小操做系統而用直觀方式寫的代碼,無關緊要。後半部分僅僅是讀取了幾個扇區的硬盤數據,加載到內存中的某個位置,而後跳轉到此位置,這部分是關鍵,也是 mbr 的職責所在。操作系統

那怎麼讀取硬盤中的數據呢,這就要從磁盤的結構提及。硬件的東西並非很懂,因此也只能說個大概。硬盤屬於磁盤的一種,磁盤分爲硬盤和軟盤。但他們的邏輯結構是同樣的:code

盤片(platter)
磁頭(head)
磁道(track)
扇區(sector)
柱面(cylinder)htm

機械式硬盤示意圖

我不想管它怎麼動的,我只須要想明白,肯定一個磁頭、柱面、扇區,就肯定了一個 512 字節大小的區域,這就夠了。這也就是硬盤的 CHS 表示法,即 Cylinder(柱面)、Head(磁頭)、Sector(扇區),只要知道了硬盤的 CHS 的數目,便可肯定硬盤的容量,硬盤的容量 = 柱面數 × 磁頭數 × 扇區數 × 512Bblog

若是不考慮這個物理結構,其實硬盤就是 n 多個 512 字節的區域構成的,咱們徹底能夠從 0 開始編號,每 512 字節加一,這樣就能夠徹底不用考慮什麼扇區啦,柱面啦,這種是我比較喜歡的(看來仍是軟件工程師思想呀),這種方式叫作 LBA 表示法接口

LBA = (柱面號 * 磁頭數 + 磁頭號) * 扇區數 + 扇區編號 - 1

因此 CPU 要和硬盤打交道,要麼用這個 CHS 表示法,就至少要告訴硬盤柱面、磁頭、扇區號是多少,要麼用 LBA 表示法告訴硬盤一個 LBA 號碼,而後再給硬盤一個是讀仍是寫的信號。硬盤製做廠商千千萬,CPU製做廠商也是各不相同,天然就會想到必定有一個硬盤接口標準,這個標準就叫作 ATA 標準,也能夠俗稱爲 IDE 硬盤接口技術標準。這個標準能夠下載 AT_Attachment_with_Packet_Interface 共三冊的內容,但咱們用不到那麼多,我這裏找到了一個還算原汁原味的中文版的論文 《IDE接口硬盤讀寫技術》 ,看這個基本就夠用了。

3、IDE硬盤接口技術

CPU 與外設是經過 IO 接口交互的,因此最核心的就是這個技術標準定義的 IO 接口都有哪些,分別有什麼做用

I/O地址 讀(主機從硬盤讀數據) 寫(主機數據寫入硬盤)
1F0H 數據寄存器 數據寄存器
1F1H 錯誤寄存器(只讀寄存器) 特徵寄存器
1F2H 扇區計數寄存器 扇區計數寄存器
1F3H 扇區號寄存器或 LBA 塊地址 0~7 扇區號或 LBA 塊地址 0~7
1F4H 磁道數低 8 位或 LBA 塊地址 8~15 磁道數低 8 位或 LBA 塊地址 8~15
1F5H 磁道數高 8 位或 LBA 塊地址 16~23 磁道數高 8 位或 LBA 塊地址 16~23
1F6H 驅動器/磁頭或 LBA 塊地址 24~27 驅動器/磁頭或 LBA 塊地址 24~27
1F7H 命令寄存器或狀態寄存器 命令寄存器

因此若是要寫一個程序來讀文件的話,不難分析出整個過程就是:

  1. 在 1F2H 寫入要讀取的扇區數
  2. 在 1F3H ~ 1F6H 這四個端口寫入計算好的起始 LBA 地址
  3. 在 1F7H 處寫入讀命令的指令號
  4. 不斷檢測 1F7H (此時已成爲狀態寄存器的含義)的忙位
  5. 若是第四步驟爲不忙,則開始不斷從 1F0H 出讀取數據到內存指定位置,知道讀完

這五步剛恰好對應着上面的代碼

最後,別忘了咱們這些代碼仍然是要加載到啓動區的,因此最後兩個字節依然要是啓動區標識符 0x55 0xaa

4、運行代碼

寫好了 mbr.asm,咱們再寫一個 loader.asm,設置其起始地址爲 0x900(由於讀寫磁盤後存入的內存位置就是這個,這是咱們本身定義的),並把它放在磁盤的第二扇區(這也是咱們本身定的,只要和讀盤的代碼保持一致就行)

#### loader.asm

section loader vstart=0x900
mov byte [gs:0xa0],'l'
mov byte [gs:0xa2],'o'
mov byte [gs:0xa4],'a'
mov byte [gs:0xa6],'d'
mov byte [gs:0xa8],'e'
mov byte [gs:0xaa],'r'

剩下的精華就在於咱們的 Makefile 文件了,能夠參考下上面的代碼

執行 make brun,能夠看到以下效果,說明加載磁盤中的 loader 代碼到內存這個過程生效了。

5、開源項目和課程規劃

若是你對自制一個操做系統感興趣,不妨跟隨這個系列課程看下去,甚至加入咱們,一塊兒來開發。

參考書籍

《操做系統真相還原》這本書真的贊!強烈推薦

項目開源

項目開源地址:https://gitee.com/sunym1993/flashos

當你看到該文章時,代碼可能已經比文章中的又多寫了一些部分了。你能夠經過提交記錄歷史來查看歷史的代碼,我會慢慢梳理提交歷史以及項目說明文檔,爭取給每一課都準備一個可執行的代碼。固然文章中的代碼也是全的,採用複製粘貼的方式也是徹底能夠的。

若是你有興趣加入這個自制操做系統的大軍,也能夠在留言區留下您的聯繫方式,或者在 gitee 私信我您的聯繫方式。

課程規劃

本課程打算出系列課程,我寫到哪以爲能夠寫成一篇文章了就寫出來分享給你們,最終會完成一個功能全面的操做系統,我以爲這是最好的學習操做系統的方式了。因此中間遇到的各類坎也會寫進去,若是你能持續跟進,跟着我一塊寫,必然會有很好的收貨。即便沒有,交個朋友也是好的哈哈。

目前的系列包括

相關文章
相關標籤/搜索