作業系統實現-boot.asm實現

部落格網址:www.shicoder.top
微信:18223081347
歡迎加群聊天 :452380935

這一次我們進入作業系統實現的真實編碼, 這一次主要是完善對boot.asm文件的全部實現,開始吧。。。

首先我們先來理一下boot.asm需要幹什麼

  • 列印出Booting System...
  • 實現磁碟讀寫
  • 將後續的loader.asm所在的區域讀入到0x1000處,然後跳轉進入loader.asm程式
  • 開始執行loader.asm程式(這一節我們下次實現)

實模式下的print

在我們平時編寫c語言時候,可以直接使用,但是在boot.asm中,完全就沒有可以用庫函數,因此為了在開始列印處start boot,我們需要自己實現print

先來看下程式碼把

mov si, booting
call print

print:
    mov ah, 0x0e
.next:
    mov al, [si]
    cmp al, 0
    jz .done
    int 0x10
    inc si
    jmp .next
.done:
    ret
booting:
    db "Booting System...", 10, 13, 0; \n\r

這段程式主要使用使用BIOS的int 10h來實現一個print功能,al暫存器存儲要顯示的字元串

磁碟讀寫

因為boot.asm在主引導扇區,磁碟記憶體太小,不能在boot.asm中實現loader.asm的功能,因此我們將loader.asm保存在磁碟的一個地方,在boot.asm中利用磁碟讀的方式,將程式碼讀入到記憶體的一個區域,然後跳轉到那個地方

先來看下磁碟讀的功能實現

; 函數參數
; edi 將磁碟內容讀到哪裡
; ecx 從磁碟哪一個扇區開始 
; bl 要讀多少個扇區
read_disk:
    ; 設置讀寫扇區的數量
    ; 0x1f2 是硬碟控制埠,表示讀寫扇區的數量
    mov dx, 0x1f2
    mov al, bl
    ; 寫埠用OUT指令 將al的值寫入到dx埠
    out dx, al

    inc dx; 0x1f3 起始扇區前8位埠
    ; 因為ecx為起始扇區 
    ; ecx中的cl就是0-7位
    mov al, cl; 起始扇區前8位
    out dx, al

    inc dx; 0x1f4 起始扇區中8位埠
    shr ecx, 8 ;右移8位
    mov al, cl; 起始扇區中8位
    out dx, al

    inc dx; 0x1f5 起始扇區高8位埠
    shr ecx, 8 ;右移8位
    mov al, cl; 起始扇區高8位
    out dx, al

    inc dx ;0x1f6
    shr ecx, 8
    and cl, 0b1111 ;將高4位置為0,對應起始扇區的24-27位
    mov al,0b1110_0000 ;第4位為0,表示主盤,第6位為1,表示LBA,5-7位必須為1
    ; 將al和cl合二為一,放在al中
    or al, cl
    out dx, al

    inc dx ;0x1f7
    mov al, 0x20 ;表示讀硬碟
    out dx, al

    xor ecx, ecx ;清空ecx
    mov cl, bl ;得到寫扇區的數量

    ; loop指令會檢查ecx是否為0 cl在ecx裡面
    .read:
        push cx ;保存下,因為函數裡面使用了
        call .waits ;等待數據準備完畢
        call .reads ;讀取一個扇區
        pop cx ;恢復
        loop .read
    ret

    .waits:
        mov dx, 0x1f7 ;讀0x1f7埠
        .check:
            in al, dx ;將dx埠的值放入al中
            jmp $+2 ;直接跳轉到下一行 其實什麼都沒做,就是為了延遲一下
            jmp $+2
            jmp $+2

            and al, 0b1000_1000 ;獲得al的第3位和第7位
            cmp al, 0b0000_1000 ;測試是否第7位為0,第3位為1 硬碟不繁忙,數據準備完畢
            jnz .check ;數據沒準備好
        ret

    .reads:
        mov dx, 0x1f0 ;用於讀寫數據
        mov cx, 256 ;一個扇區256位元組
        ; loop指定會檢查ecx cx在ecx裡面
        .readw:
            in ax, dx
            jmp $+2 ;直接跳轉到下一行 其實什麼都沒做,就是為了延遲一下
            jmp $+2
            jmp $+2

            ; edi表示讀取的目標記憶體
            mov [edi], ax
            ; 因為ax是16bit,2個位元組,所以edi+2
            add edi, 2
            loop .readw
        ret

