全面剖析《自己動手寫操作系統》第四章---加載Loader.bin

全面剖析《自己動手寫操作系統》第四章--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中。

  1. <span style="font-size: 18px;">;----------------------------------------------------------------------- 
  2. ;函數名:ReadSector 
  3. ;------------------------------------------------------ 
  4. ;作用:從第ax個Sector開始,將cl個Sector讀入es:bx中 
  5. ReadSector: 
  6.     ;--------------------------------------------- 
  7.     ;怎樣由扇區號求扇區在磁盤中的位置(扇區號->柱面號,起始扇區,磁頭號) 
  8.     ;--------------------------------------------- 
  9.     ;設扇區號爲x 
  10.     ;                           ┌ 柱面號 = y >>
  11.     ;       x           ┌ 商 y ┤ 
  12.     ; -------------- => ┤      └ 磁頭號 = y & 1 
  13.     ;  每磁道扇區數     │ 
  14.     ;                   └ 餘 z => 起始扇區號 = z + 1 
  15.  
  16.     ;闢出兩個字節的堆棧區間保存要讀取的扇區數:byte[bp-2] 
  17.     push bp 
  18.     mov bp, sp 
  19.     sub esp, 2           
  20.     mov byte[bp-2], cl      ;將參數cl,存儲在byte[bp-2],將要讀取扇區的個數。 
  21.  
  22.     push bx             ;保存bx,因爲下面要使用bx進行計算。 
  23.     mov bl, [BPB_SecPerTrk]     ;bl:除數=18 
  24.  
  25.     ;ax存儲的是扇區號,bl是每磁道扇區數,執行ax/bl=al----ah, 
  26.     ;即商y在al中,商z在ah中。 
  27.     div bl               
  28.     inc ah              ;ah(z)++,即起始扇區號=z+1, 
  29.     mov cl, ah          ;將ah值賦值給cl,中斷int 13h中,cl保存的恰好是起始扇區號 
  30.  
  31.     mov dh, al          ;將al(y),賦值給dh 
  32.  
  33.     shr al, 1           ;對al(y)進行右移一位,即得到柱面號=y>>1, 
  34.     mov ch, al          ;然後將al賦值給ch,在中斷int 13h中,ch保存着柱面(磁道)號 
  35.  
  36.     and dh, 1           ;將dl(y)進行&1運算,即得到磁頭號=y&1,在中斷int 13h中,dh保存着 
  37.                     ;磁頭號 
  38.     pop bx              ;恢復bx值 
  39.     ;到此爲止,“柱面(磁道)號(ch),起始扇區號(cl),磁頭號(dh),緩衝地址(es:bx)”全部準備就緒 
  40.     mov dl, [BS_DrvNum]     ;在中斷int 13中,dl保存着驅動器號。此時dl=[BS_DrvNum]=0 
  41. .GoOnReading: 
  42.     ;下面對ah,al進行賦值,ah=2,al=要讀取的扇區數,前面將參數cl存儲在byte[bp-2],現在從這裏重新獲取 
  43.     ;並賦值給al。 
  44.     mov ah, 2 
  45.     mov al, byte[bp-2] 
  46.     ;中斷int 13一切準備就緒,然後執行int 13 
  47.     int 13h 
  48.     jc  .GoOnReading        ;如果讀取錯誤,CF會被置爲1,這時就不停地讀,直到正確爲止。 
  49.  
  50.     add esp, 2          ;恢復堆棧 
  51.     pop bp 
  52.     ret</span><span style="font-size: 16px;"> 
  53. </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之文件名

  1. <span style="font-size: 16px;"> </span><span style="font-size: 18px;">;調用中斷int 13h,實現軟驅復位 
  2.     xor ah, ah 
  3.     xor dl, dl 
  4.     int 13h  
  5.      
  6.     ;下面在A盤的根目錄中尋找LOADER.BIN 
  7.      
  8.     ;wSectorNo表示要讀取的扇區號,SectorNoOfRootDirectory 
  9.     ;表示根目錄區的開始扇區號=19 
  10.     mov word[wSectorNo], SectorNoOfRootDirectory     
  11. LABEL_SEARCH_IN_ROOT_DIR_BEGIN: 
  12.     ;wRootDirSizeForLoop=RootDirSectors,表示根目錄佔用的扇區數,即表示要讀取的扇區數;也就是 
  13.     ;最外部循環中的控制變量(相當於i)。 
  14.     ;判斷根目錄區所有扇區是不是已經讀取完畢,如果讀完表示沒有找到LOADER.BIN, 
  15.     ;跳入到LABEL_NO_LOADERBIN,否則,減1。 
  16.     cmp word[wRootDirSizeForLoop], 0 
  17.     jz  LABEL_NO_LOADERBIN           
  18.     dec word[wRootDirSizeForLoop] 
  19.  
  20.     ;爲ReadSector函數準備參數,從第ax個Sector開始讀,將cl個Sector讀入es:bx中 
  21.     mov ax, BaseOfLoader 
  22.     mov es, ax      ;es<-BaseOfLoader 
  23.     mov bx, OffsetOfLoader  ;bx<-OffsetOfLoader,於是es:bx=BaseOfLoader:OffsetOfLoader 
  24.  
  25.     mov ax, [wSectorNo] ;ax<-Root Directory中的某Sector號,表示要讀取的扇區號 
  26.     mov cl, 1       ;cl表示要讀取扇區個數=1 
  27.     call    ReadSector 
  28.     ;調用ReadSector函數之後,es:bx將存儲該扇區數據。 
  29.  
  30.     mov si, LoaderFileName  ;ds:si->"LOADER  BIN" 
  31.     mov di, OffsetOfLoader  ;es:di->BaseOfLoader:OffsetOfLoader=es:bx 
  32.                 ;即es:di指向存儲的該扇區數據 
  33.     cld 
  34.  
  35.     ;一個扇區是512個字節,一個根目錄項佔32個字節,故512/32=16,因此需要比較16個根目錄項的文件名, 
  36.     ;故賦值dx=16,由dx來控制循環次數 
  37.     mov dx, 10h 
  38. LABEL_SEARCH_FOR_LOADERBIN: 
  39.     ;判斷dx是否爲0,0意味着這個扇區內的所有根目錄項進行比較完畢,然後跳入到下一個扇區,繼續進行比較, 
  40.     ;dx=0,則跳入LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR;否則,dx-- 
  41.     cmp dx, 0 
  42.     jz  LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR   
  43.     dec dx 
  44.      
  45.     ;一個根目錄項的文件名佔用11個字節,故必須對其每個字節與"LOADER  BIN"一一對比 
  46.     ;故賦值cx=11,由cx來控制循環次數 
  47.     mov cx, 11 
  48. LABEL_CMP_FILENAME: 
  49.     cmp cx, 0 
  50.     jz LABEL_FILENAME_FOUND ;如果cx=0,意味着11個字符都相等,表示找到,跳轉到LABEL_FILENAME_FOUND 
  51.     dec cx          ;否則,cx-- 
  52.  
  53.     lodsb           ;ds:si->al,ds:si指向的是字符串"LOADER  BIN" 
  54.     cmp al, byte[es:di] ;進行一個字符的比較,如果相等,則比較下一個字符, 
  55.     jz LABEL_GO_ON      ;跳入到LABEL_GO_ON 
  56.     jmp LABEL_DIFFERENT ;只要發現有一個不相等的字符就表明本Directory Entry不是我們要 
  57.                 ;找的LOADER.BIN,跳轉到LABEL_DIFFERENT,進如下一個Directory Entry比較。 
  58. LABEL_GO_ON:             
  59.     inc di          ;將di++,進行一個字符的比較。 
  60.     jmp LABEL_CMP_FILENAME  ;跳轉到LABEL_CMP_FILENAME,繼續進行文件名比較。 
  61.  
  62. LABEL_DIFFERENT: 
  63.     ;di&=E0是爲了讓它指向本條目開頭,di初始化爲某個條目開頭, 
  64.     ;在比較過程中,會將它不斷增1,當失敗之後,必須進行重新初始化 
  65.     ;因爲一個條目佔用32個字節,故and di,0FFE0h   add di, 20h 
  66.     ;之後,di就指向了下一個條目 
  67.     and di, 0FFE0h               
  68.     add di, 20h  
  69.  
  70.     ;重新初始化si,使其指向"LOADER  BIN"的開始位置 
  71.     mov si, LoaderFileName           
  72.     jmp LABEL_SEARCH_FOR_LOADERBIN  ;跳轉到LABEL_SEARCH_FOR_LOADERBIN 
  73.  
  74. LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:  
  75.     add word[wSectorNo], 1      ;將要讀取的扇區號+1,進行下一個扇區的比較 
  76.     jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN  ;跳轉到LABEL_SEARCH_IN_ROOT_DIR_BEGIN,開始一個扇區的比較 
  77.  
  78.     ;如果最後沒有找到"LOADER   BIN",則顯示“NO LOADER”字符串來表示。 
  79. LABEL_NO_LOADERBIN: 
  80.     mov dh, 2               ;"NO LOADER" 
  81.     call DispStr                ;顯示字符串 
  82. %ifdef  _BOOT_DEBUG_ 
  83.     mov dh, 2               ;"NO LOADER" 
  84.     call DispStr                ;顯示字符串 
  85.     mov ax, 4C00h 
  86.     int 21h                 ;沒有找到LOADER.BIN,返回到DOS 
  87. %else    
  88.     jmp $                   ;沒有找到LOADER.BIN,死循環在這裏 
  89. %endif 
  90.  
  91.     ;如果找到"LOADER   BIN",則跳轉到LABEL_FILENAME_FOUNT,然後進行第二步驟,從 
  92.     ;Directory Entry中讀取文件在數據區的開始簇號。 
  93. LABEL_FILENAME_FOUND:</span><span style="font-size: 16px;"> 
  94. </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的值。

  1. <span style="font-size: 18px;">;----------------------------------------------------- 
  2. ;函數名:GetFATEntry 
  3. ;----------------------------------------------------- 
  4. ;作用:    找到序號爲ax的Sector在FAT中的條目,結果放在ax中,需要注意的是,中間需要讀FAT的扇區es:bx處, 
  5. ;   所以函數一開始保存了es和bx 
  6. GetFATEntry: 
  7.     push es 
  8.     push bx 
  9.     push ax 
  10.      
  11.     ;在BaseOfLoader後面留出4K空間用於存放FAT 
  12.     mov ax, BaseOfLoader 
  13.     sub ax,0100h 
  14.     mov es, ax  ;此時es-> (BaseOfLoader - 100h) 
  15.  
  16.     pop ax      ;ax存儲的是要讀取的扇區號 
  17.     mov byte[bOdd], 0 
  18.     ;ax是要讀取的扇區號,如何獲得該扇區號在FAT1中的FATEntry 
  19.     ;因爲每個FATEntry佔有1個半字節,所以計算ax*3/2,找到該FATEntry所在FAT1中的偏移量 
  20.     mov bx, 3 
  21.     mul bx 
  22.     mov bx, 2 
  23.     div bx       
  24.     ;ax*3/2=ax...dx,商爲ax表示該FATEntry在FAT1中的偏移量,dx的值爲(0或者1), 
  25.     ;0表示該FATEntry爲偶數,1表示該FATEntry爲奇數, 
  26.     cmp dx, 0 
  27.     jz  LABEL_EVEN 
  28.     ;我們使用byte[bOdd]來保存dx的值,也就是該FATEntry是奇數項還是偶數項。 
  29.     mov byte[bOdd], 1 
  30. LABEL_EVEN: 
  31.     xor dx, dx   
  32.     ;此時ax中保存着FATEntry在FAT1中的偏移量,下面來計算FATEntry 
  33.     ;在哪個個扇區中(FAT1佔用9個扇區)。 
  34.     ;ax/BPB_BytsPerSec=ax/512=ax...dx,商ax存儲着該FATEntry所在FAT1表的第幾個扇區, 
  35.     ;餘數dx保存着該FATEntry在該扇區內的偏移量。 
  36.     mov bx, [BPB_BytsPerSec] 
  37.     div bx       
  38.  
  39.     push dx     ;將dx存儲在堆棧中. 
  40.     mov bx, 0   ;es:bx=(BaseOfLoader-100):00=(BaseOfLoader-100h)*10h 
  41.     ;我們知道ax是FATEntry所在FAT1中的相對扇區,而FATEntry所在的實際扇區,需要加上 
  42.     ;FAT1表的開始扇區號,即加1,之後ax就是FATEntry所在的實際扇區 
  43.     add ax, SectorNoOfFAT1           
  44.     mov cl, 2 
  45.     ;讀取FATEntry所在的扇區,一次讀2個,避免在邊界發生錯誤, 
  46.     ;因爲一個FATEntry可能跨越兩個扇區 
  47.     call ReadSector      
  48.     ;從堆棧中彈出dx,FATEntry所在扇區的偏移量,將其與bx相加,此時es:bx指向的是該FATEntry所佔用 
  49.     ;的兩個字節空間 
  50.     pop dx 
  51.     add bx, dx 
  52.     ;讀取該FATEntry 
  53.     mov ax, [es:bx] 
  54.     ;下面是對bOdd進行判斷,如果其爲0,則表示FATEntry爲偶數,此時需要取byte1和byte2的後4位, 
  55.     ;由於在80x86下,從內存中讀取數據之後,byte2在前,byte1在後。 
  56.     ;所以當FATEntry爲偶數時,需要將ax&0FFF,將byte2的前4位置0. 
  57.     ;反之,如果bOdd爲1,則表示FATEntry爲奇數,此時需要取得byte1中的前4位和byte2. 
  58.     ;所以,需要將ax右移4位,將byte1的後四位移除。 
  59.     cmp byte[bOdd], 1 
  60.     jnz LABEL_EVEN_2 
  61.     shr ax, 4 
  62. LABEL_EVEN_2: 
  63.     and ax, 0FFFh 
  64.  
  65.     ;此時ax存儲的是FATEntry的值 
  66. LABEL_GET_FAT_ENTRY_OK: 
  67.     pop bx 
  68.     pop es 
  69.     ret 
  70. ;--------------------------------------------------------------------</span><span style="font-size: 16px;"> 
  71. </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的值,就是下一個扇區號,再將其轉換爲實際扇區號,再進行讀取。

