1. 程式人生 > >操作系統引導過程探究

操作系統引導過程探究

jmp 正常 內核代碼 指定位置 下一個 文章 處理機制 0地址 光盤

操作系統引導探究

Version 0.02修改記錄:

對與GDT有關的段描述符方面的描述進行了修訂,更正了上一個版本中出現的一些錯誤,增加了一些描述,使其更完善。

與上個版本中不同的地方均用紅色標記。

前言

本篇文章並不旨在完整的討論一個多引導系統程序怎樣去引導不同的操作系統,而只打算從編寫操作系統的角度出發,談談計算機怎樣從加電開始,從無到有,將操作系統運行起來,在其中將盡量詳盡的描述從實模式到保護模式的過渡,目的只在於能將所學與廣大愛好者共享,為希望開發操作系統的朋友留下一點資料,也為自己留下一點心得。

本篇文章將以開發中的pyos系統引導程序為例,pyos是一個正在開發中的實驗型操作系統,它並不打算以目前任何一種運行中的操作系統為模式,而只想通過自己編寫一個從頭到尾的操作系統來學習知識,積累技術,如果你有興趣,非常歡迎你的加入!

本篇純屬學習過程中的一點心得體會,如果你發現其中有錯誤或不當之處,非常希望你來信指教。

一、計算機從加電開始都做了什麽?

當機算機的電源鍵被按下時,同這個鍵相聯的電信號線就會送出一個電信號給主板,主板將此電信號傳給供電系統,供電系統開始工作,為整個系統供電,並送出一個電信號給BIOS,通知BIOS供電系統已經準備完畢。隨後BIOS啟動一個程序,進行主機自檢,主機自檢的主要工作是確保系統的每一個部分都得到了電源支持,內存儲器、主板上的其它芯片、鍵盤、鼠標、磁盤控制器及一些I/O端口正常可用,此後,自檢程序將控制權還給BIOS。接下來BIOS讀取BIOS中的相關設置,得到引導驅動器的順序,然後依次檢查,直到找到可以用來引導的驅動器(或說可以用來引導的磁盤,包括軟盤、硬盤、光盤等),然後調用這個驅動器上磁盤的引導扇區進行引導。BIOS是怎麽知道或說分辨哪一個磁盤可以用來引導的呢?

二、認識引導程序

BIOS將磁盤的第一個扇區(磁盤最開始的512字節)載入內存,放在0x0000:0x7c00處(見圖三),如果這個扇區的最後兩個字節是“55 AA”,那麽這就是一個引導扇區,這個磁盤也就是一塊可引導盤。通常這個大小為512B的程序就稱為引導程序(boot)。如果最後兩個字節不是“55 AA”,那麽BIOS就檢查下一個磁盤驅動器。

通過上面的表述我們可以總結出如下三點引導程序所具有的特點:

1. 它的大小是512B,不能多一字節也不能少一字節,因為BIOS只讀512B到內存中去。

2. 它的結尾兩字節必須是“55 AA”,這是引導扇區的標誌。

3. 它總是放在磁盤的第一個扇區上(0磁頭,0磁道,1扇區),因為BIOS只讀第一個扇區。

技術分享
(圖一)

因此,在我們編寫引導程序的時候,我們也必須註意上面的三點原則,符合上面三點原則的程序都可以看作是引導程序,至少BIOS是這樣認為的,雖然它也許可能是你隨意寫的一段並沒有什麽實際意義的代碼。

因為BIOS一次只讀一個扇區也即512字節的數據到內存中,這顯然是不夠的,現在操作系統都比較龐大,因此我們必須在引導扇區裏,將存在磁盤上的操作系統的核心部分讀進內存,然後再跳轉到操作系統的核心部分去執行。

三、通過BIOS讀磁盤扇區

從上面的描述我們可以知道,引導程序需要將存在於磁盤上的操作系統讀入內存,因此這裏我們不得不再講一講,怎樣不通過操作系統(因為現在還沒有操作系統)去讀取磁盤上的內容。一般說來這有兩種方法可以實現,一種是直接讀寫磁盤的I/O端口,一種是通過BIOS中斷實現。前一種方法是最低層的方法(後一種方法也是在它的基礎上實現的),具有極高的靈活性,可以將磁盤上的內容讀到內存中的任意地方,但編程復雜。第二種方法是前一種方法稍微高層一點的實現,犧牲了一點靈活性,比如,它不能把磁盤上的內容讀到0x0000:0x0000 ~ 0x0000:0x03FF處。為什麽不能讀到此處呢?這裏我們將不得不描述一下CPU在加電後的中斷處理機制。

