2010年10月25日 星期一

從啟動到run grub

文章出處:http://bbs.tongji.net/thread-258437-1-1.html
從啟動到run grub
下面借用了 劉 吟(劉老大) OS啟動的第一步的Ppt
Os 啟動第一步
1.Bios
Cpu 的初始化
► 主機加電後,啟動時鐘發生器,在匯流排上產生POWERGOOD信號,CPU收到RESET信號,進入初始化過程
CPU轉入8086實模式。
DS = ES = FS = GS = SS = 0
CS = 0xFFFF
IP = 0XFFF0
進入BIOS加電自檢過程(Power On Self Test)
BIOS初始化
關中斷,進行所有的POST檢測
將中斷向量表的起始位址設為0x0000H
0x0000H~0x03FFH中存放了256個中斷
建立了實模式下的中斷向量表
BIOS的啟動程式調用INT 19h中斷
將控制權轉移給Boot Loader
► INT 19h中斷的功能
INT 19h按照BIOS中的啟動設備順序查詢每個啟動設備在軟碟的啟動磁區或者硬碟的MBR中有Boot Loader,那麼這個磁區的最後兩個位元組必然為0xAA55。BIOS將這個磁區(512個位元組)讀入記憶體的0000:7C00開始的位置,然後跳轉到記憶體0000:7C00的地方開始執行。如果在所有的啟動設備上都找不到Boot Loader,那麼就調用INT 18h,將控制權交給BIOS ROM Basic,鎖定機器,並且在螢幕上顯示NO BOOT DEVICE AVAILABLE
► 結論
Boot Loader應當存放在啟動設備的第一個磁區中,對於硬碟是MBR,對於軟碟是啟動磁區安裝了Boot Loader的啟動磁區的最後兩個位元組必須為0xAA55 BIOS在POST過程結束以後,調用INT 19h中斷,將Boot Loader讀入記憶體0000:7C00處。然後釋放控制權,跳轉到0000:7C00開始執行Boot Loader的代碼。
2.最簡單的Boot Loader:FDISK /MBR
硬碟的分區表結構
a) 第一個磁區為MBR
b) 一個硬碟上最多有4個主分區
c) 第一個主分區一般從cylinder 0, head 1, sector 1開始
d) cylinder 0, head 0, sector 2~n一般來說保留不用
e) 其餘的主分區一般從cylinder x, head 0, sector 1開始

Master Boot Record
f) 位於硬碟的cylinder 0, head 0, sector 1的位置
g) 大小為512位元組
h) 其中存放了4個主分區的入口,每個入口占16個位元組(這就是為什麼一個磁片最多只能有4個主分區的原因)
i) 最後兩位元為啟動標誌,如果MBR中有Boot Loader的話,則為0xAA55
j) 留給Boot Loader的空間為512-16x4-2=446位元組
MBR中的Boot Loader的功能
k) 初始化,將自身搬移到0000:0600的位置
l) 在主分區入口表中尋找活動的分區
m) 調用INT 13h AH=02將活動的分區的啟動磁區讀入記憶體0000:7C00處
n) 跳轉到0000:7C00處執行活動分區的啟動磁區中的代碼
初始化,將自身搬移到0000:0600的位置
0000:7C00 CLI 關中斷
0000:7C01 XOR AX,AX
0000:7C03 MOV SS,AX 將堆疊段(SS)設為0
0000:7C05 MOV SP,7C00 將棧頂指針(SP)設置為7C00
0000:7C08 MOV SI,SP 將SI也設為7C00
0000:7C0A PUSH AX
0000:7C0B POP ES 將ES設為0000
0000:7C0C PUSH AX
0000:7C0D POP DS 將DS設為0000
0000:7C0E STI 開中斷
0000:7C0F CLD
0000:7C10 MOV DI,0600 將DI設為0600
0000:7C13 MOV CX,0100 準備移動256個字(512位元組)
0000:7C16 REPNZ
0000:7C17 MOVSW 將MBR從0000:7c00移動到0000:0600
0000:7C18 JMP 0000:061D 開始搜索主分區入口表
► 在主分區入口表中尋找活動的分區

