多语言展示
当前在线:1484今日阅读:26今日分享:39

linux下的第一个内核程序

初学linux 了解x86机器工作原理,熟悉程序编译链接过程,了解grub使用
工具/原料
1

centos5

2

virtualbox

3

grub

4

nasm

5

ld

6

gcc

方法/步骤
1

我们喜欢用c来做所有的事情,但是我们无可避免地需要用到一点儿汇编,我们将会写一小段x86的汇编代码来作为内核入口,这一段汇编代码会在调用我们的c代码后停止整个程序流程。我们怎样确认汇编代码会作为内核的起始点呢? 我们将用一个连接器脚本将这些目标文件链接成我们最终的内核程序(稍后解释更多),在连接器脚本里,我们指定了这段二进制代码会被加载到内存 [0x100000]处。这个地址就是我之前说过的,内核所希望的起始地址。汇编代码如下:;;kernel.asmbits 32 ;nasm directive - 32 bitsection .text global startextern kmain        ;kmain is defined in the c file start:  cli ;block interrupts  call kmain  hlt ;halt the CPU第一行指令 bit32 不是x86汇编指令,它是一条NASM 指令,指定nasm汇编器产生32位的程序,这条语句并不是必不可少的,但加上它是一个好的编程习惯。第二行是text段(代码段)的开始,在这里存放着我们的代码块。global是另外一个NASM指令,用将一个符号设置为全局符号。这样做连接器才会知道符号start在哪儿开始,start是我们程序的入口地址。kmain是我们定义在kernel.c文件中的函数,extern关键字声明了该函数定义在别的文件中。到这里,我们的函数start调用kmian函数之后就会使用hlt指令将CPU挂起,中断会cpu从hlt 指令中唤醒,我们要在挂起之前用cli指令来关闭系统的中断响应,cli指令是清除中断(clear-interrupts)的缩写。

2

