自己動手從零寫桌面作業系統GrapeOS系列教學——20.組合語言讀硬碟實戰

2023-03-21 12:03:53

學習作業系統原理最好的方法是自己寫一個簡單的作業系統。


本講我們設計一個簡單的讀硬碟實驗。通過一定的方法使硬碟第二個磁區的前3個位元組依次為1、2、3,最後3個位元組依次為3、2、1,中間的506個位元組全為0。然後通過讀硬碟程式將硬碟第二個磁區的資料讀取到記憶體0x7e00-0x7fff的地方,也就是記憶體中MBR之後的512個位元組。最後通過QEMU+DGB偵錯的方式來檢視記憶體中0x7e00-0x7fff的資料,是否與硬碟第二個磁區中的資料一致,如果一致說明讀硬碟成功。
本講程式碼檔案共2個:

  • data1.asm
  • boot1.asm

下面我們開始實驗。

一、設定硬碟第二個磁區中的資料

data1.asm程式碼如下:

db 1
db 2
db 3
times 506 db 0
db 3
db 2
db 1

data1.asm就是生成512位元組的資料,前3個位元組依次是1、2、3,最後3個位元組依次為3、2、1,中間的506個位元組全為0。
在PowerShell中輸入如下命令:

nasm data1.asm -o data1.bin
hexdump data1.bin -C

上述命令是將data1.asm通過組合器生成了二進位制檔案data1.bin,然後通過hexdump命令檢視data1.bin是否正確。截圖如下:

從上面截圖可以看到,生成的data.bin檔案共512位元組,其中前3個位元組依次為1、2、3,最後3個位元組依次為3、2、1,中間的506個位元組全為0。下面將data1.bin檔案寫入到虛擬硬碟的第二個磁區中。
在PowerShell中輸入如下命令:

dd conv=notrunc if=data1.bin of=/media/VMShare/GrapeOS.img seek=1
hexdump /media/VMShare/GrapeOS.img -C

上面dd命令中的引數seek=1意思是在寫入GrapeOS.img時,跳過1個寫入塊,寫入塊預設大小為512位元組,也就將data1.bin寫入到虛擬硬碟GrapeOS.img的第二個磁區中。截圖如下:

從上面截圖中可以看到,虛擬硬碟第二個磁區中的資料達到了我們的實驗要求。

二、讀硬碟程式

boot1.asm的程式碼如下:

;定義常數
DISK_BUFFER equ 0x7e00 ;讀硬碟臨時存放資料用的快取區,放到boot程式之後。

org 0x7c00

;初始化段暫存器
mov ax,cs
mov ds,ax ;ds指向與cs相同的段

mov esi,1 ;讀取硬碟的第2個磁區
mov di,DISK_BUFFER
call func_read_one_sector

stop:
hlt
jmp stop 

;讀取硬碟1個磁區(主硬碟控制器主盤)
;輸入引數:esi,ds:di。
;esi LBA磁區號
;ds:di 將資料寫入到的記憶體起始地址
;輸出引數:無。
func_read_one_sector:
;第1步:檢查硬碟控制器狀態
mov dx,0x1f7
.not_ready1:
nop ;nop相當於稍息 hlt相當於睡覺
in al,dx ;讀0x1f7埠
and al,0xc0 ;第7位為1表示硬碟忙,第6位為1表示硬碟控制器已準備好,正在等待指令。
cmp al,0x40 ;當第7位為0,且第6位為1,則進入下一個步。
jne .not_ready1 ;若未準備好,則繼續判斷。
;第2步:設定要讀取的磁區數
mov dx,0x1f2
mov al,1
out dx,al ;讀取1個磁區
;第3步:將LBA地址存入0x1f3-0x1f6
mov eax,esi
;LBA地址7-0位寫入埠0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15-8位元寫入埠寫入0x1f4
shr eax,8
mov dx,0x1f4
out dx,al
;LBA地址23-16位元寫入埠0x1f5
shr eax,8
mov dx,0x1f5
out dx,al
;第4步:設定device埠
shr eax,8
and al,0x0f ;LBA第24-27位
or al,0xe0 ;設定7-4位元為1110,表示LBA模式,主盤
mov dx,0x1f6
out dx,al
;第5步:向0x1f7埠寫入讀命令0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第6步:檢測硬碟狀態
.not_ready2:
nop ;nop相當於稍息 hlt相當於睡覺
in al,dx ;讀0x1f7埠
and al,0x88 ;第7位為1表示硬碟忙,第3位為1表示硬碟控制器已準備好資料傳輸。
cmp al,0x08 ;當第7位為0,且第3位為1,進入下一步。
jne .not_ready2 ;若未準備好,則繼續判斷。
;第7步:從0x1f0埠讀資料
mov cx,256 ;每次讀取2位元組,一個磁區需要讀256次。
mov dx,0x1f0
.go_on_read:
in ax,dx
mov [di],ax
add di,2
loop .go_on_read
ret

times 510-($-$$) db 0
db 0x55,0xaa

上面的程式碼主要就是讀硬碟函數func_read_one_sector,總共分7個步驟,與上講中的讀硬碟操作步驟一致。結合程式碼註釋和之前講的知識,大家應該是可以看懂的。
下面我們將編譯boot1.asm,並將生成的二進位制檔案寫入到虛擬硬碟的第一個磁區。在PowerShell中輸入如下命令:

nasm boot1.asm -o boot1.bin
dd conv=notrunc if=boot1.bin of=/media/VMShare/GrapeOS.img

截圖如下:

在Windows的cmd命令列中啟動QEMU偵錯模式:

qemu-system-i386 d:\GrapeOS\VMShare\GrapeOS.img -S -s

截圖如下:

在PowerShell中啟動GDB:

[root@CentOS7 Lesson20]# gdb
(gdb) target remote 192.168.10.102:1234
(gdb) b *0x7c00
(gdb) c
Continuing.

Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x /512xb 0x7e00

截圖如下:

從截圖中可以看到,此時程式停在了斷點0x7c00處,此時記憶體中0x7e00-0x7fff中的資料都是0。
我們輸入命令c,讓程式繼續執行幾秒鐘,然後Ctrl+C,讓程式暫停。此時讀硬碟程式應該已經執行完了,我們再來檢視一下記憶體0x7e00-0x7fff中的資料。截圖如下:

從截圖中可以看到,此時記憶體中0x7e00-0x7fff的資料和硬碟第二磁區中的資料一致,說明讀取成功,實驗完畢。


本講視訊版地址:https://www.bilibili.com/video/BV1JY411z7VT/
配套的程式碼與資料在:https://gitee.com/jackchengyujia/grapeos-course
GrapeOS作業系統交流QQ群:643474045