ZeroOS—第1章—HelloWorld(下)

圖形還是文本

在顯示HelloWorld前我們需要先確定顯示的模式(方式),目前就分爲圖形顯示模式和文本顯示模式吧。本質上來講這兩種方式是相同的,都是以像素點(點陣)來顯示的,但是從實現方式來講是不同的,圖形模式可顯示的內容更豐富(只要你能做出來),但是實現更復雜;文本模式可實現內容不多,但是實現簡單,更容易上手。按我的想法來說肯定選簡單的,畢竟圖形模式涉及的知識太多了,從顯卡驅動到圖像處理,其工作量都可以作爲另一個項目來做了,而且文本模式對我們完全夠用。所以我們刪繁就簡,選文本模式吧。

80*25文本模式簡介

在這個模式下,一個屏幕最多可以顯示80(寬)*25(高)個字符,每個字符佔用兩個字節,第一個字節存儲該字符的屬性(顏色、背景色、是否閃爍等),第二個字節存儲該字符的ASCII碼,所有字符數據存儲在以物理地址0xb8000起始地址的內存中。看到這裏你有沒有明白應該怎麼操作屏幕了,整個屏幕不就是一個起始地址爲0xb8000,元素長度爲2個字節,元素個數爲80*25的一位數組嘛(如果暫時不理解也可以理解爲一個類似A[25][80]的二維數組,但是要明白所有的高維數組都可以化爲一位數組處理哦),這段內存我們以後稱之爲顯存。那讓屏幕顯示一個字符不就是向顯存中寫入一個元素嘛。到這裏顯示相關的知識就夠用了,我們可以開始輸出HelloWorld了。

向天再借500根頭髮

以下內容雖然不難,但是可以算是開啓了禿頂(變強)之路,不過不用怕,正如標題所講,最多也就掉500根。

寫代碼要三思而後行,不能上來就啪啪啪一頓亂敲,結果寫着寫着寫不下去了,於是乎Ctrl+A、delete、碼生重來。咱們寫這個輸出代碼時也要想好,不能直接把"Hello World“這個字符串寫進顯存就完事了。仔細想一想,這個顯示字符的功能不僅僅是現在用吧,內核其他部分也要向屏幕輸出字符,用戶程序也要向屏幕輸出字符,我們總不能在輸出字符時就把這段代碼再寫一遍吧,這麼一想,不如我們寫一個函數,這個函數接收並顯示一個字符串,當用的時候就調用這個函數,問題不就解決了嘛。但是,如果我們想輸出一個字符怎麼辦,這個函數就不能用了鴨,那再寫一個輸出字符的函數???大可不必,字符串是由字符組成的鴨,我們編寫一個輸出字符的函數,然後在輸出字符串時反覆調用這個字符函數不就可以了嗎,由這個思路出發,我們可以設計出如下的代碼:

//graphic.c
#include "types.h"
#define WIDTH 80
#define HEIGHT 25

static 	u16 * vm=(u16 *)0xb8000;
static u8 cursor_x=0;
static u8 cursor_y=0;

void putchar(u8 c){
	u8 color=(0<<4)|(15&0x0f);
	switch(c){
		case '\n':
			if((++cursor_y)==HEIGHT){
				cursor_y=HEIGHT-1;
				cursor_x=0;
			}else{
				cursor_x=0;
			}
			break;
		default:
			*(vm+cursor_y*WIDTH+cursor_x)=(color<<8)|c;
			if((++cursor_x)==WIDTH){
				cursor_x=0;
				if((++cursor_y)==HEIGHT){
					cursor_y=HEIGHT-1;
				}
			}
	}
}
int puts(char * str){
	for(int i=0;*(str+i)!=0;i++){
		putchar(*(str+i));
	}
	return 0;
}

我們把這個源文件存儲爲graphic.c,其中的putchar函數爲最底層的輸出函數,它是今後所有向屏幕輸出信息函數實現的基礎(比如puts),而這個源文件是內核其他模塊輸出信息的基礎,所以我們把graphic.c稱之爲顯示模塊(或者文本模式下的圖形驅動),至此我們已經對計算機硬件的顯示部分(可以說是顯卡相關部分吧)作了一個抽象。對這裏的抽象的概念一定要有自己的理解,因爲今後的工作就是在重複這個過程:學習硬件接口-->將硬件抽象爲幾個函數接口(比如將顯卡抽象爲putchar和puts)-->將這幾個函數接口歸整爲一個新的模塊並加入內核。通過不斷重複上述過程將硬件一一抽象爲模塊並加入內核,這樣我們的內核就會支持越來越多的硬件,也就擁有了越來越多的功能,這樣內核就得以不斷的成長直至成熟。這個就是我個人編寫內核的思路,雖然今後涉及的內容有很多,但是隻要運用這個思路,就可以對其逐一擊破,直至掌握一個完整的內核結構。

開始顯示HelloWorld