在kernle.asm中,我们调用了kmain()函数,所以我们的c代码将会在kmain()中开始运行:/**  kernel.c*/void kmain(void){ char *str = 'my first kernel'; char *vidptr = (char*)0xb8000; //video mem begins here. unsigned int i = 0; unsigned int j = 0; //clear all while(j < 80 * 25 * 2) { //blank character vidptr[j] = ' '; //attribute-byte: light grey on black screen vidptr[j+1] = 0x07; j = j + 2; } j = 0; while(str[j] != '\0') { vidptr[i] = str[j]; vidptr[i+1] = 0x07; ++j; i = i + 2; } return;}我们的内核首先会清空整个屏幕,然后打印出字符串。首先,我们用一个vidptr指针,指向地址[0xb8000] , 这个地址是保护模式下显存的起始地址。屏幕的文本内容对应着的内存空间中一个内存段,即屏幕的输出输出映射到了内存中地址[0xb8000]的地方,整个屏幕共支持25行,每行80个ASCII字符。在文本内存中每一个字符由16bits(2个字节)表示,这不像我们以前使用8bits来定义。其中第一个字节是该字符的ASCII码,第二个字节是属性字节, 它描述了字符的表现形式,包括了字符颜色等属性。为了在黑色的背景下打印绿色字符’s‘,我们将字符’s‘放在显存中的第一个字节,接着将[0x02]放在第二个字节中, 其中 0表示黑色背景,2表示绿色前景。下面是不同颜色的定义:0 - Black, 1 - Blue, 2 - Green, 3 - Cyan, 4 - Red, 5 - Magenta, 6 - Brown, 7 - Light Grey,8 - Dark Grey, 9 - Light Blue, 10/a - Light Green, 11/b - Light Cyan, 12/c - Light Red, 13/d - Light Magenta, 14/e - Light Brown, 15/f – White. 在我们的内核中,我们将字符颜色设置为灰色,将背景颜色设定为黑色,因此我们的属性字节的值是[0x07].在第一个while循环中,程序将属性值为[0x07]的空格字符(‘  ’)写到整个屏幕中(共25行,每行80个字符),这样就会将整个屏幕清空了。在第二个while循环中,我们将null结尾的字符串 “my first kernel” ,从显存的起始处开始写入。这样字符串就打印在屏幕上了

3

我们用NASM,GCC分别将kernale.asm,kernel.c编译成目标文件,接着将这些目标文件链接成一个可引导的内核程序。我们指定ld连接器按照我们脚本规定来进行链接。/**  link.ld*/OUTPUT_FORMAT(elf32-i386)ENTRY(start)SECTIONS {   . = 0x100000;   .text : { *(.text) }   .data : { *(.data) }   .bss  : { *(.bss)  } }脚本指定了输出格式为 32位的ELF文件格式. ELF(Executable and Linkable Format)是x86构架的类Unix系统标准的二进制格式。ENTRY 接收一个参数。它指定了可执行文件的入口符号。SECTIONS  对我们来讲是最重要的。在这里,我们定义即将生成的可执行文件的布局。我们可以定义各个段链接融合的方式以及放置的位置。在SECTIONS 后的花括号中,符号 (.) 表示的是一个位置计数器。它通常会被初始化为[0x0],作为SECTIONS 块的起始地址 ,它的值是可以被修改的。 之前我说过,内核代码需要在地址[0x100000]处,所以我们将它修改为[0x100000]。接着看下一行的 .text : { *(.text) }星号( * )是一个通配符,表示所有的文件名。*(.text)表示将所有输入文件的 .text 段因此,按照这个设定,连接器将所有目标文件的text段融合到最终可执行文件的text 段中,即在位置计数器所标识的地址处 ([0x100000])。在连接器将处理好输出的text段后,地址计数器的值会变为[0x100000]+text段的长度。类似的,data段和bss段也会相应得融合后放置到地址计数器所标识的位置。Grub和多重引导现在我们已经准备好所有制作内核所需的文件了,但我们还有一步工作,我们还需要用grub Bootloader来启动我们的内核。在按照Mutileboot 规范来编译我们的内核后,它就可以被GRUB引导了。按照Mutileboot 的规范说明,内核必须在起始的8KB中包含这一个多引导项头(Multiboot header)。而且,这个多引导项头里面必须有3个4字节对齐的块。一个魔术块:包含了魔数[0x1BADB002],是多引导项头结构的定义值。一个标志块:我们不关心这个块的内容,我们简单设定为0。一个校检块:校检块,魔术块和标志块的数值的总和必须是0。因此,我们的内核代码如下:;;kernel.asm ;nasm directive - 32 bitbits 32section .text        ;multiboot spec        align 4        dd 0x1BADB002            ;magic        dd 0x00                  ;flags        dd - (0x1BADB002 + 0x00) ;checksum. m+f+c should be zero global startextern kmain        ;kmain is defined in the c file start:  cli ;block interrupts  call kmain  hlt ;halt the CPU dd 指令定义了个4字节的双字

4

我们现在开始将kernel.asm和kernel.c编译成目标文件,接着将它们根据我们的连接器脚本的设定链接到一起:nasm -f elf32 kernel.asm -o kasm.o启动NASM汇编器将kernel.asm编译成ELF-32位格式的目标文件。gcc -m32 -c kernel.c -o kc.o-c选项告知GCC编译器在将源文件编译成目标文件后,不要对它们进行链接。ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o启动链接器,根据我们的链接脚本生成一个名为kernel的可执行的文件。

5

GRUB 需要以kernel-的形式来命名内核程序,所以,我将它重名为kernel-701.接着将它放在/boot目录下,这一步需要你需要拥有超级用户权限才能够进行操作。在你的GRUB配置文件grub.cfg中加上一个引导入口,如下:title myKernel root (hd0,0) kernel /boot/kernel-701 ro如果存在一个“hiddenmenu”的指令,记得要把它移除掉。重启电脑,你就能够看到你的内核也在启动选择项列表中了。选择启动它之后,结果如下:成功显示出来了。”这是你的内核“”不,是你的内核“。 PS:*  建议你在虚拟机中进行你所有内核hacking。*  在一些新的发行版中,使用了grub2作为默认的bootloader,你需要向下面这样来配置你的配置文件。 (感谢 Rubén Laguna提供了grub2的配置)menuentry 'kernel 7001' { set root='hd0,msdos1' multiboot /boot/kernel-7001 ro}*  如果你想用qemu模拟器代替GRUB来启动你的内核程序的话,你可以怎么做:qemu-system-i386 -kernel kernel

注意事项
1

asm 与 ld 文件注意空格

2

nasm -f elf32不行的可以nasm -f elf

3

grub中 kernel位置要与其他内核启动位置相同

推荐信息