以下連結對grub也有很棒的分析:
2010年10月26日 星期二
2010年10月25日 星期一
GRUB FOR DOS 的總體框架
文章出處:http://www.linuxeden.com/blog/?uid-19847-action-viewspace-itemid-5490
GRUB FOR DOS 的總體框架
bendany 建議我寫一寫 GRUB4DOS 的大致結構,以便準備投入開發 GRUB4DOS 的朋友們能夠對此有一個快速的瞭解。我試試吧,就寫了這篇短文。文章有什麼不妥的,或者需要補充的,希望大家提出來,我再加以完善。幾年前寫過兩三篇 GRUB4DOS 的技術文章,雖然某些內容有些陳舊,但似乎也還可以用。有需要的朋友,可以在各大搜索引擎中搜索grub for dos 或者 grub.exe 或者 grldr 等字眼,應該能夠找到那些文章。這次不重複那些文章的內容了,主要面向準備繼續開發 Grub4dos 的朋友們。
[@more@]
GRUB for DOS 是 GNU GRUB 的功能擴展,是以 GNU GRUB 為基礎的。有關 GNU GRUB 的技術資訊,可以由 Linux 下的 info grub 命令獲得。這裏主要說說 GRUB for DOS 的特有資訊,並且只是粗略的、輪廓性的介紹。詳細的資訊還是要通過源代碼才能得到。在關鍵的地方,源代碼中都有比較詳細的注釋。
1。啟動過程,啟動方式
GNU GRUB 的啟動過程大致是這樣的:512 位元組的 stage1,放在 MBR 或者軟碟第一磁區,它從 BIOS 那裏獲得控制,然後它負責查找 stage1.5 或者 stage2。其實,stage1 在被放置到 MBR 或者軟碟第一磁區的時候,已經把 stage1.5 或者 stage2 的物理磁區位置記錄到 stage1 的某個區域中了,這通常稱為硬編碼。這些都不重要,重要的是,stage1 和 stage1.5 都不是 GRUB 的主體,它們都是為了把 stage2 裝入到記憶體中,除此之外,它們沒有別的用處。
stage2 又分兩部分:第一部分是 512 位元組的開頭,它像 stage1 一樣,裏面含有 stage2 的物理磁區位置列表,整個磁區沒有別的用處,僅僅只是為了尋找 stage2 的後續部分(也就是從第二磁區開始一直到檔結尾)。這第二部分才是 GRUB 的主程序體,它叫做 pre_stage2。也就是說,stage2 = 512 位元組的頭部 + pre_stage2
stage1 和 stage2 中都含有磁區絕對定位資訊,這些資訊,是在將 GNU GRUB 安裝到 MBR 或者軟碟第一磁區時,由 setup 命令計算之後寫入的。
說了這麼多,其實只是為了說明一點:pre_stage2 才是對我們的 GRUB FOR DOS 有用的。所以,你在程式中會看到 pre_stage2_start 這個標號。
GRUB FOR DOS 主要有兩大類啟動方式。一類是從作業系統中啟動(grub.exe),一類是由 BIOS 引導啟動(GRLDR)。
GRLDR 也可以由 NTLDR 啟動,不過這其實也類似於從 BIOS 啟動,因為由 NTLDR 所賦予的啟動環境,沒有破壞 BIOS 中斷向量。
GRUB.EXE 可以從 DOS/Windows9x 下運行,也可以經由 kexec 而從 Linux 下運行,或者也可以經由 LILO 或者 syslinux 這類引導器而啟動運行。這又分為兩種情況:在 DOS/Windows9x 下,GRUB.EXE 是被作為 DOS 的可執行檔或者可以作為 CONFIG.SYS 裏的設備驅動檔而運行。在其他情況下,GRUB.EXE 是作為 LINUX 內核格式而被啟動運行的。
2。檔結構
GRLDR 的檔結構相對來說比較單一,源代碼文件是 grldrstart.S。它的第一磁區,是啟動代碼,如果這是在 MBR 上,它負責裝入位於硬碟第一磁軌上的其餘磁區。如果是經由 NTLDR 啟動,那麼 NTLDR 會裝入 GRLDR 的開頭 16 個磁區。這些磁區中的代碼,負責查找各個硬碟各個分區根目錄下的 grldr 檔,並裝載整個 grldr 檔到記憶體中。緊接著 GRLDR 開頭的 16 磁區,就是 pre_stage2 了。GRLDR 開頭的 16 磁區代碼,負責把 pre_stage2 放到 0000:8200 處,並把控制交給 pre_stage2。在 GNU GRUB 中,0000:8000 處放置的是 stage2,由於我們不用 stage2 的第一磁區,所以,我們直接把 pre_stage2 放置在 0000:8200 了。
bootlace.com 可以用來將 grldr 的啟動代碼寫入到 MBR 或者軟碟的第一磁區上。其實,寫入到 MBR 上的,不完全就是 GRLDR 開頭的 16 磁區,而是 GRLDR.MBR 檔的內容(只有第一磁區的若干個控制位元組會被改動,以及硬碟分區表會被加入到第一磁區,而第二磁區可以放置硬碟上原來的 MBR 作為備份,其餘磁區都原封不動地被拷貝到第一磁軌的相應磁區上)。bootlace.com 是雙重可執行檔格式:它可以在 DOS 下運行(作為 .com 可執行檔),也可以在 Linux 下運行(作為 ELF 可執行檔)。GRLDR.MBR 檔將來可以增大到 63 個磁區那麼長,這是一個磁軌長度的最大上限。但是 GRLDR 檔的開頭部分卻只能有 16 個磁區那麼長,因為 NTLDR 只能裝入這麼長,它不能為我們直接裝入全部的 GRLDR 檔。GRLDR.MBR 的源代碼文件是 mbrstart.S。bootlace.com 的源代碼文件是 bootlacestart.S。
GRUB.EXE 是三重檔格式。它的結構比較複雜。首次看源代碼的時候,可以先忽略它作為 DOS 設備驅動程式檔格式的那些代碼,因為那可能比較難以理解。而作為 DOS 的 EXE 格式以及作為 LINUX 的 bzImage 格式,都是比較容易理解的,因為這方面的資料很容易找到。源代碼文件是 dosstart.S。像 GRLDR 的情況那樣,GRUB.EXE 也是由開頭的啟動代碼以及 pre_stage2 這兩部分構成的。
pre_stage2 的源代碼文件是 asm.S,它會調用其他 C 語言程式檔。這些 C 檔和 asm.S 一起編譯生成 pre_stage2 檔。builtins.c 檔含有各種命令的代碼,包括 map 命令。如果要為 GRUB 增添拷貝檔或者創建檔的命令,則需要修改 fsys_*.c 檔,它們是各種檔系統的驅動程式檔。
3。記憶體的使用
在物理位址 0x800 到 0x17FF 這 4K,放置嵌入到 GRUB.EXE 命令行的那些命令。只有從 CONFIG.SYS 中用 device=grub.exe --config-file="GRUB_COMMANDS" 的方式,才可以嵌入接近 4K 的 GRUB 命令,其他方式都受具體環境的限制。比如,DOS 命令行不超過 127 個字元。GRUB.EXE 作為 Linux 內核格式,它也只能接受最多 512 位元組的命令行。
物理位址 2M 處放置備份的 640K 的 DOS 常規記憶體,以便 quit 命令可以恢復 DOS 的現場。
GRUB FOR DOS 的總體框架
bendany 建議我寫一寫 GRUB4DOS 的大致結構,以便準備投入開發 GRUB4DOS 的朋友們能夠對此有一個快速的瞭解。我試試吧,就寫了這篇短文。文章有什麼不妥的,或者需要補充的,希望大家提出來,我再加以完善。幾年前寫過兩三篇 GRUB4DOS 的技術文章,雖然某些內容有些陳舊,但似乎也還可以用。有需要的朋友,可以在各大搜索引擎中搜索grub for dos 或者 grub.exe 或者 grldr 等字眼,應該能夠找到那些文章。這次不重複那些文章的內容了,主要面向準備繼續開發 Grub4dos 的朋友們。
[@more@]
GRUB for DOS 是 GNU GRUB 的功能擴展,是以 GNU GRUB 為基礎的。有關 GNU GRUB 的技術資訊,可以由 Linux 下的 info grub 命令獲得。這裏主要說說 GRUB for DOS 的特有資訊,並且只是粗略的、輪廓性的介紹。詳細的資訊還是要通過源代碼才能得到。在關鍵的地方,源代碼中都有比較詳細的注釋。
1。啟動過程,啟動方式
GNU GRUB 的啟動過程大致是這樣的:512 位元組的 stage1,放在 MBR 或者軟碟第一磁區,它從 BIOS 那裏獲得控制,然後它負責查找 stage1.5 或者 stage2。其實,stage1 在被放置到 MBR 或者軟碟第一磁區的時候,已經把 stage1.5 或者 stage2 的物理磁區位置記錄到 stage1 的某個區域中了,這通常稱為硬編碼。這些都不重要,重要的是,stage1 和 stage1.5 都不是 GRUB 的主體,它們都是為了把 stage2 裝入到記憶體中,除此之外,它們沒有別的用處。
stage2 又分兩部分:第一部分是 512 位元組的開頭,它像 stage1 一樣,裏面含有 stage2 的物理磁區位置列表,整個磁區沒有別的用處,僅僅只是為了尋找 stage2 的後續部分(也就是從第二磁區開始一直到檔結尾)。這第二部分才是 GRUB 的主程序體,它叫做 pre_stage2。也就是說,stage2 = 512 位元組的頭部 + pre_stage2
stage1 和 stage2 中都含有磁區絕對定位資訊,這些資訊,是在將 GNU GRUB 安裝到 MBR 或者軟碟第一磁區時,由 setup 命令計算之後寫入的。
說了這麼多,其實只是為了說明一點:pre_stage2 才是對我們的 GRUB FOR DOS 有用的。所以,你在程式中會看到 pre_stage2_start 這個標號。
GRUB FOR DOS 主要有兩大類啟動方式。一類是從作業系統中啟動(grub.exe),一類是由 BIOS 引導啟動(GRLDR)。
GRLDR 也可以由 NTLDR 啟動,不過這其實也類似於從 BIOS 啟動,因為由 NTLDR 所賦予的啟動環境,沒有破壞 BIOS 中斷向量。
GRUB.EXE 可以從 DOS/Windows9x 下運行,也可以經由 kexec 而從 Linux 下運行,或者也可以經由 LILO 或者 syslinux 這類引導器而啟動運行。這又分為兩種情況:在 DOS/Windows9x 下,GRUB.EXE 是被作為 DOS 的可執行檔或者可以作為 CONFIG.SYS 裏的設備驅動檔而運行。在其他情況下,GRUB.EXE 是作為 LINUX 內核格式而被啟動運行的。
2。檔結構
GRLDR 的檔結構相對來說比較單一,源代碼文件是 grldrstart.S。它的第一磁區,是啟動代碼,如果這是在 MBR 上,它負責裝入位於硬碟第一磁軌上的其餘磁區。如果是經由 NTLDR 啟動,那麼 NTLDR 會裝入 GRLDR 的開頭 16 個磁區。這些磁區中的代碼,負責查找各個硬碟各個分區根目錄下的 grldr 檔,並裝載整個 grldr 檔到記憶體中。緊接著 GRLDR 開頭的 16 磁區,就是 pre_stage2 了。GRLDR 開頭的 16 磁區代碼,負責把 pre_stage2 放到 0000:8200 處,並把控制交給 pre_stage2。在 GNU GRUB 中,0000:8000 處放置的是 stage2,由於我們不用 stage2 的第一磁區,所以,我們直接把 pre_stage2 放置在 0000:8200 了。
bootlace.com 可以用來將 grldr 的啟動代碼寫入到 MBR 或者軟碟的第一磁區上。其實,寫入到 MBR 上的,不完全就是 GRLDR 開頭的 16 磁區,而是 GRLDR.MBR 檔的內容(只有第一磁區的若干個控制位元組會被改動,以及硬碟分區表會被加入到第一磁區,而第二磁區可以放置硬碟上原來的 MBR 作為備份,其餘磁區都原封不動地被拷貝到第一磁軌的相應磁區上)。bootlace.com 是雙重可執行檔格式:它可以在 DOS 下運行(作為 .com 可執行檔),也可以在 Linux 下運行(作為 ELF 可執行檔)。GRLDR.MBR 檔將來可以增大到 63 個磁區那麼長,這是一個磁軌長度的最大上限。但是 GRLDR 檔的開頭部分卻只能有 16 個磁區那麼長,因為 NTLDR 只能裝入這麼長,它不能為我們直接裝入全部的 GRLDR 檔。GRLDR.MBR 的源代碼文件是 mbrstart.S。bootlace.com 的源代碼文件是 bootlacestart.S。
GRUB.EXE 是三重檔格式。它的結構比較複雜。首次看源代碼的時候,可以先忽略它作為 DOS 設備驅動程式檔格式的那些代碼,因為那可能比較難以理解。而作為 DOS 的 EXE 格式以及作為 LINUX 的 bzImage 格式,都是比較容易理解的,因為這方面的資料很容易找到。源代碼文件是 dosstart.S。像 GRLDR 的情況那樣,GRUB.EXE 也是由開頭的啟動代碼以及 pre_stage2 這兩部分構成的。
pre_stage2 的源代碼文件是 asm.S,它會調用其他 C 語言程式檔。這些 C 檔和 asm.S 一起編譯生成 pre_stage2 檔。builtins.c 檔含有各種命令的代碼,包括 map 命令。如果要為 GRUB 增添拷貝檔或者創建檔的命令,則需要修改 fsys_*.c 檔,它們是各種檔系統的驅動程式檔。
3。記憶體的使用
在物理位址 0x800 到 0x17FF 這 4K,放置嵌入到 GRUB.EXE 命令行的那些命令。只有從 CONFIG.SYS 中用 device=grub.exe --config-file="GRUB_COMMANDS" 的方式,才可以嵌入接近 4K 的 GRUB 命令,其他方式都受具體環境的限制。比如,DOS 命令行不超過 127 個字元。GRUB.EXE 作為 Linux 內核格式,它也只能接受最多 512 位元組的命令行。
物理位址 2M 處放置備份的 640K 的 DOS 常規記憶體,以便 quit 命令可以恢復 DOS 的現場。
從啟動到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函數實現控制權的轉移
從啟動到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函數實現控制權的轉移
Grub for dos bootloader 分析
概述一下:
Grubfordos0.4.4的主題為三部分:MBR,bootloader,kernel
1:MBR對應的檔為stage1目錄下的stage1.S .S尾碼為GAS彙編原始檔案
Stage1:大小512位元組,編譯後的結構為DBR結構,這樣的優點是,無論你將GRUB安裝在MBR還是分區的DBR中,都能正常引導,如果安裝在硬碟的MBR中,那麼bios的int19號中斷會MBR中的內容讀入到記憶體0x7C00處,而stage1的功能只是為了將start.S編譯後存放在MBR 1(LBA)磁區的地方二進位檔載入記憶體的0x8000處後跳轉到該處執行,當然,在載入記憶體之前使用bios的int13號中斷,或者int 13擴展調用讀到緩衝區後在拷貝到相應的記憶體位址處;
2:bootload 對應檔start.S
由start1載入到記憶體0x8000處的代碼功能充當bootload的功能,之所以不寫在stage1中,是受硬碟每磁區存儲位元組數的限制,而MBR除引導代碼外還保存有分區表資訊,因此,MBR中可用的位元組數就更少了,而DBR中還應該保持BPB資料結構類型,故此,將loader單獨編譯一個磁區是情有可言的;s
Start的功能:
在末尾-8偏移的地方保存有stage2存放的磁區號(LBA)以及大小,start1只是將對應kernel載入記憶體位址0x8200處,因為start編譯後大小為512位元組,故此占了0x200,當stage2被載入完成後,就jmp到0x8200處執行,這時,真正的Grub代碼才開始;
Stage2: 相當於一個小型的作業系統,grub提供多引導支援,grub對檔系統的支援較為全面,而一個作業系統,最複雜而核心的部分恐怕在於檔系統的實現上,故此,分析grub,
無論是對想瞭解作業系統原理還是檔系統原理的人來說,應該有一定的幫助;
關於位址轉換:
磁區的編號和線性位址:
由於int 13的限制,三維位址C/H/S的最大值為1024/16/63,因此容量只能達到:1024*16*63*512Byte=504MB;故因此系統把所有的無聊磁區都按某種方式或規則看做是線性編號的磁區,從0到某一最大值;
磁區編號:
C/H/S : 磁區編號從1開始至63
LBA : 磁區編號從0開始順序編號
C/H/S到線性位址的轉換:
C : 當前柱面號 CS:起始柱面號
H : 當前磁頭號 HS:起始磁頭號
S : 當前磁區號 SS:起始磁區號
PH: 每柱面有多少個磁軌
PS: 每磁軌有多少個磁區
則有公式: LBA = ( C - CS)*PH*PS +(H - HS)*PS + (S - SS)
一般情況下: CS = 0 HS = 0 SS = 1 PS = 63 PH = 255
而LBA到C/H/S的轉換稍微複雜一些,各變數按上述,有如下公式:
C = LBA div (PH*PS) + CS
H = (LBA div PS) MOD PH + HS
S = LBA MOD PS + SS
如果只用DIV,則轉換公式如下:
C = LBA div (PH*PS) + CS
H = (LBA div PS) – (C-CS)*PH + HS
S = LBA – (C-CS)*PH*PS – (H-HS)*PS +SS
/*預處理包含定義
*其中STAGE1_5被自GRUB2後不使用
*用到的share.h被其他一些原始檔案使用,
*此檔使用到的宏不多,用到時給與說明
******************************/
#define ASM_FILE
#include
#ifndef STAGE1_5
#include
#endif
/*
* defines for the code go here
*/
/* Absolute addresses
This makes the assembler generate the address without support
from the linker. (ELF can't relocate 16-bit addresses!) */
/*重定位,並根據是否定義STAGE1_5選擇裝入位址*/
#ifdef STAGE1_5
# define ABS(x) (x-_start+0x2000)
#else
# define ABS(x) (x-_start+0x8000)
#endif /* STAGE1_5 */
/* 調用int 10h中斷以印表機模式在螢幕上顯示一字串*/
#define MSG(x) movw $ABS(x), %si; call message
/*****************************************
.file op 指定和目標檔關聯的原始檔案名
*****************************************/
.file "start.S"
/*****************************************
.text代碼段聲明; .code16使用實模式
*****************************************/
.text
/* 告訴GAS產生16位元指令,以便此代碼能在實模式下運行 */
.code16
.globl start, _start
start:
_start:
/*start由stage1載入到0x8000處,si仍然用於DiskAddressPacket結構
*
/* 首先保存設備類型號,stage1由bios初始化在dl中! */
pushw %dx
/* 在螢幕上列印字串:loading stage2 or loading stage1_5
*由於在輸出字元的時候使用了si寄存器,故此保存原si位址
*字串輸出完畢後恢復原字串*/
pushw %si
MSG(notification_string)
popw %si
/*** #define BOOTSEC_LISTSIZE 8" firstlist為此段末尾偏移位址*/
movw $ABS(firstlist - BOOTSEC_LISTSIZE), %di
/* LBA以0開始,故此,stage1占1磁區,start占一磁區,故stage2從2開始,並存 倒數第8個位元組處。 */
movl (%di), %ebp
/*由於firstlist -08h處保存著需要載入的磁區號和磁區的數目,故此,
* bootloop 子功能,迴圈將對應的磁區號中的資料載入相應記憶體中並執行,
*可能有人會問,為什麼不將stage1和start融合在一起呢,這是因為一般的
* 硬碟低格後,默認的磁區大小為512個位元組,也就是分開也是不得於而為之
*/
bootloop:
/* [edi]+4處為 .word (STAGE2_SIZE + 511) >> 9,這裏不考慮stage1_5的情況
* STAGE2_SIZE為grub編譯後的大小,>>9相當於除以512,
*4(%di)處存的是stage2占的磁區數目/
cmpw $0, 4(%di)
/* 如果為0說明使用stage1_5直接跳至bootit處執行 */
je bootit
setup_sectors:
/* stage1中,如果檢測支援LBA模式,此語句被執行:movb $1, -1(%si)
* 避免了相同功能的重複調用/
cmpb $0, -1(%si)
/* 結果為0,說明不支援LBA模式 */
je chs_mode
lba_mode:
/* 裝載邏輯磁區號入ebx */
movl (%di), %ebx
/* the maximum is limited to 0x7f because of Phoenix EDD
看一下int 43的調用的參數說明:
Format of disk address packet:
Offset Size Description
00h BYTE 10h (size of packet)
01h BYTE reserved (0)
02h WORD number of blocks to transfer (max 007Fh for Phoenix EDD)
04h DWORD -> transfer buffer
08h QWORD starting absolute block number
(for non-LBA devices, compute as
(Cylinder*NumHeads + SelectedHead) * SectorPerTrack +
SelectedSector - 1
--------N-134257DX1234----------------------- */
xorl %eax, %eax
movb $0x7f, %al
/* 比較應該讀的資料數否超過最大傳輸參數 */
cmpw %ax, 4(%di) /* compare against total number of sectors */
/* 如果大於,跳至本句後一個標號 1:處,否則將值付給ax寄存器 */
jg 1f
/* if less than, set to total */
movw 4(%di), %ax
1:
/* 用stage2的總大小減去ax中的值,ax的值為上面的判斷後的設置值 */
subw %ax, 4(%di)
/* 將ax的值加上起始磁區號,因為下面的代碼會將ax中保存的磁區數目迴圈讀出
* 這樣,即使要讀的資料超出7f也不用在定義其他的資料來保存剩下的磁區數
* /
addl %eax, (%di)
/* 設置磁片位址資料包結構體 */
/* the size and the reserved byte */
movw $0x0010, (%si)
/* 需要讀出的磁區數目,見到ax的用處了吧! */
movw %ax, 2(%si)
/* 絕對磁區位址,(low 32 bits) */
movl %ebx, 8(%si)
/* 緩衝區段位址設置,讀出的資料保存於此處 #define BUFFERSEG RAW_SEG (0x7000)*/
movw $BUFFERSEG, 6(%si)
/* 保存ax的值 */
pushw %ax
/* zero %eax */
xorl %eax, %eax
/* 緩衝區偏移位址設置 == 0 */
movw %ax, 4(%si)
/* 高32位絕對位址 (high 32 bits) */
movl %eax, 12(%si)
/*
* BIOS call "INT 0x13 Function 0x42" to read sectors from disk into memory
* Call with %ah = 0x42
* %dl = drive number
* %ds:%si = segment:offset of disk address packet
* Return:
* %al = 0x0 on success; err code on failure
*/
movb $0x42, %ah
int $0x13
jc read_error
movw $BUFFERSEG, %bx /*緩衝區位址保存*/
jmp copy_buffer /*拷貝讀出的資料*/
chs_mode:
/* lBA磁區號送eax寄存器 ,需要將LBA轉換為CHS位址在讀寫*/
movl (%di), %eax
/* edx填零*/
xorl %edx, %edx
/* divide by number of sectors */
divl (%si)
/* save sector start */
movb %dl, 10(%si)
xorl %edx, %edx /* zero %edx */
divl 4(%si) /* divide by number of heads */
/* save head start */
movb %dl, 11(%si)
/* save cylinder start */
movw %ax, 12(%si)
/* do we need too many cylinders? */
cmpw 8(%si), %ax
jge geometry_error
/* determine the maximum sector length of this read */
movw (%si), %ax /* get number of sectors per track/head */
/* subtract sector start */
subb 10(%si), %al
/* how many do we really want to read? */
cmpw %ax, 4(%di) /* compare against total number of sectors */
/* which is greater? */
jg 2f
/* if less than, set to total */
movw 4(%di), %ax
2:
/* subtract from total */
subw %ax, 4(%di)
/* add into logical sector start */
addl %eax, (%di)
/*
* This is the loop for taking care of BIOS geometry translation (ugh!)
*/
/* get high bits of cylinder */
movb 13(%si), %dl
shlb $6, %dl /* shift left by 6 bits */
movb 10(%si), %cl /* get sector */
incb %cl /* normalize sector (sectors go
from 1-N, not 0-(N-1) ) */
orb %dl, %cl /* composite together */
movb 12(%si), %ch /* sector+hcyl in cl, cylinder in ch */
/* restore %dx */
popw %dx
pushw %dx
/* head number */
movb 11(%si), %dh
pushw %ax /* save %ax from destruction! */
/*
* BIOS call "INT 0x13 Function 0x2" to read sectors from disk into memory
* Call with %ah = 0x2
* %al = number of sectors
* %ch = cylinder
* %cl = sector (bits 6-7 are high bits of "cylinder")
* %dh = head
* %dl = drive (0x80 for hard disk, 0x0 for floppy disk)
* %es:%bx = segment:offset of buffer
* Return:
* %al = 0x0 on success; err code on failure
*/
movw $BUFFERSEG, %bx
movw %bx, %es /* load %es segment with disk buffer */
xorw %bx, %bx /* %bx = 0, put it at 0 in the segment */
movb $0x2, %ah /* function 2 */
int $0x13
jc read_error
/* save source segment */
movw %es, %bx
copy_buffer:
/* load addresses for copy from disk buffer to destination */
/*附加段數據初始化。。
* blocklist_default_seg:
* #ifdef STAGE1_5
* .word 0x220
* #else
* .word 0x820 段的開始位址,*16後為 8200,其中8000 - 8200被start佔用
******************************************************************** */
movw 6(%di), %es /* load destination segment */
/* 讀了幾個磁區,吐出來。。 */
popw %ax
/* determine the next possible destination address (presuming
512 byte sectors!) */
//邏輯左移,末尾補零,移1位相當於算術*2,這裏以16進制算出下一次拷貝的起始位址;
shlw $5, %ax /* shift %ax five bits to the left */
addw %ax, 6(%di) /* add the corrected value to the destination
address for next time */
/* 環境保存,因為MSG調用int10中斷後可能會改變寄存器的值 */
pusha
pushw %ds
/* 由於開始用>>9 ,故此,兩次shl 移動的位數為9,從而得到讀出的位元組數 */
shlw $4, %ax
movw %ax, %cx
xorw %di, %di /* zero offset of destination addresses */
xorw %si, %si /* zero offset of source addresses */
movw %bx, %ds /* 讀出後保存於此movw $BUFFERSEG, %bx 緩衝區位址保存
* 這裏得分開,BUFFERSEG為0x7000 ,保存到16位的bx中取的是0x70,注意AT&T和MASM的位元組序/
cld /* 自增方向標誌*/
/* 重複拷貝,每次一個位元組,拷貝到0x8200處 */
rep /* sets a repeat */
movsb /* this runs the actual copy */
/* restore addressing regs and print a dot with correct DS
(MSG modifies SI, which is saved, and unused AX and BX) */
popw %ds
MSG(notification_step)
popa /*環境恢復*/
/*檢查資料是否已經讀完,如果沒有,繼續讀取並拷貝 */
cmpw $0, 4(%di)
jne setup_sectors
/* update position to load from */
subw $BOOTSEC_LISTSIZE, %di
/* jump to bootloop */
jmp bootloop
/* END OF MAIN LOOP */
bootit:
/* 列印\r\n後,恢復寄存器dx的值,根據是否定義了STAGE1_5跳到相應的起始位址處執行裝入的stage2的內容 */
MSG(notification_done)
popw %dx /* 保證dl寄存器的內容為boot設置的初始值 */
#ifdef STAGE1_5
ljmp $0, $0x2200
#else /* ! STAGE1_5 */
ljmp $0, $0x8200
#endif /* ! STAGE1_5 */
/*
* BIOS Geometry translation error (past the end of the disk geometry!).
*/
/*錯誤處理代碼,出錯後執行,stop : jmp stop 閉環;*/
geometry_error:
MSG(geometry_error_string)
jmp general_error
/*
* Read error on the disk.
*/
read_error:
MSG(read_error_string)
general_error:
MSG(general_error_string)
/* go here when you need to stop the machine hard after an error condition */
stop: jmp stop
#ifdef STAGE1_5
notification_string: .string "Loading stage1.5"
#else
notification_string: .string "Loading stage2"
#endif
notification_step: .string "."
notification_done: .string "\r\n"
geometry_error_string: .string "Geom"
read_error_string: .string "Read"
general_error_string: .string " Error"
/*
* message: write the string pointed to by %si
*
* WARNING: trashes %si, %ax, and %bx
*/
/*
* Use BIOS "int 10H Function 0Eh" to write character in teletype mode
* %ah = 0xe %al = character
* %bh = page %bl = foreground color (graphics modes)
*/
1:
movw $0x0001, %bx
movb $0xe, %ah
int $0x10 /* display a byte */
incw %si
message:
movb (%si), %al
cmpb $0, %al
jne 1b /* if not end of string, jmp to display */
ret
lastlist:
/*
* This area is an empty space between the main body of code below which
* grows up (fixed after compilation, but between releases it may change
* in size easily), and the lists of sectors to read, which grows down
* from a fixed top location.
*/
.word 0
.word 0
. = _start + 0x200 - BOOTSEC_LISTSIZE
/* fill the first data listing with the default */
blocklist_default_start:
.long 2 /* this is the sector start parameter, in logical
sectors from the start of the disk, sector 0 */
blocklist_default_len:
/* this is the number of sectors to read */
#ifdef STAGE1_5
.word 0 /* the command "install" will fill this up */
#else
.word (STAGE2_SIZE + 511) >> 9
#endif
blocklist_default_seg:
#ifdef STAGE1_5
.word 0x220
#else
.word 0x820 /* this is the segment of the starting address
to load the data into */
#endif
firstlist: /* this label has to be after the list data!!! */
由於是開源軟體,以及上傳的大小限制,源代碼就不上來了,大家google一下吧!
1:MBR對應的檔為stage1目錄下的stage1.S .S尾碼為GAS彙編原始檔案
Stage1:大小512位元組,編譯後的結構為DBR結構,這樣的優點是,無論你將GRUB安裝在MBR還是分區的DBR中,都能正常引導,如果安裝在硬碟的MBR中,那麼bios的int19號中斷會MBR中的內容讀入到記憶體0x7C00處,而stage1的功能只是為了將start.S編譯後存放在MBR 1(LBA)磁區的地方二進位檔載入記憶體的0x8000處後跳轉到該處執行,當然,在載入記憶體之前使用bios的int13號中斷,或者int 13擴展調用讀到緩衝區後在拷貝到相應的記憶體位址處;
2:bootload 對應檔start.S
由start1載入到記憶體0x8000處的代碼功能充當bootload的功能,之所以不寫在stage1中,是受硬碟每磁區存儲位元組數的限制,而MBR除引導代碼外還保存有分區表資訊,因此,MBR中可用的位元組數就更少了,而DBR中還應該保持BPB資料結構類型,故此,將loader單獨編譯一個磁區是情有可言的;s
Start的功能:
在末尾-8偏移的地方保存有stage2存放的磁區號(LBA)以及大小,start1只是將對應kernel載入記憶體位址0x8200處,因為start編譯後大小為512位元組,故此占了0x200,當stage2被載入完成後,就jmp到0x8200處執行,這時,真正的Grub代碼才開始;
Stage2: 相當於一個小型的作業系統,grub提供多引導支援,grub對檔系統的支援較為全面,而一個作業系統,最複雜而核心的部分恐怕在於檔系統的實現上,故此,分析grub,
無論是對想瞭解作業系統原理還是檔系統原理的人來說,應該有一定的幫助;
關於位址轉換:
磁區的編號和線性位址:
由於int 13的限制,三維位址C/H/S的最大值為1024/16/63,因此容量只能達到:1024*16*63*512Byte=504MB;故因此系統把所有的無聊磁區都按某種方式或規則看做是線性編號的磁區,從0到某一最大值;
磁區編號:
C/H/S : 磁區編號從1開始至63
LBA : 磁區編號從0開始順序編號
C/H/S到線性位址的轉換:
C : 當前柱面號 CS:起始柱面號
H : 當前磁頭號 HS:起始磁頭號
S : 當前磁區號 SS:起始磁區號
PH: 每柱面有多少個磁軌
PS: 每磁軌有多少個磁區
則有公式: LBA = ( C - CS)*PH*PS +(H - HS)*PS + (S - SS)
一般情況下: CS = 0 HS = 0 SS = 1 PS = 63 PH = 255
而LBA到C/H/S的轉換稍微複雜一些,各變數按上述,有如下公式:
C = LBA div (PH*PS) + CS
H = (LBA div PS) MOD PH + HS
S = LBA MOD PS + SS
如果只用DIV,則轉換公式如下:
C = LBA div (PH*PS) + CS
H = (LBA div PS) – (C-CS)*PH + HS
S = LBA – (C-CS)*PH*PS – (H-HS)*PS +SS
/*預處理包含定義
*其中STAGE1_5被自GRUB2後不使用
*用到的share.h被其他一些原始檔案使用,
*此檔使用到的宏不多,用到時給與說明
******************************/
#define ASM_FILE
#include
#ifndef STAGE1_5
#include
#endif
/*
* defines for the code go here
*/
/* Absolute addresses
This makes the assembler generate the address without support
from the linker. (ELF can't relocate 16-bit addresses!) */
/*重定位,並根據是否定義STAGE1_5選擇裝入位址*/
#ifdef STAGE1_5
# define ABS(x) (x-_start+0x2000)
#else
# define ABS(x) (x-_start+0x8000)
#endif /* STAGE1_5 */
/* 調用int 10h中斷以印表機模式在螢幕上顯示一字串*/
#define MSG(x) movw $ABS(x), %si; call message
/*****************************************
.file op 指定和目標檔關聯的原始檔案名
*****************************************/
.file "start.S"
/*****************************************
.text代碼段聲明; .code16使用實模式
*****************************************/
.text
/* 告訴GAS產生16位元指令,以便此代碼能在實模式下運行 */
.code16
.globl start, _start
start:
_start:
/*start由stage1載入到0x8000處,si仍然用於DiskAddressPacket結構
*
/* 首先保存設備類型號,stage1由bios初始化在dl中! */
pushw %dx
/* 在螢幕上列印字串:loading stage2 or loading stage1_5
*由於在輸出字元的時候使用了si寄存器,故此保存原si位址
*字串輸出完畢後恢復原字串*/
pushw %si
MSG(notification_string)
popw %si
/*** #define BOOTSEC_LISTSIZE 8" firstlist為此段末尾偏移位址*/
movw $ABS(firstlist - BOOTSEC_LISTSIZE), %di
/* LBA以0開始,故此,stage1占1磁區,start占一磁區,故stage2從2開始,並存 倒數第8個位元組處。 */
movl (%di), %ebp
/*由於firstlist -08h處保存著需要載入的磁區號和磁區的數目,故此,
* bootloop 子功能,迴圈將對應的磁區號中的資料載入相應記憶體中並執行,
*可能有人會問,為什麼不將stage1和start融合在一起呢,這是因為一般的
* 硬碟低格後,默認的磁區大小為512個位元組,也就是分開也是不得於而為之
*/
bootloop:
/* [edi]+4處為 .word (STAGE2_SIZE + 511) >> 9,這裏不考慮stage1_5的情況
* STAGE2_SIZE為grub編譯後的大小,>>9相當於除以512,
*4(%di)處存的是stage2占的磁區數目/
cmpw $0, 4(%di)
/* 如果為0說明使用stage1_5直接跳至bootit處執行 */
je bootit
setup_sectors:
/* stage1中,如果檢測支援LBA模式,此語句被執行:movb $1, -1(%si)
* 避免了相同功能的重複調用/
cmpb $0, -1(%si)
/* 結果為0,說明不支援LBA模式 */
je chs_mode
lba_mode:
/* 裝載邏輯磁區號入ebx */
movl (%di), %ebx
/* the maximum is limited to 0x7f because of Phoenix EDD
看一下int 43的調用的參數說明:
Format of disk address packet:
Offset Size Description
00h BYTE 10h (size of packet)
01h BYTE reserved (0)
02h WORD number of blocks to transfer (max 007Fh for Phoenix EDD)
04h DWORD -> transfer buffer
08h QWORD starting absolute block number
(for non-LBA devices, compute as
(Cylinder*NumHeads + SelectedHead) * SectorPerTrack +
SelectedSector - 1
--------N-134257DX1234----------------------- */
xorl %eax, %eax
movb $0x7f, %al
/* 比較應該讀的資料數否超過最大傳輸參數 */
cmpw %ax, 4(%di) /* compare against total number of sectors */
/* 如果大於,跳至本句後一個標號 1:處,否則將值付給ax寄存器 */
jg 1f
/* if less than, set to total */
movw 4(%di), %ax
1:
/* 用stage2的總大小減去ax中的值,ax的值為上面的判斷後的設置值 */
subw %ax, 4(%di)
/* 將ax的值加上起始磁區號,因為下面的代碼會將ax中保存的磁區數目迴圈讀出
* 這樣,即使要讀的資料超出7f也不用在定義其他的資料來保存剩下的磁區數
* /
addl %eax, (%di)
/* 設置磁片位址資料包結構體 */
/* the size and the reserved byte */
movw $0x0010, (%si)
/* 需要讀出的磁區數目,見到ax的用處了吧! */
movw %ax, 2(%si)
/* 絕對磁區位址,(low 32 bits) */
movl %ebx, 8(%si)
/* 緩衝區段位址設置,讀出的資料保存於此處 #define BUFFERSEG RAW_SEG (0x7000)*/
movw $BUFFERSEG, 6(%si)
/* 保存ax的值 */
pushw %ax
/* zero %eax */
xorl %eax, %eax
/* 緩衝區偏移位址設置 == 0 */
movw %ax, 4(%si)
/* 高32位絕對位址 (high 32 bits) */
movl %eax, 12(%si)
/*
* BIOS call "INT 0x13 Function 0x42" to read sectors from disk into memory
* Call with %ah = 0x42
* %dl = drive number
* %ds:%si = segment:offset of disk address packet
* Return:
* %al = 0x0 on success; err code on failure
*/
movb $0x42, %ah
int $0x13
jc read_error
movw $BUFFERSEG, %bx /*緩衝區位址保存*/
jmp copy_buffer /*拷貝讀出的資料*/
chs_mode:
/* lBA磁區號送eax寄存器 ,需要將LBA轉換為CHS位址在讀寫*/
movl (%di), %eax
/* edx填零*/
xorl %edx, %edx
/* divide by number of sectors */
divl (%si)
/* save sector start */
movb %dl, 10(%si)
xorl %edx, %edx /* zero %edx */
divl 4(%si) /* divide by number of heads */
/* save head start */
movb %dl, 11(%si)
/* save cylinder start */
movw %ax, 12(%si)
/* do we need too many cylinders? */
cmpw 8(%si), %ax
jge geometry_error
/* determine the maximum sector length of this read */
movw (%si), %ax /* get number of sectors per track/head */
/* subtract sector start */
subb 10(%si), %al
/* how many do we really want to read? */
cmpw %ax, 4(%di) /* compare against total number of sectors */
/* which is greater? */
jg 2f
/* if less than, set to total */
movw 4(%di), %ax
2:
/* subtract from total */
subw %ax, 4(%di)
/* add into logical sector start */
addl %eax, (%di)
/*
* This is the loop for taking care of BIOS geometry translation (ugh!)
*/
/* get high bits of cylinder */
movb 13(%si), %dl
shlb $6, %dl /* shift left by 6 bits */
movb 10(%si), %cl /* get sector */
incb %cl /* normalize sector (sectors go
from 1-N, not 0-(N-1) ) */
orb %dl, %cl /* composite together */
movb 12(%si), %ch /* sector+hcyl in cl, cylinder in ch */
/* restore %dx */
popw %dx
pushw %dx
/* head number */
movb 11(%si), %dh
pushw %ax /* save %ax from destruction! */
/*
* BIOS call "INT 0x13 Function 0x2" to read sectors from disk into memory
* Call with %ah = 0x2
* %al = number of sectors
* %ch = cylinder
* %cl = sector (bits 6-7 are high bits of "cylinder")
* %dh = head
* %dl = drive (0x80 for hard disk, 0x0 for floppy disk)
* %es:%bx = segment:offset of buffer
* Return:
* %al = 0x0 on success; err code on failure
*/
movw $BUFFERSEG, %bx
movw %bx, %es /* load %es segment with disk buffer */
xorw %bx, %bx /* %bx = 0, put it at 0 in the segment */
movb $0x2, %ah /* function 2 */
int $0x13
jc read_error
/* save source segment */
movw %es, %bx
copy_buffer:
/* load addresses for copy from disk buffer to destination */
/*附加段數據初始化。。
* blocklist_default_seg:
* #ifdef STAGE1_5
* .word 0x220
* #else
* .word 0x820 段的開始位址,*16後為 8200,其中8000 - 8200被start佔用
******************************************************************** */
movw 6(%di), %es /* load destination segment */
/* 讀了幾個磁區,吐出來。。 */
popw %ax
/* determine the next possible destination address (presuming
512 byte sectors!) */
//邏輯左移,末尾補零,移1位相當於算術*2,這裏以16進制算出下一次拷貝的起始位址;
shlw $5, %ax /* shift %ax five bits to the left */
addw %ax, 6(%di) /* add the corrected value to the destination
address for next time */
/* 環境保存,因為MSG調用int10中斷後可能會改變寄存器的值 */
pusha
pushw %ds
/* 由於開始用>>9 ,故此,兩次shl 移動的位數為9,從而得到讀出的位元組數 */
shlw $4, %ax
movw %ax, %cx
xorw %di, %di /* zero offset of destination addresses */
xorw %si, %si /* zero offset of source addresses */
movw %bx, %ds /* 讀出後保存於此movw $BUFFERSEG, %bx 緩衝區位址保存
* 這裏得分開,BUFFERSEG為0x7000 ,保存到16位的bx中取的是0x70,注意AT&T和MASM的位元組序/
cld /* 自增方向標誌*/
/* 重複拷貝,每次一個位元組,拷貝到0x8200處 */
rep /* sets a repeat */
movsb /* this runs the actual copy */
/* restore addressing regs and print a dot with correct DS
(MSG modifies SI, which is saved, and unused AX and BX) */
popw %ds
MSG(notification_step)
popa /*環境恢復*/
/*檢查資料是否已經讀完,如果沒有,繼續讀取並拷貝 */
cmpw $0, 4(%di)
jne setup_sectors
/* update position to load from */
subw $BOOTSEC_LISTSIZE, %di
/* jump to bootloop */
jmp bootloop
/* END OF MAIN LOOP */
bootit:
/* 列印\r\n後,恢復寄存器dx的值,根據是否定義了STAGE1_5跳到相應的起始位址處執行裝入的stage2的內容 */
MSG(notification_done)
popw %dx /* 保證dl寄存器的內容為boot設置的初始值 */
#ifdef STAGE1_5
ljmp $0, $0x2200
#else /* ! STAGE1_5 */
ljmp $0, $0x8200
#endif /* ! STAGE1_5 */
/*
* BIOS Geometry translation error (past the end of the disk geometry!).
*/
/*錯誤處理代碼,出錯後執行,stop : jmp stop 閉環;*/
geometry_error:
MSG(geometry_error_string)
jmp general_error
/*
* Read error on the disk.
*/
read_error:
MSG(read_error_string)
general_error:
MSG(general_error_string)
/* go here when you need to stop the machine hard after an error condition */
stop: jmp stop
#ifdef STAGE1_5
notification_string: .string "Loading stage1.5"
#else
notification_string: .string "Loading stage2"
#endif
notification_step: .string "."
notification_done: .string "\r\n"
geometry_error_string: .string "Geom"
read_error_string: .string "Read"
general_error_string: .string " Error"
/*
* message: write the string pointed to by %si
*
* WARNING: trashes %si, %ax, and %bx
*/
/*
* Use BIOS "int 10H Function 0Eh" to write character in teletype mode
* %ah = 0xe %al = character
* %bh = page %bl = foreground color (graphics modes)
*/
1:
movw $0x0001, %bx
movb $0xe, %ah
int $0x10 /* display a byte */
incw %si
message:
movb (%si), %al
cmpb $0, %al
jne 1b /* if not end of string, jmp to display */
ret
lastlist:
/*
* This area is an empty space between the main body of code below which
* grows up (fixed after compilation, but between releases it may change
* in size easily), and the lists of sectors to read, which grows down
* from a fixed top location.
*/
.word 0
.word 0
. = _start + 0x200 - BOOTSEC_LISTSIZE
/* fill the first data listing with the default */
blocklist_default_start:
.long 2 /* this is the sector start parameter, in logical
sectors from the start of the disk, sector 0 */
blocklist_default_len:
/* this is the number of sectors to read */
#ifdef STAGE1_5
.word 0 /* the command "install" will fill this up */
#else
.word (STAGE2_SIZE + 511) >> 9
#endif
blocklist_default_seg:
#ifdef STAGE1_5
.word 0x220
#else
.word 0x820 /* this is the segment of the starting address
to load the data into */
#endif
firstlist: /* this label has to be after the list data!!! */
由於是開源軟體,以及上傳的大小限制,源代碼就不上來了,大家google一下吧!
2010年10月24日 星期日
無線電波又是怎麼產生的呢? 收音機的原理--電磁波
收音機的原理--電磁波
收音機的原理:
收音機是怎樣接收到遠方電台所傳來的訊號的呢?
你注意到當收聽 FM 電台時,天線的指向會影響接收的效果。
當在有著金屬外殼的汽車內時,也不易收到訊號。所以汽車都需要將天線裝在車外。
試試看!拿著收音機靠近一大片金屬附近時,會發生怎樣的情形!
無線電話是不是也有類似的情況?
收音機以及無線電話等通訊裝置都是藉著 『無線電波--電磁波』來傳遞訊號的。
這些無線電波又是怎麼產生的呢?又是如何能傳播很遠的距離呢?
無線電波的發射端都有著和接收端一樣的天線。
靜止的電荷會產生靜電場。(庫侖定律)
運動的電荷(電流)則會在附近空間形成產生磁場。(安培定律)
隨著時間變化的磁場會在附近空間建立電場(法拉地定律)
隨著時間變化的電場則會在附近空間形成磁場。
發射端藉由發射器來變化 以改變發射天線上電荷的分佈,
於天線兩端產生的電位差(天線內形成的電場)使得金屬天線內的電子朝向一端運動,
而形成 正電荷在一端,負電荷在另一端的分佈情形。(為何只需要電子動即可?)
但是這樣的電荷分佈所形成的電場,只有鄰近的天線才容易感受到影響。
這樣的信號不僅不易偵測與接收。信號的強弱也與距離及方位有關。
若是讓發射天線上的正負電荷分別在天線兩端形成某特定頻率周期性的分佈變化
(也只需要讓天線內的電子在小區域內(平均位置附近)作簡諧運動即可,
電子流動時的平均飄移速度是很慢的。例如提供正弦交流變化的電壓),
則接收天線上也會(感應)接收到相同頻率的信號。
(信號的強弱仍然與距離及方位有關,但信號的頻率則與距離及方位無關)
發射天線上兩端正負電荷的分佈隨著時間變化,
則附近空間的電場也會因而隨著時間產生變化。
只是天線上電荷的分佈變化,向外傳遞時需要時間(電磁波以光速傳播)
較遠的地方,較晚感受到發射天線的變化。
也就是空間距離不同的地點,電磁場的變化會有時間上的延遲。
於是形成『電磁』波。講的詳細明確一點:
當發射器使得天線上的電荷加速時,於是在鄰近空間形成變化的電場。
這變化的電場於是在鄰近空間又形成變化的磁場。(馬克斯威爾稱為位移電流)
變化的磁場又在鄰近空間形成變化的電場(法拉第定律)。如此繼續週而復始。
所形成的電磁場隨著空間的變化,在時間上會有所延遲,於是形成『電磁波』。
參考 java 物理動畫: 電磁波的行進
這樣的一個週期性變化的信號可以藉由 電感以及電容 組成的共振線路來提供。
將導線繞成螺旋狀的線圈便可 形成一個 電感:
當電流流經線圈時,會在線圈內形成磁場。
若是電流隨著時間發生改變,則線圈內的磁場也會因而發生變化。
當線圈內的磁通量 將 發生變化前,便會在線圈上形成感應電動勢(未卜先知?)
(感應電動勢 = 沿著線圈感應電場與路徑向量內積的總和)。
感應電動勢的產生,是由於自然界(空間)的另外一種『慣性』:
不希望 磁通量 發生變化的特性。
因此當 磁通量將會有較最大的變化量時,也會產生較大的感應電動勢,
想要抵銷磁通量的變化。
簡單的說 感應電動勢正比於 磁通量的變化量。
電感 等於 單位時間內 想有單位電流的變化時,所產生的感應電動勢。
好厲害!當線圈的電流將要發生改變前,電感便會形成感應電動勢來減緩電流的變化。
可不是電流已經發生改變了以後,才形成感應電動勢來減緩其變化。
因此 我們說 電感兩端電壓的變化超前電流的變化。
對於 交流正弦的磁通量變化,所產生的感應電動勢 也會是 正弦函數,
不過相差了 90度 相角(餘弦)。
至於 感應電動勢(電場)的方向如何決定呢?它的存在是為了
想要阻止電感上的電流發生變化,因此必然與電感上原來的電動勢方向相反。
如此與原有外加的電動勢相加,才能造成抵銷的效果。
想一想:如果感應電動勢 與電感線圈上原有的電動勢方向並非相反而是相同。
則當電感上 電流(磁通量)將發生變化時,所感應的電動勢與原有的電動勢相加。
則會造成 更大的感應電流,於是又形成更大的感應電動勢。又 ...
如此循環,則可以源源不斷的 增加電流與磁場,而不需要外界提供任何能量。
由於 能量不可能 無中生有(只能從一種形式轉換成另一種形式)
因此 感應電動勢 必然需要與 原有的電動勢方向相反。
否則便違反 能量守恆定律。
簡單的說:電感不希望流經它的電流發生變化。
兩片平行金屬板便可以形成一個電容,
當加上電位差於兩端時,會在平行金屬板間建立電場。
任意兩金屬間,當加上一個電位差時。藉由電子的運動使得兩金屬體上分別形成正負電荷的分佈。
金屬板上電荷增加,於是在附近空間形成電場,於是金屬板間也會形成電位差。
當電位差逐漸增加時,流到金屬板上電流便會逐漸減少。直到兩者相等時,不再有電流充電。
電容可以想成是 一種儲存電荷(電能)的裝置,
當電容兩端的電壓增加時,所能夠儲存的電荷也與電壓等比例的增加。
於是 定義 電容兩端加上單位電壓時,所能儲存的電荷便是其電容值。
電容:電荷的容量
由於電容兩端的電壓(電位差)與所儲存的電荷量成正比。
若是想改變電容兩端的電壓 則必先有電流的形成,經過一段時間的充/放 電後,電壓才會改變。
我們說 電容兩端電流的變化超前 電壓變化。
(對正弦變化的電流而言,則電容電壓會是餘弦函數,兩者相位差 90度 )
若是我們將 電容與電感 串接在一起,然後兩端加上電位差時。
由於線路上即將會形成電流的變化,於是會在電感兩端形成感應電動勢。
此感應電動勢與剛接通瞬間電源電壓相同。但電感本身也具有電阻值,於是形成電流。
接下去 電流開始增加:
此電流便會在電容兩端充電,而逐漸形成電位差,
電感兩端形成感應電動勢 ,於是電感兩端的電位差便逐漸減少,
當電感電壓逐漸減少的同時時,電流逐漸增加使得電容電壓逐漸增加,
當電感電壓為零時,電流也增至最大,此時電容兩端的電壓等於電源的電壓。
接下去電流開始減少:
此電流繼續使電容充電,電容兩端電壓大於電源電壓。
由於電流的減少使得電感兩端形成 負的電位差。
電容電壓 加上 電感電壓 仍然等於電源電壓。
當回路電流變為零,此時電感兩端有最大的負電壓,電容兩端有最大的正電壓。
接下去和剛開始時類似,只是 電流方向相反。最後回復到剛開始一樣的情形。
於是以特定的頻率 周而復始繼續的變化。
此特定的頻率由 電容與電感值所決定。
由於線路中存在電阻,能量會逐漸損耗,實際的 最大電流/電壓都會逐漸減少。
若要變化繼續維持,可以加上交流變化的正弦電壓訊號。
此時 若是所外加的正弦電壓頻率 會影響 最大的電壓或電流大小。
當 外加的電壓信號頻率 與 原來振盪頻率越接近時,其電壓/電流也會越大。
這種現象稱為 共振。(請參考 Java 動畫 :RLC 交流振盪線路)
由以上分析可知 電感電容兩端的電壓有可能大於 電源的電壓。
善加運用 不是就可以用來 放大信號嗎?
運用同樣的道理,當 接收天線 感測到 電磁場的變化時,藉由改變 電容或電感值,
讓 線路的振盪頻率與 接收的信號頻率相同時,便可以逐次完全吸收 接收到的訊號
訊號加強後,便可以驅動 喇吧 而產生聲音。
當你在選擇不同的電台選轉按鈕時,便是在 改變按鈕所連接的電容值。
使得線路的共振頻率(接近或)恰等於電台頻率時,
將電台的訊號增強後輸送到放大器去,於是該電台的訊號被特殊的加強。
若是 線路對於頻率的選擇性不是很狹窄,則有時候會同時放大其他電台的訊號。
(天線會接收所有電台的電磁波訊號)
通常 收音機電台的發射天線是直立的,也就是電場在垂直方向變化。
由於地表本身是個良導體,所以當天線上的電子向上方加速時,
也會在地面上感應電荷重新分佈,其結果類似地面下方也有另一個正電荷向下加速。
有點像 照鏡子的作用。所以天線直立時,其等效長度 加倍。
當天線的長度恰好能形成駐波時,信號的強度最強。(長度為半波長整數倍)。
於是當天線實際長度為發射訊號波長的 1/4 時 能夠產生最強的訊號。
由於天線直立,於是向四面八方傳播的訊號 電場方向也大多維持向垂直的方向,
因此 接收天線的訊號 通常在 直立方向時接收效果也最好。
(汽車上的天線方向,指向那裡呢?)
當天線傳播電磁波時,在與天線垂直方向上傳送最多的功率。
當然 天線的長度若恰為波長的 1/4時 也會有最好的接收效果。
有些天線則感應磁場的訊號,繞成迴圈(必須形成回路),
這些天線對長波長的信號特別有效(為什麼呢?)
你想這種天線開如何擺設才能有效的感應空間磁通量的變化呢?
可是以上所講的電磁波都是電場與磁場在空間中的變化,
而聲波則是 空氣中壓力(密度)的變化所產生。
電磁波並不是聲波,電台傳送的也是電磁波?那麼聲波是如何傳送的呢?
電台 將聲波的訊號 藉著電磁波 傳送到遠方。
(好像人搭車到遠方一般,電磁波的速度--光速遠大於聲波的速度)
這其中到底是怎麼一回事呢?
聲波的頻率(人耳聽的到)在 20 - 20,000 比起電磁波小很多。
將聲波的訊號 併入電磁波內傳送到遠方。通常的採用的方法有兩種:
調幅 (Amplitude Modulation AM):
調整讓電磁波的振幅隨著聲波的振幅強弱而改變(振幅隨時間改變)
所傳送電磁波的頻率不變。
當聲波壓力最大時,振幅也最大,當聲波壓力最小時,振幅也最小。
當聲波完全消失時,並沒有電磁波傳送出去。如圖
參考 HP 公司的 AM Java 動畫
調頻(Frequency ModulationFM ):
調整讓電磁波的頻率隨著聲波的振幅強弱而改變(頻率隨時間改變)
所傳送電磁波的振幅則不改變。
當聲波壓力最大時,頻率也增加最大,當聲波壓力最小時,頻率也減少最小。
當聲波完全消失時,所傳送的頻率就是電台的頻率。如圖
通常一般 調幅(AM)電台的頻率在 550kHz - 1600 kHz.
而調頻(FM)電台的頻率在 88MHz - 108MHz
( 88 百萬赫茲到 108百萬赫茲,可是電台卻常說成 88 兆赫 ,很奇怪!)
參考 HP 公司的 FM Java 動畫
通常我們靠著機械或電子不同的方式去改變收音機的振盪頻率接收喜歡的電台。
可是 當收音機太靠近電台發射器時,即使沒有調到共振頻率也會接收到很強的訊號,
或者說受到電台訊號強烈的干擾。因而喪失了選台的功能。
AM 電台的訊號強弱會隨時需要調整,因而不能一直都傳輸最高功率,
當訊號過弱和背景訊號無法區分時,便產生連續的背景聲音。
而 FM 電台的訊號則可以隨時都以最高功率發射,於是雜訊的問題就相對較少。
所以 播送 音樂時 以 那一種電台效果較好呢?
現代的 FM 電台還播送立體聲呢? 那又是如何傳送的呢?
立體聲表示有兩個喇叭的聲音,若 A 與 B 代表兩個喇叭的聲波的空氣壓力訊號。
將 A+B 的訊號 以 20-19000 Hz的訊號調頻送出,C
將 A-B 的訊號 以 19020-38000 Hz的訊號調頻送出,D
19000 Hz 的訊號則代表是 立體音。
接收器接收以後再 分為 A 與 B 個別的訊號,分別送到兩個音箱,重現立體音。
知道是立體訊號時,將 D 類的訊號減去 19000 Hz 然後
與 C 相加便得 A,與 C 相減便得 B。
FM 的電台還是有其缺點:
高頻的訊號波長短於是 大多直線前進,於是傳送距離不易超過 100km.
(為什麼呢?光波直進,但聲波卻會轉彎--不需要面對面依然聽得到)
低頻的無線電訊號有另一個優點便是能藉由大氣層內的電離層反射回來,
尤其太陽下山後,反射效果更好,於是可以收到很遙遠的訊號。
歡迎批評指教!電子郵件 : 請按 hwang@phy03.phy.ntnu.edu.tw 作者:國立台灣師範大學 物理系 黃福坤
收音機的原理:
收音機是怎樣接收到遠方電台所傳來的訊號的呢?
你注意到當收聽 FM 電台時,天線的指向會影響接收的效果。
當在有著金屬外殼的汽車內時,也不易收到訊號。所以汽車都需要將天線裝在車外。
試試看!拿著收音機靠近一大片金屬附近時,會發生怎樣的情形!
無線電話是不是也有類似的情況?
收音機以及無線電話等通訊裝置都是藉著 『無線電波--電磁波』來傳遞訊號的。
這些無線電波又是怎麼產生的呢?又是如何能傳播很遠的距離呢?
無線電波的發射端都有著和接收端一樣的天線。
靜止的電荷會產生靜電場。(庫侖定律)
運動的電荷(電流)則會在附近空間形成產生磁場。(安培定律)
隨著時間變化的磁場會在附近空間建立電場(法拉地定律)
隨著時間變化的電場則會在附近空間形成磁場。
發射端藉由發射器來變化 以改變發射天線上電荷的分佈,
於天線兩端產生的電位差(天線內形成的電場)使得金屬天線內的電子朝向一端運動,
而形成 正電荷在一端,負電荷在另一端的分佈情形。(為何只需要電子動即可?)
但是這樣的電荷分佈所形成的電場,只有鄰近的天線才容易感受到影響。
這樣的信號不僅不易偵測與接收。信號的強弱也與距離及方位有關。
若是讓發射天線上的正負電荷分別在天線兩端形成某特定頻率周期性的分佈變化
(也只需要讓天線內的電子在小區域內(平均位置附近)作簡諧運動即可,
電子流動時的平均飄移速度是很慢的。例如提供正弦交流變化的電壓),
則接收天線上也會(感應)接收到相同頻率的信號。
(信號的強弱仍然與距離及方位有關,但信號的頻率則與距離及方位無關)
發射天線上兩端正負電荷的分佈隨著時間變化,
則附近空間的電場也會因而隨著時間產生變化。
只是天線上電荷的分佈變化,向外傳遞時需要時間(電磁波以光速傳播)
較遠的地方,較晚感受到發射天線的變化。
也就是空間距離不同的地點,電磁場的變化會有時間上的延遲。
於是形成『電磁』波。講的詳細明確一點:
當發射器使得天線上的電荷加速時,於是在鄰近空間形成變化的電場。
這變化的電場於是在鄰近空間又形成變化的磁場。(馬克斯威爾稱為位移電流)
變化的磁場又在鄰近空間形成變化的電場(法拉第定律)。如此繼續週而復始。
所形成的電磁場隨著空間的變化,在時間上會有所延遲,於是形成『電磁波』。
參考 java 物理動畫: 電磁波的行進
這樣的一個週期性變化的信號可以藉由 電感以及電容 組成的共振線路來提供。
將導線繞成螺旋狀的線圈便可 形成一個 電感:
當電流流經線圈時,會在線圈內形成磁場。
若是電流隨著時間發生改變,則線圈內的磁場也會因而發生變化。
當線圈內的磁通量 將 發生變化前,便會在線圈上形成感應電動勢(未卜先知?)
(感應電動勢 = 沿著線圈感應電場與路徑向量內積的總和)。
感應電動勢的產生,是由於自然界(空間)的另外一種『慣性』:
不希望 磁通量 發生變化的特性。
因此當 磁通量將會有較最大的變化量時,也會產生較大的感應電動勢,
想要抵銷磁通量的變化。
簡單的說 感應電動勢正比於 磁通量的變化量。
電感 等於 單位時間內 想有單位電流的變化時,所產生的感應電動勢。
好厲害!當線圈的電流將要發生改變前,電感便會形成感應電動勢來減緩電流的變化。
可不是電流已經發生改變了以後,才形成感應電動勢來減緩其變化。
因此 我們說 電感兩端電壓的變化超前電流的變化。
對於 交流正弦的磁通量變化,所產生的感應電動勢 也會是 正弦函數,
不過相差了 90度 相角(餘弦)。
至於 感應電動勢(電場)的方向如何決定呢?它的存在是為了
想要阻止電感上的電流發生變化,因此必然與電感上原來的電動勢方向相反。
如此與原有外加的電動勢相加,才能造成抵銷的效果。
想一想:如果感應電動勢 與電感線圈上原有的電動勢方向並非相反而是相同。
則當電感上 電流(磁通量)將發生變化時,所感應的電動勢與原有的電動勢相加。
則會造成 更大的感應電流,於是又形成更大的感應電動勢。又 ...
如此循環,則可以源源不斷的 增加電流與磁場,而不需要外界提供任何能量。
由於 能量不可能 無中生有(只能從一種形式轉換成另一種形式)
因此 感應電動勢 必然需要與 原有的電動勢方向相反。
否則便違反 能量守恆定律。
簡單的說:電感不希望流經它的電流發生變化。
兩片平行金屬板便可以形成一個電容,
當加上電位差於兩端時,會在平行金屬板間建立電場。
任意兩金屬間,當加上一個電位差時。藉由電子的運動使得兩金屬體上分別形成正負電荷的分佈。
金屬板上電荷增加,於是在附近空間形成電場,於是金屬板間也會形成電位差。
當電位差逐漸增加時,流到金屬板上電流便會逐漸減少。直到兩者相等時,不再有電流充電。
電容可以想成是 一種儲存電荷(電能)的裝置,
當電容兩端的電壓增加時,所能夠儲存的電荷也與電壓等比例的增加。
於是 定義 電容兩端加上單位電壓時,所能儲存的電荷便是其電容值。
電容:電荷的容量
由於電容兩端的電壓(電位差)與所儲存的電荷量成正比。
若是想改變電容兩端的電壓 則必先有電流的形成,經過一段時間的充/放 電後,電壓才會改變。
我們說 電容兩端電流的變化超前 電壓變化。
(對正弦變化的電流而言,則電容電壓會是餘弦函數,兩者相位差 90度 )
若是我們將 電容與電感 串接在一起,然後兩端加上電位差時。
由於線路上即將會形成電流的變化,於是會在電感兩端形成感應電動勢。
此感應電動勢與剛接通瞬間電源電壓相同。但電感本身也具有電阻值,於是形成電流。
接下去 電流開始增加:
此電流便會在電容兩端充電,而逐漸形成電位差,
電感兩端形成感應電動勢 ,於是電感兩端的電位差便逐漸減少,
當電感電壓逐漸減少的同時時,電流逐漸增加使得電容電壓逐漸增加,
當電感電壓為零時,電流也增至最大,此時電容兩端的電壓等於電源的電壓。
接下去電流開始減少:
此電流繼續使電容充電,電容兩端電壓大於電源電壓。
由於電流的減少使得電感兩端形成 負的電位差。
電容電壓 加上 電感電壓 仍然等於電源電壓。
當回路電流變為零,此時電感兩端有最大的負電壓,電容兩端有最大的正電壓。
接下去和剛開始時類似,只是 電流方向相反。最後回復到剛開始一樣的情形。
於是以特定的頻率 周而復始繼續的變化。
此特定的頻率由 電容與電感值所決定。
由於線路中存在電阻,能量會逐漸損耗,實際的 最大電流/電壓都會逐漸減少。
若要變化繼續維持,可以加上交流變化的正弦電壓訊號。
此時 若是所外加的正弦電壓頻率 會影響 最大的電壓或電流大小。
當 外加的電壓信號頻率 與 原來振盪頻率越接近時,其電壓/電流也會越大。
這種現象稱為 共振。(請參考 Java 動畫 :RLC 交流振盪線路)
由以上分析可知 電感電容兩端的電壓有可能大於 電源的電壓。
善加運用 不是就可以用來 放大信號嗎?
運用同樣的道理,當 接收天線 感測到 電磁場的變化時,藉由改變 電容或電感值,
讓 線路的振盪頻率與 接收的信號頻率相同時,便可以逐次完全吸收 接收到的訊號
訊號加強後,便可以驅動 喇吧 而產生聲音。
當你在選擇不同的電台選轉按鈕時,便是在 改變按鈕所連接的電容值。
使得線路的共振頻率(接近或)恰等於電台頻率時,
將電台的訊號增強後輸送到放大器去,於是該電台的訊號被特殊的加強。
若是 線路對於頻率的選擇性不是很狹窄,則有時候會同時放大其他電台的訊號。
(天線會接收所有電台的電磁波訊號)
通常 收音機電台的發射天線是直立的,也就是電場在垂直方向變化。
由於地表本身是個良導體,所以當天線上的電子向上方加速時,
也會在地面上感應電荷重新分佈,其結果類似地面下方也有另一個正電荷向下加速。
有點像 照鏡子的作用。所以天線直立時,其等效長度 加倍。
當天線的長度恰好能形成駐波時,信號的強度最強。(長度為半波長整數倍)。
於是當天線實際長度為發射訊號波長的 1/4 時 能夠產生最強的訊號。
由於天線直立,於是向四面八方傳播的訊號 電場方向也大多維持向垂直的方向,
因此 接收天線的訊號 通常在 直立方向時接收效果也最好。
(汽車上的天線方向,指向那裡呢?)
當天線傳播電磁波時,在與天線垂直方向上傳送最多的功率。
當然 天線的長度若恰為波長的 1/4時 也會有最好的接收效果。
有些天線則感應磁場的訊號,繞成迴圈(必須形成回路),
這些天線對長波長的信號特別有效(為什麼呢?)
你想這種天線開如何擺設才能有效的感應空間磁通量的變化呢?
可是以上所講的電磁波都是電場與磁場在空間中的變化,
而聲波則是 空氣中壓力(密度)的變化所產生。
電磁波並不是聲波,電台傳送的也是電磁波?那麼聲波是如何傳送的呢?
電台 將聲波的訊號 藉著電磁波 傳送到遠方。
(好像人搭車到遠方一般,電磁波的速度--光速遠大於聲波的速度)
這其中到底是怎麼一回事呢?
聲波的頻率(人耳聽的到)在 20 - 20,000 比起電磁波小很多。
將聲波的訊號 併入電磁波內傳送到遠方。通常的採用的方法有兩種:
調幅 (Amplitude Modulation AM):
調整讓電磁波的振幅隨著聲波的振幅強弱而改變(振幅隨時間改變)
所傳送電磁波的頻率不變。
當聲波壓力最大時,振幅也最大,當聲波壓力最小時,振幅也最小。
當聲波完全消失時,並沒有電磁波傳送出去。如圖
參考 HP 公司的 AM Java 動畫
調頻(Frequency ModulationFM ):
調整讓電磁波的頻率隨著聲波的振幅強弱而改變(頻率隨時間改變)
所傳送電磁波的振幅則不改變。
當聲波壓力最大時,頻率也增加最大,當聲波壓力最小時,頻率也減少最小。
當聲波完全消失時,所傳送的頻率就是電台的頻率。如圖
通常一般 調幅(AM)電台的頻率在 550kHz - 1600 kHz.
而調頻(FM)電台的頻率在 88MHz - 108MHz
( 88 百萬赫茲到 108百萬赫茲,可是電台卻常說成 88 兆赫 ,很奇怪!)
參考 HP 公司的 FM Java 動畫
通常我們靠著機械或電子不同的方式去改變收音機的振盪頻率接收喜歡的電台。
可是 當收音機太靠近電台發射器時,即使沒有調到共振頻率也會接收到很強的訊號,
或者說受到電台訊號強烈的干擾。因而喪失了選台的功能。
AM 電台的訊號強弱會隨時需要調整,因而不能一直都傳輸最高功率,
當訊號過弱和背景訊號無法區分時,便產生連續的背景聲音。
而 FM 電台的訊號則可以隨時都以最高功率發射,於是雜訊的問題就相對較少。
所以 播送 音樂時 以 那一種電台效果較好呢?
現代的 FM 電台還播送立體聲呢? 那又是如何傳送的呢?
立體聲表示有兩個喇叭的聲音,若 A 與 B 代表兩個喇叭的聲波的空氣壓力訊號。
將 A+B 的訊號 以 20-19000 Hz的訊號調頻送出,C
將 A-B 的訊號 以 19020-38000 Hz的訊號調頻送出,D
19000 Hz 的訊號則代表是 立體音。
接收器接收以後再 分為 A 與 B 個別的訊號,分別送到兩個音箱,重現立體音。
知道是立體訊號時,將 D 類的訊號減去 19000 Hz 然後
與 C 相加便得 A,與 C 相減便得 B。
FM 的電台還是有其缺點:
高頻的訊號波長短於是 大多直線前進,於是傳送距離不易超過 100km.
(為什麼呢?光波直進,但聲波卻會轉彎--不需要面對面依然聽得到)
低頻的無線電訊號有另一個優點便是能藉由大氣層內的電離層反射回來,
尤其太陽下山後,反射效果更好,於是可以收到很遙遠的訊號。
歡迎批評指教!電子郵件 : 請按 hwang@phy03.phy.ntnu.edu.tw 作者:國立台灣師範大學 物理系 黃福坤
2010年10月21日 星期四
GBUB4DOS一開始一定是先處理MBR:stage1.S
stage1.s源代碼分析(很詳盡): 文章出處:http://www.civilnet.cn/blog/browse.php?operation=display&type=blog&entryno=85
Stage1.s原始檔案是用古老的at&t彙編編寫而成,是大名鼎鼎的unix家族作業系統引導程式GRUB中的第一個檔。它編譯後產生的二進位碼正好是512位元組(故意的,也是必須的),剛好填充滿硬碟初始的一個磁區,也即0柱面、0磁軌、1磁區。人們稱之為MBR——主引導記錄。它的作用是載入stage2檔(GRLDR)。閱讀本段代碼,gemfield建議你首先具備以下能力:cpu寄存器、BIOS中斷、PC架構、at&t彙編、GRUB背景知識。幸運的,青島之光論壇(bbs.civilnet.cn)的嵌入系統版塊裏都或多或少包含了這些介紹。並且可以從青島之光論壇上查找stage1.s的源代碼,此處不一一羅列了。
程式剛開始處的巨集定義使用了和gcc相同的規範,定義的3個巨集變數在後面用到的地方再由gemfield詳細闡述。在定義了一個總體變數_start後,程式的真正入口就到了。事實上,在二進位碼中,開始部分的代碼是eb48,其中eb就是jmp的機器碼,在標號_start後,緊跟著的就是這個jmp指令,跳轉到after_BPB處。Jmp後的nop指令,恐怕永遠也不會執行了。注意,剛開機時cpu會調用int19h將第一個磁區的內容調到記憶體位址為0x0000:0x7coo處,你要問為什麼是這個地址或者為什麼會發生這樣的調用,原因大抵和usb為什麼是2根資料線是一樣的。
.=_start + 4是一個讓人困惑的語句,其實這個dot是一個特殊的標號,在as彙編規範中,就代表當前的地址。從開始處的_start處填充空間至_start+4處,相當於4個位元組的空間。但是,從_start開始後的jmp nop 和jmp的參數已經佔用了3個位元組的空間,相當於在它們的後面再用0填充1個位元組的空間即可。
後面緊跟的是一系列稱之為彙編directive的“虛擬指令”。這一部分是對磁片等一些參數進行設置。像起始的磁區、磁軌和柱面以及它們的起始地址、還有stage1的版本號、boot_drive變數、force_lba變數、stage2的位址、磁區、段等參數,這在後面的代碼中涉及的時候再由gemfield闡述,到時候gemfield會稱這部分為初始化參數部分,切記。但在這一系列的參數設置中,還有個相似的語句,就是.=_start+STAGE1_BPBEND,照樣是從上一條指令處填充0直至到達_start+0x3e處。
在jmp之後,清中斷允許位,然後陳列80ca這個二進位碼。80ca就相當於orb $ox80,%dl,意思是給dl寄存器賦值80,要知道,在開機初始, BIOS載入完啟動代碼會把%dl寄存器設置成啟動盤號(boot drive number):
***************************************************************
DL = 00h 1st floppy disk ( "drive A:" )
DL = 01h 2nd floppy disk ( "drive B:" )
DL = 80h 1st hard disk
DL = 81h 2nd hard disk
***************************************************************
硬碟的代號是80,所以上面代表的是stage1裝到硬碟上的情形,如果是軟碟的話,就是orb $0x00,%dl,很顯然,軟盤機代號是0x00。關於boot_drive_mask這一部分,包含的ljmp $0,ABS(real_start)指令的意思是,跳轉到cs:ip = 0x0000:$ABS(real_start)這個地方執行指令。程式的開頭部分定義了ABS這個巨集,在此處就相當於real_start-_start+0x7c00。如果是“正常的”int19h中斷,這句就是廢話。因為物理位址是(Segment value * 16) + Offset value,正常情況下MBR被載入到cs:ip = 0x0000:0x7c00上,而有些糟糕的BIOS會將其載入到07c0:0000上,其實這兩個代表的物理位址是完全一樣的(你可以用上上行的公式計算)。有些人從來就不考慮這種事實,那就是大多數人常常把segment值設為0,這樣引導代碼就可以假定任何段寄存器都是0從而只對付ip裏的偏移量。所以,在grub裏,加上這麼一個長轉移,就防止了這類糟糕的BIOS帶來的大麻煩。 接著進入real_start了,ax清零,ds賦值0,ss賦值0,將STAGE1_STACKSEG(0x2000)賦值給sp,這樣就設置了實模式下的堆疊段位址(棧頂位置)ss:sp = 0x0000:0x2000。接著置中斷允許位,然後檢查是否設置了啟動的磁片。先用MOV_MEM_TO_AL宏將boot_drive量存到al中,然後與0xff進行比較,用的是cmpb $0xff,%al ;je 1f。cmpb指令是將兩個運算元進行相減,對標誌位元的影響同sub指令,但是不保存結果。其中,此處用到的是zf標誌位元(因為是je指令),這樣,當運算元相等(即相減為零時)zf被置1。所以,cmpb和je一起使用時,就是指,當運算元相等時,跳轉至je制定的標號。所以,在這裏,若boot_drive等於0xff,則使用BIOS傳遞過來的默認的驅動器進行啟動;如果不是,movb %al,%dl,將boot_drive的值保存至dl中,表示由boot_drive的值確定啟動設備。不管怎麼樣,現在開始正式啟動了……驅動器號資訊壓棧、輸出資訊“GRUB”,注意,在螢幕上輸出資訊時調用了MSG巨集。下面分析一下這個宏,#define MSG(X) movw $ABS(x),%si ;call message 輸出GRUB字樣時,變數是notification_string,相當於將notification_string位址上的16位元內容送入si寄存器,然後調用message函數,而message函數使用了int10中斷來在螢幕上顯示字元。涉及到串操作指令。message函數:lodsb,從%si指向的源位址中逐一讀取一個字元,送入al中,然後檢查al是否為零,如果為零,表示字元已經傳輸完成了(.string虛擬指令會在指定的字串後加入一個位元組的0),此時調用ret返回。而若不為零,表明字元還未傳輸完,此時跳轉到int 10h“中斷前夕”,用int 10h 的oeh子功能在螢幕上以telemode模式寫字元,其中,ah是子功能號,al是字元,bh是頁,bl是前背景色(在圖形模式下)。所以這裏movw $0x0001,%bx ; movb $0x0e,%ah ;int $0x10(顯示一個字元)就ok了。
在螢幕上顯示完GRUB後,要來決定是進入chs模式還是lba模式(也就是看硬碟是否支援LBA模式,因為兩種模式對硬碟的讀寫等操作有很不一樣的地方),但在這之前,你得首先判斷這裏是硬碟而不是軟碟或者根本就沒有盤(言下之意就是,如果不是硬碟,判斷LBA或者CHS模式就沒有意義了),所以,在判斷硬碟是否支援LBA時,先判斷是不是硬碟。這裏用testb $STAGE1_BIOS_HD_FLAG,%dl來判斷,dl寄存器裏裝載的是磁片號,有三大類情況:硬碟(0x80、0x81)、軟碟(0x00、0x01)、無效的盤(0xff)。而前面的宏就是0x80,所以通過testb和jz指令判斷,如果dl中不是80或81(也就是不是硬碟),就跳轉到chs_mode函數下面。另外,如果此處判斷出是硬碟的話,再接著判斷是否支援LBA,使用的工具就是BIOS的int 13h中斷。 通過 BIOS 調用 INT 0x13 來確定是否支援擴展,LBA 擴展功能分兩個子集 , 如下 : 第一個子集提供了訪問大硬碟所必須的功能 , 包括: ****************************************************************
1.檢查擴展是否存在 : ah = 41h , bx = 0x55aa , dl = drive( 0x80 ~ 0xff )
2.擴展讀 : ah = 42h
3.擴展寫 : ah = 43h
4.校驗磁區 : ah = 44h
5.擴展定位 : ah = 47h
6.取得驅動器參數 : ah = 48h
****************************************************************
第二個子集提供了對軟體控制驅動器鎖定和彈出的支援 ,包括: ****************************************************************
1.檢查擴展 : ah = 41h
2.鎖定/解鎖驅動器 : ah = 45h
3.彈出驅動器 : ah = 46h
4.取得驅動器參數 : ah = 48h
5.取得擴展驅動器改變狀態: ah = 49h ****************************************************************
下面開始具體檢測 , 首先檢測擴展是否存在。此時寄存器的值和 BIOS 調用分別是:AH = 0x41,BX = 0x55AA,DL = driver( 0x80 ~ 0xFF ),然後INT 13H,看返回結果:如果支持CF= 0;否則 CF = 1;CF = 0 (支持LBA) 時的寄存器值代表含義: ****************************************************************
ah:擴展功能的主版本號( major version of extensions )
al:內部使用( internal use )
bx :AA55h ( magic number )
cx:Bits Description
0 extended disk access functions
1 removable drive controller functions supported
2 enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported.
Extended drive parameter table is valid
3~15 reserved (0) CF = 1 (不支持LBA) 時的寄存器值 :
ah = 0x01 ( invalid function )
****************************************************************
現在stage1.s使用movb $0x41, %ah;movw $0x55aa, %bx;int $0x13; jc chs_mode來進行上述判斷。如果不支援LBA,則cf就是1,跳轉到chs_mode函數運行。有的bios的int 13h中斷會影響到dl,所以此處用pop和push指令將其保護起來。然而cf不等於1也不表示就支持LBA了,還得再判斷bx是不是aa55h,使用cmpb $0xaa55,%bx ;jne chs_mode再判斷一次,如果bx裏存的不是預期的返回值,同樣不支持lba,也要進入chs_mode函數。這裏有個強制LBA模式要注意下,就是說,當cf是1,bx也是aa55,那麼可以不用在判斷就進入強制LBA模式,代碼是這樣寫的,使用MOV_MEM_TO_AL巨集將force_lba變數值傳遞到al,判斷是否為0。不為零強行進入lba_mode函數。然後判斷cx,如果cx為0的話表明不支持擴展第一子集,這時也進入chs_mode函數。所以總結進入chs_mode的情況,如下: *****************************************************************
第一、 磁片號非80h或81h,進入chs_mode
第二、 int13h,41h子功能,返回cf為0,進入進入chs_mode
第三、 int13h,41h子功能,返回bx不為aa55,進入chs_mode
第四、 如果沒有設置強制LBA,而且也不支持擴展第一子集,進入chs_mode
第五、 其他情況,進入lba模式。 *****************************************************************
那我們就先來分析進入chs模式的代碼,你看,我們是以以上種種情況的發生而進入chs模式的,所以進入chs模式時,再來進行一些檢測,來確定具體的情況。首先就是int13h的08功能號的使用。使用08功能可以檢測chs模式中硬碟的參數,保存在各寄存器裏: *****************************************************************
DL:本機軟碟驅動器的數目
DH:最大磁頭號(或說磁面數目)。0表示有1個磁面,1表示有2個磁面
CH:存放10位元磁軌柱面數的低8位(高2位在CL的D7、D6中)。1表示有1個柱面,2表示有2個柱面,依次類推。
CL:0~5位元存放每磁軌的磁區數目。6和7位元表示10位元磁軌柱面數的高2位。
AX=0
BH=0
BL表示驅動器類型:
1=360K 5.25
2=1.2M 5.25
3=720K 3.5
4=1.44M 3.5
ES:SI 指向軟碟參數表
******************************************************************
如果成功返回參數,則進入final_init函數;但是如果調用失敗,進位元標誌CF=1,AH存放錯誤資訊碼。表明不支援硬碟的chs模式(前面也判斷了不支持lba),那就要考慮是不是軟碟了。再使用testb和jz指令,若dl是00或01,則認為是軟碟,就跳轉到floppy_probe函數執行(後文討論此函數)。但是若連軟碟也不是,只好準備報錯了。跳轉到hd_probe_error函數,這個函數調用MSG函數連同general_error函數一道輸出“hard disk error”的字元。
好了,現在我們回來。剛開始經過一些列的判斷,我們進入了LBA模式。然後,代碼做了以下工作,movl 0x10(%si),%ecx,這個代碼就是個廢話,ecx寄存器被置入了一個無意義的值;然後將標號disk_address_packet處的位址賦給si,再接著將[si-1]記憶體處置1(也就是mode被置1,表示LBA擴展讀;如果是0,就是CHS定址讀)、將stage2的磁區數賦予ebx、在[si]和[si+1]處存放10和00(movw $0x0010,(si))、在[si+2]和[si+3]處存放01和00、在[si+4]和[si+5]處存放00和00、在[si+6]和[si+7]處存放0x00和0x70(這是stage1_bufferseg的值)、在[si+8][si+9][si+A][si+B]處存放0x01/0x00/0x00/0x00、在[si+c][si+d][si+e][si+f]處存放0x00/0x00/0x00/0x00。設置完畢後,開始調用int 13h的42功能中斷。如果出錯,就跳轉到chs_mode處。那麼中斷執行成功呢?
由si及其偏移量指向的記憶體保存著磁片參數塊,如下: ******************************************************************
偏移量 大小 位數 描述
00h BYTE 8 資料塊的大小 (10h or 18h)
01h BYTE 8 保留,必須為0
02h WORD 16 傳輸資料塊數,傳輸完成後保存傳輸的塊數
04h DWORD 32 傳輸時的資料緩存位址
08h QWORD 64 起始絕對磁區號(即起始磁區的LBA號碼) ******************************************************************
所以,通過int13h(42)中斷的作用,硬碟上第二個磁區上的內容就被讀到由si偏移量為4h、5h、6h、7h確定的記憶體區域上了,此處是0x7000:0x0000。執行成功,將bx賦值0x7000,然後跳至copy_buffer子函數處。
LBA已完,gemfield在閱讀copy_buffer前再回頭看當初程式跳至chs_mode後是怎麼運行的。上文中已經指出了,到達chs_mode後經過條件判斷,一共產生了三種情況,第一是進入硬碟的chs子函數(final_init);第二是進入軟碟副程式(floppy_probe);第三種情況是進入報錯子函數,在螢幕上輸出一系列錯誤。那就由gemfield從第一種情況開始吧。程式運行到final_init後,將磁區數保存到si、設置mode為0、eax清零為存放磁頭數做準備、將dh中存放的磁頭數保存到al中、使用incw %ax指令(因為磁頭數是以0~n-1方式排列的,所以增1後才是真正的磁頭數)、將磁頭數保存至[si+4][si+5][si+6][si+7]記憶體位址上、清dx為存放磁區數做準備、cl中的0~5位元存放的是磁區數,所以dx邏輯左移2位元後在dh中出現的兩位就是柱面數的高2位,並且把這2位移到ah中,而ch存放的柱面數低8為移至al中,這樣ax裏就是柱面數了,這裏因為同樣的道理要進行incw %ax操作,並且把真正的柱面數放到位址為[si+8][si+9]的記憶體上、然後用同樣的移動方法產生真正的磁區數並保存在位址為[si][si+1][si+2][si+3]的記憶體上。
然後在使用int 13h(0x02)功能前要進行必備的參數設置:eax存放stage2的磁區編號(stage2_sector,默認為1)、清edx寄存器、然後通過(stage2磁區數)/(磁區數)獲得引導磁區數。注意對於div指令來說,eax恒定存放被除數,div後面的寄存器存放的是除數。餘數在edx中存放,第一個餘數(磁區數)放到位址為[si+10]的記憶體上並將edx清零、再用(上一步除法的商) /(磁頭數)得到的餘數為磁頭數,存放在[si+11]記憶體位址上。商為柱面數並存放在eax中並同時保存至[si+12][si+13]記憶體位址上。然後將之前中斷獲得的柱面數與此處stage2所占柱面數相比較,如果stage2柱面數大,那麼明顯錯誤,程式將跳至geometry_error處。
現在,將[si+13]的內容賦值給dl(柱面數的高2位)並且左移6位元、將磁區數放到cl中再增1、然後通過orb %dl,%cl和movb 12(%si),%dh指令達到這麼一種情況,即:cl中存放的是磁區數和柱面數的高2位,ch中存放的是柱面數的低8位、然後恢復驅動器號(popw %dx)、然後將磁頭數放置到dh中,然後將0x7000賦值給es並將bx清零,賦值0x0201給ax(獲得中斷功能號),參數現在設置完畢,開始調用int 13h中斷: ******************************************************************
%al = number of sectors(需要讀的磁區數)
%ah= 0x02(功能號)
%ch = cylinder(起始柱面數)
%cl = sector (bits 6-7 are high bits of "cylinder")
%dh = head
%dl = drive (0x80 for hard disk, 0x0 for floppy disk)
%es:%bx = segment:offset of buffer ******************************************************************
調用中斷後,將0柱面、0磁軌、2磁區的內容讀到0x7000:0x0000記憶體處。然後程式跳轉至copy_buffer處,和LBA殊途同歸呀。
我們看看copy_buffer做了什麼。將0x8000賦值給es、給cx賦值0x100、給ds賦值0x7000、si和di清零、方向標誌DF置零,然後使用rep和movsw指令將ds:si處連續的512位元組內容傳輸到es:di指定的記憶體位址(0x8000:0x0000)。其中,rep指令的含義就是重複執行後一句指令,沒執行一次。cx減1,直至cx為0。這也是前面cx賦值0x100(256)的原因。movsw每次傳輸一個字,256次就是512位元組。然後popw %ds; popa還原寄存器。
接著,程式跳轉到0x8000處繼續執行,到此就開始執行新的模組了,stage1的任務也已經結束了。代碼中*(stage2_address)的星號是at&t彙編的規範:絕對跳轉/調用(相對於與程式計數器有關的跳轉/調用)運算元前面要加星號"*"。
然而,前面所述的chs模式中的第二種情況——軟盤機情況將會帶領gemfield進入floppy_probe子函數,此處要使用int 13h(0x00功能號)來進行軟盤機的復位。成功的話cf=0;然後準備調用int 13h(功能號是0x02),這和chs中的int 13h,ah=0x02是一樣的。所以,先來為中斷準備必須的參數:軟盤機復位後,將[si]處的值賦給cl(cl是起始磁區數),我們知道,由於迴圈,我們給了cl 4次機會,因為迴圈中有incw %si指令,所以si中的值是遞增的,從probe_values開始,在每一次的機會中依次給cl賦予了0x24、0x12、0x0f、0x09這幾種值,當然,試完後還不對的話就要執行報錯函數了。
像以前那樣,依次準備好bx、ah、al、ch、cl、dh的值後,就要int 13h了。成功後,dh賦值1、ch賦值0x4f,dh 設置為 79 , 表示柱面最大值為 79(80柱:0~79),dh 設置為 1 , 表示磁頭數最大值為 1(2頭:0~1),然後跳轉至 final_init,在上文中關於final_init的分析 , 我們知道保存時會把柱面和磁頭分別加 1 , 磁區不變,因此 , 在軟碟載入時 , 將設置 Cylinder : Head : Sector = 80 : 2 : start_sector。最終就跳轉至final_init函數處執行了。
gemfield的本文中,依然要注意的還有為了相容性而設置的windows nt魔術頭標識的偏移、part_start作為標識的分區表起始位址的標記的偏移、以及引導磁區結束標誌0xaa55。
總的說來,在gemfield這篇稍顯淩亂的文章裏,主要介紹了stage1.s的使命,簡介來說,就是開機時首先被BIOS INT19H裝載到記憶體0x7c00處,然後判斷chs和lba模式,然後使用int13h中斷將磁片上第二磁區的內容讀到0x7000處,然後通過子函數copy_buffer再將其調到0x8000的位置上,這個第二磁區的內容就是以後gemfield的嵌入系統版塊中將要介紹的start.s模組。
Stage1.s原始檔案是用古老的at&t彙編編寫而成,是大名鼎鼎的unix家族作業系統引導程式GRUB中的第一個檔。它編譯後產生的二進位碼正好是512位元組(故意的,也是必須的),剛好填充滿硬碟初始的一個磁區,也即0柱面、0磁軌、1磁區。人們稱之為MBR——主引導記錄。它的作用是載入stage2檔(GRLDR)。閱讀本段代碼,gemfield建議你首先具備以下能力:cpu寄存器、BIOS中斷、PC架構、at&t彙編、GRUB背景知識。幸運的,青島之光論壇(bbs.civilnet.cn)的嵌入系統版塊裏都或多或少包含了這些介紹。並且可以從青島之光論壇上查找stage1.s的源代碼,此處不一一羅列了。
程式剛開始處的巨集定義使用了和gcc相同的規範,定義的3個巨集變數在後面用到的地方再由gemfield詳細闡述。在定義了一個總體變數_start後,程式的真正入口就到了。事實上,在二進位碼中,開始部分的代碼是eb48,其中eb就是jmp的機器碼,在標號_start後,緊跟著的就是這個jmp指令,跳轉到after_BPB處。Jmp後的nop指令,恐怕永遠也不會執行了。注意,剛開機時cpu會調用int19h將第一個磁區的內容調到記憶體位址為0x0000:0x7coo處,你要問為什麼是這個地址或者為什麼會發生這樣的調用,原因大抵和usb為什麼是2根資料線是一樣的。
.=_start + 4是一個讓人困惑的語句,其實這個dot是一個特殊的標號,在as彙編規範中,就代表當前的地址。從開始處的_start處填充空間至_start+4處,相當於4個位元組的空間。但是,從_start開始後的jmp nop 和jmp的參數已經佔用了3個位元組的空間,相當於在它們的後面再用0填充1個位元組的空間即可。
後面緊跟的是一系列稱之為彙編directive的“虛擬指令”。這一部分是對磁片等一些參數進行設置。像起始的磁區、磁軌和柱面以及它們的起始地址、還有stage1的版本號、boot_drive變數、force_lba變數、stage2的位址、磁區、段等參數,這在後面的代碼中涉及的時候再由gemfield闡述,到時候gemfield會稱這部分為初始化參數部分,切記。但在這一系列的參數設置中,還有個相似的語句,就是.=_start+STAGE1_BPBEND,照樣是從上一條指令處填充0直至到達_start+0x3e處。
在jmp之後,清中斷允許位,然後陳列80ca這個二進位碼。80ca就相當於orb $ox80,%dl,意思是給dl寄存器賦值80,要知道,在開機初始, BIOS載入完啟動代碼會把%dl寄存器設置成啟動盤號(boot drive number):
***************************************************************
DL = 00h 1st floppy disk ( "drive A:" )
DL = 01h 2nd floppy disk ( "drive B:" )
DL = 80h 1st hard disk
DL = 81h 2nd hard disk
***************************************************************
硬碟的代號是80,所以上面代表的是stage1裝到硬碟上的情形,如果是軟碟的話,就是orb $0x00,%dl,很顯然,軟盤機代號是0x00。關於boot_drive_mask這一部分,包含的ljmp $0,ABS(real_start)指令的意思是,跳轉到cs:ip = 0x0000:$ABS(real_start)這個地方執行指令。程式的開頭部分定義了ABS這個巨集,在此處就相當於real_start-_start+0x7c00。如果是“正常的”int19h中斷,這句就是廢話。因為物理位址是(Segment value * 16) + Offset value,正常情況下MBR被載入到cs:ip = 0x0000:0x7c00上,而有些糟糕的BIOS會將其載入到07c0:0000上,其實這兩個代表的物理位址是完全一樣的(你可以用上上行的公式計算)。有些人從來就不考慮這種事實,那就是大多數人常常把segment值設為0,這樣引導代碼就可以假定任何段寄存器都是0從而只對付ip裏的偏移量。所以,在grub裏,加上這麼一個長轉移,就防止了這類糟糕的BIOS帶來的大麻煩。 接著進入real_start了,ax清零,ds賦值0,ss賦值0,將STAGE1_STACKSEG(0x2000)賦值給sp,這樣就設置了實模式下的堆疊段位址(棧頂位置)ss:sp = 0x0000:0x2000。接著置中斷允許位,然後檢查是否設置了啟動的磁片。先用MOV_MEM_TO_AL宏將boot_drive量存到al中,然後與0xff進行比較,用的是cmpb $0xff,%al ;je 1f。cmpb指令是將兩個運算元進行相減,對標誌位元的影響同sub指令,但是不保存結果。其中,此處用到的是zf標誌位元(因為是je指令),這樣,當運算元相等(即相減為零時)zf被置1。所以,cmpb和je一起使用時,就是指,當運算元相等時,跳轉至je制定的標號。所以,在這裏,若boot_drive等於0xff,則使用BIOS傳遞過來的默認的驅動器進行啟動;如果不是,movb %al,%dl,將boot_drive的值保存至dl中,表示由boot_drive的值確定啟動設備。不管怎麼樣,現在開始正式啟動了……驅動器號資訊壓棧、輸出資訊“GRUB”,注意,在螢幕上輸出資訊時調用了MSG巨集。下面分析一下這個宏,#define MSG(X) movw $ABS(x),%si ;call message 輸出GRUB字樣時,變數是notification_string,相當於將notification_string位址上的16位元內容送入si寄存器,然後調用message函數,而message函數使用了int10中斷來在螢幕上顯示字元。涉及到串操作指令。message函數:lodsb,從%si指向的源位址中逐一讀取一個字元,送入al中,然後檢查al是否為零,如果為零,表示字元已經傳輸完成了(.string虛擬指令會在指定的字串後加入一個位元組的0),此時調用ret返回。而若不為零,表明字元還未傳輸完,此時跳轉到int 10h“中斷前夕”,用int 10h 的oeh子功能在螢幕上以telemode模式寫字元,其中,ah是子功能號,al是字元,bh是頁,bl是前背景色(在圖形模式下)。所以這裏movw $0x0001,%bx ; movb $0x0e,%ah ;int $0x10(顯示一個字元)就ok了。
在螢幕上顯示完GRUB後,要來決定是進入chs模式還是lba模式(也就是看硬碟是否支援LBA模式,因為兩種模式對硬碟的讀寫等操作有很不一樣的地方),但在這之前,你得首先判斷這裏是硬碟而不是軟碟或者根本就沒有盤(言下之意就是,如果不是硬碟,判斷LBA或者CHS模式就沒有意義了),所以,在判斷硬碟是否支援LBA時,先判斷是不是硬碟。這裏用testb $STAGE1_BIOS_HD_FLAG,%dl來判斷,dl寄存器裏裝載的是磁片號,有三大類情況:硬碟(0x80、0x81)、軟碟(0x00、0x01)、無效的盤(0xff)。而前面的宏就是0x80,所以通過testb和jz指令判斷,如果dl中不是80或81(也就是不是硬碟),就跳轉到chs_mode函數下面。另外,如果此處判斷出是硬碟的話,再接著判斷是否支援LBA,使用的工具就是BIOS的int 13h中斷。 通過 BIOS 調用 INT 0x13 來確定是否支援擴展,LBA 擴展功能分兩個子集 , 如下 : 第一個子集提供了訪問大硬碟所必須的功能 , 包括: ****************************************************************
1.檢查擴展是否存在 : ah = 41h , bx = 0x55aa , dl = drive( 0x80 ~ 0xff )
2.擴展讀 : ah = 42h
3.擴展寫 : ah = 43h
4.校驗磁區 : ah = 44h
5.擴展定位 : ah = 47h
6.取得驅動器參數 : ah = 48h
****************************************************************
第二個子集提供了對軟體控制驅動器鎖定和彈出的支援 ,包括: ****************************************************************
1.檢查擴展 : ah = 41h
2.鎖定/解鎖驅動器 : ah = 45h
3.彈出驅動器 : ah = 46h
4.取得驅動器參數 : ah = 48h
5.取得擴展驅動器改變狀態: ah = 49h ****************************************************************
下面開始具體檢測 , 首先檢測擴展是否存在。此時寄存器的值和 BIOS 調用分別是:AH = 0x41,BX = 0x55AA,DL = driver( 0x80 ~ 0xFF ),然後INT 13H,看返回結果:如果支持CF= 0;否則 CF = 1;CF = 0 (支持LBA) 時的寄存器值代表含義: ****************************************************************
ah:擴展功能的主版本號( major version of extensions )
al:內部使用( internal use )
bx :AA55h ( magic number )
cx:Bits Description
0 extended disk access functions
1 removable drive controller functions supported
2 enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported.
Extended drive parameter table is valid
3~15 reserved (0) CF = 1 (不支持LBA) 時的寄存器值 :
ah = 0x01 ( invalid function )
****************************************************************
現在stage1.s使用movb $0x41, %ah;movw $0x55aa, %bx;int $0x13; jc chs_mode來進行上述判斷。如果不支援LBA,則cf就是1,跳轉到chs_mode函數運行。有的bios的int 13h中斷會影響到dl,所以此處用pop和push指令將其保護起來。然而cf不等於1也不表示就支持LBA了,還得再判斷bx是不是aa55h,使用cmpb $0xaa55,%bx ;jne chs_mode再判斷一次,如果bx裏存的不是預期的返回值,同樣不支持lba,也要進入chs_mode函數。這裏有個強制LBA模式要注意下,就是說,當cf是1,bx也是aa55,那麼可以不用在判斷就進入強制LBA模式,代碼是這樣寫的,使用MOV_MEM_TO_AL巨集將force_lba變數值傳遞到al,判斷是否為0。不為零強行進入lba_mode函數。然後判斷cx,如果cx為0的話表明不支持擴展第一子集,這時也進入chs_mode函數。所以總結進入chs_mode的情況,如下: *****************************************************************
第一、 磁片號非80h或81h,進入chs_mode
第二、 int13h,41h子功能,返回cf為0,進入進入chs_mode
第三、 int13h,41h子功能,返回bx不為aa55,進入chs_mode
第四、 如果沒有設置強制LBA,而且也不支持擴展第一子集,進入chs_mode
第五、 其他情況,進入lba模式。 *****************************************************************
那我們就先來分析進入chs模式的代碼,你看,我們是以以上種種情況的發生而進入chs模式的,所以進入chs模式時,再來進行一些檢測,來確定具體的情況。首先就是int13h的08功能號的使用。使用08功能可以檢測chs模式中硬碟的參數,保存在各寄存器裏: *****************************************************************
DL:本機軟碟驅動器的數目
DH:最大磁頭號(或說磁面數目)。0表示有1個磁面,1表示有2個磁面
CH:存放10位元磁軌柱面數的低8位(高2位在CL的D7、D6中)。1表示有1個柱面,2表示有2個柱面,依次類推。
CL:0~5位元存放每磁軌的磁區數目。6和7位元表示10位元磁軌柱面數的高2位。
AX=0
BH=0
BL表示驅動器類型:
1=360K 5.25
2=1.2M 5.25
3=720K 3.5
4=1.44M 3.5
ES:SI 指向軟碟參數表
******************************************************************
如果成功返回參數,則進入final_init函數;但是如果調用失敗,進位元標誌CF=1,AH存放錯誤資訊碼。表明不支援硬碟的chs模式(前面也判斷了不支持lba),那就要考慮是不是軟碟了。再使用testb和jz指令,若dl是00或01,則認為是軟碟,就跳轉到floppy_probe函數執行(後文討論此函數)。但是若連軟碟也不是,只好準備報錯了。跳轉到hd_probe_error函數,這個函數調用MSG函數連同general_error函數一道輸出“hard disk error”的字元。
好了,現在我們回來。剛開始經過一些列的判斷,我們進入了LBA模式。然後,代碼做了以下工作,movl 0x10(%si),%ecx,這個代碼就是個廢話,ecx寄存器被置入了一個無意義的值;然後將標號disk_address_packet處的位址賦給si,再接著將[si-1]記憶體處置1(也就是mode被置1,表示LBA擴展讀;如果是0,就是CHS定址讀)、將stage2的磁區數賦予ebx、在[si]和[si+1]處存放10和00(movw $0x0010,(si))、在[si+2]和[si+3]處存放01和00、在[si+4]和[si+5]處存放00和00、在[si+6]和[si+7]處存放0x00和0x70(這是stage1_bufferseg的值)、在[si+8][si+9][si+A][si+B]處存放0x01/0x00/0x00/0x00、在[si+c][si+d][si+e][si+f]處存放0x00/0x00/0x00/0x00。設置完畢後,開始調用int 13h的42功能中斷。如果出錯,就跳轉到chs_mode處。那麼中斷執行成功呢?
由si及其偏移量指向的記憶體保存著磁片參數塊,如下: ******************************************************************
偏移量 大小 位數 描述
00h BYTE 8 資料塊的大小 (10h or 18h)
01h BYTE 8 保留,必須為0
02h WORD 16 傳輸資料塊數,傳輸完成後保存傳輸的塊數
04h DWORD 32 傳輸時的資料緩存位址
08h QWORD 64 起始絕對磁區號(即起始磁區的LBA號碼) ******************************************************************
所以,通過int13h(42)中斷的作用,硬碟上第二個磁區上的內容就被讀到由si偏移量為4h、5h、6h、7h確定的記憶體區域上了,此處是0x7000:0x0000。執行成功,將bx賦值0x7000,然後跳至copy_buffer子函數處。
LBA已完,gemfield在閱讀copy_buffer前再回頭看當初程式跳至chs_mode後是怎麼運行的。上文中已經指出了,到達chs_mode後經過條件判斷,一共產生了三種情況,第一是進入硬碟的chs子函數(final_init);第二是進入軟碟副程式(floppy_probe);第三種情況是進入報錯子函數,在螢幕上輸出一系列錯誤。那就由gemfield從第一種情況開始吧。程式運行到final_init後,將磁區數保存到si、設置mode為0、eax清零為存放磁頭數做準備、將dh中存放的磁頭數保存到al中、使用incw %ax指令(因為磁頭數是以0~n-1方式排列的,所以增1後才是真正的磁頭數)、將磁頭數保存至[si+4][si+5][si+6][si+7]記憶體位址上、清dx為存放磁區數做準備、cl中的0~5位元存放的是磁區數,所以dx邏輯左移2位元後在dh中出現的兩位就是柱面數的高2位,並且把這2位移到ah中,而ch存放的柱面數低8為移至al中,這樣ax裏就是柱面數了,這裏因為同樣的道理要進行incw %ax操作,並且把真正的柱面數放到位址為[si+8][si+9]的記憶體上、然後用同樣的移動方法產生真正的磁區數並保存在位址為[si][si+1][si+2][si+3]的記憶體上。
然後在使用int 13h(0x02)功能前要進行必備的參數設置:eax存放stage2的磁區編號(stage2_sector,默認為1)、清edx寄存器、然後通過(stage2磁區數)/(磁區數)獲得引導磁區數。注意對於div指令來說,eax恒定存放被除數,div後面的寄存器存放的是除數。餘數在edx中存放,第一個餘數(磁區數)放到位址為[si+10]的記憶體上並將edx清零、再用(上一步除法的商) /(磁頭數)得到的餘數為磁頭數,存放在[si+11]記憶體位址上。商為柱面數並存放在eax中並同時保存至[si+12][si+13]記憶體位址上。然後將之前中斷獲得的柱面數與此處stage2所占柱面數相比較,如果stage2柱面數大,那麼明顯錯誤,程式將跳至geometry_error處。
現在,將[si+13]的內容賦值給dl(柱面數的高2位)並且左移6位元、將磁區數放到cl中再增1、然後通過orb %dl,%cl和movb 12(%si),%dh指令達到這麼一種情況,即:cl中存放的是磁區數和柱面數的高2位,ch中存放的是柱面數的低8位、然後恢復驅動器號(popw %dx)、然後將磁頭數放置到dh中,然後將0x7000賦值給es並將bx清零,賦值0x0201給ax(獲得中斷功能號),參數現在設置完畢,開始調用int 13h中斷: ******************************************************************
%al = number of sectors(需要讀的磁區數)
%ah= 0x02(功能號)
%ch = cylinder(起始柱面數)
%cl = sector (bits 6-7 are high bits of "cylinder")
%dh = head
%dl = drive (0x80 for hard disk, 0x0 for floppy disk)
%es:%bx = segment:offset of buffer ******************************************************************
調用中斷後,將0柱面、0磁軌、2磁區的內容讀到0x7000:0x0000記憶體處。然後程式跳轉至copy_buffer處,和LBA殊途同歸呀。
我們看看copy_buffer做了什麼。將0x8000賦值給es、給cx賦值0x100、給ds賦值0x7000、si和di清零、方向標誌DF置零,然後使用rep和movsw指令將ds:si處連續的512位元組內容傳輸到es:di指定的記憶體位址(0x8000:0x0000)。其中,rep指令的含義就是重複執行後一句指令,沒執行一次。cx減1,直至cx為0。這也是前面cx賦值0x100(256)的原因。movsw每次傳輸一個字,256次就是512位元組。然後popw %ds; popa還原寄存器。
接著,程式跳轉到0x8000處繼續執行,到此就開始執行新的模組了,stage1的任務也已經結束了。代碼中*(stage2_address)的星號是at&t彙編的規範:絕對跳轉/調用(相對於與程式計數器有關的跳轉/調用)運算元前面要加星號"*"。
然而,前面所述的chs模式中的第二種情況——軟盤機情況將會帶領gemfield進入floppy_probe子函數,此處要使用int 13h(0x00功能號)來進行軟盤機的復位。成功的話cf=0;然後準備調用int 13h(功能號是0x02),這和chs中的int 13h,ah=0x02是一樣的。所以,先來為中斷準備必須的參數:軟盤機復位後,將[si]處的值賦給cl(cl是起始磁區數),我們知道,由於迴圈,我們給了cl 4次機會,因為迴圈中有incw %si指令,所以si中的值是遞增的,從probe_values開始,在每一次的機會中依次給cl賦予了0x24、0x12、0x0f、0x09這幾種值,當然,試完後還不對的話就要執行報錯函數了。
像以前那樣,依次準備好bx、ah、al、ch、cl、dh的值後,就要int 13h了。成功後,dh賦值1、ch賦值0x4f,dh 設置為 79 , 表示柱面最大值為 79(80柱:0~79),dh 設置為 1 , 表示磁頭數最大值為 1(2頭:0~1),然後跳轉至 final_init,在上文中關於final_init的分析 , 我們知道保存時會把柱面和磁頭分別加 1 , 磁區不變,因此 , 在軟碟載入時 , 將設置 Cylinder : Head : Sector = 80 : 2 : start_sector。最終就跳轉至final_init函數處執行了。
gemfield的本文中,依然要注意的還有為了相容性而設置的windows nt魔術頭標識的偏移、part_start作為標識的分區表起始位址的標記的偏移、以及引導磁區結束標誌0xaa55。
總的說來,在gemfield這篇稍顯淩亂的文章裏,主要介紹了stage1.s的使命,簡介來說,就是開機時首先被BIOS INT19H裝載到記憶體0x7c00處,然後判斷chs和lba模式,然後使用int13h中斷將磁片上第二磁區的內容讀到0x7000處,然後通過子函數copy_buffer再將其調到0x8000的位置上,這個第二磁區的內容就是以後gemfield的嵌入系統版塊中將要介紹的start.s模組。
2010年10月20日 星期三
GRUB2太龐大,所以先轉戰GRUB4DOS
圖一
圖二
下載http://nufans.net/grub4dos/current_release/ 之grub4dos-0.4.4-2009-10-16-src.zip,在我的ubuntu910 下./configure結果產生錯誤如下:
checking whether objcopy works for absolute addresses... no
configure: error: GRUB requires a working absolute objcopy; upgrade your binutils
結果查了一下我的binutils版本已經是最新的。隨後又到其他連結download其它版本的source code,編譯一樣產生相同錯誤。所以本來想放棄,後來想說反正我的virtualbox裡頭還有ubuntu710版本,其gcc也相對比較舊。果不其然原來是binutils太新了,就這樣可以正常編譯成功了。後來我又試了其他版本的binutils 其objcopy版本只要大於GNU objcopy (GNU Binutils for Ubuntu) 2.18.0,則執行./configure就會出錯。
它的使用方法網路上已經很多了在此就不贅述。還是來研究它的source code,比較實際。
首先進入source code目錄,並下make clean及make distclean,接著再作一次./configure並產生Makefile。查看目錄結構如圖一,圖二為其他一起產生的子目錄Makefile;今天我們先說明這個Makefile如何產生的吧!其實這個在之前已經說明過,而且又查獲此處講解autoconf及automake,說明的太詳細了,所以直接參考它就可以了。
2010年10月3日 星期日
Bochs install for smp
Bochs安裝在windows時從http://sourceforge.net/projects/bochs/files/bochs/2.4.5/下載下來的Bochs-2.4.5.exe 雖然可以直接使用,而且還很好用,在windows下若要使用debug可以呼叫bochsdbg.exe,正常使用狀況一般都呼叫bochs.exe;並針對配置檔:bochsrc.bxrc作修改便可以使用。但是今天我的須求必須能夠執行在smp的狀況下;然而使用Bochs-2.4.5.exe 所安裝的版本是不支援smp的,因此必須下載source code,自己編譯及安裝,由於sourceforge提供了bochs-2.4.5-msvc-src.zip for windows vs2008的source code,下載下來安裝總是失敗,我想應該是什麼選項沒有去打開所造成的;這部分等我以後編譯成功再來說明。也因為沒有辦法在windows下使用smp並作debug,因此我便下載bochs-2.4.5.tar.gz ,並到linux下去build;以下是building and install的過程說明:
要在linux下安裝bochs首先必須先安裝幾個套件:g++、libc6-dev、build-essential、xorg-dev、libgtk2.0-dev;若是你使用ubuntu的Synaptic可以很快速的將這些套件安裝完成;若你是使用red hat linux,則請自行找到相對應的rpm檔。
我們知道編譯不過是幾個指令便可完成:./configure及make和make install。然而我們必須清楚到底我們要安裝哪些功能;
因此我們可以先下./configure --help從畫面上便可得知可設定的選項有哪些。所以我為了能夠順利編譯for SMP功能的bochs;所下指令如下:
(1)./configure --enable-smp --enable-x2apic --enable-debugger --enable-disasm --enable-vmx=2 --enable-configurable-msrs --enable-x86-64 --enable-a20-pin --enable-acpi --enable-pci
(2)make
(3)sudo make install
註:--enable-gdb-stub和--enable-debugger是互斥的
install完成時,在/usr/local/bin會有三個執行檔:bochs bxcommit bximage;但和windows狀況下不一樣的是少了bochsdbg;其實應該說debugger的功能也整合到bochs執行檔了。
接下來是如何使用bochs;首先是建立一個image檔,這部分可參考很多地方,請自行搞定;接著修改bochsrc配置檔:由於我的例子是使用floppy來boot,若是有使用ide boot請自行修正;
###############################################################
# bochsrc begin.
###############################################################
# how much memory the emulated machine will have
megs: 32
cpu: count=2, ips=10000000
# filename of ROM images
romimage: file=$BXSHARE/BIOS-bochs-latest
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
# what disk images will be used
floppya: 1_44=MultiCore.img, status=inserted
# hard disk
# ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
# ata0-master: type=disk, path="hd10meg.img", cylinders=306, heads=4, spt=17
# choose the boot disk.
boot: a
# default config interface is textconfig.
#config_interface: textconfig
#config_interface: wx
#display_library: x
# other choices: win32 sdl wx carbon amigaos beos macintosh nogui rfb term svga
# where do we send log messages?
log: bochsout.txt
############################################################### bochsrc end.
# bochsrc begin.
###############################################################
# how much memory the emulated machine will have
megs: 32
cpu: count=2, ips=10000000
# filename of ROM images
romimage: file=$BXSHARE/BIOS-bochs-latest
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
# what disk images will be used
floppya: 1_44=MultiCore.img, status=inserted
# hard disk
# ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
# ata0-master: type=disk, path="hd10meg.img", cylinders=306, heads=4, spt=17
# choose the boot disk.
boot: a
# default config interface is textconfig.
#config_interface: textconfig
#config_interface: wx
#display_library: x
# other choices: win32 sdl wx carbon amigaos beos macintosh nogui rfb term svga
# where do we send log messages?
log: bochsout.txt
############################################################### bochsrc end.
###############################################################
有了cpu: count=2, ips=10000000,這一行,便可產生雙核心了。
只要到含有bochsrc檔的目錄下執行以下指令便可開始進行debug了!
訂閱:
文章 (Atom)