3.1 BIOS的中斷處理

中斷是什麽?相信學過計算機的人都不會陌生,如果你對中斷一點都不了解建議你翻看一下《計算機組成原理》(高等教育出版社 唐朔飛),上面有非常詳盡的描述,而一般的匯編教材也多有談及,因此這裏只打算講講BIOS對中斷的處理。

技術分享
(圖二)

由上圖(圖二)我們可以清楚的看到,當中斷信號產生時,中斷信號通過“中斷地址形成部件”產生一個中斷向量地址,此向量地址其實就是指向一個實際內存地址的指針,而這個實際內存地址中往往安排一條跳轉指令(jmp)跳轉到實際處理此中斷的中斷服務程序中去執行。這一塊專門用於處理中斷跳轉的內存就被稱為中斷向量表。在內存中,這塊中斷向量表被放在什麽地方呢?而實際的中斷處理程序又在什麽地方呢?

3.2 系統的內存安排(1M)

要回答上面的兩個問題,我們需要看看系統中內存是怎麽安排的。在CPU被加電的時候,最初的1M的內存,是由BIOS為我們安排好了的,每一字節都有特殊的用處。

技術分享
(圖三)

由上圖我們現在可以很方便的問答上面提出的兩個問題。由於0x00000~0x003FF是中斷向量表所在,因此不能將磁盤中的操作系統讀到此處,因為這樣會覆蓋中斷向量表,就無法再通過BIOS中斷讀取磁盤內容了。你也許會說:我是先調用中斷,再讀的啊。但事實上BIOS在讀的過程中自己會多次調用其它中斷輔助完成。

3.3 利用BIOS 13號中斷讀取磁盤扇區

有了前面的描述作為基礎,下面我們可以正式描述怎樣通過BIOS中斷讀取磁盤扇區了。要讀取磁盤扇區,我們需要使用BIOS的13號中斷,13號中斷會將幾個寄存器的值作為其參數,因此,我們在調用13號中斷的過程中需要首先設置寄存器。那麽當怎樣設置寄存器呢?會用到哪些寄存器呢?請往下看:

AH寄存器:存放功能號,為2的時候,表示使用讀磁盤功能

DL寄存器:存驅動器號,表示欲讀哪一個驅動器

CH寄存器:存磁頭號,表示欲讀哪一個磁頭

CL寄存器:存扇區號,表示欲讀的起始扇區

AL寄存器:存計數值,表示欲讀入的扇區數量

在設置了這幾個寄存器後,我們就可以使用 int 13這條指令調用BIOS 13號中斷讀取指定的磁盤扇區,它將磁盤扇區讀到ES:BX處,因此,在調用它之前,我們實際上還需要設置ES與BX寄存器,以指出數據在內存中存放的位置。

四、保護模式下,段模式內存地址的訪問

寫程序離不開對內存的訪問,然而在保護模式下內存的訪問與在實模式下內存的訪問完全不同,這裏我們將詳細描述一下保護模式下內存的訪問方法。當然,這裏並不打算完整的介紹保護模式下所有的內存訪問方法與機制,只介紹從實模式轉到保護模式下所需要進行的轉換,完整的內存訪問請你參見《Intel用戶手冊》。當然,隨著pyos的實驗進行,我也會在後面的實驗報告與心得體會中漸漸描述。

4.1 實模式下的內存訪問

計算機在加電時,處於“實模式”,在計算機中有一個CR0寄存器,又稱為0號控制寄存器,在這個寄存器中,最低位也即第0位,被稱為PM(Protected Modle:保護模式)位,當它被清零的時候表示CPU在“實模式”下工作,當它被置位的時候表示CPU在“保護模式”下工作。在計算機加電的時候,它是被清零的,所在這個時候的計算機,處於“實模式”。