下面是磁碟的相關埠

Primary 通道 Secondary 通道 in 操作 out 操作
0x1F0 0x170 Data Data
0x1F1 0x171 Error Features
0x1F2 0x172 Sector count Sector count
0x1F3 0x173 LBA low LBA low
0x1F4 0x174 LBA mid LBA mid
0x1F5 0x175 LBA high LBA high
0x1F6 0x176 Device Device
0x1F7 0x177 Status Command
  • 0x1F0:16bit 埠,用於讀寫數據
  • 0x1F1:檢測前一個指令的錯誤
  • 0x1F2:讀寫扇區的數量
  • 0x1F3:起始扇區的 0 ~ 7 位
  • 0x1F4:起始扇區的 8 ~ 15 位
  • 0x1F5:起始扇區的 16 ~ 23 位
  • 0x1F6:
    • 0 ~ 3:起始扇區的 24 ~ 27 位
    • 4: 0 主盤, 1 從片
    • 6: 0 CHS, 1 LBA
    • 5 ~ 7:固定為1
  • 0x1F7: out
    • 0xEC: 識別硬碟
    • 0x20: 讀硬碟
    • 0x30: 寫硬碟
  • 0x1F7: in / 8bit
    • 0 ERR
    • 3 DRQ 數據準備完畢
    • 7 BSY 硬碟繁忙

注意上面的out和in指令

讀埠用IN指令,寫埠用OUT指令

out a,b 將b的值寫入到a埠

in a,b 將b埠的值讀到a中

先來看4個起始扇區的暫存器 :0x1F30x1F40x1F50x1F6,假如此時的起始扇區ecx=123456789 ,即32位bit為00000111010110111100110100010101

  • 0-7位:00010101 => 0x1F3

  • 8-15位:11001101 => 0x1F4

  • 16-23位:01011011 => 0x1F5

  • 24-31位:00000111

    • 24-27位:0111 => 0x1F6(0-3)
    • mov al,0b1110_0000
      • 0 => 0x1F6(4) 表示主盤
      • 111 => ox1F6(5-7) 固定為1

再來看0x1F7,值為0x20,表示讀磁碟

然後通過mov cl,bl,將扇區數量放在cl中,後面進行循環,彙編中循環的次數和ecx有關。因為是要讀磁碟,因此需要先等待磁碟數據處理好,然後才進行讀取,.wait便是這個作用,其餘的相關解析可以通過程式碼注釋看懂,這裡就不贅述了

jmp $+2

可以通過反彙編看到

0000:jmp $+2
0002:xxx

所以這行程式碼就是跳到下一行,起到等待的作用

經過編寫這個函數,我們就可以從磁碟中得到我們想要的程式碼啦,前面說過,我們本身就想將loader.asm程式碼放在磁碟的一個地方,然後再讀進來,那怎麼放呢,這樣,我們先簡單寫一個loader.asm

loader.asm

程式碼如下

[org 0x1000]

; 列印字元串
mov si, loading
call print

; 阻塞
jmp $

print:
    mov ah, 0x0e
.next:
    mov al, [si]
    cmp al, 0
    jz .done
    int 0x10
    inc si
    jmp .next
.done:
    ret

loading:
    db "Loading System...", 10, 13, 0; \n\r

同樣的,我們只是列印出一句話即可,那我們怎麼將這些程式碼複製到磁碟中去呢,下面兩行命令

nasm -f bin loader.bin loader.asm
dd if=loader.bin of=master.img bs=512 count=4 seek=2 conv=notrunc

利用dd命令,將bin文件從偏移為2的地方,寫入4個到master.img中,這樣就可以知道loader.bin在磁碟哪裡,就可以讀入了

boot.asm中程式碼如下

; 因為loader.bin是從第2個扇區開始寫入,寫了4個扇區
mov edi, 0x1000;讀取的目標記憶體
mov ecx, 2 ;起始扇區
mov bl, 4 ;扇區數量
call read_disk

經過上面一番折騰,終於從boot跳轉到loader中了,後續我們將對loader.asm進行完善,實現loader所需要的功能,下次見啦。。。