我们需要做的第一件事是在自己的电脑上搭建一个操作系统的开发环境。然后写个能显示"Hello World"的操作系统。

下面是我们需要准备的东西: 1、一个你操作熟练的编辑器,比如Vim 2、编辑C语言代码的gcc和binutils 3、用于自动化编译和链接的GNU Make 4、一个用于运行我们的操作系统的虚拟机,推荐使用Bochs。

安装上面的软件非常容易。在我的archlinux系统上面只需要一行命令

yaourt -S vim gcc binutils make bochs

就瞬间全部搞定了。 Ubuntu和Fedora系统安装这些工具应该也非常容易,如果有问题,大家自己用百度Google下就行了。 都安装好后,我们就要分析下下面需要做的事情了。我们已经有了Bochs作为虚拟机,而这个虚拟机可以通过模拟插入软盘或硬盘然后引导启动。我们可以把Bochs看作是一台电脑——事实上我们真的可以用一台电脑来代替它,只是在一开始的阶段,使用Bochs更方便于开发调试。 有了“电脑“后我们需要开发的第一个东西就是启动引导程序(Boot Loader),也就是让电脑在通电后通过引导程序运行我们的操作系统。但是这个Boot Loader的开发非常复杂,需要用到大量汇编知识。在这里我要介绍下UIUC的SigOps uBoot System。 这个uBoot系统是我们初学者的一个神器,它可以让你跳过令人头疼的Boot Loader的开发,而直接进入到下一阶段。它的用法很简单,使用一个bootmaker程序编译elf或者是其它文件然后创建一个SBBB (SigOps Bitchin' Boot Block 哈哈)的启动映像。然后我们就可以利用我们的Bochs来运行这个SBBB的启动映像了——这实在是太强大的一个工具了! 下面我来叙述下过程。 首先下载下面的文件

http://os-quake.googlecode.com/files/kernel_0_0_f.tar.gz

解压后我们看到里面有8个文件。其中main.c是我们写的"hello world"的主程序,bloader.h是对应的头文件,Makefile是对应的make文件,helloworld.ini是配置文件。bochsrc是我们所用的虚拟机bochs的配置文件,bootmaker我之前介绍过了。kernel.elf和helloworld.img是我们编译时才会生成的文件,我刚刚打包的时候一并打包进去了,你可以删掉然后让它重新生成。 再进行下一步之前,我们先熟悉下这些文件。首先看下main.c

#include "bloader.h"
int main();

/*Global Variables BAD BAD:) */
unsigned int oldEBP;
struct boot_dir *viewableDirectory;
int totalMem;
char * passedParams;
/*end global vars */

void _start(int memSize, char *parms, struct boot_dir *loadedfiles)
{
	asm("mov %%ebp, %0":"=m"(oldEBP));
	viewableDirectory = loadedfiles; /*make file mem locations global*/
	totalMem = memSize; /*make mem of system global*/
	passedParams = parms; /*make paramaters passed to system global*/
	main();

	asm("hlt");		/* this halts the machine, solving the problem of triple-faults on 
							some machines, but also making it impossible to return to DOS */
}

int main()
{
	char *vidmem = (char *) 0xb8000;
	
	/* "Hello " */
	vidmem[0] = 'H';
	vidmem[1] = 0x7;
	vidmem[2] = 'H';
	vidmem[3] = 0x7;
	vidmem[4] = 'e';
	vidmem[5] = 0x7;
	vidmem[6] = 'l';
	vidmem[7] = 0x7;
	vidmem[8] = 'l';
	vidmem[9] = 0x7;
	vidmem[10] = 'o';
	vidmem[11] = 0x7;

	/* "World " */
	vidmem[12] = 'W';
	vidmem[13] = 0x7;
	vidmem[14] = 'o';
	vidmem[15] = 0x7;
	vidmem[16] = 'r';
	vidmem[17] = 0x7;
	vidmem[18] = 'l';
	vidmem[19] = 0x7;
	vidmem[20] = 'd';
	vidmem[21] = 0x7;
	vidmem[22] = ' ';
	vidmem[23] = 0x7;

	/* "OS" */
	vidmem[24] = 'O';
	vidmem[25] = 0x7;
	vidmem[26] = 'S';
	vidmem[27] = 0x7;

	return 0;
}

main.c只有60多行,仔细看下。void start()应该就是我们的C程序和SigOps提供的bootloader之间的桥接函数,也就是我们使用bootmaker为我们的程序添加bootloader外壳时,它会让我们的程序从start()开始运行。事实上,实现这一点并不难。 我们可以通过类似下面的代码来实现一个类似的内核的main函数,并实现汇编和C之间的链接。 kernel_asm.asm

[bits 32] ; hey, we're in PMode

[global start]
[extern _kernel_main] ; always add a "_" in front of a C function to call it

start:
  call _kernel_main
  jmp $ ; halt

kernel_c.c

kernel_main()
{
     k_init();
     k_sayhello();
     ...
};

void _start()函数一开始做了一些初始化的工作,然后就直接跳转到main()函数。char vidmem = (char )0xb8000是CGA显示卡显示内存的地址(确切来说是 0XB800-0XBC00),一般的IBM PC在这一部分使用了统一编址的方式。因此若要让一个彩色字符显示在屏幕上,我们可以直接使用内存操作指令往这个内存区域执行写操作即可。0x7是一个控制字符,它用来设置字体的颜色为黑底白字。你可以查阅相关资料把它替换为其它颜色。(相关资料见 《Linux 内核完全剖析--基于0.12内核》 赵炯编著 机械工业出版社 2009.1 第24页 2.4.6 “显示控制”一节) Makefile没什么稀奇的,调用gcc把main.c编译为main.o 然后调用bootmaker读取helloworld.ini中的配置文件,bootloader生成一个包含bootloader的kernel.elf,然后利用ld命令把main.o和kernel.elf链接到一起生成helloworld.img。

好了,在了解了我们的程序之后,我们需要做的就是启动bochs虚拟机加载helloworld.img这个映像文件,然后启动。 下面是我们动手操作下,先输入命令make,生成helloworld.img映像文件,然后再用bochs加载它。

$ make $ bochs -f bochsrc

之后我们根据提示一路回车就可以看到我们的"Hello world"了! 就在屏幕的左上方,仔细找下 :)

如果运行起来有问题的话,请检查下虚拟机的配置文件里面的内容是否正确。确保里面romimage和varomimage的目录确实在对应的目录下。

好了,我们的"hello world"已经完成了。下面我们需要做的是要为它添加一些基础的功能,比如初学者最喜欢的printf()。