“實模式”下的內存訪問通過段寄存器與偏移量構成,比如前面描述中常常出現的0x:0000:0x0001就是一個實模式下的內存地址。分號前面的值表示段寄存器中的值,分號後面的值表示偏移量,實際物理地址的形成如下圖所示:

技術分享
(圖四)

然而在保護模式下,內存地址卻不是如上圖所示的方法形成的。那麽它又是怎樣形成的呢?

4.2 保護模式下的內存地址形成

保護模式下內存地址就復雜多了, 我們首先要分清三個概念:邏輯地址、線性地址與物理地址。物理地址很好理解,邏輯地址也好理解,就是程序所使用的地址。那麽什麽是線性地址呢?

其實如果不使用分頁機制的話,線性地址就是物理地址,它與物理地址是一一對應的,線性地址0,也就是物理地址0。但我們知道,32位的CPU擁有32根地址線,也就是可以訪問:

技術分享 = 4GB

的內存空間,這實在是一個太大的空間了!現在很少有機器的物理內存能有這麽大。那怎麽在有限的物理空間中使用4GB的空間呢?人們把物理內存分成許多頁,同樣也把整個4GB的線性地址空間分成大小相同的許多頁。在線性地址空間中,當某些頁被使用的時候,某些頁可能沒有被使用,操作系統可以讓CPU將沒有被使用的頁調出物理內存(存放在磁盤的某個地方,以備需要的時候再次調入),而把需要使用的頁調入,這樣,雖然物理內存空間有限,但也幾乎可以使用所有的線性地址空間了。這就稱為從線性地址到物理地址的映射,這是一個多對一的映射,也就是說多個線性空間中的頁對應一個物理空間中的頁,希望下面一幅圖能有助於你理解這樣的分頁機制。

技術分享
(圖五)

上面是一種最簡單的映射方式,術語稱作“直接相連”映射,它大約只能用來說明問題,而在一個實際的操作系統中通常是“全相聯相連”映射,也就是說線性地址中的頁可以是映射到物理地址中的任何一個頁中,只要那塊物理地址空間現在是空閑的。不過,通過上圖也能說明問題,當線性地址中的頁5需要被訪問時,CPU通過地址映射機制將其轉換到物理地址,發現其對應物理地址中的頁1。於是CPU會產生一個所謂的缺頁中斷來通知操作系統進行處理,操作系統相應這個中斷,並在中斷服務程序中將物理地址頁1中的內容放到磁盤上的一個地方(虛擬內存),然後將線性地址中的頁5載入物理內存頁1中。這裏就當可以比較明顯的區別什麽是線性地址,什麽是物理地址了。

然而,當不使用分頁機制的時候,線性地址就會被CPU當做物理地址來使用,線性地址會被直接放在CPU的地址信號線上。不過,在編寫應用程序的時候,我們通常使用的卻是另一種地址——邏輯地址,從邏輯地址到線性地址也存在著與上述機制類似的一種映射機制,不過這個機制常常稱為“段模式”,它是由操作系統與CPU硬件共同完成的。操作系統的任務就是分配映射表,而CPU硬件的任務就是按著映射表進行映射。而這樣的映射表在操作系統編寫中又稱之為“描述符表”,有兩種重要的描述符表,一種是“全局描述符表(GDT)”,另一種是“局部描述符表(LDT)”,這兩種表的用途不同,但它們的用法卻是近似的,下面我們就來描述一下全局描述符表。

說到表,學過數據結構的人都知道,其實它就是一種數據結構,全局描述符表也是一種數據結構,當這種結構放在一塊連續的內存之中就稱之為表了。表由表項組成,全局描述符表由它的表項“全局描述符”組成,其實單純的術語就叫“描述符”,只因為它放在全局描述符表中就成了全局描述符了。這個描述符由8個字節組成,下面我們就來看看它的結構:

技術分享
(圖六)

TYPE: 表明此段的類型,4位中的最高位被清0的時候表是它是數據段,相應的余下的三位,從左到右依次為E、W、A,即數據段的TYPE為:0EWA。其中E表示向下增長位,置1時表示向下增長(這主要是在大小需要動態改變的堆棧段中使用,如果是向下增長的段,動態的改變段的大小限制,會讓堆棧空間加到堆棧的底部。如果堆棧的大小不需要改變,那麽這個段既可以是向下增長的段,也可以是非向下增長的段);W表示可寫位,置1表示可寫;A表示被訪問位(如果CPU訪問了它,此位將會被置1)。

