4 - 誰都能寫的操作系統

今天已經30號了,從開始決定寫focus到現在,已經過去了快二十天了,今天才開始真正的進入正題,深感慚愧,之前忙畢業,實在忙的不可開交,請各位朋友見諒,謝謝哈。


andrew就不和大家磨嘴皮子了,開始給大家動點真格的了。。。嘿嘿。。。


先跟大家分享一下主引導扇區的概念。在我們使用的硬盤和早期的軟盤,盤片的物理組織結構都是由扇區、柱面和盤面組成。一個扇區大小爲512字節,一個柱面由18個扇區組成,一個盤面由80個柱面組成。在程序當中,扇區的標號是由1~18標記,柱面的編號由0~79標記,盤面由0開始標記,andrew開始就搞錯了,因爲andrew開始以爲扇區標號是從0開始的,這個是比較容易出錯的,主要是程序員的習慣性問題,你們懂的。。。那麼在硬盤和軟盤中的第一個扇區,即第0個盤面的第0個磁道的第1個扇區,在整個存儲設備中具有特殊的地位,這個扇區稱爲主引導扇區(MBR),也有稱主引導記錄,因爲這個扇區中存儲着存儲系統的分區表,但我們不考慮這些,因爲我們暫時不關心存儲系統的分區情況。這裏大家需要知道存儲設備中的第一個扇區就是主引導扇區即可,其大小爲512字節。實驗中,我們使用bochs模擬出來的軟驅作爲啓動設備。


之前和大家分享過BIOS這個東東,大家都記得吧,如果不記得的話請看看第1篇文章哈。計算機上電後,CPU的各個寄存器會被初始化,這個時候CPU會從一個固定的地址去讀取指令,這個地址就是0xffff0,其實這個地址就是BIOS的地址,在計算機剛上電的時候,CPU的地址線只有20位,尋址空間最大爲1MB(爲什麼是這樣的,以後會和大家介紹,莫着急哈),而我們看到CPU上電後執行的第一條指令地址爲0xffff0,那麼這個地址後最多隻有16個字節的程序,不可能完成上電自檢等一系列的功能,因此,其實地址0xffff0的地方只是一個跳轉指令,這個跳轉指令會跳轉到BIOS的代碼部分,然後執行BIOS的指令,爲什麼是這個樣子的,andrew也不曉得,這要請教Inter的大叔們,哦,不對,應該是大爺們了。對於寫操作系統來說,我們最關心的是,BIOS會將主引導扇區中512字節的代碼拷貝到內存0x7c00的地方,然後跳轉到這裏去執行引導扇區的代碼。
主引導扇區的代碼就由我們自己來寫了,BIOS先把我們的代碼帶上位,如果我們的代碼不在這個時候掉鏈子的話,BIOS大哥就會把操作系統的控制大權,交給我們的代碼了。

好了,那麼今天我們就先試着,來體驗一下,開始寫我們的第一個操作系統哈。

BOOT_ADDR = 0x07c0			

entry start			

start:
	jmpi run,BOOT_ADDR		

run:
	mov ax,cs
	mov ds,ax
	mov es,ax

	mov cx,#30
	mov dx,#0x1004
	mov bx,#0x000c
	mov bp,#msg
	mov ax,#0x1301
	int 0x10

halt:
	jmp halt

msg:
	.byte 13,10
	.ascii " focus!"
	.byte 13,10
	.ascii " I'm Andrew."
	.byte 13,10

.org 510
	.word 0xAA55

以上是我們第一個操作系統的全部代碼,代碼在boot.s文件中。計算機剛上電的時候,只能夠執行16位的指令,地址線能夠尋址20位,以後會介紹爲什麼。在介紹focus開發環境的時候,我們說過,編寫16位的彙編代碼時,我們使用as86和ld86進行源程序的編譯。as86彙編語法遵循的是Inter的彙編語法,語法近似於微軟的MASM、NASM等彙編器,但和GNU as的彙編語法有很大的不同。andrew在介紹代碼的時候會詳細介紹使用到的語法規則。


在as86的彙編語法中,有三種語句,1-賦值語句;2-指示性語句;3-指令性語句。

賦值語句就是boot.s代碼中的第一句

BOOT_ADDR = 0x07c0
相當於定義了一個符號常量,將0x07c0這個值賦予BOOT_ADDR這個符號,0x07c0左移四位就是0x7c00了,即這裏的BOOT_ADDR表示的是段基址,學過微機原理的朋友肯定會明白,沒學過的話,先不要緊,能理解多少就先理解多少,等以後講到分段時,就明白了。