0000:061D MOV SI,07BE SI指向分區表入口(在總共512byte的主引導記錄中,MBR的引導程式占了其中的前446個位元組(偏移0H~偏移1BDH),隨後的64個位元組(偏移1BEH~偏移1FDH)為DPT(Disk PartitionTable,硬碟分區表),最後的兩個位元組“55 AA”(偏移1FEH~偏移1FFH)是分區有效結束標誌)
0000:0620 MOV BL,04 一共有4個表項
0000:0622 CMP BYTE PTR [SI],80 是否為活動分區
0000:0625 JZ FOUND_ACTIVE 找到了一個活動表項
0000:0627 CMP BYTE PTR [SI],00 是否為非活動分區
0000:062A JNZ NOT_ACTIVE 不可識別的分區標識
0000:062C ADD SI,+10 指向下一個表項(+16)
0000:062F DEC BL 迴圈標誌減一
0000:0631 JNZ 0000:061D 繼續迴圈
0000:0633 INT 18 未找到可啟動的分區,轉到ROM Basic

► 調用INT13 AH = 02h讀取啟動磁區

0000:0635 MOV DX,[SI] 設置INT 13調用中Head和Driver的值
0000:0637 MOV CX,[SI+02] 設置INT 13調用中Cylinder的值
0000:063A MOV BP,SI 保存活動分區入口表項位址

0000:065D MOV DI,0005 設置讀取的重試次數
0000:0660 MOV BX,7C00 將啟動磁區讀取到0000:7C00(ES:BX)
0000:0663 MOV AX,0201 準備調用INT 13讀取一個磁區AL = 01
0000:0666 PUSH DI 保存重試次數DI
0000:0667 INT 13 調用INT 13讀取一個磁區到0000:7c00
0000:0669 POP DI 恢復重試次數DI
0000:066A JNB INT13OK 如果讀取成功,則跳轉至啟動代碼
0000:066C XOR AX,AX 準備INT 13 AH = 0重定磁片
0000:066E INT 13 調用INT 13重定磁片
0000:0670 DEC DI 重試次數減一
0000:0671 JNZ 0000:0660 進行下一次重試

► 運行啟動磁區中的代碼

0000:067B MOV DI,7DFE 指向啟動磁區中的啟動標識
0000:067E CMP WORD PTR [DI],AA55 檢查啟動表識是否為0xAA55
0000:0682 JNZ DISPLAY_MSG 如果不是,則保錯
0000:0684 MOV SI,BP 恢復SI,指向活動分區入口表項
0000:0686 JMP 0000:7C00 跳轉至啟動磁區的代碼

► 結論
Boot Loader放在可啟動設備的第一個磁區中Boot Loader的大小受磁區大小和其他附加資訊的限制。在MBR中,為446位元組Boot Loader在從BIOS中接手CPU的控制權時,位於記憶體位址0000:7C00處FDISK /MBR產生的Boot Loader不具備啟動OS的能力,其本質上是一個Chain Loader,用於引導一個有啟動OS能力的Boot Loader。
3.如何引導OS:DOS Boot Loader
► Boot Sector的結構
Boot Sector位於每個分區的第一個磁區,Boot Sector的第一個部分是一個跳轉指令和一個NOP,以跳轉實際的Boot Loader的代碼中,BIOS Parameter Block中存放了和這個分區相關的一系列參數BPB之後就是實際的Boot Loader的代碼最後是一個可啟動分區標識0xAA55
► 使用Format /s對Boot Sector做的修改,在0x000h處寫入BPB,在0x03Eh處寫入DOS Boot Loader的代碼,在0x1FEh處寫入0xAA55標識。
► 此外,Format /s還要初始化FAT表,將IO.SYS和MSDOS.SYS寫入,佔據FAT表前兩個表項。
► DOS Boot Loader的功能初始化;計算根目錄所在的FAT表的磁區號讀取根目錄的第一個磁區到0000:0500檢查前兩個表項是否為IO.SYS和MSDOS.SYS將IO.SYS的前3個磁區讀入0000:0700或者0070:0000的位置中在寄存器中保留一些資訊,然後跳轉到0070:0000處執行作業系統代碼DOS Boot Loader和MBR中的Boot Loader的最大區別在於對於檔系統的理解。
► MBR中的Boot Loader不理解檔系統,所以無法啟動特定的OS
► DOS Boot Loader提供了對於FAT檔系統的支援,所以能夠啟動在FAT檔系統上的DOS
► DOS Boot Loader知道OS的內核檔的位置,其主要的工作就是將內核檔讀入記憶體,然後將控制權轉交給OS
4.硬碟定址方式:CHS vs LBA
► 最早期的CHS定址,最早期的磁片定址是通過Cylinder/Head/Sector進行的,INT 13h中給出的CHS參數直接指定了資料的物理位置例:INT 13h AH = 02。
► AH = 02 AL = 讀入的磁區數目
► CH = Cylinder低8位 CL = Cylinder高兩位
► DH = Head DL = 操作的磁片(80h for C:)
 限制:
► Cylinder <= 1024
► Head <= 16
► Sector <= 63
► 總容量 < 1024 x 16 x 63 x 512B = 528MB
► L-CHS & P-CHS;為了解決直接定址的問題,現代的HD中,CHS只有邏輯上的意義,不表達物理上的實際位置。L-CHS用在支援CHS Translation的BOIS的INT 13 AH = 0xh的調用中。
► Cylinder <= 1024
► Head <= 256
► Sector <= 63
► 總容量 <= 1024 x 256 x 63 x 512B = 8GB,P-CHS用在HD的介面上
► Cylinder <= 65535
► Head <= 16
► Sector <= 63
► 總容量 <= 65535 x 16 x 63 x 512B = 136GB
► LBA:Large Block Addressing
► LBA的出現是為了解決CHS模式位址受限的問題
► LBA提供了一種線性的定址方式:Cylinder 0,Head 0,Sector 1相當於LBA地址0。往後每增加一個磁區,LBA位址加一
► LBA位址和CHS位址可以通過如下公式轉換
► LBA = ((cylinder * heads_per_cylinder + heads) * sectors_per_track )+sector-1
► 新型的BOIS提供了INT 13h AH = 4xh的擴展磁片調用以支援LBA模式
► LBA:Large Block Addressing;INT 13h AH = 42h
► AH = 42H DL = 操作的磁片(80h for C:)
► DS:SI = Disk Address Packet;Disk Address Packet的格式
► 結論:定址方式和Boot Loader的關係;BIOS在POST過程以後調用INT 19h,使用的是CHS定址;如果一個OS自身的Boot Loader使用的是CHS定址模式,那麼在老式BIOS上不能啟動528MB以後的分區,在新式BIOS上不能啟動8G以後的分區(LILO的問題);只有支持LBA的Boot Loader才能支持啟動8G以後分區上的OS
5.支持多OS引導的Boot Loader
► 支持多OS引導的Boot Loader是:
 一段在啟動時被BIOS INT 19調用的代碼
 能夠理解系統中安裝的多個不同的OS
 用戶可以選擇啟動特定的OS
 程式根據用戶選擇,通過直接載入或者Chain Load的方法,啟動選中的OS
► 支援多OS引導的Boot Loader需要:
 自身足夠的小,或者支持多階段載入,以能夠安裝在MBR或者Boot Sector中
 能夠理解多種檔系統的格式,以便能夠找到存放在不同檔系統下的系統內核
 能夠理解多種檔格式(ELF/a.out),以便能夠正確的載入不同的系統內核
 支援CHS和LBA兩種磁片訪問模式,以便能夠正確的啟動8G以後的分區
 支持Chain Loader,以便通過特定的Boot Loader載入OS
► 支持多OS引導的Boot Loader在執行32位OS的內核代碼前,需要構建的機器狀態:
 CS必須是一個32位的可讀可執行的代碼段,偏移為0,上限為0xFFFFFFFF
 DS, ES, FS, GS和SS必須為32位的可讀寫段,偏移為0,上限為0xFFFFFFFF
 20號地址線必須在32位位址空間中可用(初始固定為0)
 分頁機制必須被關閉
 處理器中斷標記被關閉
 EAX的值為0x2BADB002
 EBX中存放了一個32位的位址,指向由Boot Loader填充的一系列啟動資訊
引自:Multiboot Specification
6.實例分析:Grub
► Grub是:
 GRand Unified Bootloader的縮寫
 是一個靈活而強大的Boot Loader
 其能夠理解多種不同的檔系統和可執行檔格式,從而能夠引導多種OS
 通過將Boot Loader所需要的功能封裝成一套腳本語言,從而能夠按照特定的方式引導OS
► Grub的I/O
 支援CHS和LBA兩種磁片訪問模式
 (device[,part-num][,bsd-subpart-letter])的方式訪問設備:(hd0), (hd1, 0), (hd0, a), (hd0, 1, a)
 檔訪問
► 通過路徑形式訪問:/boot/grub/menu.lst
► 通過磁區形式訪問:0+1,200+1,300+300
► Grub的腳本:
 root指定一個啟動的設備
 kernel指定作業系統的內核
 boot正式啟動一個OS
 makeactive啟動一個分區
 chainloader調用啟動設備上的Boot Loader
► 啟動Linux
 root (hd0,0)
 kernel /vmlinuz root=/dev/hda1
 boot
► 啟動Windows
 root (hd0,0)
 chainloader +1
 makeactive
 boot
► Grub的組成
 Stage1
► Grub的第一部分,安裝在MBR或者Boot Sector中
► 用於引導Stage2或者Stage1.5
 Stage2