4位中的最高位被置1時表示它是代碼段,相應的余下的三位,從左到右依次為C、R、A,即代碼段的TYPE為:1CRA。其中C是表明此代碼段是否是一致代碼段,如果C被置1,表明此是一致代碼段。一致代碼段主要是用於特權級訪問控制,這在以後的實驗報告中會詳細論述;R表明此段是否可讀,置1表示可讀;A表示被訪問位,這與前述一樣。

S:為1時表示這是一個代碼段或數據段描述符,為0時表示這是一個系統段描述符。系統段描述符又稱為特殊段描述符,包括:局部描述符表(LDT)描述符,任務狀態段(TSS)描述符,調用門描述符,中斷門描述符,陷阱門及任務門描述符等。

DPL:表示特權級,從00~11,共0,1,2,3四個特權級

P:為0是表示此描述符無效,不能被使用

AVL:留給程序員隨便用的

D/B:為0的時候表示它是一個16位的段,為1時表示它是一個32位的段

G:為0時,表示段限的單位是1字節,為1時表示段限的單位是4KB,並且段偏移量的最低12位將不被檢測是否在段限之中。(這一點現在可能不好理解,但我下面馬上會解釋)。

這裏面有兩個部份比較有意思,一個是“基址”,一個是“段限”。基址應當比較好理解,它給出的是一個段在線性內存中的起始地址,對於“段限”,顧名思義,就是段大小的限制。不過它有點特別,對於一個段的最大可訪問的地址CPU是通過下面的公式計算得到的:

段基址 + 段限值 * 段限單位 =此段最大可訪問地址

如果一個偏移地址大於了此段最大可訪問地址的話,CPU就將產生一個錯誤中斷,這樣一來就可以防止一個程序非法訪問另一個程序的內存空間,這對內存起到了保護作用,“保護模式”由此得名。

所以,如果段限是0,那麽此段最大可訪問地址就是段的基址,因此,當段限單位為一字節時,此段的段大小就是1字節;當段限單位為4KB時,因為CPU將不檢測偏移量的最低12位,而這12位最大可能為0xFFF,因此,這時此段的可訪問範圍就為4KB,所以:

(段限值 + 1)*段限單位 = 此段大小

現在我們可以正式開始描述在保護模式下段模式是怎樣訪問內存的了。這裏之所以要強調“段模式”是因為在保護模式下還有一種前面敘述過的內存訪問模式——頁模式,它負責將線性地址再按某種映射轉換為物理地址。“頁模式”也是基於段模式的,在不使用它的情況下,線性地址會被直接放到地址線上當做物理地址使用。“段模式”是不可避免的,所謂的“純頁模式”只是將整個線性地址當作一個整段,沒有什麽方法可以真正繞過“段模式”,因為這是由CPU內存訪問機制所規定的。本篇只描述段模式。

我們已經知道從程序使用的邏輯地址到線性地址的映射是通過“描述符”來完成的,而“描述符”又是放在描述符表中的,那麽,一個描述表中有許多描述符,到底選用哪一個描述符呢?這就由一個索引來決定,這個索引將指出是表中的第幾個描述符,這個索引有一個專門的術語來描述,常常稱它為“段選擇子”。“段選擇子”由2個字節共16位組成,下面,我們就來看看它提供了哪些信息:

技術分享
(圖七)

其中:

RPL:指示出特權級,00~11,共0、1、2、3四個特權級,與前述一樣。

TI:為0時表明這是個用於全局描述符表的選擇子,為1時表明用於局部描述符表。

索引值:用來指示表中第幾個描述符。索引值共有13位,因此,每張描述符表共可有8K個表項,而一個表項如前所述,占8個字節,因此一張描述符表最大可達 64K。