下面是函數的實現和註釋

  1. <span style="font-size: 18px;">LABEL_FILENAME_FOUND:            ;找到了LOADER.BIN後便來到這裏繼續 
  2.     mov ax, RootDirSectors      ;根目錄區所佔用扇區數=14 
  3.     and di, 0FFE0h          ;di->當前Directory Entry的開始位置 
  4.     add di, 01Ah            ;di->此條目對應的開始簇號,DIR_FstClus 
  5.     mov cx, word[es:di]     ;將開始簇號存儲在寄存器cx中 
  6.     push cx             ;將cx入棧 
  7.     ;實現cx+RootDirSectors+DeltaSectorNo之後,此時cx保存着文件的實際開始扇區號, 
  8.     ;即數據區內的扇區 
  9.     add cx, ax           
  10.     add cx, DeltaSectorNo            
  11.  
  12.     mov ax, BaseOfLoader 
  13.     mov es, ax 
  14.     mov bx, OffsetOfLoader      ;es:bx=BaseOfLoader:OffsetOfLoader 
  15.     mov ax, cx          ;ax表示要讀取的扇區號 
  16.  
  17. LABEL_GOON_LOADING_FILE: 
  18.     push ax 
  19.     push bx 
  20.     mov ah, 0Eh 
  21.     mov al, '.' 
  22.     mov bl, 0Fh 
  23.     int 10h              
  24.     pop bx 
  25.     pop ax 
  26.     ;每讀一個扇區就在”Booting   “後面打一個點,形成這樣的效果:Booting...... 
  27.  
  28.     ;繼續爲ReadSector函數的參數做準備,cl=1,表示要讀取一個扇區 
  29.     mov cl, 1        
  30.     call ReadSector 
  31.  
  32.     pop ax      ;讀完一個扇區之後,然後重新讀取此Sector在FAT中的序號 
  33.     call GetFATEntry     
  34.     cmp ax, 0FFFh 
  35.     jz  LABEL_FILE_LOADED    
  36.     ;如果讀取的FAT值爲FFF,表示該扇區爲該文件的最後一個扇區, 
  37.     ;因此結束加載,也就是加載成功 
  38.     ;如果讀取的FAT表中的值不是FFF,則表示還有扇區,故保存下一個扇區序號 
  39.     push ax      
  40.                  
  41.     mov dx, RootDirSectors 
  42.     add ax, dx 
  43.     add ax, DeltaSectorNo 
  44.     add bx, [BPB_BytsPerSec] 
  45.     ;爲call ReadSector的參數做準備,es:bx表示要緩存的地址, 
  46.     ;ax表示要讀取的扇區號=DirEntry中的開始Sector號+根目錄佔用Sector數目+DeltaSectorNo 
  47.     ;進入下一次循環。 
  48.     jmp LABEL_GOON_LOADING_FILE 
  49.  
  50. LABEL_FILE_LOADED: 
  51.     mov dh, 1               ;"Ready." 
  52.     call DispStr                ;顯示字符串 
  53. </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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章