01_实验一_操作系统的启动start
實(shí)驗(yàn)一 操作系統(tǒng)的啟動(dòng)
從源代碼到可運(yùn)行的操作系統(tǒng)(前置知識(shí))
API 與 SDK
以 C 語言編寫的操作系統(tǒng)為背景進(jìn)行介紹,EOS 是由 C 語言編寫的
操作系統(tǒng)和應(yīng)用程序之間一個(gè)重要的紐帶就是應(yīng)用程序接口(簡(jiǎn)稱 API)。操作系統(tǒng)通過開放 API 為應(yīng) > 用程序提供服務(wù),應(yīng)用程序通過使用這些 API 實(shí)現(xiàn)其功能。在操作系統(tǒng)或應(yīng)用程序運(yùn)行時(shí),API 可能只是
一個(gè)簡(jiǎn)單的調(diào)用和被調(diào)用的關(guān)系。但是在編寫操作系統(tǒng)的源代碼時(shí),必須要解決如何才能開放 API 的問題;
在編寫應(yīng)用程序的源代碼時(shí),又必須要解決如何才能使用 API 的問題。SDK 是 Software Development Kit 的縮寫,翻譯成中文就是“軟件開發(fā)工具包”。操作系統(tǒng)通過向開發(fā)者提供 SDK 來開放其 API,開發(fā)者在為操作系統(tǒng)編寫應(yīng)用程序時(shí),通過使用 SDK 來調(diào)用 API。所以,如果要為操作系統(tǒng)開發(fā)應(yīng)用程序,就需要首先獲得操作系統(tǒng)的 SDK。
SDK:一般采用文件的形式并結(jié)合特定的編程語言向開發(fā)者提供操作系統(tǒng)的 API。有些 SDK 還會(huì)提供相關(guān)的文檔、編程范例和工具軟件等。SDK 為了向開發(fā)者提供操作系統(tǒng)的 API,往往會(huì)包含頭文件、導(dǎo)入庫(kù)文件和動(dòng)態(tài)鏈接庫(kù)文件。
- 頭文件
以特定編程語言(C、C++等)編寫的文本文件,通常使用.H 做為后綴名。頭文件的主要作用是導(dǎo)出操作系統(tǒng)使用的一些數(shù)據(jù)類型(例如操作系統(tǒng)中使用的結(jié)構(gòu)體類型)和 API 函數(shù)的聲明。
- 導(dǎo)入庫(kù)文件
是根據(jù)操作系統(tǒng)需要導(dǎo)出的 API 函數(shù)而生成的特定格式的二進(jìn)制文件。
導(dǎo)入庫(kù)文件在 Linux 中的后綴名是.A,在 Windows 中的后綴名是.LIB。導(dǎo)入庫(kù)文件的主要作用是告訴應(yīng)用程序的可執(zhí)行文件,其調(diào)用的 API 函數(shù)在操作系統(tǒng)中的地址。導(dǎo)入庫(kù)文件一般會(huì)被放在 SDK 中的 Lib(Library)
- 動(dòng)態(tài)鏈接庫(kù)文件
包含了操作系統(tǒng)導(dǎo)出的 API 函數(shù)的可執(zhí)行代碼的二進(jìn)制文件。例如 Windows 導(dǎo)出的 API 函數(shù)主要保存在 kernel32.dll、user32.dll 和 gdi32.dll 三個(gè)文件中。動(dòng)態(tài)鏈接庫(kù)文件在 Linux 中的后綴名是.SO,在 Windows 中的后綴名是.DLL。動(dòng)態(tài)鏈接庫(kù)文件的格式一般與可執(zhí)行文件是相同的,只是不能直接執(zhí)行。應(yīng)用程序的可執(zhí)行文件在執(zhí)行時(shí)必須依賴這些動(dòng)態(tài)鏈接庫(kù)文件,因?yàn)槠湔{(diào)用的系統(tǒng) API 函數(shù)的可執(zhí)行代碼都保存在這些文件中。動(dòng)態(tài)鏈接庫(kù)文件一般會(huì)被放在 SDK 中的 Bin(Binary)文件夾中。Windows 提供的 SDK 中之所以沒有包含動(dòng)態(tài)鏈接庫(kù)文件,是因?yàn)樵?Windows 的系統(tǒng)目錄中已經(jīng)存在這些文件了。
EOS 操作系統(tǒng)內(nèi)核從源代碼變?yōu)榭梢栽谔摂M機(jī)上運(yùn)行的過程
- 在 IDE 環(huán)境將 EOS 操作系統(tǒng)內(nèi)核包含的源代碼文件生成為二進(jìn)制文件時(shí),會(huì)將 boot.asm 文件生成為 boot.bin 文件
(軟盤引導(dǎo)扇區(qū)程序),將 loader.asm 文件生成為 loader.bin 文件(加載程序),將其它的源代碼文件生
成為 kernel.dll 文件和 libkernel.a 文件。- 其中 kernel.dll 文件是 EOS 操作系統(tǒng)的內(nèi)核,EOS 操作系統(tǒng)的 API 函數(shù)就是從此文件導(dǎo)出的,所以在生成此文件的同時(shí)還要生成配套的導(dǎo)入庫(kù)文件 libkernel.a。
- 當(dāng) EOS 應(yīng)用程序的可執(zhí)行文件與導(dǎo)入庫(kù)文件 libkernel.a 鏈接后,就可以在執(zhí)行時(shí)調(diào)用 kernel.dll 文件導(dǎo)出的 API 函數(shù)了。
IDE 環(huán)境成功生成 EOS 的二進(jìn)制文件后,會(huì)自動(dòng)生成 EOS SDK。IDE 環(huán)境會(huì)首先新建一個(gè) SDK 文件夾,然后將 eos.h(導(dǎo)出 API 函數(shù)的聲明)、eosdef.h(導(dǎo)出數(shù)據(jù)類型的定義)和 error.h(導(dǎo)出錯(cuò)誤碼)三個(gè)頭文件復(fù)制到 SDK 文件夾中的 INC 文件夾中。
將生成的四個(gè)二進(jìn)制文件都復(fù)制到 BIN 文件夾中(EOS SDK 為了簡(jiǎn)單,將導(dǎo)入庫(kù)文件也放入了 BIN 文件夾,而沒有使用 LIB 文件夾)。
疑問:
EOS 操作系統(tǒng)的 API 函數(shù)不是從 kernel.dll 文件導(dǎo)出的嗎?為什么還要將 boot.bin 和 loader.bin 放入 SDK 中呢?在后面的介紹中讀者會(huì)得到這個(gè)問題的答案。
- 在 IDE 環(huán)境啟動(dòng)執(zhí)行 EOS 操作系統(tǒng)時(shí),會(huì)將 boot.bin、loader.bin 和 kernel.dll 三個(gè)二進(jìn)制文件寫入軟盤鏡像文件中,然后讓虛擬機(jī)來執(zhí)行軟盤中的 EOS 操作系統(tǒng)。
- 這三個(gè)二進(jìn)制文件是 EOS 操作系統(tǒng)運(yùn)行所必需的,之后的學(xué)習(xí)中會(huì)知道學(xué)會(huì)他們的作用。
- EOS 內(nèi)核源代碼可以生成兩種不同版本的二進(jìn)制文件:DEBUG 版本(有調(diào)試信息)和 RELEASE 版本(無調(diào)試信息)。IDE 環(huán)境會(huì)將 DEBUG 版本的二進(jìn)制文件復(fù)制到 SDK/BIN/DEBUG 文件夾中;將 RELEASE 版本的二進(jìn)制文件復(fù)制到 SDK/BIN/RELEASE 文件夾中。相應(yīng)的,EOS 應(yīng)用程序也會(huì)有 DEBUG 版本和 RELEASE 版本的可執(zhí)行文件,并且只能與對(duì)應(yīng)版本的 EOS 內(nèi)核二進(jìn)制文件一起使用。
EOS 應(yīng)用程序從源代碼變?yōu)榭梢栽谔摂M機(jī)上運(yùn)行的過程
- 在編寫 EOS 應(yīng)用程序的源代碼之前,必須首先獲得 EOS SDK 文件夾。然后,在 EOS 應(yīng)用程序的頭文件 eosapp.h 中包含 SDK/INC 文件夾中的三個(gè)頭文件。圖中用點(diǎn)劃線表示。
- 實(shí)際上,eosapp.h 只需要包含 eos.h 文件就即可,因?yàn)樵?eos.h 文件中已經(jīng)包含了 eosdef.h 和 error.h 文件。
- Eosapp.c 源文件和各個(gè)頭文件的包含關(guān)系可以參見下圖。
- 在 IDE 環(huán)境將 EOS 應(yīng)用程序包含的源代碼文件生成為二進(jìn)制文件時(shí),鏈接器會(huì)將由 eosapp.c 文件和 C 運(yùn)行時(shí)庫(kù)源代碼文件生成的目標(biāo)文件與 SDK/BIN 文件夾中的導(dǎo)入庫(kù)文件 libkernel.a 一同鏈接,生成 EOS 應(yīng)用程序的可執(zhí)行文件 eosapp.exe。
- 之前提出的為什么要將 boot.bin 和 loader.bin 也放入 SDK ?原因如下:
- 在 IDE 環(huán)境運(yùn)行 EOS 應(yīng)用程序時(shí),會(huì)將 SDK/BIN 文件夾中的 boot.bin、loader.bin 和 kernel.dll 寫入軟盤鏡像,這樣 EOS 操作系統(tǒng)才能夠啟動(dòng)運(yùn)行。同時(shí)會(huì)將 EOS 應(yīng)用程序的可執(zhí)行文件 eosapp.exe 和一個(gè)內(nèi)容為“eosapp.exe”的文本文件 autorun.txt 寫入軟盤鏡像,這樣在 EOS 操作系統(tǒng)成功啟動(dòng)后,就會(huì)自動(dòng)運(yùn) autorun.txt 文件中記錄的應(yīng)用程序可執(zhí)行文件 eosapp.exe。
EOS 編程基礎(chǔ)
EOS 內(nèi)核源代碼的結(jié)構(gòu)
在“項(xiàng)目管理器”窗口中可以看到如圖所示的結(jié)構(gòu)。
各個(gè)結(jié)構(gòu)的文件夾各自的說明如下表格:
| 文件夾或文件 | 說明 |
|---|---|
| api/ | 文件夾包含了內(nèi)存管理器的源代碼文件。此模塊公開的信息在 inc/mm.h 頭文件中聲明,供其他模塊使用。 |
| bochs/ | 包含了內(nèi)核對(duì)象管理器的源代碼文件。此模塊公開信息在 inc/ob.h 頭文件中聲明,供其他模塊使用 |
| boot/ | 只包含了兩個(gè)匯編文件 boot.asm 和 loader.asm。這兩個(gè)文件生成的二進(jìn)制文件 boot.bin 和 loader.bin 會(huì)被寫入軟盤文件 |
| inc/ | 此文件包含三個(gè)公共頭文件 eos.h eosdef.h error.h,會(huì)被復(fù)制到 SDK 文件夾的 INC 子文件夾中。文件還包括了各個(gè)模塊用于公開其信息的頭文件,通過包含這些頭文件,各個(gè)模塊間可以相互提供服務(wù)。 |
| io/ | 包括了 IO 管理器以及各種設(shè)備驅(qū)動(dòng)程序的源代碼文件。此模塊公開的信息在 inc/io.h 頭文件中聲明,供其他模塊使用。 |
| ke/ | 包含了 EOS內(nèi)核管理功能和系統(tǒng)進(jìn)程的源代碼文件。內(nèi)核管理功能包括了內(nèi)核初始化、中斷管理、時(shí)鐘管理和系統(tǒng)進(jìn)程等。其中 start.c 文件包含了內(nèi)核的入口函數(shù) KiSystemStartup 的源代碼,sysproc.c 文件包含了系統(tǒng)進(jìn)程的源代碼。此模塊的公開信息在 inc/ke.h 頭文件中聲明,供其他模塊使用。 |
| mm/ | 包含了內(nèi)存管理器的源代碼文件。此模塊公開的信息在 inc/mm.h 頭文件中聲明,供其他模塊使用。 |
| ob/ | 包含了內(nèi)核對(duì)象管理器的源代碼文件。此模塊公開的信息在 inc/ob.h 頭文件中聲明,供其他模塊使用 |
| ps/ | 包含了進(jìn)程、線程管理器的源代碼文件。此模塊的公開信息在 inc/ps.h 頭文件中聲明,供其他模塊使用。 |
| rtl/ | 此文件包含了內(nèi)核運(yùn)行時(shí)庫(kù)的源代碼文件。內(nèi)核運(yùn)行時(shí)庫(kù)為內(nèi)核各個(gè)模塊提供了一些常用的函數(shù)和數(shù)據(jù)結(jié)構(gòu)算法,例如 C 運(yùn)行時(shí)庫(kù)中的字符串函數(shù)在源文件 crt.c 中實(shí)現(xiàn),鏈表數(shù)據(jù)結(jié)構(gòu)算法在源文件 list.c 中實(shí)現(xiàn)。此模塊公開的信息在 inc/rtl.h 頭文件中聲明,供其他模塊使用。 |
| Floppy.img | 軟盤鏡像文件。由 EOS 內(nèi)核源代碼文件生成的二進(jìn)制文件會(huì)被寫入此軟盤鏡像文件中。然后虛擬機(jī)就可以執(zhí)行軟盤中的 EOS 操作系統(tǒng)。直接雙擊此文件,可以使用 Floppy Image Editor 工具打開此文件,并編輯軟盤中的內(nèi)容。 |
| License.txt | 《EOS 核心源代碼協(xié)議》 |
另外,有些文件夾中還包含了名稱為“i386”的子文件夾,這些子文件夾中包含了和本模塊相關(guān)的 X86 硬件平臺(tái)特有功能的源代碼。
預(yù)定義的 C 數(shù)據(jù)類型
待學(xué)習(xí) ing。
EOS 的啟動(dòng)過程
雖然操作系統(tǒng)可以從多種存儲(chǔ)設(shè)備啟動(dòng),例如硬盤、軟盤、光盤和閃存等。但是無論操作系統(tǒng)從哪種設(shè)備啟動(dòng),其基本原理都是一樣的:BIOS 程序首先將存儲(chǔ)設(shè)備的引導(dǎo)記錄(Boot Record)載入內(nèi)存,并執(zhí)行引導(dǎo)記錄中的引導(dǎo)程序;然后,引導(dǎo)程序會(huì)將存儲(chǔ)設(shè)備中的操作系統(tǒng)內(nèi)核載入內(nèi)存,并進(jìn)入內(nèi)核的入口點(diǎn)開始執(zhí)行;最后操作系統(tǒng)內(nèi)核完成系統(tǒng)的初始化,并允許用戶與操作系統(tǒng)進(jìn)行交互。
BIOS 程序的執(zhí)行過程
BIOS(Basic Input/Output System)是基本輸入輸出系統(tǒng)的簡(jiǎn)稱。BIOS 能為電腦提供最低級(jí)、最直接的硬件控制與支持,是聯(lián)系最底層的硬件系統(tǒng)和軟件系統(tǒng)的橋梁。為了在關(guān)機(jī)后使 BIOS 不會(huì)丟失,早期的 BIOS 存儲(chǔ)在 ROM 中,并且其大小不會(huì)超過 64KB;而目前的 BIOS 大多有 1MB 到 2MB,所以會(huì)被存儲(chǔ)在閃存(Flash Memory)中。
BIOS 用到的 CPU、軟盤、硬盤、顯卡、內(nèi)存等系統(tǒng)硬件配置信息,會(huì)和計(jì)算機(jī)的實(shí)時(shí)時(shí)鐘信息一起存放在一塊可讀寫的 CMOS 存儲(chǔ)器中。當(dāng)關(guān)機(jī)后,系統(tǒng)會(huì)通過一塊后備電池向 CMOS 供電,以確保其中的信息不會(huì)丟失。
BIOS 作用:
自檢及初始化
CPU 加電后會(huì)首先執(zhí)行 BIOS 程序,其中 POST(Power-On Self-Test)加電自檢程序是執(zhí)行的第一個(gè)例行程序,主要是對(duì) CPU、內(nèi)存等硬件設(shè)備進(jìn)行檢測(cè)和初始化。
設(shè)定中斷
BIOS 中斷調(diào)用即 BIOS 中斷服務(wù)程序,是計(jì)算機(jī)系統(tǒng)軟、硬件之間的一個(gè)可編程接口。開機(jī)時(shí),BIOS 會(huì)通知 CPU 各種硬件設(shè)備的中斷號(hào),并提供中斷服務(wù)程序。軟件可以通過調(diào)用 BIOS 中斷對(duì)軟盤驅(qū)動(dòng)器、鍵盤及顯示器等外圍設(shè)備進(jìn)行管理。
將 CPU 移交操作系統(tǒng)
BIOS 會(huì)根據(jù)在 CMOS 中保存的配置信息來判斷使用哪種設(shè)備啟動(dòng)操作系統(tǒng),并將 CPU 移交給操作系統(tǒng)使用。例如,如果是從軟盤啟動(dòng)操作系統(tǒng),BIOS 會(huì)在完成自檢及初始化后,將軟盤的引導(dǎo)扇區(qū)加載到物理內(nèi)存的 0x7C00 處,然后讓CPU 執(zhí)行軟盤引導(dǎo)扇區(qū)中的引導(dǎo)程序(從 0x7C00 處執(zhí)行),然后啟動(dòng)操作系統(tǒng)。
CPU 在加電瞬間,其各個(gè)寄存器會(huì)自動(dòng)初始化為默認(rèn)值,其中CS 和 IP 寄存器的默認(rèn)值指向了 BIOS 程序的第一條指令,從而使 CPU 開始執(zhí)行 BIOS 程序。BIOS 程序在執(zhí)行一些必要的開機(jī)自檢和初始化后,會(huì)將自己復(fù)制到從 0xA0000 開始的物理內(nèi)存中并繼續(xù)執(zhí)行。然后,BIOS 開始搜尋可引導(dǎo)的存儲(chǔ)設(shè)備。如果找到,則將存儲(chǔ)設(shè)備中的引導(dǎo)扇區(qū)讀入物理內(nèi)存 0x7C00 處,并跳轉(zhuǎn)到 0x7C00 繼續(xù)執(zhí)行,從而將 CPU 交給引導(dǎo)扇區(qū)中的 Boot 程序。
- 在 CPU 中,CS 的全拼為“Code Segment”,翻譯為“代碼段寄存器”,對(duì)應(yīng)于內(nèi)存中的存放代碼的內(nèi)存區(qū)域,用來存放內(nèi)存代碼段區(qū)域的入口地址(段基址)。
大小為 512 字節(jié))就會(huì) 被 BIOS 程序加載到物理內(nèi)存的 0x7C00 處。此時(shí)的物理內(nèi)存如圖 3-2 所示。其中,常規(guī)內(nèi)存(640K)與上位內(nèi)存(384K)組成了在實(shí)模式下 CPU 能夠訪問的 1M 地址空間。并且此時(shí)只有兩個(gè)區(qū)域的空白物理內(nèi)存可供正在運(yùn)行的 Boot 程序使用,即“用戶可用(1)”和“用戶可用(2)”。
Boot 程序的執(zhí)行過程
Loader 程序的執(zhí)行過程
內(nèi)核的初始化過程
實(shí)驗(yàn)正式開始
準(zhǔn)備過程
- 在“項(xiàng)目管理器”窗口中打開 boot/boot.asm 和 boot/loader.asm 兩個(gè)匯編文件。boot.asm 是軟盤引導(dǎo)扇區(qū)程序的源文件,loader.asm 是加載程序的源文件。簡(jiǎn)單閱讀一下這兩個(gè)文件中的 NASM 匯編代碼和注釋。
- 生成項(xiàng)目。
- 生成完成后,使用 Windows 資源管理器打開項(xiàng)目文件夾中的 Debug 文件夾。找到由 boot.asm 文件生成的軟盤引導(dǎo)扇區(qū)程序 boot.bin 文件,該文件的大小一定為 512 字節(jié)(與軟盤引導(dǎo)扇區(qū)的大小一致)。找到由 loader.asm 生成的加載程序 loader.bin 文件,記錄下此文件的大小 1566 字節(jié),在下面的實(shí)驗(yàn)過程中會(huì)用到。找到由其它源文件生成的 EOS 操作系統(tǒng)內(nèi)核文件 kernel.dll。
調(diào)試 EOS 操作系統(tǒng)的啟動(dòng)過程
使用 Boch Debug 作為遠(yuǎn)程目標(biāo)機(jī)
- 在“項(xiàng)目管理器”窗口中,右鍵點(diǎn)擊項(xiàng)目節(jié)點(diǎn),在彈出的快捷菜單中選擇“屬性”。
- 在彈出的“屬性頁”對(duì)話框右側(cè)的屬性列表中找到“遠(yuǎn)程目標(biāo)機(jī)”屬性,將此屬性值修改為“Bochs Debug”(此時(shí)按 F1 可以獲得關(guān)于此屬性的幫助)。
- 點(diǎn)擊“確定”按鈕關(guān)閉“屬性頁”對(duì)話框。接下來就可以使用 Bochs 虛擬機(jī)提供的調(diào)試功能單步調(diào)試 BIOS 程序和 EOS 操作系統(tǒng)的軟盤引導(dǎo)扇區(qū)程序了。
調(diào)試 BIOS 程序
- 啟動(dòng)調(diào)試,此時(shí)會(huì)彈出兩個(gè) Bochs 窗口。標(biāo)題為“Bochs for windows - Display”的窗口相當(dāng)于計(jì)算機(jī)的顯示器,用于顯示操作系統(tǒng)的輸出。標(biāo)題為“Bochs for windows - Console”的窗口是 Bochs 的控制臺(tái),用來輸入調(diào)試命令,輸出各種調(diào)試信息。
- 啟動(dòng)調(diào)試后,Bochs 會(huì)在 CPU 要執(zhí)行的第一條指令(即 BIOS 的第一條指令)處中斷。 此時(shí),Display 窗口還沒有顯示任何內(nèi)容,Console 窗口會(huì)顯示將要執(zhí)行的 BIOS 第一條指令的相關(guān)信息,并等待用戶輸入調(diào)試命令
- 行首的[0xfffffff0]表示此條指令所在的物理地址。
- f000:fff0 表示此條指令所在的邏輯地址(段地址:偏移地址),如果將其轉(zhuǎn)換為物理地址,則得到的物理地址與行首顯示的物理地址是一致的。
- jmp far f000:e05b 是此條指令的反匯編代碼。
- 行尾的 ea5be000f0 是此條指令的十六進(jìn)制字節(jié)碼,可以看出此條指令長(zhǎng)度為 5 個(gè)字節(jié)。
查看 CPU 在沒有執(zhí)行指令之前主要寄存器和內(nèi)存中的數(shù)據(jù):
- 在 Console 窗口中輸入調(diào)試命令 sreg 后按回車,顯示當(dāng)前 CPU 中各個(gè)段寄存器的值,如圖 10-2。其中 CS 寄存器信息行中的“s=0xf000”表示 CS 寄存器的值為 0xf000。
- 輸入調(diào)試命令 r 后按回車,顯示當(dāng)前 CPU 中各個(gè)通用寄存器的值,如圖 10-3。其中“rip: 0x00000000:0000fff0”表示 IP 寄存器的值為 0xfff0。
- 輸入調(diào)試命令 xp /1024b 0x0000,查看開始的 1024 個(gè)字節(jié)的物理內(nèi)存。在 Console 中輸出的這 1K 物理內(nèi)存的值都為 0,說明 BIOS 中斷向量表還沒有被加載到此處。
- 輸入調(diào)試命令 xp /512b 0x7c00,查看軟盤引導(dǎo)扇區(qū)應(yīng)該被加載到的內(nèi)存位置。輸出的內(nèi)存值都為 0,說明軟盤引導(dǎo)扇區(qū)還沒有被加載到此處。
- 通過以上的調(diào)試步驟可以驗(yàn)證 BIOS 第一條指令所在邏輯地址中的段地址和 CS 寄存器值是一致的,偏移地址和 IP 寄存器的值是一致的。由于此時(shí)還沒有執(zhí)行任何指令,內(nèi)存也還沒有被使用,所以其中的值都為 0。
- "段地址" 通常是指在計(jì)算機(jī)存儲(chǔ)器中的一個(gè)段(segment)的地址。在 x86 架構(gòu)的計(jì)算機(jī)中,內(nèi)存地址通常由兩個(gè)部分組成:段地址和偏移地址。段地址指的是存儲(chǔ)器中的段的起始地址,而偏移地址則是相對(duì)于段起始地址的偏移量。
調(diào)試軟盤引導(dǎo)扇區(qū)程序
BIOS 在執(zhí)行完自檢和初始化工作后,會(huì)將軟盤引導(dǎo)扇區(qū)(512 字節(jié))加載到物理地址 0x7c00-0x7dff 位置,并從 0x7c00 處的指令開始執(zhí)行引導(dǎo)程序,所以接下來練習(xí)從 0x7c00 處調(diào)試軟盤引導(dǎo)扇區(qū)程序:
以上這個(gè)是軟盤引導(dǎo)扇區(qū)被加載后的物理內(nèi)存的布局。
- 輸入調(diào)試命令 vb 0x0000:0x7c00,這樣就在邏輯地址 0x0000:0x7c00(相當(dāng)于物理地址 0x7c00)處添加了一個(gè)斷點(diǎn)。
- 輸入調(diào)試命令 c 繼續(xù)執(zhí)行,在 0x7c00 處的斷點(diǎn)中斷。中斷后會(huì)在 Console 窗口中輸出下一個(gè)要執(zhí)行的指令,即軟盤引導(dǎo)扇區(qū)程序的第一條指令,如下(0)
[0x00007c00] 0000:7c00 (unk. ctxt): jmp .+0x006d (0x00007c6f) ; eb6d
- 輸入調(diào)試命令 sreg 驗(yàn)證 CS 寄存器(0x0000)的值。
如下圖 CS 寄存器的數(shù)值為 0x0000;
- 輸入調(diào)試命令 r 驗(yàn)證 IP 寄存器(0x7c00)的值。
如下圖 IP 寄存器的數(shù)值為 0x7c00
- 由于 BIOS 程序此時(shí)已經(jīng)執(zhí)行完畢,輸入調(diào)試命令 xp /1024b 0x0000 驗(yàn)證此時(shí) BIOS 中斷向量表已經(jīng)被載入。
- 輸入調(diào)試命令 xp /512b 0x7c00 顯示軟盤引導(dǎo)扇區(qū)程序的所有字節(jié)碼。觀察此塊內(nèi)存最開始的兩個(gè)字節(jié)分別為 0xeb 和 0x6d,這和引導(dǎo)程序第一條指令的字節(jié)碼(eb6d)是相同的。此塊內(nèi)存最后的兩個(gè)字節(jié)分別為 0x55 和 0xaa,表示引導(dǎo)扇區(qū)是激活的,可以用來引導(dǎo)操作系統(tǒng),這兩個(gè)字節(jié)是 boot.asm 中最后一行語句。
- 輸入調(diào)試命令 xp /512b 0x0600 驗(yàn)證圖 3-2 中第一個(gè)用戶可用區(qū)域是空白的。
- 輸入調(diào)試命令 xp /512b 0x7e00 驗(yàn)證圖 3-2 中第二個(gè)用戶可用區(qū)域是空白的。
- 輸入調(diào)試命令 xp /512b 0xa0000 驗(yàn)證圖 3-2 中上位內(nèi)存已經(jīng)被系統(tǒng)占用。
下面是 Boch for Windows - Display 的界面圖
boot.lst 列表文件,幫助開發(fā)者調(diào)試 boot.asm 文件中的匯編代碼步驟
NASM 匯編器在將 boot.asm 生成為 boot.bin 的同時(shí),會(huì)生成一個(gè) boot.lst 列表文件,幫助開發(fā)者調(diào)試 boot.asm 文件中的匯編代碼。按照下面的步驟查看 boot.lst 文件:
-
選擇“視圖”菜單中的“項(xiàng)目管理器”,打開項(xiàng)目管理器窗口。
-
在“項(xiàng)目管理器”窗口中,右鍵點(diǎn)擊“boot”文件夾中的 boot.asm 文件。
-
在彈出的快捷菜單中選擇“打開生成的列表文件”,在源代碼編輯器中就會(huì)打開文件 boot.lst。
-
將 boot.lst 文件和 boot.asm 文件對(duì)比可以發(fā)現(xiàn),此文件包含了 boot.asm 文件中所有的匯編代碼,同時(shí)在代碼的左側(cè)又添加了更多的信息。
-
在 boot.lst 中查找到軟盤引導(dǎo)扇區(qū)程序第一條指令所在的行(第 73 行)
73 00000000 EB6Djmp short Start此行包含的信息有:
73 是行號(hào)。 00000000 是此條指令相對(duì)于程序開始位置的偏移(第一條指令應(yīng)該為 0)。 EB6D 是此條指令的字節(jié)碼,和之前記錄下來的指令字節(jié)碼是一致的。軟盤引導(dǎo)扇區(qū)程序的主要任務(wù)就是將軟盤中的 loader.bin 文件加載到物理內(nèi)存的 0x1000 處,然后跳轉(zhuǎn)到 loader 程序的第一條指令(物理地址 0x1000 處的指令)繼續(xù)執(zhí)行 loader 程序。按照下面的步驟調(diào)試此過程:
- 在 boot.lst 文件中查找到加載完畢 loader.bin 文件后要跳轉(zhuǎn)到 loader 程序中執(zhí)行的指令(第 278 行)
根據(jù)此指令相對(duì)于程序開始(0x7C00)的偏移 (0x0181)可以得到此指令的邏輯地址為 0x0000:7D81。
- 輸入調(diào)試命令 vb 0x0000:0x7d81 添加一個(gè)斷點(diǎn)。(這個(gè)斷點(diǎn)是相加計(jì)算出來的)
- 輸入調(diào)試命令 c 繼續(xù)執(zhí)行,到斷點(diǎn)處中斷。在 Console 窗口中顯示
此條指令會(huì)跳轉(zhuǎn)到物理內(nèi)存 0x1000 處(即 Loader 程序的第一條指令)繼續(xù)執(zhí)行。
- 按照打開 boot.lst 文件的方法打開 loader.lst 文件,并在此文件中查找到 loader 程序的第一條指令(第 33 行)
- 輸入調(diào)試命令 xp /8b 0x1000 查看內(nèi)存 0x1000 處的數(shù)據(jù),驗(yàn)證此塊內(nèi)存的前三個(gè)字節(jié)和 loader.lst 文件中的第一條指令的字節(jié)碼是相同的。說明 loader 程序已經(jīng)加載到從地址 0x1000 開始的內(nèi)存中了。
- 根據(jù)之前記錄的 loader.bin 文件的大小,自己設(shè)計(jì)一個(gè)查看內(nèi)存的調(diào)試命令,查看內(nèi)存中 loader 程序結(jié)束位置的字節(jié)碼,并與 loader.lst 文件中最后指令的字節(jié)碼比較。
loader.bin 的文件大小為 1566 字節(jié)(0h061e)。0x061e + 0x1000 = 0x161e
所以命令為
xp /1566b 0x1000結(jié)果如下:
比較后是一樣的。
至此調(diào)試軟盤引導(dǎo)扇區(qū)程序結(jié)束。
調(diào)試加載程序
Loader 程序的主要任務(wù)是將操作系統(tǒng)內(nèi)核文件(kernel.dll 文件)加載到內(nèi)存中,然后讓 CPU 進(jìn)入保護(hù)模式并且啟用分頁機(jī)制,最后進(jìn)入操作系統(tǒng)內(nèi)核開始執(zhí)行(調(diào)用 kernel.dll 的入口點(diǎn)函數(shù))。
調(diào)試過程如下:
- 在 loader.lst 文件中查找到準(zhǔn)備進(jìn)入 EOS 操作系統(tǒng)內(nèi)核執(zhí)行的指令(第 755 行)
- 計(jì)算此條指令物理地址的過程要復(fù)雜一些。首先,其偏移地址 0x14f 是相對(duì)于節(jié)(節(jié) SECTION 是 NASM 匯編中的概念)開始的。由于在 boot.asm 程序中只有一個(gè)節(jié),所以之前計(jì)算的結(jié)果都是正確的,但是在 loader.asm 程序中有兩個(gè)節(jié),并且此條指令是在第二個(gè)節(jié)(32 位的保護(hù)模式代碼)。下面引用的代碼是 loader.lst 文件中第一個(gè)節(jié)的最后一條指令(第 593 行)
因?yàn)榈谝粋€(gè)節(jié)中最后一條指令的偏移為 0x03c0,并且此條指令長(zhǎng)度為 3 個(gè)字節(jié)(字節(jié)碼為 C20600),所以在進(jìn)入第二個(gè)節(jié)之前的物理地址為 0x13c3(0x1000+0x03c0+0x3),由于第二個(gè)節(jié)是 32 位匯編程序,因此需要補(bǔ)一個(gè)字節(jié)確保四字節(jié)對(duì)齊,所以第二個(gè)節(jié)的起始物理地址是 0x13c4,從而可以計(jì)算出進(jìn)入內(nèi)核執(zhí)行的指令所在的物理地址為 0x1513(0x13c4+0x14f),如下圖所示。
- 使用添加物理地址斷點(diǎn)的調(diào)試命令 pb 0x1513 添加一個(gè)斷點(diǎn)。
- 輸入調(diào)試命令 c 繼續(xù)執(zhí)行,在剛剛添加的斷點(diǎn)處中斷。在 Console 窗口中顯示要執(zhí)行的下一條指令(注意,此時(shí)指令中訪問的地址均為邏輯地址):
(此時(shí)的 Display 窗口)
由于這里使用了函數(shù)指針的概念,所以,根據(jù)反匯編指令可以確定內(nèi)核入口點(diǎn)函數(shù)的地址就保存在邏輯地址 ds:0x8000117 處的四個(gè)字節(jié)中。
- 使用查看虛擬內(nèi)存的調(diào)試命令 x /1wx ds:0x80001117 查看內(nèi)存中保存的 32 位函數(shù)入口地址,在 Console 窗口中會(huì)輸出類似下面的內(nèi)容:
記錄下此塊內(nèi)存中保存的函數(shù)地址 0x80017de0,后面的實(shí)驗(yàn)會(huì)驗(yàn)證內(nèi)核入口點(diǎn)函數(shù)的地址與此地址是一致的。
- 選擇“調(diào)試”菜單中的“停止調(diào)試”菜單項(xiàng),停止調(diào)試。
調(diào)試內(nèi)核
前面使用 Bochs 虛擬機(jī)提供的調(diào)試功能了解了 EOS 操作系統(tǒng)的引導(dǎo)和加載過程,接下來可以繼續(xù)調(diào)試 EOS 操作系統(tǒng)的內(nèi)核,驗(yàn)證從加載程序進(jìn)入內(nèi)核入口點(diǎn)函數(shù)的過程。步驟如下:
- 在“項(xiàng)目管理器”窗口中,右鍵點(diǎn)擊項(xiàng)目節(jié)點(diǎn),在彈出的快捷菜單中選擇“屬性”。
- 在彈出的“屬性頁”對(duì)話框右側(cè)的屬性列表中找到“遠(yuǎn)程目標(biāo)機(jī)”屬性,將此屬性值修改為“Bochs GDB stub”。
-
點(diǎn)擊“確定”按鈕關(guān)閉“屬性頁”對(duì)話框。
-
在“項(xiàng)目管理器”窗口中打開 ke 文件夾中的 start.c 文件,此文件中只定義了一個(gè)函數(shù),就是操作系統(tǒng)內(nèi)核的入口點(diǎn)函數(shù) KiSystemStartup。
-
在 KiSystemStartup 函數(shù)中的代碼行(第 52 行)KiInitializePic();
添加一個(gè)斷點(diǎn)。
-
按 F5 啟動(dòng)調(diào)試,會(huì)在剛剛添加的斷點(diǎn)處中斷。
-
在 start.c 源代碼文件中的 KiSystemStartup 函數(shù)名上點(diǎn)擊鼠標(biāo)右鍵,在彈出的快捷菜單中選擇“添加監(jiān)視”,KiSystemStartup 函數(shù)就被添加到了“監(jiān)視”窗口中。在“監(jiān)視”窗口中可以看到此函數(shù)地址為
與之前記錄的在內(nèi)存 ds:x80001117 處保存的函數(shù)入口地址相同,說明的確是由 Loader 程序進(jìn)入了操作系統(tǒng)內(nèi)核。
- 按 F5 繼續(xù)執(zhí)行 EOS 操作系統(tǒng)內(nèi)核,在 Display 窗口中顯示 EOS 操作系統(tǒng)已經(jīng)啟動(dòng),并且控制臺(tái)程序已經(jīng)開始運(yùn)行了。
前后的狀態(tài)截圖如下:
- 刪除斷點(diǎn),停止調(diào)試。
EOS 啟動(dòng)后的狀態(tài)和行為
已經(jīng)完整學(xué)習(xí)了 EOS 操作系統(tǒng)的啟動(dòng)過程。當(dāng) EOS 啟動(dòng)完畢進(jìn)入內(nèi)核后,會(huì)進(jìn)一步完成一系列的內(nèi)核初始化工作,直到 EOS 操作系統(tǒng)可以接收用戶輸入的命令為止。因?yàn)閮?nèi)核初始化階段涉及到的知識(shí)較多,不太適合剛剛接觸操作系統(tǒng)原理的我深入學(xué)習(xí),所以,可以在全面掌握了操作系統(tǒng)概念后再回過頭來仔細(xì)研究 EOS 內(nèi)核的初始化過程,這對(duì)于深入理解操作系統(tǒng)原理也是有很大幫助的。但是,還是需要在這里了解一下 EOS 在完成內(nèi)核初始化后,處于什么樣的狀態(tài),具有什么樣的行為,這對(duì)于順利完成后續(xù)的實(shí)驗(yàn)是很重要的。
實(shí)驗(yàn)步驟如下:
- 在 ke/sysproc.c 文件的第 372 行,也就是“ver”命令函數(shù)中添加一個(gè)斷點(diǎn)。
- 啟動(dòng)調(diào)試。
- 待 EOS 啟動(dòng)完成后,在控制臺(tái)中輸入命令“ver”后按回車,會(huì)在剛剛添加的斷點(diǎn)處中斷。
- 刷新進(jìn)程線程窗口,會(huì)得到如圖所示的內(nèi)容。
表格中的數(shù)據(jù)進(jìn)行詳細(xì)的記錄和說明:
在進(jìn)程列表中只有一個(gè) ID 為 1 的系統(tǒng)進(jìn)程,其優(yōu)先級(jí)為 24,包含有 6 個(gè)線程,其中 ID 為 2 的線程是該進(jìn)程的主線程,系統(tǒng)進(jìn)程是沒有映像名稱的,或者可以認(rèn)為“kernel.dll”就是系統(tǒng)進(jìn)程的鏡像名稱。
在線程列表中有 6 個(gè)線程,它們都是系統(tǒng)線程。其中優(yōu)先級(jí)為 0 的是空閑線程,當(dāng)沒有優(yōu)先級(jí)大于 0 的線程占用處理器時(shí),空閑線程就會(huì)在處理器上運(yùn)行并處于運(yùn)行狀態(tài)(Running),否則就處于就緒狀態(tài)(Ready),隨時(shí)準(zhǔn)備繼續(xù)在處理器上運(yùn)行。ID 為 17 的線程是控制臺(tái)派遣線程,用于將鍵盤事件派遣到活動(dòng)的控制臺(tái)線程,所以在沒有鍵盤事件發(fā)生的時(shí)間里,該線程總是處于阻塞狀態(tài)(Waiting),等待鍵盤事件的到來。余下的四個(gè)線程都是控制臺(tái)線程,分別對(duì)應(yīng)于四個(gè)控制臺(tái),由于它們執(zhí)行的是同一個(gè)控制臺(tái)線程函數(shù)(ke/sysproc.c 文件中的 KiShellThread 函數(shù)),所以它們的起始地址都是相同的。控制臺(tái)線程只有在執(zhí)行控制臺(tái)命令的時(shí)候才會(huì)處于運(yùn)行狀態(tài),其它時(shí)間它們都在等待控制臺(tái)派遣線程為它們分配鍵盤事件,會(huì)處于阻塞狀態(tài)。由于本次是在控制臺(tái) 1 中執(zhí)行的 ver 命令,所以控制臺(tái) 1 對(duì)應(yīng)的 ID 號(hào)為 18 的線程會(huì)處于運(yùn)行狀態(tài)(使用綠色底色,并且由當(dāng)前線程指針 PspCurrentThread 指向),而其它的三個(gè)控制臺(tái)線程都處于阻塞狀態(tài)。
轉(zhuǎn)換控制臺(tái)之后,重新運(yùn)行,發(fā)現(xiàn)另一個(gè)線程處于運(yùn)行態(tài)。
驗(yàn)證當(dāng)沒有任何程序或者命令運(yùn)行時(shí)候,空閑線程 ID = 2 就會(huì)處于運(yùn)行狀態(tài)。
刪除所有斷點(diǎn)后,在 ke/sysproc.c 文件的第 143 行添加一個(gè)斷點(diǎn)。讀者會(huì)注意到這是在一個(gè)死循環(huán)中添加了一個(gè)斷點(diǎn),沒錯(cuò),當(dāng)沒有其它線程運(yùn)行時(shí),空閑線程總是會(huì)不停的執(zhí)行這個(gè)死循環(huán),直到有中斷發(fā)生,或者有更高優(yōu)先級(jí)的線程搶占了處理器。
也可以刪除全部斷點(diǎn)后,調(diào)試 輸入 pt命令查看 EOS 啟動(dòng)后的進(jìn)程和線程的信息。
如下圖所示:
在 FloppyImageEditor 中加入 Hello.exe 之后選擇“文件”菜單中的“保存”后關(guān)閉 FloppyImageEditor。
明哪個(gè)是應(yīng)用程序的進(jìn)程,它和系統(tǒng)進(jìn)程有什么區(qū)別,哪個(gè)是應(yīng)用程序的主線程,它和系統(tǒng)線程有什么區(qū)別。
為 EOS 內(nèi)核添加 help 命令
操作系統(tǒng)通常都會(huì)提供一個(gè) help 命令,用戶啟動(dòng)操作系統(tǒng)后,可以使用這個(gè)命令查看系統(tǒng)提供的命令和功能介紹,從而學(xué)習(xí)操作系統(tǒng)的基本使用方法。
要求:
修改 ke/sysproc.c 文件,在其中實(shí)現(xiàn) help 命令,其功能是列出系統(tǒng)提供的其它命令和功能介紹。
在這之前內(nèi)核中是沒有 help 命令的。
實(shí)驗(yàn)步驟:
- 在 ke/sysproc.c 文件中,添加 help 命令的函數(shù)的聲明
//help命令函數(shù)的聲明。
PRIVATE
VOID
ConsoleCmdHelp(
IN HANDLE StdHandle
);
//
- 在 ke/sysproc.c 文件中,修改 KiShellThread 函數(shù),在該函數(shù)中添加 help 控制臺(tái)命令。
else if (0 == stricmp(Line,"help")){
ConsoleCmdVersionNumber(StdHandle);
continue;
}
- 在 ke/sysproc.c 文件中,添加 ConsoleCmdHelp 函數(shù)的實(shí)現(xiàn)。
/*++
功能描述:
查看系統(tǒng)提供的命令和功能介紹
參數(shù):
返回值:
無。
--*/
PRIVATE VOID ConsoleCmdHelp(
IN HANDLE StdHandle
)
{
fprintf(StdHandle, "ver:show EOS version.\n");
//
// TODO
//
}
思考與練習(xí)
一
為什么 EOS 操作系統(tǒng)從軟盤啟動(dòng)時(shí)要使用 boot.bin 和 loader.bin 兩個(gè)程序?使用一個(gè)可以嗎?它們各自的主要功能是什么?如果將 loader.bin 的功能移動(dòng)到 boot.bin 文件中,則 boot.bin 文件的大小是否仍然能保持小于 512 字節(jié)?
二
軟盤引導(dǎo)扇區(qū)加載完畢后內(nèi)存中有兩個(gè)用戶可用的區(qū)域,為什么軟盤引導(dǎo)扇區(qū)程序選擇將 loader.bin 加載到第一個(gè)可用區(qū)域的 0x1000 處呢?這樣做有什么好處?這樣做會(huì)對(duì) loader.bin 文件的大小有哪些限制。
三
練習(xí)使用 Bochs 單步調(diào)試 BIOS 程序、軟盤引導(dǎo)扇區(qū)程序和 loader 程序,加深對(duì)操作系統(tǒng)啟動(dòng)過程的理解。
四
EOS 空閑線程在其死循環(huán)中不停的執(zhí)行 i++,從效率和節(jié)能的角度來說,這種方式都是不可取的。請(qǐng)讀者嘗試使用內(nèi)聯(lián)匯編將 i++替換為停機(jī)指令“HLT”
總結(jié)
以上是生活随笔為你收集整理的01_实验一_操作系统的启动start的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浏览器事件循环Event Loop
- 下一篇: 记一次 .NET 某券商论坛系统 卡死分