mbr簡介
大家都知道,在我們按下電腦電源的時候,首先啓動的BIOS(基本輸入輸出系統),那麼BIOS又是如何被啓動的呢,誰來喚醒他呢,它又在何處運行呢。要了解這些的話,首先得介紹一下我們實模式的內存佈局
實模式的內存佈局
圖中的內容我們現在只需要關注紅色框出來的地方,可以看到BIOS的入口地址處只有16BYTE的空間,很顯然,這一小塊空間肯定存放的不是數據,只能是指令了,圖中也寫的很明顯了
jmp f000:e05b
也就是跳轉到了(f000 << 4) + e05b = fe05b處,這裏的段基址左移四位的原因是,在實模式下段基址寄存器只有16位,想一下,16位的寄存器最多訪問2^16=64KB的空間,我們想訪問實模式下1MB的空間的話就需要將段基址左移4位,自然就可以訪問到1MB的空間了,這麼做的原因也是出於兼容性而採取的曲線救國方式,雖然我們現在的OS都已經到了64位,它也還得向下兼容不是嗎
當我們的電腦加電的一瞬間cs:ip就會被強制置位f000:e05b了,接下來就對內存,顯卡等外設進行檢查,做好它的初始化工作之後就完成它的任務了,在最後的時候,BIOS會通過絕對遠跳
jmp 0:0x7c00
將接力棒交由MBR來加載我們的內核,我們初步的工作就是編寫MBR。在進行內核加載之前,我們先通過MBR打印一些字符,來驗證我們之前所說是否正確
編寫MBR驗證程序
;主引導程序
;---------------------
SECTION MBR vstart=0x7c00 ;程序開始的地址
mov ax, cs ;使用cs初始化其他的寄存器
mov ds, ax ;因爲是通過jmp 0:0x7c00到的MBR開始地址
mov es, ax ;所以此時的cs爲0,也就是用0初始化其他寄存器
mov ss, ax ;此類的寄存器不同通過立即數賦值,採用ax中轉
mov fs, ax
mov sp, 0x7c00 ;初始化棧指針
;清屏利用0x10中斷的0x6號功能
;------------------------
mov ax, 0x600
mov bx, 0x700
mov cx, 0
mov dx, 0x184f
int 0x10
;獲取光標位置
;---------------------
mov ah, 3 ; 3號子功能獲取光標位置
mov bh, 1 ; bh寄存器存儲帶獲取光標位置的頁號,從0開始,此處填1可以看成將光標移動到最開始
int 0x10
;打印字符串
;------------------
mov ax, message
mov bp, ax
mov cx, 6 ;字符串長度,不包括'\0'
mov ax, 0x1301
mov bx, 0x2
int 0x10
jmp $
message db "My MBR"
times 510-($-$$) db 0
db 0x55, 0xaa
這段代碼通過0x10號中斷直接操控顯卡,達到打印字符串的目的
編寫好後通過
nasm -o mbr.bin mbr.S
dd if=mbr.bin of=/home/ba/bochs/hd60M.img bs=512 count=1 conv=notrunc
對我們的彙編代碼進行編譯並寫入之前創建的磁盤中,接下來運行bochs,應該可以看到如下結果
現在我們通過bochs的調試看一下程序到底是怎麼執行的,和我們之前所說的是否一致
這幅圖是在我們開啓bochs時顯示的結果,很明顯可以看到他的cs:ip寄存器的值和我們之前所說的結果一致,在這裏進行跳轉之後接下來肯定就是一系列的初始化工作了,我們跳過這些初始化的工作,直接進入到MBR執行的開始位置,也就是地址0x7c00處
可以看到,左邊是bochs初始化完成之後的輸出,這是已經運行到了0x7c00後的結果,看紅框標記的地方,有沒有感覺很熟悉,這就是我們mbr的第一行代碼啦,接下來就會按照我們所寫的那樣,清屏,打印了。
讀取硬盤
前面通過打印字符串對開機啓動過程做了個小小的驗證,接下來需要讓我們的MBR讀取硬盤啦,因爲加載kernel的話肯定需要從硬盤中讀入數據
; 主引導程序
;---------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00 ;程序開始的地址
mov ax, cs ;使用cs初始化其他的寄存器
mov ds, ax ;因爲是通過jmp 0:0x7c00到的MBR開始地址
mov es, ax ;所以此時的cs爲0,也就是用0初始化其他寄存器
mov ss, ax ;此類的寄存器不同通過立即數賦值,採用ax中轉
mov fs, ax
mov sp, 0x7c00 ;初始化棧指針,sp也就是32位下的esp
;清屏利用0x10中斷的0x6號功能
;------------------------
mov ax, 0x600
mov bx, 0x700
mov cx, 0
mov dx, 0x184f
int 0x10
;獲取光標位置
;---------------------
mov ah, 3 ; 3號子功能獲取光標位置
mov bh, 1 ; bh寄存器存儲帶獲取光標位置的頁號,從0開始,此處填1可以看成將光標移動到最開始
int 0x10
;打印字符串`
;------------------
mov ax, message
mov bp, ax
mov cx, 6
mov ax, 0x1301
mov bx, 0x2
int 0x10
message db "My MBR"
mov eax, LOADER_START_SECTOR ;起始扇區的lba地址
mov bx, LOADER_BASE_ADDR ;寫入的地址
mov cx, 1 ;讀入的扇區數
call rd_disk_m_16
jmp LOADER_BASE_ADDR
;讀取n個扇區
;---------------------
rd_disk_m_16: ;eax=扇區號,cx=讀入的扇區數,bx=將數據寫入的內存地址
mov esi, eax ;備份eax和cx
mov di, cx
;設置要讀取的扇區數
mov dx, 0x1f2
mov al, al
out dx, al
mov eax, esi
;將lba地址存入0x1f3-0x1f6
;lba地址0-7位寫入端口0x1f3
mov dx, 0x1f3
out dx, al
;lba地址8-15位寫入端口0x1f4
mov cl, 8
shr eax, cl
mov dx, 0x1f4
out dx, al
;lba地址16-23位寫入端口0x1f5
shr eax, cl
mov dx, 0x1f5
out dx, al
shr eax, cl
and al, 0x0f
or al, 0xe0
mov dx, 0x1f6
out dx, al
;向0x1f7端口寫入讀命令
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
;從0x1f0端口讀數據
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
times 510-($-$$) db 0
db 0x55, 0xaa
這是boot.inc中的內容
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
先看看程序的執行流程
從0x7c00入口處進入mbr
打印My MBR
爲讀取磁盤操作傳遞參數,包括讀入的扇區數,讀取的數據寫入的內存地址
將讀取到的數據寫入0x900,並跳到此處去執行
MBR中的內容差不多就多了,接下來的工作就是逐步完善內核。