entry start
entry是關鍵字,告訴連接器程序的入口start,相當於我們在編寫C程序時的main函數,其實這裏要不要這一句都可以,因爲在編譯的時候,我們自己控制着代碼的鏈接順序,這部分代碼始終都會被放在引導扇區中,並且第一個指令的地址必定爲0x7c00的地方。

start:
	jmpi run,BOOT_ADDR		

run:
指令性語句就是程序中進行實際操作的指令,即直接有CPU執行的指令。指令性語句通常由標號、操作符、操作數和註釋四個部分組成,其中標號和註釋是可選部分,操作數的個數根據操作符的不同,也不相同。start稱爲標號,標號有一個標示符後跟一個冒號":"組成,其總是位於一個語句的第一個字段,代表該條指令的地址,通常作爲跳轉指令的目標位置。jmpi是一個段間跳轉指令,後跟兩個參數run和BOOT_ADDR,第一個參數run既是下一條指令的標號,從源代碼中可以看到哈,run在這條指令中作爲偏移地址,第二個參數BOOT_ADDR是一個符號常量,值爲0x07c0,在這條指令中作爲段基址。jmpi指令執行後將會跳轉到由run和BOOT_ADDR組成的地址去運行,跳轉指令執行後,CS段寄存器就被設爲了跳轉指令中的段基址0x07c0,其實這條指令的主要作用也是爲了更新CS段寄存器,因爲BIOS將引導扇區的代碼拷貝到內存0x7c00地址後,且開始執行引導扇區代碼之前,CS段寄存器的值爲0,IP寄存器的值爲0x7c00。

run:
	mov ax,cs
	mov ds,ax
	mov es,ax

大家可以看到,前面的標號start和run所在的行都沒有指令,其實他們都和下一行的指令是一夥的,即標號run和指令mov  ax,cs可以理解爲一行。mov指令是數據傳輸指令,在as86彙編語法中,mov將第二個操作數的值拷貝到第一個操作數中,即將CS段寄存器中的值拷貝到AX寄存器中。後面兩條語句也是同樣的功能,其實這三句就是完成DS段寄存器和ES段寄存器的賦值。

	mov cx,#30
	mov dx,#0x1004
	mov bx,#0x000c
	mov bp,#msg
	mov ax,#0x1301
	int 0x10
對寄存器進行了設置後,這一部分代碼就是完成打印信息功能,打印信息功能主要調用了BIOS的0x10號中斷,這裏先暫時不講BIOS的中斷,等到下一期會專一補充BIOS的中斷部分內容。

halt:
	jmp halt
這裏的代碼實現一個死循環功能,今天跑了這個程序,惱火的要死,代碼跑起來後,我的ubuntu差點就崩潰了。。。無語。。。jmp是一個段內的短跳轉指令,其後的操作數一個偏移地址,這樣代碼執行起來後,就不停的執行這條指令,即死循環。。。爲了就是讓屏幕顯示的內容保留下來。

msg:
	.byte 13,10
	.ascii " focus!"
	.byte 13,10
	.ascii " I'm Andrew."
	.byte 13,10

msg是一個標號,這裏大家應該明白了,大家還沒有見過指示性語句,指示性語句指示告訴編譯器如何編譯代碼,並不生成代碼。指示性語句由點號"."和表示符組成。如“.byte”,".byte"用於定義字節型數據,後跟要定義的數據,數據間以","分開。語句“.byte  13,10”定義了一個回車和換行。".ascii"僞操作符用於定義字符串,定義的字符串用雙引號括起來。這一部分相當於定義了一些字符串數據,並沒有可執行的代碼。

.org 510
	.word 0xAA55
僞操作符org用於設置程序的計數器,即當前彙編的位置,在編譯的時候,org語句後面的部分,編譯的地址會以org設置的地址開始。這裏org將編譯的位置設置在第510字節處。".word"定義了一個字大小的數據0xAA55,共兩個字節。而後面就沒有任何的代碼和數據了,因此,我們希望boot.s編譯後的大小爲512字節,正好可以完整的放在主引導扇區中。0xAA55是一個特殊的標記,用於標記該扇區爲有效的引導扇區。BIOS會掃描有效的存儲設備,通過驗證這些存儲設備的主引導扇區中的最後兩個字節是否爲0xAA55來確定是否爲有效的啓動設備。

代碼時講完了,但是代碼要怎麼編譯和運行呢?接下來andrew和大家分享下,看看最終我們的第一個操作系統會是什麼樣。。。
在andrew的開發環境中,andrew新建了一個focusOS文件夾,與focus有關的所有代碼和相關文件都在這個文件夾中,具體focusOS這個文件夾在系統的哪裏不重要,下面來看看我們這一次實驗中會用到哪些文件。


