自己動手從零寫桌面操作系統GrapeOS系列教程——23.從硬盤讀取文件

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


本講代碼文件爲boot.asm,要讀取的文件爲data.txt。

一、在FAT16文件系統中讀取文件的流程

在GrapeOS中用到的文件少且小,所有文件都放在了根目錄下,數量不會超過16個,佔用的簇不會超過254個。所以讀取目錄項只需要讀取根目錄的第1個扇區即可,讀取FAT表項也只需讀取FAT1表的第1個扇區即可。
以下是讀取文件的流程圖:

二、代碼及講解

boot.asm中的代碼如下:

;--------------------定義常量--------------------
;FAT16目錄項中各成員的偏移量:
;名稱		    偏移	    長度	描述
DIR_Name	    equ 0	;11	文件名8B,擴展名3B
DIR_Attr	    equ 11	;1	目錄項屬性
;保留位	            12   10	保留位
DIR_WrtTime     equ 22	;2	最後一次寫入時間
DIR_WrtDate     equ 24	;2	最後一次寫入日期
DIR_FstClus     equ 26	;2	起始簇號
DIR_FileSize    equ 28	;4	文件大小

BOOT_ADDRESS equ 0x7c00 ;boot程序加載到內存的地址。
FILE_ADDRESS equ 0x1000 ;文件讀到內存中的地址。
DISK_BUFFER  equ 0x7e00 ;讀磁盤臨時存放數據用的緩存區,放到boot程序之後。
DISK_SIZE_M equ 4 ;磁盤容量,單位M。
FAT1_SECTORS     equ 32 ;FAT1佔用扇區數
ROOT_DIR_SECTORS equ 32 ;根目錄佔用扇區數
SECTOR_NUM_OF_FAT1_START     equ 1 ;FAT1表起始扇區號
SECTOR_NUM_OF_ROOT_DIR_START equ 33 ;根目錄區起始扇區號
SECTOR_NUM_OF_DATA_START     equ 65 ;數據區起始扇區號,對應簇號爲2。
SECTOR_CLUSTER_BALANCE       equ 63 ;簇號加上該值正好對應扇區號。
FILE_NAME_LENGTH     equ 11 ;文件名8字節加擴展名3字節共11字節。
DIR_ENTRY_SIZE       equ 32 ;目錄項爲32字節。
DIR_ENTRY_PER_SECTOR equ 16 ;每個扇區能存放目錄項的數目。

;--------------------MBR開始--------------------
org BOOT_ADDRESS
jmp boot_start
nop

;FAT16參數區:
BS_OEMName 		db 'GrapeOS '   ;廠商名稱(8字節,含空格)
BPB_BytesPerSec dw 0x0200		;每扇區字節數
BPB_SecPerClus	db 0x01			;每簇扇區數
BPB_RsvdSecCnt	dw 0x0001		;保留扇區數(引導扇區的扇區數)
BPB_NumFATs		db 0x01			;FAT表的份數
BPB_RootEntCnt	dw 0x0200		;根目錄可容納的目錄項數
BPB_TotSec16	dw 0x2000 		;扇區總數(4MB)
BPB_Media		db 0xf8 		;介質描述符
BPB_FATSz16		dw 0x0020		;每個FAT表扇區數
BPB_SecPerTrk	dw 0x0020		;每磁道扇區數
BPB_NumHeads	dw 0x0040		;磁頭數
BPB_hiddSec		dd 0x00000000	;隱藏扇區數
BPB_TotSec32	dd 0x00000000	;如果BPB_TotSec16是0,由這個值記錄扇區數。
BS_DrvNum		db 0x80			;int 13h的驅動器號
BS_Reserved1	db 0x00			;未使用
BS_BootSig		db 0x29			;擴展引導標記
BS_VolID		dd 0x00000000   ;卷序列號
BS_VolLab		db 'Grape OS   ';卷標(11字節,含空格)
BS_FileSysType	db 'FAT16   '	;文件系統類型(8字節,含空格)

;通過以上參數可知硬盤容量爲4MB,共8K個扇區。扇區具體分佈如下:
;區域名     扇區數      扇區號          字節偏移            說明
;引導扇區   1個扇區     扇區0           0x0000~0x01ff
;FAT1表     32個扇區    扇區1~32        0x0200~0x41ff   可記錄8K-2個簇
;FAT2表     無          無              無              無
;根目錄區   32個扇區    扇區33~64       0x4200~0x81ff   可容納512個目錄項
;數據區     8127個扇區  扇區65~0x1fff   0x8200~0x3fffff

