• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            旅途

            如果想飛得高,就該把地平線忘掉

            編寫自己的操作系統(tǒng) 轉

            編寫自己的操作系統(tǒng)- -

            ??????????????????????????????????????

            看看吧!很不錯的!

            自由軟件社區(qū)是一個充滿自由和夢想的地方,在10余年的時間里它創(chuàng)造了一個又一個奇跡。然而,這些奇跡的創(chuàng)造者不只是Stallman,也不只是Linus Torvalds,而是活躍在世界各地的不計其數(shù)的開發(fā)人員。


            作者:伊梅

            自由軟件社區(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級別。從這一點上來講,可以使用一些軟件把處理器轉換到著名的保護模式。只有這樣,我們才可以充分利用處理器的強大功能。

            編寫操作系統(tǒng)開始是對BIOS控制,取出存儲在ROM里的程序。BIOS是用來執(zhí)行POST(Power On Self Test,自檢)的。自檢是檢查計算機的完整性(比如外設是否工作正常、鍵盤是否連接等)。這一切完成以后,你就會聽到PC喇叭發(fā)出一聲清脆的響聲。如果一切正常,BIOS就會選擇一個啟動設備,并且讀取該設備的第一扇區(qū)(即啟動扇區(qū)),然后控制過程就會轉移到指定位置。啟動設備可能是一個軟盤、光盤、硬盤,或者其它所選擇的設備。在此我們把軟盤作為啟動設備。如果我們已經(jīng)在軟盤的啟動扇區(qū)里寫了一些代碼,這時它就被執(zhí)行。因此,我們的目的很明確,就是往軟盤的啟動扇區(qū)寫一些程序。

            首先使用8086匯編來寫一個小程序,然后將其拷貝至軟盤的啟動扇區(qū)。為了實現(xiàn)拷貝,要寫一個C程序。最后,使用軟盤啟動計算機。


            需要的工具


            ● as86:這是一個匯編程序,它負責把寫的代碼轉換成目標文件。

            ● ld86:這是一個連接器,as86產(chǎn)生的目標代碼由它來轉換成真正的機器語言。機器語言是8086能夠解讀的形式。

            ● GCC:著名的C編程器。因為我們需要寫一個C程序將自己的OS轉移到軟盤中。

            ● 一張空軟盤:它用于存儲編寫的操作系統(tǒng),也是啟動設備。

            ● 一臺裝有Linux的計算機:這臺機器可以很舊,386、486都可以。

            在大部分標準Linux發(fā)行版中都會帶有as86和ld86。在我使用的Red Hat 7.3中就包含有這兩個工具,并且在默認的情況下,它已經(jīng)安裝在機器里。如果使用的Linux沒有這兩個工具,可以從網(wǎng)上下載(http: //www.cix.co.uk/~mayday/),這兩個工具都包含在一個名為bin86的軟件包中。此外,有關的文檔也可以在網(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有一個分段的體系結構。它的各段寄存器為代碼段、數(shù)據(jù)段、堆棧段和附加段,對應的寄存器名稱分別為cs、ds、ss和es。事實上,我們把顯存地址送入了附加段,因此,任何送入附加段的東西都會被送到顯存中。

            要在屏幕上顯示字符,就需要向顯存中寫兩個字節(jié)。前一個是所要顯示字符的ASCⅡ值,第二個字節(jié)表示該字符的屬性。屬性包括字符的前景色、背景色及是否閃爍等等。seg es指明下一個將要執(zhí)行的指令是指向es段的。所以,我們把值0x41(在ASCⅡ中表示的字符是A)送到顯存的第一個字節(jié)中。接下來要把字符的屬性送到下一個字節(jié)當中。在此輸入的是0x1f,該屬性指的是在藍色背景下顯示白色的字符。因此,如果執(zhí)行這個程序,就可以在屏幕上得到顯示在藍底上的一個白色的 A。接著是一個循環(huán)。因為在執(zhí)行完顯示字符的任務后,要么讓程序結束,要么使用一個循環(huán)使其永遠運行下去。把該文件命名為boot.s,然后存盤。

            此處顯存的概念說得不是很清楚,有必要進一步解釋一下。假設屏幕由80列×25行組成,那么第一行就需要160字節(jié),其中一個字節(jié)用于表示字符,另外一個字節(jié)用于表示字符的屬性。如果要在第三行顯示某一字符的話,就要跳過顯存的第0和1字節(jié)(它們是用于顯示第1列的),第2和3字節(jié)(它們是用于顯示第2列的),然后把需要顯示字符的ASCⅡ碼值入第4字節(jié),把字符的屬性寫入第5字節(jié)。


            把程序寫至啟動扇區(qū)


            下面寫一個C程序,把我的操作系統(tǒng)寫入軟盤第一扇區(qū)。程序內(nèi)容如下:

            #include /* 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文件,然后在打開文件時把文件描述符復制到file_desc變量中。從文件中讀取510個字符,或者讀取直到文件結束。在本例中由于文件很小,所以是讀取至文件結束。然后關閉文件。

            最后4行代碼打開軟盤驅動設備(一般來說是/dev/fd0)。使用lseek找到文件開始處,然后從緩沖中向軟盤寫512個字節(jié)。

            在read、write、open和lseek的幫助頁中,可以看到與函數(shù)所有有關的參數(shù)及其使用方法。程序中有兩行比較難懂:

            boot_buf[510] = 0x55;
            boot_buf[511] = 0xaa;


            該信息是用于BIOS的,如果它識別出該設備是一個可啟動的設備,那么在第510和511的位置,該值就應該是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的界面設置,并且把軟盤設為第一個啟動的設備。然后插入軟盤,電腦從軟盤上啟動。

            啟動完成后,在屏幕上可以看到一個字母A(藍底白字),啟動速度很快,幾乎是在瞬間完成。這就意味著系統(tǒng)已經(jīng)從我們制作的軟盤上啟動了,并且執(zhí)行了剛才寫入啟動扇區(qū)的程序。現(xiàn)在,它正處在一個無限循環(huán)的狀態(tài)。所以,如果想進入Linux,必需拿掉軟盤,并且重啟機器。

            至此,這個操作系統(tǒng)就算完成了,雖然它沒有實現(xiàn)什么功能,但是它已經(jīng)可以啟動機器了。

            下一期我將在這個啟動扇區(qū)程序里加入一些代碼,使它可以做一些比較復雜的事情(比如使用BIOS中斷、保護模式切換等等)。

            自己動手寫操作系統(tǒng)(二)
            作者:伊梅

            上一期,我講述了如何在軟盤的啟動扇區(qū)寫一些代碼,然后再從軟盤啟動的過程。制作好一個啟動扇區(qū),在切換到保護模式之前,我們還應該知道如何使用BIOS中斷。BIOS中斷是一些由BIOS提供的、為了使操作系統(tǒng)的創(chuàng)建更容易的低級程序。在本文中,我們將學習處理BIOS的中斷。

            為什么要用BIOS

            BIOS 會把啟動扇區(qū)拷貝至RAM中,并且執(zhí)行這些代碼。除此之外,BIOS還要做很多其它的事情。當一個操作系統(tǒng)剛開始啟動時,系統(tǒng)中并沒有顯卡驅動、軟盤驅動等任何驅動程序。因此,啟動扇區(qū)中不可能包含任何一個驅動程序,我們要采取其它的途徑。這個時候,BIOS就可以幫助我們了。BIOS中包含有各種可以使用的程序,包括檢測安裝的設備、控制打印機、計算內(nèi)存大小等用于各種目的的程序。這些程序就是所說的BIOS中斷。

            如何調(diào)用BIOS中斷

            在一般的程序設計語言中,函數(shù)的調(diào)用是一件非常容易的事情。比如在C語言中,如果有一個名為display的程序,它帶有兩個參數(shù),其中參數(shù) noofchar表示顯示的字符數(shù),參數(shù)attr表示顯示字符的屬性。那么要調(diào)用它,只需給出程序的名稱即可。對于中斷的調(diào)用,我們使用的是匯編語言中的 int指令。

            比如,在C語言中要顯示一些東西時,使用的指令如下所示:

            display(nofchar,attr);
            而使用BIOS時,要實現(xiàn)相同功能使用的指令如下:

            int 0x10

            如何傳遞參數(shù)

            在調(diào)用BIOS中斷之前,我們需要先往寄存器中送一些特定的值。假設要使用BIOS的中斷13h,該中斷的功能是把數(shù)據(jù)從軟盤傳送至內(nèi)存之中。在調(diào)用該中斷之前,要先指定拷貝數(shù)據(jù)的段地址,指定驅動器號、磁道號、扇區(qū)號,以及要傳送的扇區(qū)數(shù)等等。然后,就要往相應的寄存器送入相應的值。在進行下面的步驟前,讀者有必要對這一點有比較明確地認識。

            此外,一個比較重要的事實是同一個中斷往往可以實現(xiàn)各種不同的功能。中斷所實現(xiàn)的確切功能取決于所選擇的功能號,功能號一般都存在ah寄存器之中。比如中斷13h可以用于讀磁盤、寫磁盤等功能,如果把3送入ah寄存器中,那么中斷選擇的功能就是寫磁盤;如果把2送入ah寄存器中,選擇的功能則是讀磁盤等。

            我們要做的事情

            這次我們的源代碼由兩個匯編語言程序和一個C程序組成。第一個匯編文件是引導扇區(qū)的代碼。在引導扇區(qū)中,我們寫的代碼是要把軟盤中第二扇區(qū)拷貝至內(nèi)存段的0x500處(地址是0x5000,即偏移地址為0)。這時我們需要使用BIOS的中斷13h。這時啟動扇區(qū)的代碼就會把控制權轉移至0x500處。在第二個匯編文件中,代碼會使用BIOS中斷 10h在屏幕上顯示一個信息。C程序實現(xiàn)的功能則是把可執(zhí)行的文件1拷貝至啟動扇區(qū),把可執(zhí)行的文件2拷貝至軟盤的第二扇區(qū)。

            啟動扇區(qū)代碼

            使用中斷13h,啟動扇區(qū)把軟盤第二扇區(qū)里的內(nèi)容加載至內(nèi)存的0x5000處(段地址為0x500)。下面的代碼是用于實現(xiàn)這一目的的代碼,將其保存至文件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寄存器中,這是軟盤上第二扇區(qū)代碼將拷貝到的地方(第一扇區(qū)是啟動扇區(qū))。這時,把段內(nèi)的偏移設為0。

            接下來把驅動器號送入dl寄存器中,其中磁頭號送入dl寄存器中,磁道號送入ch寄存器中,扇區(qū)號送入cl寄存器中,扇區(qū)數(shù)送入al寄存器之中。我們想要實現(xiàn)的功能是把扇區(qū)2、磁道號為0、驅動器號為0的內(nèi)容送至段地址0x500處。所有這些參數(shù)都和1.44MB的軟盤相對應。

            把2送入ah寄存器中,是選擇了由中斷13h提供的相應功能,即實現(xiàn)從軟驅轉移數(shù)據(jù)的功能。

            最后調(diào)用中斷13h,并且轉至偏移為0的段地址0x500處。

            第二個扇區(qū)的代碼

            第二個扇區(qū)中的代碼如下所示(把這些代碼保存至文件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處,并且被執(zhí)行。在這段代碼中,使用了中斷10h來獲取目前的光標位置,然后顯示信息。

            從第3行到第5行用于得到目前光標的位置,在此中斷10h選用的是功能3。然后,清除了bh寄存器的內(nèi)容,并把字符串送至ch寄存器中。在bx中,我們送入了頁碼及顯示的屬性。此處,我們想要在黑背景上顯示白色的字符。然后,把要顯示字符的地址送到bp之中,信息由兩個字節(jié)組成,其值分別為13的10,它們分別對應回車和LF(換行)的ASCⅡ值。接下來是一個由29個字符組成的串;在下面實現(xiàn)的功能是輸出字符串然后移動光標;最后是調(diào)用中斷,然后進入循環(huán)。

            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);
            boot_buf[510] = 0x55;
            boot_buf[511] = 0xaa;
            floppy_desc = open("/dev/fd0", O_RDWR);
            lseek(floppy_desc, 0, SEEK_SET);
            write(floppy_desc, boot_buf, 512);
            file_desc = open("./sect2", O_RDONLY);
            read(file_desc, boot_buf, 512);
            close(file_desc);
            lseek(floppy_desc, 512, SEEK_SET);
            write(floppy_desc, boot_buf, 512);
            close(floppy_desc);
            }


            在上一期中,我曾經(jīng)介紹過如何操作能啟動的軟盤。現(xiàn)在這一個過程稍微有點不同,首先把由bsect.s編譯出來的可執(zhí)行文件bsect拷貝至軟盤的啟動扇區(qū)。然后再把由sect2.s產(chǎn)生的可執(zhí)行文件sect2拷貝至軟盤的第二個扇區(qū)。

            把上述文件置于同一目錄之下,然后分別對其進行編譯,方法如下所示:

            as86 bsect.s -o bsect.o
            ld86 -d bsect.o -o bsect

            對sect2.s文件重復以上的操作,得出可執(zhí)行文件sect2。編譯write.c,插入軟盤后執(zhí)行write文件,命令如下所示:

            cc write.c -o write
            ./write

            下一步我們要做的事情

            從軟盤啟動以后,可以看到顯示出來的字符串。這是使用了BIOS中斷來完成的。下一期要做的事情是在這個操作系統(tǒng)中實現(xiàn)實模式向保護模式的轉換。

            自己動手寫操作系統(tǒng)(三)
            作者:伊梅


            在上兩期中(自己動手寫操作系統(tǒng)1,2),我向大家講述了如何使用Linux 提供的開發(fā)工具在軟盤的啟動扇區(qū)寫一些代碼,以及如何調(diào)用BIOS的問題。現(xiàn)在,這個操作系統(tǒng)已經(jīng)越來越接近當年Linus Torvalds的那個具有"歷史意義"的Linux內(nèi)核了。因此,要馬上把這個系統(tǒng)切換到保護模式之下。

            什么是保護模式

            自從1969年推出第一個微處理器以來,Intel處理器就在不斷地更新?lián)Q代,從8086、8088、80286,到80386、80486、奔騰、奔騰 Ⅱ、奔騰4等,其體系結構也在不斷變化。80386以后,提供了一些新的功能,彌補了8086的一些缺陷。這其中包括內(nèi)存保護、多任務及使用640KB以上的內(nèi)存等,并仍然保持和8086家族的兼容性。也就是說80386仍然具備了8086和80286的所有功能,但是在功能上有了很大的增強。早期的處理器是工作在實模式之下的,80286以后引入了保護模式,而在80386以后保護模式又進行了很大的改進。在80386中,保護模式為程序員提供了更好的保護,提供了更多的內(nèi)存。事實上,保護模式的目的不是為了保護程序,而是要保護程序以外的所有程序(包括操作系統(tǒng))。

            簡言之,保護模式是處理器的一種最自然的模式。在這種模式下,處理器的所有指令及體系結構的所有特色都是可用的,并且能夠達到最高的性能。

            保護模式和實模式

            從表面上看,保護模式和實模式并沒有太大的區(qū)別,二者都使用了內(nèi)存段、中斷和設備驅動來處理硬件,但二者有很多不同之處。我們知道,在實模式中內(nèi)存被劃分成段,每個段的大小為64KB,而這樣的段地址可以用16位來表示。內(nèi)存段的處理是通過和段寄存器相關聯(lián)的內(nèi)部機制來處理的,這些段寄存器(CS、DS、 SS和ES)的內(nèi)容形成了物理地址的一部分。具體來說,最終的物理地址是由16位的段地址和16位的段內(nèi)偏移地址組成的。用公式表示為:

            物理地址=左移4位的段地址+偏移地址。

            在保護模式下,段是通過一系列被稱之為"描述符表"的表所定義的。段寄存器存儲的是指向這些表的指針。用于定義內(nèi)存段的表有兩種:全局描述符表(GDT)和局部描述符表(LDT)。GDT是一個段描述符數(shù)組,其中包含所有應用程序都可以使用的基本描述符。在實模式中,段長是固定的(為64KB),而在保護模式中,段長是可變的,其最大可達4GB。LDT也是段描述符的一個數(shù)組。與GDT不同,LDT是一個段,其中存放的是局部的、不需要全局共享的段描述符。每一個操作系統(tǒng)都必須定義一個GDT,而每一個正在運行的任務都會有一個相應的LDT。每一個描述符的長度是8個字節(jié),格式如圖3所示。當段寄存器被加載的時候,段基地址就會從相應的表入口獲得。描述符的內(nèi)容會被存儲在一個程序員不可見的影像寄存器(shadow register)之中,以便下一次同一個段可以使用該信息而不用每次都到表中提取。物理地址由16位或者32位的偏移加上影像寄存器中的基址組成。實模式和保護模式的不同可以從圖1和圖2中很清楚地看出來。


            圖1 實模式的尋址


            圖2 保護模式下的尋址


            圖3 段描述俯的格式

            此外,還有一個中斷描述符表(IDT)。這些中斷描述符會告訴處理器到那里可以找到中斷處理程序。和實模式一樣,每一個中斷都有一個入口,但是這些入口的格式卻完全不同。因為在切換到保護模式的過程中沒有使用到IDT,所以在此就不多做介紹了。

            進入保護模式

            80386 有4個32位控制寄存器,名字分別為CR0、CR1、CR2和CR3。CR1是保留在未來處理器中使用的,在80386中沒有定義。CR0包含系統(tǒng)的控制標志,用于控制處理器的操作模式和狀態(tài)。CR2和CR3是用于控制分頁機制的。在此,我們關注的是CR0寄存器的PE位控制,它負責實模式和保護模式之間的切換。當PE=1時,說明處理器運行于保護模式之下,其采用的段機制和前面所述的相應內(nèi)容對應。如果PE=0,那么處理器就工作在實模式之下。

            切換到保護模式,實際就是把PE位置為1。為了把系統(tǒng)切換到保護模式,還要做一些其它的事情。程序必須要對系統(tǒng)的段寄存器和控制寄存器進行初始化。把PE位置1后,還要執(zhí)行跳轉指令。過程簡述如下:

            1.創(chuàng)建GDT表;

            2.通過置PE位為1進入保護模式;

            3.執(zhí)行跳轉以清除在實模式下讀取的任何指令。

            下面使用代碼來實現(xiàn)這個切換過程。

            需要的東西

            ◆ 一張空白軟盤

            ◆ NASM編譯器

            下面是整個程序的源代碼:

            org 0x07c00; 起始地址是0000:7c00
            jmp short begin_boot ; 跳過其它的數(shù)據(jù),跳轉到引導程序的開始處
            bootmesg db "Our OS boot sector loading ......"
            pm_mesg db "Switching to protected mode ...."
            dw 512 ; 每一扇區(qū)的字節(jié)數(shù)
            db 1 ; 每一簇的扇區(qū)數(shù)
            dw 1 ; 保留的扇區(qū)號
            db 2
            dw 0x00e0
            dw 0x0b40
            db 0x0f0
            dw 9
            dw 18
            dw 2 ; 讀寫扇區(qū)號
            dw 0 ; 隱藏扇區(qū)號
            print_mesg :
            mov ah,0x13 ; 使用中斷10h的功能13,在屏幕上寫一個字符串
            mov al,0x00 ; 決定調(diào)用函數(shù)后光標所處的位置
            mov bx,0x0007 ; 設置顯示屬性
            mov cx,0x20 ; 在此字符串長度為32
            mov dx,0x0000 ; 光標的起始行和列
            int 0x10 ; 調(diào)用BIOS的中斷10h
            ret ; 返回調(diào)用程序
            get_key :
            mov ah,0x00
            int 0x16 ; Get_key使用中斷16h的功能0,讀取下一個字符
            ret
            clrscr :
            mov ax,0x0600 ; 使用中斷10h的功能6,實現(xiàn)卷屏,如果al=0則清屏
            mov cx,0x0000 ; 清屏
            mov dx,0x174f ; 卷屏至23,79
            mov bh,0 ; 使用顏色0來填充
            int 0x10 ; 調(diào)用10h中斷
            ret
            begin_boot :
            call clrscr ; 先清屏
            mov bp,bootmesg ; 提供串地址
            call print_mesg ; 輸出信息
            call get_key ; 等待用戶按下任一鍵
            bits 16
            call clrscr ; 清屏
            mov ax,0xb800 ; 使gs指向顯示內(nèi)存
            mov gs,ax ; 在實模式下顯示一個棕色的A
            mov word [gs:0],0x641 ; 顯示
            call get_key ; 調(diào)用Get_key等待用戶按下任一鍵
            mov bp,pm_mesg ; 設置串指針
            call print_mesg ; 調(diào)用print_mesg子程序
            call get_key ; 等待按鍵
            call clrscr ; 清屏
            cli ; 關中斷
            lgdt[gdtr] ; 加載GDT
            mov eax,cr0
            or al,0x01 ; 設置保護模式位
            mov cr0,eax ; 將更改后的字送至控制寄存器中
            jmp codesel:go_pm
            bits 32
            go_pm :
            mov ax,datasel
            mov ds,ax ; 初始化ds和es,使其指向數(shù)據(jù)段
            mov es,ax
            mov ax,videosel ; 初始化gs,使其指向顯示內(nèi)存
            mov gs,ax
            mov word [gs:0],0x741 ; 在保護模式下顯示一個白色的字符A
            spin : jmp spin ; 循環(huán)
            bits 16
            gdtr :
            dw gdt_end-gdt-1 ; gdt的長度
            dd gdt ; gdt的物理地址
            gdt
            nullsel equ $-gdt ; $指向當前位置,所以nullsel = 0h
            gdt0 ; 空描述符
            dd 0
            dd 0 ; 所有的段描述符都是64位的
            codesel equ $-gdt ; 這是8h也就是gdt的第二個描述符
            code_gdt
            dw 0x0ffff ; 段描述符的界限是4Gb
            dw 0x0000
            db 0x00
            db 0x09a
            db 0x0cf
            db 0x00
            datasel equ $-gdt
            data_gdt
            dw 0x0ffff
            dw 0x0000
            db 0x00
            db 0x092
            db 0x0cf
            db 0x00
            videosel equ $-gdt
            dw 3999
            dw 0x8000 ; 基址是0xb8000
            db 0x0b
            db 0x92
            db 0x00
            db 0x00
            gdt_end
            times 510-($-$$) db 0
            dw 0x0aa55


            把上面的代碼存在一個名為abc.asm的文件之中,使用命令nasm abc.asm,將得出一個名為abc的文件。然后插入軟盤,輸入命令:dd if=abc of=/dev/fd0。該命令將把文件abc寫入到軟盤的第一扇區(qū)之中。然后重新啟動系統(tǒng),就會看到如下的信息:

            *Our os booting................
            * A (棕色)
            * Switching to protected mode....
            * A (白色)


            對代碼的解釋

            上面給出了所有的代碼,下面我對上述代碼做一些解釋。

            ◆ 使用的函數(shù)

            下面是代碼中一些函數(shù)的說明:

            print_mesg 該子程序使用了BIOS中斷10h的功能13h,即向屏幕寫一字符串。屬性控制是通過向一些寄存器中送入不同的值來實現(xiàn)的。中斷10h是用于各種字符串操作,我們把子功能號13h送到ah中,用于指明要打印一個字符串。al寄存器中的0說明了光標返回的起始位置,0表示調(diào)用函數(shù)后光標返回到下一行的行首。如果al為1則表示光標位于最后一個字符處。

            顯存被分成了幾頁,在同一時刻只能顯示其中的一頁。bh指明的是頁號;bl則指明要顯示字符的顏色;cx指明要顯示字符串的長度;dx指明光標的位置(即起始的行和列)。所有相關寄存器初始化完成以后,就可以調(diào)用BIOS中斷10h了。

            get_key 使用中斷16h的子功能00h,從屏幕得到下一個字符。

            clrscr 該函數(shù)使用了中斷10h的另外一個子功能06h,用于輸出開始前清屏。初始化時給al中送入0。寄存器cx和dx指明要清屏的屏幕范圍,在本例中是整個屏幕。寄存器bh指明屏幕填充的顏色,在本例中是黑色。

            ◆ 其它內(nèi)容

            程序一開始是一條短跳轉指令,跳到begin_boot處。在實模式下,在此打印一個棕色的"A",并且設置一個GDT。切換到保護模式,并且打印一個白色的"A"。這兩種模式使用的都是自己的尋址方法。

            在實模式下,使用段寄存器gs指示顯存位置,我們使用的是CGA顯卡(默認基址是0xb8000)。在代碼中是不是漏了一個0呢?沒有,因為實模式下會提供一個附加的0。這種方式也被80386繼承下來了。A的ASCⅡ是0x41,0x06指明了需要一個棕色的字符。該顯示會一直持續(xù)直至按下任意鍵。下面要在屏幕上顯示一句話,告訴使用者下面馬上要進入保護模式了。

            啟動到保護模式,在進行切換時不希望此時有中斷的影響,故要關閉所有的中斷(使用cli來實現(xiàn))。然后對GDT初始化。在整個切換過程中,對4個描述符進行了初始化。這些描述符對代碼段(code_gdt)、數(shù)據(jù)和堆棧段 (data_gdt),以及為了訪問顯存而對顯示段進行初始化。此外,還會對一個空描述符進行初始化。

            GDT的基址要加載至GDTR 系統(tǒng)寄存器之中。gdtr段的第一個字加載的是GDT的大小,在下一個雙字中則加載的是基址。然后,lgdt指令把把gdt段加載至GDTR寄存器中。現(xiàn)在已經(jīng)做好了切換到保護模式前的所有準備。最后一件事情就是把CR0寄存器的PE位置1。不過,即使這樣還沒有處于保護模式狀態(tài)之下。

            設置了PE位以后,還需要通過執(zhí)行JMP指令來清除處理器指令預取隊列。在80386中,使用指令前總是先將其從內(nèi)存中取出,并且進行解碼和尋址。然而,當進入保護模式以后,預取指令信息(它還處于實地址模式)就無效了。使用JMP指令的目的就是強迫處理器放棄無效的信息。

            現(xiàn)在,已經(jīng)在保護模式下了。那么,如何檢測是在保護模式狀態(tài)之下呢?讓我們來看一看屏幕上這個白色的字母A。在這里,使用了數(shù)據(jù)段選擇符(datase1)對數(shù)據(jù)段和附加段進行了初始化,使用顯示段選擇符(videose1)對gs進行了初始化。告示的字符"A"其ASCⅡ值和屬性位于[gs:0000]處,也就是 b8000:0000處。循環(huán)語句使得該字符一直在屏幕上顯示,直至重新啟動系統(tǒng)。

            posted on 2008-01-07 16:28 旅途 閱讀(746) 評論(0)  編輯 收藏 引用 所屬分類: 一步一步操作系統(tǒng)

            久久婷婷五月综合成人D啪| 国产精品一久久香蕉国产线看| 波多野结衣AV无码久久一区| 久久精品国产亚洲av麻豆色欲| 国产福利电影一区二区三区久久老子无码午夜伦不 | 久久精品国内一区二区三区| 草草久久久无码国产专区| 伊人久久大香线蕉AV一区二区| 久久精品国产福利国产秒| 国产福利电影一区二区三区久久老子无码午夜伦不 | 国产欧美久久久精品| 一本一本久久a久久精品综合麻豆| 久久久久久亚洲Av无码精品专口| 精品综合久久久久久88小说| 99精品久久精品一区二区| 久久久综合香蕉尹人综合网| 欧美熟妇另类久久久久久不卡| 777午夜精品久久av蜜臀| 国产精品一区二区久久精品无码| 亚洲国产美女精品久久久久∴ | 久久人人爽人人人人爽AV| 欧美久久久久久精选9999| www.久久热.com| 日韩精品久久无码人妻中文字幕| 久久久久久国产精品无码下载| 久久久中文字幕日本| 国产成人精品久久一区二区三区av| 一本色道久久综合狠狠躁| 欧美性猛交xxxx免费看久久久| 93精91精品国产综合久久香蕉| 欧美激情精品久久久久久久| 精品综合久久久久久88小说| 国产成人精品久久亚洲高清不卡 | 国产精品无码久久久久久| 66精品综合久久久久久久| 久久er国产精品免费观看2| 国内精品久久久久影院一蜜桃 | 精品久久久久中文字幕一区| 久久免费高清视频| 人人狠狠综合88综合久久| 日韩精品无码久久一区二区三|