该教程演示了如何制作一个简单的内核。让我们从例子文件kernel_start.asm的开始进入内核。
[BITS 32]
[global start]
[extern _k_main] ; _k_main
在
c
程序里
start:
call _k_main
cli ;
禁止中断
hlt ;
暂停
cpu
这些是32位的代码([BITS 32]表示)调用k_main函数,这个函数的定义在一个C的文件kernel.c里。令人疑惑的是为什么在c文件里它是k_main而在汇编文件里它是_k_main。这是因为c/c++的编译器在c/c++的函数前面加上了一个下划线(_),除非把它连接到一个ELF文件。ELF不需要这个下划线。一旦k_main被调用,指令cli就被执行。Cli关闭中断(虽然在本例中没有中断)。然后,hlt被执行停止cpu的继续运行。可以使用jmp $来代替这个指令,但是它会占用大量的cpu时间而导致cpu过热。注意,hlt指令可以使cpu能被中断唤醒。这就是为什么要在hlt之前禁止中断,为的是让cpu完全的停止。
现在来到kernel.c的开始位置看一看定义和函数的原型。
#define WHITE_TXT 0x07 //
黑底白字
void k_clear_screen();
unsigned int k_printf(char *message, unsigned int line);
void update_cursor(int row, int col);
除了#define WHITE_TXT 0X07之外没有什么特别的。稍后在回来,记住这个里。
现在看到k_main函数。
k_main() //
象在一般
c
程序里的
main
一样
{
k_clear_screen();
k_printf("Hi!/nHow's this for a starter OS?", 0);
};
k_main是一个指向内核的入口。我们在kernel_start.asm文件里调用这个函数。
k_clear_screen 清除屏幕。 k_printf(“Hi!/nHow’s this for a starter OS?”,0); 显示该字符:
Hi!
How’s this for a starter OS?
在视频内存的第一行开始(0是第一行,1是第二,2是第三,如此类推)。符号/n象c/c++里的printf函数一个指定一新行。
在保护模式下,不能调用bios中断来清屏,我们不得不直接写视频内存。
void k_clear_screen() //
清除全部的文本屏幕
{
char *vidmem = (char *) 0xb8000;
unsigned int i=0;
while(i < (80*25*2))
{
vidmem[i]=' ';
i++;
vidmem[i]=WHITE_TXT;
i++;
};
};
在上面的函数中,指针vidmem指向0xb8000是保护模式下视频内存的起始地址。定义该指针为char为的是一次可以向视频内存写一个字节。X86的文本模式是80x25个字符。每一个字符需要两个字节。第一个字节是字符内容,第二个是属性控制颜色和闪烁。所以在访问整个视频内存的时候要用80*25*2。Vidmem[I]=’ ‘;是写入一个空格到视频内存(I代表位置)。把I加一, i++;取得下一个视频位置(属性位),把0x07写入。0x07表示一个黑底白字,无闪烁的文本。
现在看看k_printf函数!
unsigned int k_printf(char *message, unsigned int line) // the message and then the line #
{
char *vidmem = (char *) 0xb8000;
unsigned int i=0;
i=(line*80*2);
while(*message!=0)
{
if(*message=='/n') // check for a new line
{
line++;
i=(line*80*2);
*message++;
} else {
vidmem[i]=*message;
*message++;
i++;
vidmem[i]=WHITE_TXT;
i++;
};
};
return(1);
};
k_printf函数和k_clear_screen函数类似。While(*message!=0)循环搜索传入函数字符串的结尾。If(*message == ‘/n’)检查字符串的下一个字符是不是一个新行。如果是,则在line加一,在/n后面的字符将会下一行。如果不是新行(/n),那么就把它放入视频内存并设置属性位0x07。
编译内核
首先,要下在一个内核源代码。还需要一个汇编编译器(nasm),c语言编译器(DJGPP或者gcc),和一个连接器(LD)。
现在,到最靠近连接器文件的顶端,看到这样一行:
.text 0x100000
16进制数表示内核要被载入内存的位置。这里是1MB。
首先编译我们的”平底锅”汇编代码文件:
nasm -f aout kernel_start.asm -o ks.o
把kernel_start.asm文件编译成aout格式的ks.o文件,现在是我们的c文件:
gcc -c kernel.c -o kernel.o
下一步也就是最后一步是连接ks.o和kernel.o文件到一起。在此,我们要把它用link.ld脚本文件连接到二进制文件里。用下面的命令:
ld -T link.ld -o kernel.bin ks.o kernel.o
ks.o首先要被连接,否则内核不会正常工作。内核是kernel.bin,并且将要被bootsector/loader运行,设置成保护模式允许A20的使用。
结论
一个基本的内核就完成了。你可能想要一个更好的k_printf函数,我们的示例非常简单而且不能处理%s,%d,%c的一些字符。但是已经可以使我们遵循这样的方法继续作下去了。
This tutorial was written by Joachim Nock and K.J.
Updated September 13, 2002 by K.J.
翻译:flyback 2004/10/2,http://blog.youkuaiyun.com/flyback
fly-back@163.com