;--------------------程序開始--------------------
boot_start:
;初始化寄存器
mov ax,cs
mov ds,ax
mov es,ax ;cmpsb會用到ds:si和es:di

;讀取文件開始
;讀取根目錄的第1個扇區(1個扇區可以存放16個目錄項,我們用到的文件少,不會超過16個。)
mov esi,SECTOR_NUM_OF_ROOT_DIR_START 
mov di,DISK_BUFFER
call func_read_one_sector

;在16個目錄項中通過文件名查找文件
cld ;cld將標誌位DF置0,在串處理指令中控制每次操作後讓si和di自動遞增。std相反。下面repe cmpsb會用到。
mov bx,0 ;用bx記錄遍歷第幾個目錄項。
next_dir_entry:
mov si,bx
shl si,5 ;乘以32(目錄項的大小)
add si,DISK_BUFFER              ;源地址指向目錄項中的文件名。
mov di,read_file_name_string  ;目標地址指向文件在硬盤中的正確文件名。
mov cx,FILE_NAME_LENGTH ;字符比較次數爲FAT16文件名長度,每比較一個字符,cx會自動減一。
repe cmpsb ;逐字節比較ds:si和es:di指向的兩個字符串。
jcxz file_found ;當cx爲0時跳轉,cx爲0表示上面比較的兩個字符串相同。找到了文件。
inc bx
cmp bx,DIR_ENTRY_PER_SECTOR
jl next_dir_entry ;檢查下一個目錄項。
jmp file_not_found ;沒有找到文件。

file_found: ;找到了文件
;從目錄項中獲取文件的起始簇號
shl bx,5 ;乘以32
add bx,DISK_BUFFER
mov bx,[bx+DIR_FstClus] ;文件的起始簇號

;讀取FAT1表的第1個扇區(我們用到的文件少且小,只用到了該扇區中的簇號。)
mov esi,SECTOR_NUM_OF_FAT1_START 
mov di,DISK_BUFFER ;放到boot程序之後
call func_read_one_sector

mov bp,FILE_ADDRESS ;文件內容讀取到內存中的起始地址

;按簇號讀文件內容
read_file: 
xor esi,esi ;esi清零
mov si,bx ;簇號
add esi,SECTOR_CLUSTER_BALANCE
mov di,bp
call func_read_one_sector
add bp,512 ;下一個目標地址

;獲取下一個簇號(每個FAT表項爲2字節)
shl bx,1 ;乘2,每個FAT表項佔2個字節
mov bx,[bx+DISK_BUFFER]

;判斷下一個簇號
cmp bx,0xfff8 ;大於等於0xfff8表示文件的最後一個簇
jb read_file ;jb無符號小於則跳轉,jl有符號小於則跳轉。

read_file_finish: ;讀取文件結束
jmp stop

file_not_found: ;沒有找到文件

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

read_file_name_string:db "DATA    TXT",0  ;要讀取的文件在硬盤中存儲的文件名,共11個字節,含空格。

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

關於代碼的講解基本都寫在註釋中了,結合之前講的內容,大家應該能看懂。

三、通過Linux將文件複製到虛擬硬盤中

本講要讀取的文件是data.txt,如何將該文件複製到虛擬硬盤的FAT16文件系統中呢?我們這裏採用的方法是將該虛擬硬盤掛載到Linux系統上,然後就可以將data.txt複製到虛擬硬盤中了。前提是需要先將虛擬硬盤格式化,格式化的方法就是將boot程序寫入到虛擬硬盤的第一個扇區。因爲boot程序中含有FAT16的結構化數據,Linux系統就知道如何讀寫該文件系統了。

1.將boot程序寫入到虛擬硬盤的第一個扇區

dd if=/dev/zero of=/media/VMShare/GrapeOS.img bs=1M count=4
nasm boot.asm -o boot.bin
dd if=boot.bin of=/media/VMShare/GrapeOS.img conv=notrunc

截圖如下:

2.將虛擬硬盤掛載到Linux系統上並將data.txt複製到虛擬硬盤中

mount /media/VMShare/GrapeOS.img /mnt/ -t msdos -o loop
ll /mnt/
cp data.txt /mnt/
sync #數據同步,立馬把數據寫入硬盤。
ll /mnt/
umount /mnt/

截圖如下:

上圖中在複製完data.txt後,通過ll /mnt/查看虛擬硬盤根目錄,此時雖然看到的文件名是小寫“data.txt”,但實際上在虛擬硬盤裏存儲的文件名已經是全部大寫的了,在下面的分析中可以看到。

3.虛擬硬盤數據分析

通過hexdump查看虛擬硬盤數據:

hexdump /media/VMShare/GrapeOS.img -C

截圖如下:

截圖中的部分數據如下:

000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200  00 00 00 00 00 00 04 00  05 00 ff ff 00 00 00 00  |................|
00000210  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00004200  44 41 54 41 20 20 20 20  54 58 54 20 00 00 00 00  |DATA    TXT ....|
00004210  00 00 00 00 00 00 fa 4e  78 56 03 00 58 04 00 00  |.......NxV..X...|
00004220  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

前面我們計算過,FAT1表的起始扇區是扇區1,字節偏移是0x200,根目錄區的起始扇區是扇區33,字節偏移是0x4200。
從上面的截圖和數據可以看到:

  1. 在根目錄區的第一個目錄項就是我們要讀的文件,文件名已是全大寫。
  2. 從目錄項中可以看到文件的起始簇號是0x0003。
  3. 在FAT表中第3個FAT表項的值是0x0004,表示該文件的第二個簇號是0x0004。
  4. 在FAT表中第4個FAT表項的值是0x0005,表示該文件的第三個簇號是0x0005。
  5. 在FAT表中第5個FAT表項的值是0xffff,表示該文件沒有下一個簇了,到此結束。
  6. 這個文件的內容共佔用3個簇的空間,依次是簇3、簇4、簇5,讀取該文件就是依次將這3個簇中的數據讀取出來。

四、程序演示

在cmd命令行中啓動QEMU的調試模式:

C:\Users\CYJ>qemu-system-i386 d:\GrapeOS\VMShare\GrapeOS.img -S -s

在Linux命令行中啓動GDB:

[root@CentOS7 Lesson23]# gdb
(gdb) target remote 你的Windows的IP地址:1234
(gdb) b *0x7c00
(gdb) c
(gdb) x /32xb 0x1000 #在讀文件前查看此時0x1000處的內存數據,可以看到都是0。
0x1000: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x1008: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x1010: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x1018: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
(gdb) c
運行幾秒,然後Ctrl鍵+C鍵暫停運行,此時讀取文件的程序已運行完畢。
(gdb) x /32xb 0x1000 #在讀完文件後查看此時0x1000處的內存數據,可以看到已經不是0了。
0x1000: 0x78    0x38    0x36    0x20    0x28    0x61    0x6c    0x73
0x1008: 0x6f    0x20    0x6b    0x6e    0x6f    0x77    0x6e    0x20
0x1010: 0x61    0x73    0x20    0x38    0x30    0x78    0x38    0x36
0x1018: 0x20    0x6f    0x72    0x20    0x74    0x68    0x65    0x20
(gdb) x /32c 0x1000 #爲了方便觀察可以以字符形式展示數據。通過對比,下面的32個字符的確和data.txt中前32個字符相同。
0x1000: 120 'x' 56 '8'  54 '6'  32 ' '  40 '('  97 'a'  108 'l' 115 's'
0x1008: 111 'o' 32 ' '  107 'k' 110 'n' 111 'o' 119 'w' 110 'n' 32 ' '
0x1010: 97 'a'  115 's' 32 ' '  56 '8'  48 '0'  120 'x' 56 '8'  54 '6'
0x1018: 32 ' '  111 'o' 114 'r' 32 ' '  116 't' 104 'h' 101 'e' 32 ' '
(gdb) x /32c 0x1440 #查看文件的最後二十多個字符。通過對比可以看到和data.txt中的相同。
0x1440: 105 'i' 116 't' 115 's' 32 ' '  90 'Z'  105 'i' 108 'l' 111 'o'
0x1448: 103 'g' 32 ' '  90 'Z'  45 '-'  56 '8'  48 '0'  32 ' '  118 'v'
0x1450: 97 'a'  114 'r' 105 'i' 97 'a'  110 'n' 116 't' 41 ')'  46 '.'
0x1458: 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000'

通過上述演示說明讀取文件成功。


視頻版地址:https://www.bilibili.com/video/BV1xN411K7Lc/
配套的代碼與資料在:https://gitee.com/jackchengyujia/grapeos-course
GrapeOS操作系統交流QQ羣:643474045

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章