不知道大家是否註意到這樣一個事實,如果將“段選擇子”的最後3位置0,這整個段選擇子其實就是一個描述符在描述符表中的偏移量!這裏我們可以發現Intel的工程師在設計的時候真的是非常精巧,如此的安排,可以使選取一個描述符的速度極大加快,因為將一個段選擇子最後3位清零後與描述符表的基址相加,就立即可以得到一個描述符的物理地址,通過這個地址就可以直接得到一個描述符。那麽這個描述符表的基址又是放在哪兒的呢?

所為描述符表的基址也就是此描述符表在內存中的起始地址,也即表中第一個描述符所在的內存地址,系統中用兩個特殊的寄存器來存放,一個用於存放全局描述符表的基址,稱之為“全局描述符表寄存器(GDTR)”,另一個用來存放局部描述符表的基址,稱之為“局部描述符表寄存器(LDTR)”,它們的結構如下圖所示:

技術分享
(圖八)

其中表限也即表的大小限制,它的使用與前面所描述的段限是類似的,因此,這裏就不在描述了。

在保護模式下,以前實模式下的段寄存器還是有用的,不過它不再用來存放段的基址,而是用來存放“段選擇子”,它的名字也變成了“段選擇子寄存器”,在訪問內存的時候,我們需要給出的是“段選擇子”,而不是段基址了。

比如,我現在想使用全局描述符表中第二個表項,即其中的第二個“段描述符”,這個“段選擇子”就需按如下的方式構成:

RPL:00,因為我們現在是在寫操作系統,工作在0特權級

TI:0,我們使用全局描述符

索引值:1,我們使用第二個全局描述符,第一個全局描述符編號為0,第二個為1

因此,我們的“段選擇子”為:0000 0000 0000 10000,也即 0x0008,因此,對於0x0008:0x0000 這樣一個邏輯地址,在保護模式下就應看成是使用全局描述符段中第二個描述符所描述的段,並且偏移量為0的內存地址。

這個邏輯地址的線性地址是怎樣形成的呢?請看如下的圖示:

技術分享
(圖九)

相信,從上圖中你可以清楚的看出一個邏輯地址是怎樣轉換為一個32位的線性地址的。

五、pyos引導程序編寫

pyos是一個正在編寫中的操作系統,是一個實驗中的項目,關於編寫的目的與動機我已在前言中談論過了,這裏,僅就此篇所講述的內容,談談pyos引導程序的編寫,在編寫期間參考了linux 0.11 內核引導程序的編寫,不過pyos並不是基於linux的,就它們的引導程序之間也有許多不一樣的地方。下面我們先來看看pyos的整個引導區的內存安排:

技術分享
(圖十)

上面一幅圖就是pyos的內存安排圖,也是引導程序流程圖,pyos是兩級引導系統,首先是boot被BIOS讀入,隨後boot讀入setup,setup讀入system程序到暫存區,然後把system程序搬到內存頂部,並建立指向system程序所在段的段描述符及建立GDT,然後切換CPU到保護模式,最後跳轉到system程序中執行,至此pyos系統引導完畢,system程序將是pyos真正的系統內核。圖中的數據存放區用來存發在boot、setup、pyos三者程序間需要傳遞的參數。

之所以做成兩級引導主要是考慮到以後擴展時的方便,各程序間都差不多是獨立的,以後可以重寫boot或者setup以提供更多可選擇的引導方式。System程序暫存區是因為如前所述,不能直接將數據讀到中斷向量表中覆蓋原中斷向量表,當數據讀完之後,不再調用中斷了,才將程序搬到內存頂部覆蓋原中斷向量表,對於保護模式下的中斷向量表,將由system程序負責建立,交給system使用的是一塊完整而幹凈的內存。

對於pyos進程內存安排,準備參照Linux 0.11 進行,內存安排如下:

技術分享
(圖十一,來源《Linux 0.11 內核代碼完全註釋》)

一個進程享有64M空間,4GB / 64M = 64,也即系統最大進程數為64。因此一個段的段限為:64MB,每個進程占用全局描術符表中兩個描述符,一個為數據段描述符,一個為代碼段描述符,段限均為64MB。

六、pyos引導程序源代碼