在focusOS目錄下,focus.bxrc文件是bochs虛擬機的配置文件,這個之前已經介紹過,但這裏需要注意一點,在focus.bxrc文件的第388行附近,

floppya: 1_44="focus", status=inserted

軟盤的鏡像名爲focus,且用雙引號括起。boot是一個文件夾,裏面只有一個boot.s文件,建立一個文件夾是爲了以後編寫代碼的方便,否則以後代碼多了,還要重新整理,索性andrew就直接把啓動代碼的文件夾建好了,boot.s代碼已經介紹過了,這裏就略過了哈。至於bochsout.txt和parport.out這兩個文件是bochs虛擬機運行時生成的文件,不必管它。那麼接下來的重頭戲就是Makefile文件了,Makefile文件決定着我們的代碼如何編譯。我們先來看看哈。。。

AS86 = as86
LD86 = ld86

BOOTDIR = boot


focus: $(BOOTDIR)/boot.s
	$(AS86) -o $(BOOTDIR)/boot.o $(BOOTDIR)/boot.s
	$(LD86) -s -o $(BOOTDIR)/boot $(BOOTDIR)/boot.o
	dd bs=32 if=$(BOOTDIR)/boot of=focus skip=1

clean:
	rm $(BOOTDIR)/boot.o $(BOOTDIR)/boot 

在Makefile中可以使用=號來定義變量,AS86和LD86、BOOTDIR就是定義的變量,=號後就是賦予他們的值,在引用變量的時候,使用$(變量名)的方式引用,比如

AS86 = as86
<pre name="code" class="plain">BOOTDIR = boot
$(AS86) -o $(BOOTDIR)/boot.o $(BOOTDIR)/boot.s


<pre name="code" class="plain">        as86 -o <span style="font-family: Arial, Helvetica, sans-serif;">boot</span><span style="font-family: Arial, Helvetica, sans-serif;">/boot.o  boot/boot.s</span>

上面定義了AS86和BOOTDIR兩個變量,上面的兩個語句一個是使用了變量,一個是沒有使用變量,但意思完全一樣。

focus: $(BOOTDIR)/boot.s
	$(AS86) -o $(BOOTDIR)/boot.o $(BOOTDIR)/boot.s
	$(LD86) -s -o $(BOOTDIR)/boot $(BOOTDIR)/boot.o
	dd bs=32 if=$(BOOTDIR)/boot of=focus skip=1

這裏介紹一下Makefile的規則,Makefile首先要聲明目標和依賴,目標就是我們想要得到的文件,依賴就是我們要得到我們想要的文件,需要哪些文件,focus就是目標,boot.s就是生成focus所需要的依賴。接下來的三個語句,第一個語句是使用as86彙編器將boot.s彙編爲目標代碼boot.o,其中-o選項是指定輸出文件,第二條語句是將目標代碼boot.o鏈接爲可執行程序,-s選項是鏈接時去掉符號信息。但是作爲操作系統的代碼和普通的應用程序代碼不同,生成的可執行文件帶有文件頭,as86生成的文件帶有32字節的文件頭,因此,我們在裸機上跑就不需要了,第三條指令就是進行文件的拷貝,同時將輸入文件boot的前32個字節忽略掉,這樣我們就得到了一個沒有文件頭的鏡像文件。當我們執行make命令時,生成的文件如下圖所示:


生成的可執行文件boot在boot目錄下面,大小爲544字節,使用dd命令處理後,生成的操作系統鏡像文件focus的大小爲512字節,正好少了32字節的頭結構,操作系統鏡像的大小也正好可以放入512字節的引導扇區。

我們來運行虛擬機,讓虛擬機加載鏡像文件focus,看看結果如何。輸入命令bochs  -f  focus.bxrc


虛擬機運行後,會出現一個選項表,選擇6來啓動focus。


bochs加載focus後,會出現一個調試的界面,和一個模擬的計算機終端界面,可以理解爲bochs模擬計算機的屏幕,如下。


輸入c,計算機開始從上電的步驟,開始啓動,並加載focus操作系統。


ok!我們的focus操作系統正式運行起來了,現在的focus僅僅就是引導扇區的代碼,並沒有加載其他程序,focus運行時打印出兩行字符串。雖然很簡單,但已經介紹了計算機啓動的關鍵內容了,也揭示着focus的開發步入了正規了,後面的日子裏,andrew還是會抓緊時間了。。。


————————————————————————————

QQ:64879927

博客:http://blog.csdn.net/andrew_yau

請關注focus微信公共平臺:OS的探索之旅


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章