嗶嗶了這麼多,咱們終於可以進入正題了,這裏想必也不必多說了,思路已經十分明顯了,在boot.S的死循環之前調用puts輸出“Hello World"就可以唄。這樣寫當然可以,但是我們的內核不止顯示字符串這一個功能鴨,今後還有很多功能,還需要在內核開始調用很多函數,總不能都在boot.S這個彙編程序中調用吧(如果不明白AT&T彙編語言和C語言之間的互相調用,可以直接搜索相關內容,或者查看我寫的附錄),畢竟寫彙編程序真的很麻煩,還是儘量把工作都放在C代碼中吧,所以首先由boot.S調用一個內核的C主函數bootmain,然後由bootmain調用puts函數進行輸出,今後的其他函數也由bootmain函數進行調用,這樣就儘可能的避免了編寫彙編代碼。修改後的代碼如下:

#boot.S
#Multiboot頭,可以通過grub kernel指令加載並通過boot啓動

.align 4
.text
multiboot_header:
  #define magic 0x1badb002
  #define mboot_mem_info 1<<1
  #define flags	mboot_mem_info
  .long magic
  .long flags
  .long (-magic-flags)
#內核彙編入口
.global _start
.align 4
_start:
  	movl $stack_top,%esp#設置函數調用所需的棧
    call bootmain
stop:
	jmp stop
.data
stack_bottom:
.skip 16384
stack_top:
//bootmain.c
#include "types.h"
#include "graphic.h"

void bootmain(void){
	puts("Hello World!");
}

既然添加了新的源文件,那麼Makefile也是需要修改的(後面會學習Makefile一勞永逸的寫法),新的Makefile如下:

MAKE=make
GCC=gcc
LD=ld
CFLAGS=-m32 -ggdb -gstabs+ -fno-stack-protector -fno-builtin -fno-strict-aliasing -O0 -Wall -fno-pic -nostdinc  -I include
LDFLAGS=-m elf_i386 -nostdlib
QEMU_OPTION= -m 128M
OBJS=\
	boot.o\
	graphic.o\
	bootmain.o
all:
	$(MAKE) kernel

kernel:$(OBJS) kernel.ld
	$(LD) $(LDFLAGS) -T kernel.ld $(OBJS) -o kernel

boot.o:boot.S
	$(GCC) $(CFLAGS) -c boot.S -o boot.o

graphic.o:graphic.c
	$(GCC) $(CFLAGS) -c graphic.c -o graphic.o

bootmain.o:bootmain.c
	$(GCC) $(CFLAGS) -c bootmain.c -o bootmain.o

run:
	sudo qemu-system-i386 $(QEMU_OPTION) --kernel kernel

debug:
	sudo qemu-system-i386 $(QEMU_OPTION) -S -s --kernel kernel &
	gdb -x gdbinit

clean:
	rm *.o

還有一個重要的頭文件types.h,這是個內核中基本數據類型的定義,不直接用C語言的原因有兩個,主要是爲了方便內核以後適配其他平臺,其次是爲了少敲點鍵盤(懶是人類進步的第一動力[\滑稽]),最後要說的是該內核所有的頭文件都放在include目錄下哦,這一點在編譯參數"-I include"中可以看出。

//types.h
#ifndef TYPES_H
#define TYPES_H

#define NULL (void*)0

typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef u32 pdt;
typedef u32 ptt;

#endif

現在我們終於可以編譯了,如果不出意外就是以下結果。

其實直接複製粘貼代碼後的結果肯定不是這個樣子,是有幾個問題需要你自己解決的,需要你自己創建幾個目錄、文件,具體做什麼就需要你對照錯誤信息(這對於我們排除錯誤是很重要的,不要怕)逐一解決。

編譯成功後運行的結果如下

至此我們的HelloWorld工程終於完成了,讓我們先在成功的喜悅中沉浸一會兒,啊~~~爽~~~

納尼???看着多餘的字還有點不爽,那就自己親手清除它們吧,自己動手,豐衣足食哦!

最後囉嗦一下

對於這個HelloWorld工程,上下兩篇羅裏吧嗦得說了一堆,主要就是爲了能讓剛剛入門的萌新能走的不那麼坎坷,同時呢也是想結合具體實例敘述編寫內核的思路,相關代碼我自己也重新敲了一遍、運行了一遍,如果還有不夠詳細的地方請留言,我會酌情進行對應的修改。

需要說明的是以後的文章就不會這麼詳細了,千萬不要說我懶哦(看破不說破)。

最後最後再囉嗦一句,如果在閱讀過程中對環境安裝、編譯鏈接、代碼調試等有任何疑問還沒有解決的話請閱讀對應的附錄,相關的文獻資料以後會上傳的。

終於寫完了,我又想起了我的快樂風男~~~哈塞給!!!

發佈了15 篇原創文章 · 獲贊 8 · 訪問量 5627
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章