下面將提供pyos引導程序的全部源代碼,因為system還未完全完成,因此這裏只是讓它簡單的打印一個字符以示引導工作完成,代碼中已有較為詳盡的註釋,如果仍有不太清楚的地方,可以去http://purec.binghua.com(純C論壇)操作系統實驗專區,查看pyos以前的實驗報告,上面有非常詳盡的註釋及相關原理說明,並詳細描述了怎樣編譯及實驗。

;文件名: boot.asm

;作 者; 謝煜波

;Emailv: [email protected]

;

;內存分配如下

;內存起始地址為 0x90000

;最大結束地址為 0x9ffff

;最大共 64KB

;所有啟動代碼在一個段內,方便調用

;啟動代碼共分兩部分,一是boot,一是setup,這點照搬linux 0.11的設計

;但與其不同的是,boot不會將自己搬到0x90000處,而直接跳到 0x90100處運行

;0x90000~0x900ff (256B) 系統保留來存放一些從BIOS中取出的關鍵數據

;0x90100~0x904ff (1KB):此處開始存放setup,setup大小為1KB

[BITS 16] ;編譯成16位的指令

[ORG 0x7C00]

;-------------------------------------------------------------------------------------------

jmp Main

;-------------------------------------------------------------------------------------------

;數據定義

MSG db "Loading pyos ..." ;輸出信息

db 13 , 10 , 0 ;13表示回車,10表示換行,

;0表示字符串結束

BOOTSEG equ 0x0000 ;boot所在的段基址

SETUPSEG equ 0x9000 ;setup所在的段基址

SETUPOFFSET equ 0x0100 ;setup所在的偏移量

SETUPSIZE equ 1024 ;setup的大小,必須是512的倍數

BOOTDRIVER db 0 ;保存啟動的驅動器號

;-------------------------------------------------------------------------------------------

ShowMessage:

;以下程序行為顯示輸出信息

mov ah , 0x0e ;設置顯示模式

mov bh , 0x00 ;設置頁碼

mov bl , 0x07 ;設置字體屬性

.nextchar:

lodsb

or al , al

jz .return

int 0x10

jmp .nextchar

.return:

ret

;-------------------------------------------------------------------------------------------

Main:

mov [BOOTDRIVER] , dl ;得到啟動的驅動器號

;以下程序設置數據段

mov ax , BOOTSEG

mov ds , ax

mov si , MSG

call ShowMessage ;顯示信息

;讀入setup

;從磁盤的第二個扇區讀到0x90100處

.readfloopy:

mov ax , SETUPSEG

mov es , ax

mov bx , SETUPOFFSET

mov ah , 2

mov dl , [BOOTDRIVER]

mov ch , 0

mov cl , 2

mov al , SETUPSIZE / 512 ;讀入扇區數( 2個共1KB )

int 0x13

jc .readfloopy

;把啟動驅動器號保存在0x90000處

mov al , [BOOTDRIVER]

mov [0] , al

;跳轉

jmp SETUPSEG : SETUPOFFSET

;---------------------------------------------------------------------------

times 510-($-$$) db 0

db 0x55

db 0xAA

;文件名: setup.asm

;作 者; 謝煜波

;Emailv: [email protected]

;此setup程序完成boot未完成的啟動工作,

;包括從BIOS中讀出系統信息存放在指定位置

;初始化GDT,LDT表,完成從保護模式到實模式的轉換

;實模式的代碼也由此程序讀入

[BITS 16]

[ORG 0x0100]

;-------------------------------------------------------------------------------------

jmp Main

;-------------------------------------------------------------------------------------

SETUPSEG equ 0x9000

SETUPOFFSET equ 0x0100

SETUPSIZE equ 1024 ;setup的大小1KB,必須是 512 的倍數

SYSTEMSEG equ 0x0000

SYSTEMOFFSET equ 0x0000

SYSTEMSIZE equ 1024 ;SYSTEM的大小1KB,此值必須是 512 的倍數

;實際值可以不符

;下面定義臨時GDT表的描述符

;總共定義三個段,一個空段由intel保留,一個代碼段,一個數據段

gdt_addr:

dw 0x7fff ;GDT表的大小

dw gdt ;GDT表的位置

dw 0x0009

gdt:

gdt_null:

dw 0x0000

dw 0x0000

dw 0x0000

dw 0x0000

gdt_system_code:

dw 0x3fff ;段限(0x3fff+1)*4KB=64KB

