計算機啓動時BIOS會把啓動盤第一個扇區的數據讀入內存0x7C00開始處,然後跳到這裏繼續執行。從硬盤啓動和從軟盤啓動唯一的區別就是映象文件存儲方式的不同:
1. 對於從軟盤啓動的方式,映象文件連續地存放在軟盤開始的位置處。放在第一個扇區的bootsect.s被BIOS讀入內存後,就會把餘下的映象文件讀入內存,然後繼續執行
2. 對於從硬盤啓動的方式,映象文件存放在Minix格式的硬盤分區裏,MBR(硬盤第一個扇區)中的程序需要根據硬盤的參數和Minix文件系統的存儲格式讀出它
本文描述瞭如何編寫MBR中的程序,把存放在硬盤第一個分區根目錄下的linux0.11映像文件Image讀入內存。
文章中小字體的括號裏的數字表示參考文獻的編號(見後面的參考文獻列表)。
本文分5個部分: 1、硬盤簡介
2、Minix 1.0文件系統簡介
3、程序流程
4、上機實驗
5、程序代碼(以附件形式給出)
一.硬盤簡介(1)
圖1. 硬盤扇區編號(此圖來源文獻1)
我們需要了解的是物理扇區編號方式和絕對扇區編號方式。物理扇區號直接按柱面、磁頭、扇區3者的組合來定位某個扇區。對於硬盤的第一個扇區,其編號爲“0柱面0磁頭1扇區”。我們假設硬盤磁頭數爲16,每磁道扇區數爲63,下面描述遍歷整個硬盤時柱面號、磁頭號、扇區號的變化規律(以(x,y,z)表示x柱面y磁頭z扇區): (0,0,1)、(0,0,2)、……、(0,0,63)、
(0,1,1)、(0,1,2)、……、(0,1,63)、
(0,2,1)、(0,2,2)、……、(0,2,63)、
……
(0,15,1)、(0,15,2)、……、(0,15,63)、
(1,0,1)、(1,0,2)、……、(1,0,63)、
(1,1,1)、(1,1,2)、……、(1,1,63)、
換句話說就是扇區號是從1到63的63進制,磁頭號是0到15的16進制,“百位”是柱面號,“十位”是磁頭號,“個位”是扇區號。
絕對扇區號從0開始,遍歷硬盤時依次增1。
兩者的換算關係如下(abs_sector表示絕對扇區號,cyl表示柱面號,head表示磁頭號,sector表示扇區號,nheads表示磁頭數,nspt表示每磁道扇區數):
sector = abs_sector % nspt + 1
track = abs_sector / nspt
head = track % nheads
cyl = track / nheads
如何知道上面說的磁頭數nheads、每磁道扇區數nspt呢?啓動時BIOS會把硬盤參數表放在內存某個位置。對於第一個硬盤,硬盤參數表的首地址放在中斷0x41處,即內存地址4*0x41=0x104開始的4個字節表示硬盤參數表的段地址(後面2字節)和偏移地址(前面2字節)。硬盤參數表的結構如下:
dw cylinders
db nheads
dw 0
dw write pre-comp
db 0
db 0 "control byte"
db 0, 0, 0
dw landing zone
db nspt(sectors/track)
db 0
第二個硬盤的參數表地址放在BIOS中斷向量0x46處。
對於硬盤,我們還需要稍微知道一點分區表的信息。分區表放在硬盤第一個扇區的0x1be~0x1fd處(共64字節,第一個分區的信息在0x1be~0x1ce)。我們需要使用的僅僅是存放在0x1c6處的“分區起始絕對扇區號”(對應於程序中的start_sect變量)
二.Minix 1.0文件系統簡介(2) (3)
關於Minix 1.0文件系統的更詳細知識請參考文獻2、3,下面僅介紹理解本程序需要了解的知識。
Minix 1.0格式的分區結構如下:
引導塊
超級塊
i點位圖塊…
邏輯塊位圖塊…
i節點塊…
數據區………
1、對於Minix 1.0,每個盤塊佔1k字節(即2個扇區)。引導塊的第一個扇區就是本分區的第一個扇區,扇區號存在MBR中0x1c6處(就是上文的“分區起始絕對扇區號”)。盤塊從0開始編號,對於盤塊n,其絕對扇區號 = 2*n +分區起始絕對扇區號。
2、i點位圖塊和邏輯塊位圖塊的大小、數據區的起始盤塊號等信息存放在超級塊中,超級塊的數據結構如下:
struct d_super_block{
unsigned short s_ninodes; //節點數
unsigned short s_nzones; //邏輯塊數
unsigned short s_imap_blocks; //i節點位圖所佔用的數據塊數
unsigned short s_zmap_blocks; //邏輯塊位圖所佔用的數據塊數
unsigned short s_firstdatazone; //第一個數據邏輯塊
unsigned short s_log_zone_size; //log2(數據塊數/邏輯塊)
unsigned long s_max_size; //文件最大長度
unsigned short s_magic;}; //文件系統魔數
我們只需要知道開始存放i節點的盤塊號inode_start_zone和第一個數據邏輯塊號firstdatazone:
inode_start_zone = 2 + s_imap_blocks + s_zmap_blocks
firstdatazone = s_firstdatazone
3、每個i節點佔32字節,其數據結構如下:
struct d_inode{
unsigned short i_mode; //文件類型和屬性
unsigned short i_uid; //用戶id
unsigned long i_size; //文件大小(字節數)
unsigned long i_time; //修改時間(1970.1.1:0算起,秒)
unsigned char i_gid; //組id
unsigned char i_nlinks; //鏈接數
unsigned short i_zone[9];}; //直接(0~6)、間接(7)、雙重間接
//(8)邏輯塊號
根據其中的i_size可以計算出文件佔用的盤塊數 = (i_size + 1023) / 1024
文件數據的前面7k存放在稱爲直接塊的7個盤塊中,這7個盤塊的盤塊號存在i_zone[0]~i_zone[6]中。如果文件大於7k,則需要使用到一次間接塊(i_zone[7]),它對應的盤塊裏存放的不是文件數據,而是7k之後的文件數據存放的盤塊號。比如一次間接塊的第1、2字節指出第8k文件數據存放的盤塊號碼。一次間接塊可以存放512個盤塊號。如果文件大於(512+7)k,則需要使用二次間接塊i_zone[8]。圖示如下:
圖2. 文件數據存儲結構(此圖來源文獻3)
根據i_zone[0]~i_zone[6],可以直接讀出前面7k數據;然後讀出一次間接塊i_zone[7],根據其中的盤塊號即可讀出其餘的數據;忽略i_zone[8],因爲linux0.11映像文件沒那麼大。
圖3是映像文件Image一次間接塊的前面部分數據。每兩個字節表示一個盤塊號。“00 00”表示這1k的數據都是0(比如紅圈裏的),藍圈裏的“70 05”表示這1k數據存放的盤塊號是0x0570。
圖3. 映像文件Image一次間接塊的前面部分數據
4、第一個數據塊存放的就是根目錄文件的前1k數據,根目錄文件存放的是稱爲目錄項的信息。其數據結構如下:
struct dir_entry{
unsigned short inode; //i節點
char name[14];}; //文件名
圖4就是根目錄文件第一個盤塊的部分信息,圖中紅圈裏面的數據表示本本目錄項的i節點,藍圈裏面的數據表示該目錄形的名稱是Image。從圖上我們可以看到,前面兩個目錄項分別是目錄“.”和目錄“..”,緊隨其後的是bin、dev、etc、mnt、root、tmp、usr等目錄,再後面的是hello.c、Image、image文件(至於是目錄還是文件,需要找出相應的i節點根據其i_mode項來判斷)。
圖4. 根目錄文件第一個數據塊部分信息
三.程序流程
現在可以描述查找映像文件並讀出它的過程了:
a. 獲取硬盤參數——磁頭數、每磁道扇區數(以便計算絕對扇區號對應的柱面號、磁頭號、扇區號),獲取分區參數——第一個分區起始絕對扇區號(以定位Minix文件系統的邏輯塊)
b. 讀出超級塊,計算出i節點開始存放的盤塊號inode_start_zone和第一個數據邏輯塊號firstdatazone
c. 讀出根目錄文件(本程序只讀出了它前面的1k數據——即第一個數據邏輯塊)
d. 根據映像文件名Image找到對應的目錄項,取得其i節點編號(0x004A,圖4紅圈部分)
e. 根據映像文件i節點編號和inode_start_zone讀出i節點
f. 根據i節點的i_size信息確定要讀取的盤塊數,根據i_zone[0]~i_zone[6]讀出前面7k數據
g. 根據i_zone[7]讀出一次間接塊的數據
h. 根據一次間接塊確定的盤塊號,讀出
linux0.11映像文件由4部分組成:1、bootsect.s,2、setup.s,3、head.s,4、系統模塊。從軟盤啓動時,bootsect.s生成的可執行代碼(512字節)被讀入0x7C00處,它執行時先把自己移到0x90000處,然後把setup.s生成的可執行代碼(大小爲2k)從軟盤讀入到內存0x90200處,並把映像文件餘下部分讀到內存0x10000處。本MBR程序把映像文件前面2.5k數據(對應512字節的bootsect.s可執行代碼和2k的setup.s可執行代碼)讀到0x90000處,把映像文件餘下部分讀到內存0x10000處,然後跳到0x90200執行setup.s代碼。可見,本程序的功能只是與bootsect.s相似,bootsect.s從軟盤讀出映像文件,而本程序從硬盤中讀出。
程序代碼見第5部分的bootload.s,圖5是流程圖。
圖5. MBR程序流程圖
四.上機實驗
實驗工具:
1、Bochs PC仿真軟件上運行的SLS-Linux
下載地址:
http://oldlinux.org/Linux.old/bochs/sls-1.0.zip
2、linux-0.11-devel-040809.zip壓縮包(裏面有Bochs安裝程序)
下載地址:
http://oldlinux.org/Linux.old/bochs/linux-0.11-devel-040809.zip
3、DOS格式映像文件工具WinImage
下載地址:
http://ourworld.compuserve.com/homepages/gvollant/winima70.exe
實驗步驟:
hdc-0.11.img硬盤映像文件上有linux0.11根文件系統,但現在它不能引導linux0.11:把bochsrc-hd.bxrc文件中的“boot:a”改爲“boot:c”,運行bochsrc-hd.bxrc將導致引導失敗。下面修改其MBR使得可以引導:
1、把sls-1.0.zip和linux-0.11-devel-040809.zip解壓到同一個目錄下,安裝Bochs2.1.1。
2、用文本編輯器打開bochsrc.bxrc,把69行“floppyb:1_44=tmp1.imz, status=inserted”中的tmp1.imz改爲tmp.imz(原文有誤)
3、在bochsrc.bxrc中添加一行,掛接hdc-0.11.img:
“ata0-slave: type=disk, path="hdc-0.11.img", mode=flat, cylinders=121, heads=16, spt=63”
4、使用WimImage打開tmp.imz,把我們所寫的MBR程序bootload.s和makefile拖入,保存
5、雙擊運行bochsrc.bxrc,按空格鍵進入,使用root登錄
6、運行如下命令將linux0.11映像文件複製到hdb1的根目錄下:
mount /dev/hdb1 /mnt
cp /mnt/usr/src/linux/Image /mnt/Image
7、下面把存在b盤(即tmp.imz軟盤映像)中的bootload.s和makefile複製出來:
mkdir asm
cd asm
mcopy b:\makefile makefile
mcopy b:\bootload.s bootload.s
8、彙編、鏈接bootload.s,把可執行代碼寫到hdb1的MBR中去:
make //參考附錄makefile文件的說明
9、退出:
umount /mnt
關閉Bochs
步驟6~9的運行界面如圖6
圖6. 修改硬盤映像文件MBR
10、修改bochsrc-hd.bxrc,把boot:a改爲boot:c,雙擊運行。成功!界面如下:
圖7. 從硬盤啓動linux0.11
五.程序代碼
見隨本文件發佈的附件(一個是MBR程序bootload.s,一個是makefile文件)
參考文獻:
1、《帶您深入瞭解硬盤分區表與邏輯鎖》
作者:邁克爾
來源:家緣硬件網
日期:2005.1.9
鏈接:
http://www.pcnethome.com/Article/NEWS/skill/200501/1037.html
2、《Linux0.11學習體會》
作者:chenfangjian
來源:http://www.oldlinux.org/論壇子論壇“Linux內核完全註釋”
鏈接:
http://www.oldlinux.org/cgi-bin/LB5000XP/topic.cgi?forum=1&topic=1507
3、《Linux內核完全註釋》
作者:趙炯
機械工業出版社
4、 http://www.oldlinux.org/論壇子論壇“Linux 0.11系統的建立和實驗” 上linuxlala 回覆的一篇帖子,他詳細描述了編寫linux0.11引導程序的方法。
鏈接:
http://www.oldlinux.org/cgi-bin/LB5000XP/topic.cgi?forum=4&topic=226