► Grub的核心影像,用於提供Grub的主要功能
 Stage1.5
► Stage1與Stage2之間的橋樑,安裝在0磁軌上第一個磁區之後
► Stage1不理解檔系統,但是Stage1.5可以
► Stage1.5最終調用Stage2
 nbgrub/pxegrub
► Grub的網路啟動模組
► Stage1的結構
 為了保持和FAT/HPFS BIOS的相容性,所以保存BPB
 在BPB之後的Stage1配置資料區,在安裝的時候被填寫
 在資料區之後,才是代碼段
 最後是0xAA55啟動磁區標誌

► Stage1的流程
 在Stage1的配置資料區中存放了Stage2所在的磁片號、LBA位址以及Stage2的載入地址
 Stage1不需要理解任何的檔系統,只需要根據給出的磁區號,讀入Stage2的第一個磁區即可
 Stage1相當於前面分析的MBR中的Boot Loader
► Stage2的第一部分Start.S
 Start.S存放在Stage2檔的第一個磁區裏面
 Stage2剩餘部分的LBA位址和記憶體的載入位址是放在Start.S的firstlist和lastlist之間的,這個資料段位於Start.S代碼的尾部,在安裝的時候被寫入,稱為Block List
 Block List以全0項結尾
► Stage2的第一部分Start.S
 在Start.S的代碼開始執行的時候,DS:SI所指向的記憶體位址的內容是Stage1中準備好的,用於為INT 13h調用準備參數
 Start.S的功能就是根據Block List,將Stage2剩餘的部分讀入記憶體,然後跳轉到0x8200h處執行Stage2的功能代碼
► Stage2的第二部分ASM.S
 在ASM.S中定義了一系列的函數的實現,包括Grub得主入口函數main
 在main函數中,完成了如下的工作:
► DS = ES = SS = 0
► 建立實模式/BIOS棧,esp = 0x2000 - 0x10,向低位址方向增長
► 轉入保護模式
► 建立並清空保護模式棧
► 調用cmain,進入Grub的C代碼中(Stage2.c)
► 在cmain中,完成了如下的工作:
 設法打開/boot/grub/menu.lst這個配置檔
 根據配置檔,構建用戶功能表
 如果功能表構建成功,則調用run_menu
 如果功能表構建失敗,則調用enter_cmdline
► 問題:檔系統
 在這個時候,Grub已經開始訪問檔系統
 Grub如何對付不同的檔系統?
► Grub中的檔系統層:Disk_IO.C
 Grub中為每一個檔系統提供了一個抽象層
 檔系統用fsys_entry描述(filesys.h)
struct fsys_entry
{
char *name;
int (*mount_func) (void);
int (*read_func) (char *buf, int len);
int (*dir_func) (char *dirname);
void (*close_func) (void);
int (*embed_func) (int *start_sector, int needed_sectors);
};
 總體變數fsys_table包含了Grub支援所有檔系統,通過fsys_table和fsys_type,從而可以以統一的方式訪問不同的檔系統
 Grub中的命令處理:buildin
 Grub支持的每個命令,均有一個buildin和其對應
 這些buildin被定義在buildins.c中
 分析以下3個命令:
 chainloader
 kernel
 boot

► chainloader
 檢查--force標記
 調用grub_open打開檔,這裏的檔用block list表示(+1)
 調用grub_read讀入一個磁區到0000:7C00的位置
 檢查啟動磁區標誌0xAA55
► kernel
 檢查--type和--no_mem_option標誌
 調用load_image,讀入指定的內核檔
► load_image中處理了ELF和A.OUT的各種變形
► load_image通過對於內核檔的分析,識別出被啟動的OS的種類(通過內核檔的Magic Number)
► load_image針對不同種類的OS的內核提供了特定的載入代碼(這裏的代碼異常複雜,牽涉到了不同的OS的實現細節,未作分析)
► 最終填充mbi (MultiBoot Information)結構
► boot
 通過執行chainloader或者kernel以後,當前需要啟動的內核的類型已經確定了
 在執行boot的時候,根據確定的內核類型,每一種內核均有一種啟動的方法
► 對於linux,最後調用了stop函數實現控制權的轉移
► 對於chainloader,最後也是調用了stop函數實現控制權的轉移
► boot
 通過執行chainloader或者kernel以後,當前需要啟動的內核的類型已經確定了
 在執行boot的時候,根據確定的內核類型,每一種內核均有一種啟動的方法
► 對於linux,最後調用了stop函數實現控制權的轉移
► 對於chainloader,最後也是調用了stop函數實現控制權的轉移

沒有留言:

張貼留言