作業系統實現-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個起始扇區的暫存器 :0x1F3、0x1F4、0x1F5、0x1F6,假如此時的起始扇區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_00000=>0x1F6(4) 表示主盤111=>ox1F6(5-7) 固定為1
- 24-27位:
再來看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所需要的功能,下次見啦。。。

