?? 自己動手寫操作系統.txt
字號:
自己動手寫操作系統
--------------------------------------------------------------------------------
第八軍團 時間:2003-12-28 14:09:21
有人可能擔心自己既沒有學過計算機原理,也沒有學過操作系統原理,更不懂匯編語言,對C語言也一知半解,能寫操作系統嗎?答案是沒問題。我將帶大家一步一步完成自己的操作系統。當然如果學一學上述內容再好不過。
首先要明確處理器(也就是CPU)控制著計算機。對PC而言,啟動的時候,CPU都處在實模式狀態,相當于只是一個Intel 8086處理器。也就是說,即使你現在擁有一個奔騰處理器,它的功能也只能是8086級別。從這一點上來講,可以使用一些軟件把處理器轉換到著名的保護模式。只有這樣,我們才可以充分利用處理器的強大功能。
編寫操作系統開始是對BIOS控制,取出存儲在ROM里的程序。BIOS是用來執行POST(Power On Self Test,自檢)的。自檢是檢查計算機的完整性(比如外設是否工作正常、鍵盤是否連接等)。這一切完成以后,你就會聽到PC喇叭發出一聲清脆的響聲。如果一切正常,BIOS就會選擇一個啟動設備,并且讀取該設備的第一扇區(即啟動扇區),然后控制過程就會轉移到指定位置。啟動設備可能是一個軟盤、光盤、硬盤,或者其它所選擇的設備。在此我們把軟盤作為啟動設備。如果我們已經在軟盤的啟動扇區里寫了一些代碼,這時它就被執行。因此,我們的目的很明確,就是往軟盤的啟動扇區寫一些程序。
首先使用8086匯編來寫一個小程序,然后將其拷貝至軟盤的啟動扇區。為了實現拷貝,要寫一個C程序。最后,使用軟盤啟動計算機。
需要的工具
● as86:這是一個匯編程序,它負責把寫的代碼轉換成目標文件。
● ld86:這是一個連接器,as86產生的目標代碼由它來轉換成真正的機器語言。機器語言是8086能夠解讀的形式。
● GCC:著名的C編程器。因為我們需要寫一個C程序將自己的OS轉移到軟盤中。
● 一張空軟盤:它用于存儲編寫的操作系統,也是啟動設備。
● 一臺裝有Linux的計算機:這臺機器可以很舊,386、486都可以。
在大部分標準Linux發行版中都會帶有as86和ld86。在我使用的Red Hat 7.3中就包含有這兩個工具,并且在默認的情況下,它已經安裝在機器里。如果使用的Linux沒有這兩個工具,可以從網上下載(http://www.cix.co.uk/~mayday/),這兩個工具都包含在一個名為bin86的軟件包中。此外,有關的文檔也可以在網上獲得(www.linux.org/docs/ldp/howto/Assembly-HOWTO/as86.html)。
開始工作
使用一個你喜歡的編輯器輸入以下內容:
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處開始執行。0xb800是顯存的開始地址。#表明其后是一個立即數。執行語句:
mov ax,#oxb800
ax寄存器的值就變為0xb800,這就是顯存的地址。下面再將這個值移至es寄存器,es是附加段寄存器。請記住8086有一個分段的體系結構。它的各段寄存器為代碼段、數據段、堆棧段和附加段,對應的寄存器名稱分別為cs、ds、ss和es。事實上,我們把顯存地址送入了附加段,因此,任何送入附加段的東西都會被送到顯存中。
要在屏幕上顯示字符,就需要向顯存中寫兩個字節。前一個是所要顯示字符的ASCⅡ值,第二個字節表示該字符的屬性。屬性包括字符的前景色、背景色及是否閃爍等等。seg es指明下一個將要執行的指令是指向es段的。所以,我們把值0x41(在ASCⅡ中表示的字符是A)送到顯存的第一個字節中。接下來要把字符的屬性送到下一個字節當中。在此輸入的是0x1f,該屬性指的是在藍色背景下顯示白色的字符。因此,如果執行這個程序,就可以在屏幕上得到顯示在藍底上的一個白色的A。接著是一個循環。因為在執行完顯示字符的任務后,要么讓程序結束,要么使用一個循環使其永遠運行下去。把該文件命名為boot.s,然后存盤。
此處顯存的概念說得不是很清楚,有必要進一步解釋一下。假設屏幕由80列×25行組成,那么第一行就需要160字節,其中一個字節用于表示字符,另外一個字節用于表示字符的屬性。如果要在第三行顯示某一字符的話,就要跳過顯存的第0和1字節(它們是用于顯示第1列的),第2和3字節(它們是用于顯示第2列的),然后把需要顯示字符的ASCⅡ碼值入第4字節,把字符的屬性寫入第5字節。
把程序寫至啟動扇區
下面寫一個C程序,把我的操作系統寫入軟盤第一扇區。程序內容如下:
#include <sys/types.h> /* unistd.h 需要這個文件 */
#include <unistd.h> /* 包含有read和write函數 */
#include <fcntl.h>
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文件,然后在打開文件時把文件描述符復制到file_desc變量中。從文件中讀取510個字符,或者讀取直到文件結束。在本例中由于文件很小,所以是讀取至文件結束。然后關閉文件。
最后4行代碼打開軟盤驅動設備(一般來說是/dev/fd0)。使用lseek找到文件開始處,然后從緩沖中向軟盤寫512個字節。
在read、write、open和lseek的幫助頁中,可以看到與函數所有有關的參數及其使用方法。程序中有兩行比較難懂:
boot_buf[510] = 0x55;
boot_buf[511] = 0xaa;
該信息是用于BIOS的,如果它識別出該設備是一個可啟動的設備,那么在第510和511的位置,該值就應該是0x55和0xaa。程序會把文件boot讀至名為boot_buf的緩沖中。它要求改變第510和第511字節,然后把boot_buf寫至軟盤之上。如果執行代碼,軟盤上的前512字節就包含了啟動代碼。最后,把文件存為write.c。
編譯運行
使用下面的命令把文件變為可執行文件:
as86 boot.s -o boot.o
ld86 -d boot.o -o boot
cc write.c -o write
首先將boot.s文件編譯成目標文件boot.o,然后將該文件連接成最終的boot文件。最后C程序編譯成可執行的write文件。
插入一個空白軟盤,運行以下程序:
./write
重新啟動電腦,進行BIOS的界面設置,并且把軟盤設為第一個啟動的設備。然后插入軟盤,電腦從軟盤上啟動。
啟動完成后,在屏幕上可以看到一個字母A(藍底白字),啟動速度很快,幾乎是在瞬間完成。這就意味著系統已經從我們制作的軟盤上啟動了,并且執行了剛才寫入啟動扇區的程序。現在,它正處在一個無限循環的狀態。所以,如果想進入Linux,必需拿掉軟盤,并且重啟機器。
至此,這個操作系統就算完成了,雖然它沒有實現什么功能,但是它已經可以啟動機器了。
我講述了如何在軟盤的啟動扇區寫一些代碼,然后再從軟盤啟動的過程。制作好一個啟動扇區,在切換到保護模式之前,我們還應該知道如何使用BIOS中斷。BIOS中斷是一些由BIOS提供的、為了使操作系統的創建更容易的低級程序。在本文中,我們將學習處理BIOS的中斷。
為什么要用BIOS
BIOS會把啟動扇區拷貝至RAM中,并且執行這些代碼。除此之外,BIOS還要做很多其它的事情。當一個操作系統剛開始啟動時,系統中并沒有顯卡驅動、軟盤驅動等任何驅動程序。因此,啟動扇區中不可能包含任何一個驅動程序,我們要采取其它的途徑。這個時候,BIOS就可以幫助我們了。BIOS中包含有各種可以使用的程序,包括檢測安裝的設備、控制打印機、計算內存大小等用于各種目的的程序。這些程序就是所說的BIOS中斷。
如何調用BIOS中斷
在一般的程序設計語言中,函數的調用是一件非常容易的事情。比如在C語言中,如果有一個名為display的程序,它帶有兩個參數,其中參數noofchar表示顯示的字符數,參數attr表示顯示字符的屬性。那么要調用它,只需給出程序的名稱即可。對于中斷的調用,我們使用的是匯編語言中的int指令。
比如,在C語言中要顯示一些東西時,使用的指令如下所示:
display(nofchar,attr);
而使用BIOS時,要實現相同功能使用的指令如下:
int 0x10
如何傳遞參數
在調用BIOS中斷之前,我們需要先往寄存器中送一些特定的值。假設要使用BIOS的中斷13h,該中斷的功能是把數據從軟盤傳送至內存之中。在調用該中斷之前,要先指定拷貝數據的段地址,指定驅動器號、磁道號、扇區號,以及要傳送的扇區數等等。然后,就要往相應的寄存器送入相應的值。在進行下面的步驟前,讀者有必要對這一點有比較明確地認識。
此外,一個比較重要的事實是同一個中斷往往可以實現各種不同的功能。中斷所實現的確切功能取決于所選擇的功能號,功能號一般都存在ah寄存器之中。比如中斷13h可以用于讀磁盤、寫磁盤等功能,如果把3送入ah寄存器中,那么中斷選擇的功能就是寫磁盤;如果把2送入ah寄存器中,選擇的功能則是讀磁盤等。
我們要做的事情
這次我們的源代碼由兩個匯編語言程序和一個C程序組成。第一個匯編文件是引導扇區的代碼。在引導扇區中,我們寫的代碼是要把軟盤中第二扇區拷貝至內存段的0x500處(地址是0x5000,即偏移地址為0)。這時我們需要使用BIOS的中斷13h。這時啟動扇區的代碼就會把控制權轉移至0x500處。在第二個匯編文件中,代碼會使用BIOS中斷10h在屏幕上顯示一個信息。C程序實現的功能則是把可執行的文件1拷貝至啟動扇區,把可執行的文件2拷貝至軟盤的第二扇區。
啟動扇區代碼
使用中斷13h,啟動扇區把軟盤第二扇區里的內容加載至內存的0x5000處(段地址為0x500)。下面的代碼是用于實現這一目的的代碼,將其保存至文件sbect.s中。
LOC1=0x500
entry start
start:
mov ax,#LOC1
mov es,ax
mov bx,#0
mov dl,#0
mov dh,#0
mov ch,#0
mov cl,#2
mov al,#1
mov ah,#2
int 0x13
jmpi 0,#LOC1
上面代碼第一行類似于一個宏。接下去的兩行則是把值0x500加載至es寄存器中,這是軟盤上第二扇區代碼將拷貝到的地方(第一扇區是啟動扇區)。這時,把段內的偏移設為0。
接下來把驅動器號送入dl寄存器中,其中磁頭號送入dl寄存器中,磁道號送入ch寄存器中,扇區號送入cl寄存器中,扇區數送入al寄存器之中。我們想要實現的功能是把扇區2、磁道號為0、驅動器號為0的內容送至段地址0x500處。所有這些參數都和1.44MB的軟盤相對應。
把2送入ah寄存器中,是選擇了由中斷13h提供的相應功能,即實現從軟驅轉移數據的功能。
最后調用中斷13h,并且轉至偏移為0的段地址0x500處。
第二個扇區的代碼
第二個扇區中的代碼如下所示(把這些代碼保存至文件sbect2.s之中):
entry start
start:
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#26
mov bx,#0x0007
mov bp,#mymsg
mov ax,#0x1301
int 0x10
loop1: jmp loop1
mymsg:
.byte 13,10
.ascii “Operating System is Loading......”
上面代碼將被加載至段地址為0x500處,并且被執行。在這段代碼中,使用了中斷10h來獲取目前的光標位置,然后顯示信息。
從第3行到第5行用于得到目前光標的位置,在此中斷10h選用的是功能3。然后,清除了bh寄存器的內容,并把字符串送至ch寄存器中。在bx中,我們送入了頁碼及顯示的屬性。此處,我們想要在黑背景上顯示白色的字符。然后,把要顯示字符的地址送到bp之中,信息由兩個字節組成,其值分別為13的10,它們分別對應回車和LF(換行)的ASCⅡ值。接下來是一個由29個字符組成的串;在下面實現的功能是輸出字符串然后移動光標;最后是調用中斷,然后進入循 環。
C程序代碼
C程序的源代碼如下所示,將其存儲為write.c文件。
#include /* unistd.h needs this */
#include /* contains read/write */
#include
int main()
{
char boot_buf[512];
int floppy_desc, file_desc;
file_desc = open(“./bsect”, O_RDONLY);
read(file_desc, boot_buf, 510);
close(file_desc);
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -