自己動手寫操作系統(tǒng)(一)
2024-07-21 02:37:15
供稿:網(wǎng)友
自由軟件社區(qū)是一個布滿自由和夢想的地方,在10余年的時間里它創(chuàng)造了一個又一個奇跡。然而,這些奇跡的創(chuàng)造者不只是Stallman,也不只是Linus Torvalds,而是活躍在世界各地的不計其數(shù)的開發(fā)人員。
在使用各種功能強大的自由軟件時,我總會對其開發(fā)者布滿崇敬之情,期盼有朝一日自己也能成為他們中的一員。很多對自由社區(qū)布滿向往之情的人,雖然也想努力融身于其中,但又不知該怎么做。那么,就請與我們一起從編寫一個簡單的操作系統(tǒng)開始吧!
我們要做的事情
有人可能擔心自己既沒有學過計算機原理,也沒有學過操作系統(tǒng)原理,更不懂匯編語言,對C語言也一知半解,能寫操作系統(tǒng)嗎?答案是沒問題。我將帶大家一步一步完成自己的操作系統(tǒng)。當然假如學一學上述內(nèi)容再好不過。
首先要明確處理器(也就是CPU)控制著計算機。對PC而言,啟動的時候,CPU都處在實模式狀態(tài),相當于只是一個Intel 8086處理器。也就是說,即使你現(xiàn)在擁有一個奔騰處理器,它的功能也只能是8086級別。從這一點上來講,可以使用一些軟件把處理器轉(zhuǎn)換到聞名的保護模式。只有這樣,我們才可以充分利用處理器的強大功能。
編寫操作系統(tǒng)開始是對BIOS控制,取出存儲在ROM里的程序。BIOS是用來執(zhí)行POST(Power On Self Test,自檢)的。自檢是檢查計算機的完整性(比如外設(shè)是否工作正常、鍵盤是否連接等)。這一切完成以后,你就會聽到PC喇叭發(fā)出一聲清脆的響聲。假如一切正常,BIOS就會選擇一個啟動設(shè)備,并且讀取該設(shè)備的第一扇區(qū)(即啟動扇區(qū)),然后控制過程就會轉(zhuǎn)移到指定位置。啟動設(shè)備可能是一個軟盤、光盤、硬盤,或者其它所選擇的設(shè)備。在此我們把軟盤作為啟動設(shè)備。假如我們已經(jīng)在軟盤的啟動扇區(qū)里寫了一些代碼,這時它就被執(zhí)行。因此,我們的目的很明確,就是往軟盤的啟動扇區(qū)寫一些程序。
首先使用8086匯編來寫一個小程序,然后將其拷貝至軟盤的啟動扇區(qū)。為了實現(xiàn)拷貝,要寫一個C程序。最后,使用軟盤啟動計算機。
需要的工具
● as86:這是一個匯編程序,它負責把寫的代碼轉(zhuǎn)換成目標文件。
● ld86:這是一個連接器,as86產(chǎn)生的目標代碼由它來轉(zhuǎn)換成真正的機器語言。機器語言是8086能夠解讀的形式。
● GCC:聞名的C編程器。因為我們需要寫一個C程序?qū)⒆约旱腛S轉(zhuǎn)移到軟盤中。
● 一張空軟盤:它用于存儲編寫的操作系統(tǒng),也是啟動設(shè)備。
● 一臺裝有l(wèi)inux的計算機:這臺機器可以很舊,386、486都可以。
在大部分標準Linux發(fā)行版中都會帶有as86和ld86。在我使用的Red Hat 7.3中就包含有這兩個工具,并且在默認的情況下,它已經(jīng)安裝在機器里。假如使用的Linux沒有這兩個工具,可以從網(wǎng)上下載(http://www.cix.co.uk/~mayday/),這兩個工具都包含在一個名為bin86的軟件包中。此外,有關(guān)的文檔也可以在網(wǎng)上獲得(www.linux.org/docs/ldp/howto/Assembly-HOWTO/as86.Html)。
開始工作
使用一個你喜歡的編輯器輸入以下內(nèi)容:
entry start
start:
mov ax,#0xb800
mov es,ax
seg es
mov [0],#0x41
seg es
mov [1],#0x1f
loop1: jmp loop1
這是as86可以讀懂的一段匯編程序。第一個句子指明了程序的入口點,聲明整個過程從start處開始。第二行指明了start的位置,說明整個程序要從start處開始執(zhí)行。0xb800是顯存的開始地址。#表明其后是一個立即數(shù)。執(zhí)行語句:
mov ax,#oxb800
ax寄存器的值就變?yōu)?xb800,這就是顯存的地址。下面再將這個值移至es寄存器,es是附加段寄存器。請記住8086有一個分段的體系結(jié)構(gòu)。它的各段寄存器為代碼段、數(shù)據(jù)段、堆棧段和附加段,對應(yīng)的寄存器名稱分別為cs、ds、ss和es。事實上,我們把顯存地址送入了附加段,因此,任何送入附加段的東西都會被送到顯存中。
要在屏幕上顯示字符,就需要向顯存中寫兩個字節(jié)。前一個是所要顯示字符的ASCⅡ值,第二個字節(jié)表示該字符的屬性。屬性包括字符的前景色、背景色及是否閃爍等等。seg es指明下一個將要執(zhí)行的指令是指向es段的。
所以,我們把值0x41(在ASCⅡ中表示的字符是A)送到顯存的第一個字節(jié)中。接下來要把字符的屬性送到下一個字節(jié)當中。在此輸入的是0x1f,該屬性指的是在藍色背景下顯示白色的字符。因此,假如執(zhí)行這個程序,就可以在屏幕上得到顯示在藍底上的一個白色的A。接著是一個循環(huán)。因為在執(zhí)行完顯示字符的任務(wù)后,要么讓程序結(jié)束,要么使用一個循環(huán)使其永遠運行下去。把該文件命名為boot.s,然后存盤。
此處顯存的概念說得不是很清楚,有必要進一步解釋一下。假設(shè)屏幕由80列×25行組成,那么第一行就需要160字節(jié),其中一個字節(jié)用于表示字符,另外一個字節(jié)用于表示字符的屬性。假如要在第三行顯示某一字符的話,就要跳過顯存的第0和1字節(jié)(它們是用于顯示第1列的),第2和3字節(jié)(它們是用于顯示第2列的),然后把需要顯示字符的ASCⅡ碼值入第4字節(jié),把字符的屬性寫入第5字節(jié)。
把程序?qū)懼羻由葏^(qū)
下面寫一個C程序,把我的操作系統(tǒng)寫入軟盤第一扇區(qū)。程序內(nèi)容如下:
#include <sys/types.h> /* unistd.h 需要這個文件 */
#include /* 包含有read和write函數(shù) */
#include
int main()
{
char boot_buf[512];
int floppy_desc, file_desc;
file_desc = open("./boot", O_RDONLY);
read(file_desc, boot_buf, 510);
close(file_desc);
boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;
floppy_desc = open("/dev/fd0", O_RDWR);
lseek(floppy_desc, 0, SEEK_CUR);
write(floppy_desc, boot_buf, 512);
close(floppy_desc);
}
首先,以只讀模式打開boot文件,然后在打開文件時把文件描述符復(fù)制到file_desc變量中。從文件中讀取510個字符,或者讀取直到文件結(jié)束。在本例中由于文件很小,所以是讀取至文件結(jié)束。然后關(guān)閉文件。
最后4行代碼打開軟盤驅(qū)動設(shè)備(一般來說是/dev/fd0)。使用lseek找到文件開始處,然后從緩沖中向軟盤寫512個字節(jié)。
在read、write、open和lseek的幫助頁中,可以看到與函數(shù)所有有關(guān)的參數(shù)及其使用方法。程序中有兩行比較難懂:
boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;
該信息是用于BIOS的,假如它識別出該設(shè)備是一個可啟動的設(shè)備,那么在第510和511的位置,該值就應(yīng)該是0x55和0xaa。程序會把文件boot讀至名為boot_buf的緩沖中。它要求改變第510和第511字節(jié),然后把boot_buf寫至軟盤之上。假如執(zhí)行代碼,軟盤上的前512字節(jié)就包含了啟動代碼。最后,把文件存為write.c。
編譯運行
使用下面的命令把文件變?yōu)榭蓤?zhí)行文件:
as86 boot.s -o boot.o
ld86 -d boot.o -o boot
cc write.c -o write
首先將boot.s文件編譯成目標文件boot.o,然后將該文件連接成最終的boot文件。最后C程序編譯成可執(zhí)行的write文件。
插入一個空白軟盤,運行以下程序:
./write
重新啟動電腦,進行BIOS的界面設(shè)置,并且把軟盤設(shè)為第一個啟動的設(shè)備。然后插入軟盤,電腦從軟盤上啟動。
啟動完成后,在屏幕上可以看到一個字母A(藍底白字),啟動速度很快,幾乎是在瞬間完成。這就意味著系統(tǒng)已經(jīng)從我們制作的軟盤上啟動了,并且執(zhí)行了剛才寫入啟動扇區(qū)的程序?,F(xiàn)在,它正處在一個無限循環(huán)的狀態(tài)。所以,假如想進入Linux,必需拿掉軟盤,并且重啟機器。
至此,這個操作系統(tǒng)就算完成了,雖然它沒有實現(xiàn)什么功能,但是它已經(jīng)可以啟動機器了。
下一期我將在這個啟動扇區(qū)程序里加入一些代碼,使它可以做一些比較復(fù)雜的事情(比如使用BIOS中斷、保護模式切換等等)。