上节提到过引导程序在将控制权交给内核时,相关寄存器状态已确定,并且程序入口地址为boot.S文件中的_start,下个面就来看看boot.S文件。
kernel目录下文件如下:
其中Sconscript内容如下:
可以看到,最终生成的可执行文件的名字为kernel,它的生成依赖很多文件,可以去查看关于scons的语法,其中需要关注的是’ -T kernel/X86/kernel.ld’,ld连接器使用了链接脚本kernel.ld,在上图中:
ENTRY(_start)——ENTRY 接收一个参数_start,它指定了可执行文件的入口符号,_start在boot.S文件中。
SECTIONS 对我们来讲是最重要的。在这里,我们定义即将生成的可执行文件的布局。我们可以定义各个段链接融合的方式以及放置的位置,可以看到,SECTIONS块的起始地址是0x0010000(1MB),结束地址是0x0040000(4MB),其中的关键符号
kernelStart/kernelEnd 、CTOR_LIST/CTOR_END(C++构造函数所在地址段)、DTOR_LIST/DTOR_END(C++析构函数所在地址段)、initStart/initEnd等,都可以在相关的cpp文件中找到定义或者被引用的地方,后续结合源码分析。
既然知道了程序入后地址_start, 那么先来看看boot.S文件:
结合注释不难看懂此段代码的含义,需要注意的是以下比较指令:
cmpl $ MULTIBOOT_BOOTLOADER_MAGIC, %eax
eax=0x2BADB002,而宏定义MULTIBOOT_BOOTLOADER_MAGIC也为 0x2BADB002,即二者的值相等。
在上一节曾提到过进入_start前的寄存器状态:
然后需要将ebx所指向的Multiboot信息结构体复制到multibootInfo所指内存区,大小为52byte:
mulitbootInfo是一个struct MultibootInfo:
现在需要知道的是mulitbootInfo已经被初始化了,给定了从grub传递过来的值。
后面设置GDT、IDT、segments、页目录以及开启页面保护模式,这些知识要找相关书籍了解一下,比较复杂,基本上与linux内核中是一致的,有少部分变动。最后跳转到kmain,也就是最后终于脱离汇编语言,进入到高级语言C++源码部分:
看到initStart、initEnd是不是很熟悉,它们在kernel.ld文件中出现过:
所有被连接器放在__attribute__((section(".init*")))的函数按顺序排列在initStart与initEnd之间,也就是说,
调用执行按顺序排列在initStart与initEnd之间函数。
那么函数怎样才能放到initStart与initEnd之间,具体举一个例子说明:
INITACLASS是一个宏,其中Memory是一个类——class Memory, initialize是一个函数——void Memory::initialize(),PMEORY是宏——#define PMEMORY “0”。我们来层层展开宏:
出现字眼".init" 以及 attribute((section(s))),基本上将宏完整展开就可以发现与以下部分的联系。
initStart = .;
KEEP ((SORT(.init)))
initEnd = .;
基本上其他函数初始化被调用都是使用同样的方法完成的。