dw 0x0000

dw 0x9a00

dw 0x00c0

gdt_system_data:

dw 0x3fff

dw 0x0000

dw 0x9200

dw 0x00c0

;-------------------------------------------------------------------------------------

;等待鍵盤控制器空閑的子程序

Empty_8042:

in al , 0x64

test al , 0x2

jnz Empty_8042

ret

;-------------------------------------------------------------------------------------

Main:

;初始化寄存器,因為Bios中斷及call會用到堆棧或ss寄存器

;在CPU啟動或復位時是由BIOS初始化的,而現在進行了段轉移,需要我們重新設置

mov ax , SETUPSEG

mov ds , ax

mov es , ax

mov ss , ax

mov sp , 0xffff

;-------------------------------------------------------------------------

;從BIOS中到底應讀出哪些有用信息,現在還不確定,因此暫時跳過此功能塊

;-----------------------------------------------

;0x90000 (1B): 保存啟動驅動器號,由boot程序存入

;--------------------------------------------------------------------------

;下面讀入system到setup程序的後面

;因為0x00000現在是放BIOS中斷的地方,因此還不能直接將system讀到0x00000處,

;否則將無法調用BIOS中斷讀入磁盤

.readfloopy:

mov ax , SETUPSEG

mov es , ax

mov bx , SETUPOFFSET + SETUPSIZE

mov ah , 2

mov dl , [0]

mov ch , 0

mov cl , 1 + 1 + SETUPSIZE / 512 ;system所在的啟始扇區

;第一個1是指從1開始記數

;第二個1是boot所占扇區數

mov al , SYSTEMSIZE / 512 ;讀入扇區數( 2個扇區共1KB )

int 0x13

jc .readfloopy

;下面將讀入的system搬移到0x00000位置

cld

mov si , SETUPOFFSET + SETUPSIZE

mov ax , SYSTEMSEG

mov es , ax

mov di , SYSTEMOFFSET

mov cx , SYSTEMSIZE / 4

rep movsd

;下面開始為進入保護模式而進行初始化工作

cli ;關中斷

lgdt [gdt_addr] ;載入gdt的描述符

;下面打開A20地址線

call Empty_8042

mov al , 0xd1

out 0x64 , al

call Empty_8042

mov al , 0xdf

out 0x60 , al

call Empty_8042

;下面設置進入32位保護模式運行

mov eax , cr0

or eax , 1

mov cr0 , eax

jmp dword 0x8:0x0

;-------------------------------------------------------------------------------------

times 1024-($-$$) db 0

;文件名: kernel.asm

;作 者; 謝煜波

;Emailv: [email protected]

[BITS 32]

[ORG 0x0]

;------------------------------------------------------------------------------------------

jmp Main

;------------------------------------------------------------------------------------------

Main:

;設置寄存器

mov ax , 0x10

mov ds , ax

mov cl , ‘1‘

mov [0xb8000] , cl

mov cl , 0x04

mov [0xb8001] , cl

jmp $

以上程序中有一個地方本篇及以前的實驗報告中也未提到,這就是A20地址線的問題,對於有關A20地址線的問題,在《Linux 0.11 內核源代碼完全註釋》中有非常詳細的描述,作者還列舉了其它幾種打開A20地址線的方法,並分析了可能存在的問題。這是一本非常好的書,推薦大家閱讀。純C論壇上(http://purec.binghua.com)可以下載本書的電子版(PDF格式),也可以在上面找到另外一些相關資源。

下面就是運行時的截圖,現在它只能引導,什麽也幹不了,希望下次它能多幹一點~~

技術分享

參考資料

1. 《IA-32 Intel® Architecture Software Developer’s Manual Volume 3:System Programming Guide》 (Intel 2001)

2. 《Linux 內核 0.11 完全註釋》(趙炯,2003)

3. 《計算機組成原理》(唐朔飛,高等教育出版社)

【全文完·責任編輯:[email protected]】

註: 此文發表在<<純C論壇·電子雜誌>>2004.10期上
(http://purec.binghua.com/Soft/Class2/dl_hpcem/200410/74.html)

http://blog.csdn.net/daiyutage/article/details/8895895

操作系統引導過程探究