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工程,上下两篇罗里吧嗦得说了一堆,主要就是为了能让刚刚入门的萌新能走的不那么坎坷,同时呢也是想结合具体实例叙述编写内核的思路,相关代码我自己也重新敲了一遍、运行了一遍,如果还有不够详细的地方请留言,我会酌情进行对应的修改。

需要说明的是以后的文章就不会这么详细了,千万不要说我懒哦(看破不说破)。

最后最后再啰嗦一句,如果在阅读过程中对环境安装、编译链接、代码调试等有任何疑问还没有解决的话请阅读对应的附录,相关的文献资料以后会上传的。

终于写完了,我又想起了我的快乐风男~~~哈塞给!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值