全面剖析《自己動手寫操作系統》第四章--FAT12文件系統
http://blog.csdn.net/zgh1988/article/details/7284834
1、突破512字節的限制
2、加載Loader進入內存
一、突破512字節的限制
一個操作系統從開機到開始運行,大致經歷"引導—》加載內核入內存—》跳入保護模式—》開始執行內核"這樣一個過程。也就是說,在內核開始執行之前不但要加載內核,還要準備保護模式等一系列工作,如果全部交給引導扇區來做,512字節很可能不夠用,所以,不放把這個過程交給另外的模塊來完成,我們把這個模塊叫做Loader。引導扇區負責把Loader加載如內存並且把控制權交它,其他的工作放心地交給 Loader來做,因爲它沒有512字節的限制,將會靈活很多。
二、加載Loader進入內存
上一節我們已經詳細介紹了FAT12文件系統的數據結構,下面我們需要思考的是兩個問題:1、引導扇區通過怎樣的步驟才能找到文件;2、如何能夠把文件內容全都讀出來並加載進入內存。
下面我們先解決第一個問題:
1、 如何讀取軟盤?
(1) 我們需要使用BIOS中斷int 13h來讀取軟盤。它的用法如下表所示:
在這裏我們只介紹了2種工作方式,中斷int 13h還有其他的工作方式,如果需要可以自行查看內容。
(2) 由上表我們可以知道:當讀取某些扇區時,需要柱面(磁道)號(ch),起始扇區號(cl),磁頭號(dh)。我們如何通過計算得到這些數據呢?
(3) 現在萬事俱備只欠東風了,下面我們就書寫讀取軟盤扇區的函數ReadSector。
首先我們要知道該函數需要什麼參數,這些參數存儲在什麼位置?
參數1:扇區號,存儲在ax中
參數2:要讀取扇區的個數,存儲在cl中
參數3:數據緩衝區,即讀取扇區數據後,將其存儲在什麼位置,用es:bx指向該緩衝區。
即函數的作用:從第ax個Sector開始,將cl個Sector讀入es:bx中。
- <span style="font-size: 18px;">;-----------------------------------------------------------------------
- ;函數名:ReadSector
- ;------------------------------------------------------
- ;作用:從第ax個Sector開始,將cl個Sector讀入es:bx中
- ReadSector:
- ;---------------------------------------------
- ;怎樣由扇區號求扇區在磁盤中的位置(扇區號->柱面號,起始扇區,磁頭號)
- ;---------------------------------------------
- ;設扇區號爲x
- ; ┌ 柱面號 = y >> 1
- ; x ┌ 商 y ┤
- ; -------------- => ┤ └ 磁頭號 = y & 1
- ; 每磁道扇區數 │
- ; └ 餘 z => 起始扇區號 = z + 1
- ;闢出兩個字節的堆棧區間保存要讀取的扇區數:byte[bp-2]
- push bp
- mov bp, sp
- sub esp, 2
- mov byte[bp-2], cl ;將參數cl,存儲在byte[bp-2],將要讀取扇區的個數。
- push bx ;保存bx,因爲下面要使用bx進行計算。
- mov bl, [BPB_SecPerTrk] ;bl:除數=18
- ;ax存儲的是扇區號,bl是每磁道扇區數,執行ax/bl=al----ah,
- ;即商y在al中,商z在ah中。
- div bl
- inc ah ;ah(z)++,即起始扇區號=z+1,
- mov cl, ah ;將ah值賦值給cl,中斷int 13h中,cl保存的恰好是起始扇區號
- mov dh, al ;將al(y),賦值給dh
- shr al, 1 ;對al(y)進行右移一位,即得到柱面號=y>>1,
- mov ch, al ;然後將al賦值給ch,在中斷int 13h中,ch保存着柱面(磁道)號
- and dh, 1 ;將dl(y)進行&1運算,即得到磁頭號=y&1,在中斷int 13h中,dh保存着
- ;磁頭號
- pop bx ;恢復bx值
- ;到此爲止,“柱面(磁道)號(ch),起始扇區號(cl),磁頭號(dh),緩衝地址(es:bx)”全部準備就緒
- mov dl, [BS_DrvNum] ;在中斷int 13中,dl保存着驅動器號。此時dl=[BS_DrvNum]=0
- .GoOnReading:
- ;下面對ah,al進行賦值,ah=2,al=要讀取的扇區數,前面將參數cl存儲在byte[bp-2],現在從這裏重新獲取
- ;並賦值給al。
- mov ah, 2
- mov al, byte[bp-2]
- ;中斷int 13一切準備就緒,然後執行int 13
- int 13h
- jc .GoOnReading ;如果讀取錯誤,CF會被置爲1,這時就不停地讀,直到正確爲止。
- add esp, 2 ;恢復堆棧
- pop bp
- ret</span><span style="font-size: 16px;">
- </span>
;-----------------------------------------------------------------------
;函數名:ReadSector
;------------------------------------------------------
;作用:從第ax個Sector開始,將cl個Sector讀入es:bx中
ReadSector:
;---------------------------------------------
;怎樣由扇區號求扇區在磁盤中的位置(扇區號->柱面號,起始扇區,磁頭號)
;---------------------------------------------
;設扇區號爲x
; ┌ 柱面號 = y >> 1
; x ┌ 商 y ┤
; -------------- => ┤ └ 磁頭號 = y & 1
; 每磁道扇區數 │
; └ 餘 z => 起始扇區號 = z + 1
;闢出兩個字節的堆棧區間保存要讀取的扇區數:byte[bp-2]
push bp
mov bp, sp
sub esp, 2
mov byte[bp-2], cl ;將參數cl,存儲在byte[bp-2],將要讀取扇區的個數。
push bx ;保存bx,因爲下面要使用bx進行計算。
mov bl, [BPB_SecPerTrk] ;bl:除數=18
;ax存儲的是扇區號,bl是每磁道扇區數,執行ax/bl=al----ah,
;即商y在al中,商z在ah中。
div bl
inc ah ;ah(z)++,即起始扇區號=z+1,
mov cl, ah ;將ah值賦值給cl,中斷int 13h中,cl保存的恰好是起始扇區號
mov dh, al ;將al(y),賦值給dh
shr al, 1 ;對al(y)進行右移一位,即得到柱面號=y>>1,
mov ch, al ;然後將al賦值給ch,在中斷int 13h中,ch保存着柱面(磁道)號
and dh, 1 ;將dl(y)進行&1運算,即得到磁頭號=y&1,在中斷int 13h中,dh保存着
;磁頭號
pop bx ;恢復bx值
;到此爲止,“柱面(磁道)號(ch),起始扇區號(cl),磁頭號(dh),緩衝地址(es:bx)”全部準備就緒
mov dl, [BS_DrvNum] ;在中斷int 13中,dl保存着驅動器號。此時dl=[BS_DrvNum]=0
.GoOnReading:
;下面對ah,al進行賦值,ah=2,al=要讀取的扇區數,前面將參數cl存儲在byte[bp-2],現在從這裏重新獲取
;並賦值給al。
mov ah, 2
mov al, byte[bp-2]
;中斷int 13一切準備就緒,然後執行int 13
int 13h
jc .GoOnReading ;如果讀取錯誤,CF會被置爲1,這時就不停地讀,直到正確爲止。
add esp, 2 ;恢復堆棧
pop bp
ret
2、 如何在軟盤中尋找Loader.bin文件
(1) 結合上一節所介紹的FAT12數據結構,從中我們可以知道,要尋找一個文件,首先需要在根目錄區中尋找該文件的根目錄條目;然後根據根目錄條目獲取文件開始簇數(也就是在數據區中存儲的扇區);最後讀取文件內容到內存。
(2) 嗯,是的,下面就讓我們來完成第一步-----在根目錄區中尋找該文件的根目錄條目。
讓我們開始思考這個問題,
首先要知道根目錄區的開始扇區號是19,也就是說從第19扇區開始,根目錄區佔用扇區總數爲14,也就是說,如果不能發現Loader.bin,需要將14個扇區都進行查找,於是需要一個大的循環在外圍,控制着扇區的讀取。
緊接着,我們每讀取一個扇區,一個扇區是512個字節,一個根目錄條目佔32個字節,故一個扇區中存在512/32=16個根目錄條目,所以需要添加一個循環,控制根目錄條目的變化,從0—16進行循環。
最後,針對每一個根目錄條目,我們只是要比較文件名,一個根目錄條目的文件名佔用11個字節,所以需要對每一個字節與"LOADER BIN"中的每一個字節進行比較,所以還是要添加一個循環,來控制字符的變化,即從0—11.
用C語言來表示該問題就是:
for( i = 根目錄區的起始扇區號(19); i < 根目錄區佔有的扇區數(14); i++) {
for( j = 0;j < 一個扇區內存儲的根目錄條目個數(512/32=16); j++) {
for(k =0; k < 根目錄條目中文件名佔用的空間(11個字符); k++) {
if(k=10)jmp LABEL_FILENAMEFOUND
if(ds:si= es:di) si++; di++;
else break;
}
}
}
(3) 下面讓我們來分析代碼:
首先需要介紹下面可能需要用到的幾個變量值:
BaseOfLoader equ 09000h ;LOADER.BIN被加載到的位置---段地址
OffsetOfLoader equ 0100h ;LOADER.BIN被加載到的位置---偏移地址
RootDirSectors equ 14 ;根目錄佔用空間(BPB_RootEntCnt*32+511)/512
SectorNoOfRootDirectory equ 19 ;Root Directory 的第一個扇區號
;變量
wRootDirSizeForLoop dw RootDirSectors ;Root Directory佔用的扇區數,在循環中會遞減至0
wSectorNo dw 0 ;要讀取的扇區號
bOdd db 0 ;奇數還是偶數
;字符串
LoaderFileName db "LOADER BIN", 0 ;LOADER.BIN之文件名
- <span style="font-size: 16px;"> </span><span style="font-size: 18px;">;調用中斷int 13h,實現軟驅復位
- xor ah, ah
- xor dl, dl
- int 13h
- ;下面在A盤的根目錄中尋找LOADER.BIN
- ;wSectorNo表示要讀取的扇區號,SectorNoOfRootDirectory
- ;表示根目錄區的開始扇區號=19
- mov word[wSectorNo], SectorNoOfRootDirectory
- LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
- ;wRootDirSizeForLoop=RootDirSectors,表示根目錄佔用的扇區數,即表示要讀取的扇區數;也就是
- ;最外部循環中的控制變量(相當於i)。
- ;判斷根目錄區所有扇區是不是已經讀取完畢,如果讀完表示沒有找到LOADER.BIN,
- ;跳入到LABEL_NO_LOADERBIN,否則,減1。
- cmp word[wRootDirSizeForLoop], 0
- jz LABEL_NO_LOADERBIN
- dec word[wRootDirSizeForLoop]
- ;爲ReadSector函數準備參數,從第ax個Sector開始讀,將cl個Sector讀入es:bx中
- mov ax, BaseOfLoader
- mov es, ax ;es<-BaseOfLoader
- mov bx, OffsetOfLoader ;bx<-OffsetOfLoader,於是es:bx=BaseOfLoader:OffsetOfLoader
- mov ax, [wSectorNo] ;ax<-Root Directory中的某Sector號,表示要讀取的扇區號
- mov cl, 1 ;cl表示要讀取扇區個數=1
- call ReadSector
- ;調用ReadSector函數之後,es:bx將存儲該扇區數據。
- mov si, LoaderFileName ;ds:si->"LOADER BIN"
- mov di, OffsetOfLoader ;es:di->BaseOfLoader:OffsetOfLoader=es:bx
- ;即es:di指向存儲的該扇區數據
- cld
- ;一個扇區是512個字節,一個根目錄項佔32個字節,故512/32=16,因此需要比較16個根目錄項的文件名,
- ;故賦值dx=16,由dx來控制循環次數
- mov dx, 10h
- LABEL_SEARCH_FOR_LOADERBIN:
- ;判斷dx是否爲0,0意味着這個扇區內的所有根目錄項進行比較完畢,然後跳入到下一個扇區,繼續進行比較,
- ;dx=0,則跳入LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR;否則,dx--
- cmp dx, 0
- jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
- dec dx
- ;一個根目錄項的文件名佔用11個字節,故必須對其每個字節與"LOADER BIN"一一對比
- ;故賦值cx=11,由cx來控制循環次數
- mov cx, 11
- LABEL_CMP_FILENAME:
- cmp cx, 0
- jz LABEL_FILENAME_FOUND ;如果cx=0,意味着11個字符都相等,表示找到,跳轉到LABEL_FILENAME_FOUND
- dec cx ;否則,cx--
- lodsb ;ds:si->al,ds:si指向的是字符串"LOADER BIN"
- cmp al, byte[es:di] ;進行一個字符的比較,如果相等,則比較下一個字符,
- jz LABEL_GO_ON ;跳入到LABEL_GO_ON
- jmp LABEL_DIFFERENT ;只要發現有一個不相等的字符就表明本Directory Entry不是我們要
- ;找的LOADER.BIN,跳轉到LABEL_DIFFERENT,進如下一個Directory Entry比較。
- LABEL_GO_ON:
- inc di ;將di++,進行一個字符的比較。
- jmp LABEL_CMP_FILENAME ;跳轉到LABEL_CMP_FILENAME,繼續進行文件名比較。
- LABEL_DIFFERENT:
- ;di&=E0是爲了讓它指向本條目開頭,di初始化爲某個條目開頭,
- ;在比較過程中,會將它不斷增1,當失敗之後,必須進行重新初始化
- ;因爲一個條目佔用32個字節,故and di,0FFE0h add di, 20h
- ;之後,di就指向了下一個條目
- and di, 0FFE0h
- add di, 20h
- ;重新初始化si,使其指向"LOADER BIN"的開始位置
- mov si, LoaderFileName
- jmp LABEL_SEARCH_FOR_LOADERBIN ;跳轉到LABEL_SEARCH_FOR_LOADERBIN
- LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
- add word[wSectorNo], 1 ;將要讀取的扇區號+1,進行下一個扇區的比較
- jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN ;跳轉到LABEL_SEARCH_IN_ROOT_DIR_BEGIN,開始一個扇區的比較
- ;如果最後沒有找到"LOADER BIN",則顯示“NO LOADER”字符串來表示。
- LABEL_NO_LOADERBIN:
- mov dh, 2 ;"NO LOADER"
- call DispStr ;顯示字符串
- %ifdef _BOOT_DEBUG_
- mov dh, 2 ;"NO LOADER"
- call DispStr ;顯示字符串
- mov ax, 4C00h
- int 21h ;沒有找到LOADER.BIN,返回到DOS
- %else
- jmp $ ;沒有找到LOADER.BIN,死循環在這裏
- %endif
- ;如果找到"LOADER BIN",則跳轉到LABEL_FILENAME_FOUNT,然後進行第二步驟,從
- ;Directory Entry中讀取文件在數據區的開始簇號。
- LABEL_FILENAME_FOUND:</span><span style="font-size: 16px;">
- </span>
;調用中斷int 13h,實現軟驅復位
xor ah, ah
xor dl, dl
int 13h
;下面在A盤的根目錄中尋找LOADER.BIN
;wSectorNo表示要讀取的扇區號,SectorNoOfRootDirectory
;表示根目錄區的開始扇區號=19
mov word[wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
;wRootDirSizeForLoop=RootDirSectors,表示根目錄佔用的扇區數,即表示要讀取的扇區數;也就是
;最外部循環中的控制變量(相當於i)。
;判斷根目錄區所有扇區是不是已經讀取完畢,如果讀完表示沒有找到LOADER.BIN,
;跳入到LABEL_NO_LOADERBIN,否則,減1。
cmp word[wRootDirSizeForLoop], 0
jz LABEL_NO_LOADERBIN
dec word[wRootDirSizeForLoop]
;爲ReadSector函數準備參數,從第ax個Sector開始讀,將cl個Sector讀入es:bx中
mov ax, BaseOfLoader
mov es, ax ;es<-BaseOfLoader
mov bx, OffsetOfLoader ;bx<-OffsetOfLoader,於是es:bx=BaseOfLoader:OffsetOfLoader
mov ax, [wSectorNo] ;ax<-Root Directory中的某Sector號,表示要讀取的扇區號
mov cl, 1 ;cl表示要讀取扇區個數=1
call ReadSector
;調用ReadSector函數之後,es:bx將存儲該扇區數據。
mov si, LoaderFileName ;ds:si->"LOADER BIN"
mov di, OffsetOfLoader ;es:di->BaseOfLoader:OffsetOfLoader=es:bx
;即es:di指向存儲的該扇區數據
cld
;一個扇區是512個字節,一個根目錄項佔32個字節,故512/32=16,因此需要比較16個根目錄項的文件名,
;故賦值dx=16,由dx來控制循環次數
mov dx, 10h
LABEL_SEARCH_FOR_LOADERBIN:
;判斷dx是否爲0,0意味着這個扇區內的所有根目錄項進行比較完畢,然後跳入到下一個扇區,繼續進行比較,
;dx=0,則跳入LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR;否則,dx--
cmp dx, 0
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
dec dx
;一個根目錄項的文件名佔用11個字節,故必須對其每個字節與"LOADER BIN"一一對比
;故賦值cx=11,由cx來控制循環次數
mov cx, 11
LABEL_CMP_FILENAME:
cmp cx, 0
jz LABEL_FILENAME_FOUND ;如果cx=0,意味着11個字符都相等,表示找到,跳轉到LABEL_FILENAME_FOUND
dec cx ;否則,cx--
lodsb ;ds:si->al,ds:si指向的是字符串"LOADER BIN"
cmp al, byte[es:di] ;進行一個字符的比較,如果相等,則比較下一個字符,
jz LABEL_GO_ON ;跳入到LABEL_GO_ON
jmp LABEL_DIFFERENT ;只要發現有一個不相等的字符就表明本Directory Entry不是我們要
;找的LOADER.BIN,跳轉到LABEL_DIFFERENT,進如下一個Directory Entry比較。
LABEL_GO_ON:
inc di ;將di++,進行一個字符的比較。
jmp LABEL_CMP_FILENAME ;跳轉到LABEL_CMP_FILENAME,繼續進行文件名比較。
LABEL_DIFFERENT:
;di&=E0是爲了讓它指向本條目開頭,di初始化爲某個條目開頭,
;在比較過程中,會將它不斷增1,當失敗之後,必須進行重新初始化
;因爲一個條目佔用32個字節,故and di,0FFE0h add di, 20h
;之後,di就指向了下一個條目
and di, 0FFE0h
add di, 20h
;重新初始化si,使其指向"LOADER BIN"的開始位置
mov si, LoaderFileName
jmp LABEL_SEARCH_FOR_LOADERBIN ;跳轉到LABEL_SEARCH_FOR_LOADERBIN
LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
add word[wSectorNo], 1 ;將要讀取的扇區號+1,進行下一個扇區的比較
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN ;跳轉到LABEL_SEARCH_IN_ROOT_DIR_BEGIN,開始一個扇區的比較
;如果最後沒有找到"LOADER BIN",則顯示“NO LOADER”字符串來表示。
LABEL_NO_LOADERBIN:
mov dh, 2 ;"NO LOADER"
call DispStr ;顯示字符串
%ifdef _BOOT_DEBUG_
mov dh, 2 ;"NO LOADER"
call DispStr ;顯示字符串
mov ax, 4C00h
int 21h ;沒有找到LOADER.BIN,返回到DOS
%else
jmp $ ;沒有找到LOADER.BIN,死循環在這裏
%endif
;如果找到"LOADER BIN",則跳轉到LABEL_FILENAME_FOUNT,然後進行第二步驟,從
;Directory Entry中讀取文件在數據區的開始簇號。
LABEL_FILENAME_FOUND:
(4) 對上面這段代碼畫出它的簡易流程圖如下:
3、 如何將Loader.bin文件加載到內存?
現在我們已經有了Loader.bin的起始扇區號,我們需要用這個扇區號來做兩件事情:一件是把起始扇區裝入內存,另一件則是通過它找到FAT中的項,從而找到Loader佔用的其餘所有扇區。
此時裝入一個扇區對我們來說已經是很輕鬆的事了,可從FAT中找到一個項還是多少有些麻煩,下面我們就根據扇區號去FAT表中找到相應的項。在這裏,將要寫一個函數GetFATEntry,函數的輸入就是扇區號(ax),輸出則是其對應的FAT項的值(ax)。
我們一起來思考這個函數如何去實現,我們知道了扇區號x,然後我們去FAT1中尋找x所對應的FATEntry,我們已經知道一個FAT項佔1.5個字節。所以我們用x*3/2=y………z,y爲商(偏移量)(字節),相對於FAT1的初始位置的偏移量;Z爲餘數(0或者1),是判斷FATEntry是奇數還是偶數,0表示偶數,1表示奇數。然後我們讓y/512=m………n,m爲商,n爲餘數,此時m爲FATEntry所在的相對扇區,n爲在此扇區內的偏移量(字節)。因爲FAT1表前面還有1個引導扇區,所以FATEntry所在的實際扇區號爲m+1。然後讀取m+1和m+2兩個扇區,然後在偏移n個字節處,取出FATEntry,相當於讀取兩個字節。此時再利用z,如果z爲0的話,此FAT項爲前一個字節和後一個字節的後4位,如果z爲1的話,此FATEntry取前一個字節的前4位和後一個字節。
下面我們實現GetFATEntry函數,函數的輸入就是扇區號,輸出則是其對應的FATEntry的值。
- <span style="font-size: 18px;">;-----------------------------------------------------
- ;函數名:GetFATEntry
- ;-----------------------------------------------------
- ;作用: 找到序號爲ax的Sector在FAT中的條目,結果放在ax中,需要注意的是,中間需要讀FAT的扇區es:bx處,
- ; 所以函數一開始保存了es和bx
- GetFATEntry:
- push es
- push bx
- push ax
- ;在BaseOfLoader後面留出4K空間用於存放FAT
- mov ax, BaseOfLoader
- sub ax,0100h
- mov es, ax ;此時es-> (BaseOfLoader - 100h)
- pop ax ;ax存儲的是要讀取的扇區號
- mov byte[bOdd], 0
- ;ax是要讀取的扇區號,如何獲得該扇區號在FAT1中的FATEntry
- ;因爲每個FATEntry佔有1個半字節,所以計算ax*3/2,找到該FATEntry所在FAT1中的偏移量
- mov bx, 3
- mul bx
- mov bx, 2
- div bx
- ;ax*3/2=ax...dx,商爲ax表示該FATEntry在FAT1中的偏移量,dx的值爲(0或者1),
- ;0表示該FATEntry爲偶數,1表示該FATEntry爲奇數,
- cmp dx, 0
- jz LABEL_EVEN
- ;我們使用byte[bOdd]來保存dx的值,也就是該FATEntry是奇數項還是偶數項。
- mov byte[bOdd], 1
- LABEL_EVEN:
- xor dx, dx
- ;此時ax中保存着FATEntry在FAT1中的偏移量,下面來計算FATEntry
- ;在哪個個扇區中(FAT1佔用9個扇區)。
- ;ax/BPB_BytsPerSec=ax/512=ax...dx,商ax存儲着該FATEntry所在FAT1表的第幾個扇區,
- ;餘數dx保存着該FATEntry在該扇區內的偏移量。
- mov bx, [BPB_BytsPerSec]
- div bx
- push dx ;將dx存儲在堆棧中.
- mov bx, 0 ;es:bx=(BaseOfLoader-100):00=(BaseOfLoader-100h)*10h
- ;我們知道ax是FATEntry所在FAT1中的相對扇區,而FATEntry所在的實際扇區,需要加上
- ;FAT1表的開始扇區號,即加1,之後ax就是FATEntry所在的實際扇區
- add ax, SectorNoOfFAT1
- mov cl, 2
- ;讀取FATEntry所在的扇區,一次讀2個,避免在邊界發生錯誤,
- ;因爲一個FATEntry可能跨越兩個扇區
- call ReadSector
- ;從堆棧中彈出dx,FATEntry所在扇區的偏移量,將其與bx相加,此時es:bx指向的是該FATEntry所佔用
- ;的兩個字節空間
- pop dx
- add bx, dx
- ;讀取該FATEntry
- mov ax, [es:bx]
- ;下面是對bOdd進行判斷,如果其爲0,則表示FATEntry爲偶數,此時需要取byte1和byte2的後4位,
- ;由於在80x86下,從內存中讀取數據之後,byte2在前,byte1在後。
- ;所以當FATEntry爲偶數時,需要將ax&0FFF,將byte2的前4位置0.
- ;反之,如果bOdd爲1,則表示FATEntry爲奇數,此時需要取得byte1中的前4位和byte2.
- ;所以,需要將ax右移4位,將byte1的後四位移除。
- cmp byte[bOdd], 1
- jnz LABEL_EVEN_2
- shr ax, 4
- LABEL_EVEN_2:
- and ax, 0FFFh
- ;此時ax存儲的是FATEntry的值
- LABEL_GET_FAT_ENTRY_OK:
- pop bx
- pop es
- ret
- ;--------------------------------------------------------------------</span><span style="font-size: 16px;">
- </span>
;-----------------------------------------------------
;函數名:GetFATEntry
;-----------------------------------------------------
;作用: 找到序號爲ax的Sector在FAT中的條目,結果放在ax中,需要注意的是,中間需要讀FAT的扇區es:bx處,
; 所以函數一開始保存了es和bx
GetFATEntry:
push es
push bx
push ax
;在BaseOfLoader後面留出4K空間用於存放FAT
mov ax, BaseOfLoader
sub ax,0100h
mov es, ax ;此時es-> (BaseOfLoader - 100h)
pop ax ;ax存儲的是要讀取的扇區號
mov byte[bOdd], 0
;ax是要讀取的扇區號,如何獲得該扇區號在FAT1中的FATEntry
;因爲每個FATEntry佔有1個半字節,所以計算ax*3/2,找到該FATEntry所在FAT1中的偏移量
mov bx, 3
mul bx
mov bx, 2
div bx
;ax*3/2=ax...dx,商爲ax表示該FATEntry在FAT1中的偏移量,dx的值爲(0或者1),
;0表示該FATEntry爲偶數,1表示該FATEntry爲奇數,
cmp dx, 0
jz LABEL_EVEN
;我們使用byte[bOdd]來保存dx的值,也就是該FATEntry是奇數項還是偶數項。
mov byte[bOdd], 1
LABEL_EVEN:
xor dx, dx
;此時ax中保存着FATEntry在FAT1中的偏移量,下面來計算FATEntry
;在哪個個扇區中(FAT1佔用9個扇區)。
;ax/BPB_BytsPerSec=ax/512=ax...dx,商ax存儲着該FATEntry所在FAT1表的第幾個扇區,
;餘數dx保存着該FATEntry在該扇區內的偏移量。
mov bx, [BPB_BytsPerSec]
div bx
push dx ;將dx存儲在堆棧中.
mov bx, 0 ;es:bx=(BaseOfLoader-100):00=(BaseOfLoader-100h)*10h
;我們知道ax是FATEntry所在FAT1中的相對扇區,而FATEntry所在的實際扇區,需要加上
;FAT1表的開始扇區號,即加1,之後ax就是FATEntry所在的實際扇區
add ax, SectorNoOfFAT1
mov cl, 2
;讀取FATEntry所在的扇區,一次讀2個,避免在邊界發生錯誤,
;因爲一個FATEntry可能跨越兩個扇區
call ReadSector
;從堆棧中彈出dx,FATEntry所在扇區的偏移量,將其與bx相加,此時es:bx指向的是該FATEntry所佔用
;的兩個字節空間
pop dx
add bx, dx
;讀取該FATEntry
mov ax, [es:bx]
;下面是對bOdd進行判斷,如果其爲0,則表示FATEntry爲偶數,此時需要取byte1和byte2的後4位,
;由於在80x86下,從內存中讀取數據之後,byte2在前,byte1在後。
;所以當FATEntry爲偶數時,需要將ax&0FFF,將byte2的前4位置0.
;反之,如果bOdd爲1,則表示FATEntry爲奇數,此時需要取得byte1中的前4位和byte2.
;所以,需要將ax右移4位,將byte1的後四位移除。
cmp byte[bOdd], 1
jnz LABEL_EVEN_2
shr ax, 4
LABEL_EVEN_2:
and ax, 0FFFh
;此時ax存儲的是FATEntry的值
LABEL_GET_FAT_ENTRY_OK:
pop bx
pop es
ret
;--------------------------------------------------------------------
下面我們開始加載Loader.bin進入內存。
首先我們從根目錄區中的Loader.bin的條目,獲取文件的起始扇區號,然後加上BPB_RsrvSecCnt+BPB_FATSz16*2-2+RootDirSectors=1+(9*2)+14-2=31,,其中DeltaSectorNo=BPB_RsrvSecCnt+BPB_FATSz16*2-2=17。得到的結果纔是文件的實際的起始扇區。獲得起始扇區後,我們就可以調用ReadSector來讀取扇區了。然後從FAT1表中獲取FATEntry的值,判斷是否爲0FFFh,如果是,結束加載;如果不爲0FFFh,意味着該文件沒有讀取完成,需要讀取下一個扇區,此時的FATEntry的值,就是下一個扇區號,再將其轉換爲實際扇區號,再進行讀取。
下面是函數的實現和註釋
- <span style="font-size: 18px;">LABEL_FILENAME_FOUND: ;找到了LOADER.BIN後便來到這裏繼續
- mov ax, RootDirSectors ;根目錄區所佔用扇區數=14
- and di, 0FFE0h ;di->當前Directory Entry的開始位置
- add di, 01Ah ;di->此條目對應的開始簇號,DIR_FstClus
- mov cx, word[es:di] ;將開始簇號存儲在寄存器cx中
- push cx ;將cx入棧
- ;實現cx+RootDirSectors+DeltaSectorNo之後,此時cx保存着文件的實際開始扇區號,
- ;即數據區內的扇區
- add cx, ax
- add cx, DeltaSectorNo
- mov ax, BaseOfLoader
- mov es, ax
- mov bx, OffsetOfLoader ;es:bx=BaseOfLoader:OffsetOfLoader
- mov ax, cx ;ax表示要讀取的扇區號
- LABEL_GOON_LOADING_FILE:
- push ax
- push bx
- mov ah, 0Eh
- mov al, '.'
- mov bl, 0Fh
- int 10h
- pop bx
- pop ax
- ;每讀一個扇區就在”Booting “後面打一個點,形成這樣的效果:Booting......
- ;繼續爲ReadSector函數的參數做準備,cl=1,表示要讀取一個扇區
- mov cl, 1
- call ReadSector
- pop ax ;讀完一個扇區之後,然後重新讀取此Sector在FAT中的序號
- call GetFATEntry
- cmp ax, 0FFFh
- jz LABEL_FILE_LOADED
- ;如果讀取的FAT值爲FFF,表示該扇區爲該文件的最後一個扇區,
- ;因此結束加載,也就是加載成功
- ;如果讀取的FAT表中的值不是FFF,則表示還有扇區,故保存下一個扇區序號
- push ax
- mov dx, RootDirSectors
- add ax, dx
- add ax, DeltaSectorNo
- add bx, [BPB_BytsPerSec]
- ;爲call ReadSector的參數做準備,es:bx表示要緩存的地址,
- ;ax表示要讀取的扇區號=DirEntry中的開始Sector號+根目錄佔用Sector數目+DeltaSectorNo
- ;進入下一次循環。
- jmp LABEL_GOON_LOADING_FILE
- LABEL_FILE_LOADED:
- mov dh, 1 ;"Ready."
- call DispStr ;顯示字符串
- </span>
LABEL_FILENAME_FOUND: ;找到了LOADER.BIN後便來到這裏繼續
mov ax, RootDirSectors ;根目錄區所佔用扇區數=14
and di, 0FFE0h ;di->當前Directory Entry的開始位置
add di, 01Ah ;di->此條目對應的開始簇號,DIR_FstClus
mov cx, word[es:di] ;將開始簇號存儲在寄存器cx中
push cx ;將cx入棧
;實現cx+RootDirSectors+DeltaSectorNo之後,此時cx保存着文件的實際開始扇區號,
;即數據區內的扇區
add cx, ax
add cx, DeltaSectorNo
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader ;es:bx=BaseOfLoader:OffsetOfLoader
mov ax, cx ;ax表示要讀取的扇區號
LABEL_GOON_LOADING_FILE:
push ax
push bx
mov ah, 0Eh
mov al, '.'
mov bl, 0Fh
int 10h
pop bx
pop ax
;每讀一個扇區就在”Booting “後面打一個點,形成這樣的效果:Booting......
;繼續爲ReadSector函數的參數做準備,cl=1,表示要讀取一個扇區
mov cl, 1
call ReadSector
pop ax ;讀完一個扇區之後,然後重新讀取此Sector在FAT中的序號
call GetFATEntry
cmp ax, 0FFFh
jz LABEL_FILE_LOADED
;如果讀取的FAT值爲FFF,表示該扇區爲該文件的最後一個扇區,
;因此結束加載,也就是加載成功
;如果讀取的FAT表中的值不是FFF,則表示還有扇區,故保存下一個扇區序號
push ax
mov dx, RootDirSectors
add ax, dx
add ax, DeltaSectorNo
add bx, [BPB_BytsPerSec]
;爲call ReadSector的參數做準備,es:bx表示要緩存的地址,
;ax表示要讀取的扇區號=DirEntry中的開始Sector號+根目錄佔用Sector數目+DeltaSectorNo
;進入下一次循環。
jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:
mov dh, 1 ;"Ready."
call DispStr ;顯示字符串
《自己動手寫操作系統》讀後感 http://blog.csdn.net/zgh1988/article/details/7059936
全面剖析《自己動手寫操作系統》第一章 http://blog.csdn.net/zgh1988/article/details/7060032
全面剖析《自己動手寫操作系統》第二章 http://blog.csdn.net/zgh1988/article/details/7062065
全面剖析《自己動手寫操作系統》第三章1 http://blog.csdn.net/zgh1988/article/details/7098981
全面剖析《自己動手寫操作系統》--“實模式--保護模式--實模式” http://write.blog.csdn.net/postedit/7256254
全面剖析《自己動手寫操作系統》--堆棧段的工作方式 http://blog.csdn.net/zgh1988/article/details/7256254
全面剖析《自己動手寫操作系統》---特權級 以及 不同特權級代碼段之間的跳轉規則 http://blog.csdn.net/zgh1988/article/details/7262901
全面剖析《自己動手寫操作系統》--分頁機制 http://blog.csdn.net/zgh1988/article/details/7270748
全面剖析《自己動手寫操作系統》--中斷機制 http://blog.csdn.net/zgh1988/article/details/7276259