bootloader实际做了两件事:进入保护模式和加载内核程序
。这篇文章将介绍bootloader加载内核程序的过程
内核的装入过程
对 ELF 文件有了基础地认识后,我们再来研究一下 Boot Loadr 具体是如何将 kernel 的可执行 ELF 文件加载到内存中的。boot/main.c 就是从硬盘读取 kernel
,下面我们就分块来分析一下这个C程序
void bootmain(void)
{
struct Proghdr *ph, *eph;
// read 1st page off disk
// 把操作系统映像文件的elf头部读取出来放入内存中,把内核的第一个页
//的内容读取到内存地址ELFHDR(0x10000)处
// 程序就将文件的前 4KB 读入内 存,这其中包括 ELF 文件头以及程序头表
// 这样就可以找到文件的每一段
readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);
// is this a valid ELF?
// elf文件的头部就是用来描述这个elf文件如何在存储器中存储,文件是可链
// 接文件还是可执行文件,会有不同的elf头部格式.对于一个可执行程序,通
// 常包含存放代码的文本段(text section),存放全局变量的data段,存放
// 字符串常量的rodata段
if (ELFHDR->e_magic != ELF_MAGIC)
goto bad;
// load each program segment (ignores ph flags)
// 部中一定包含Program Header Table。这个表格存放着程序中所有段的信息。
// 通过这个表我们才能找到要执行的代码段,数据段等等。所以我们先获得这个表
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum; //表尾
//把操作系统内核的各个段从外存读入内存中
for (; ph < eph; ph++)
// p_pa is the load address of this segment (as well
// as the physical address)
readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
// call the entry point from the ELF header
// note: does not return!
// 跳转到操作系统内核程序的起始指令处.把控制权从boot loader转交给了操作
// 系统的内核
((void (*)(void)) (ELFHDR->e_entry))();
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
while (1)
/* do nothing */;
}
可以看到 SECTSIZE*8 代表 4KB,这就是说在一开始 ,程序就将文件的前4KB 读入内存,这其中包括 ELF文件头以及程序头表
, 根据这些信息,我们可以找到文件的每一段 。于是紧接着程序利用 readseg函数将内核文件的每一段依次读入内存
然后转到内核入口地址执行。实际上我们可以通过“objdump –f 可执行文件”命令来查看 ELF 文件的入口地址。
通过实验可以看到 kernel 的入口地址是 0xf010000c,然而在程序跳转的时候,却
将 0xf010000c 与 0xFFFFFF 相与以后的值作为入口地址,这是因为 kernel 程序的链接地址 是 0xf0100000,而由于实际的物理内存没有那么大,在 0xf0100000 地址处并没有物理内存
, 所以真正的加载地址实际上是 0x100000,相应的入口地址也就是 0x10000c,即是 0xf010000c 与 0xFFFFFF 相与以后的值。
内核程序的分析
当我们查看内存的中的 ELF 文件头的相应信息后,我们发现 kernel 可执行文件实际上是分为 两段
第一段在文件中的偏移是 0x1000,而在内存中占据的字节数是 0x6daa,链接地址 0xf0100000;
第二段在文件中的偏移是 0x8000,而在内存中占据的字节数是 0x8bc0,链接地址 0xf0107000
在上图中可以看到kernel可执行文件的第一段包含了 ELF 文件的.test 节、.rodata 节、.stab节以及.stabstr 节
,而文件的第二段包含了.data 节以及在硬盘上不占用空间但在内存中占据 660 字节的.bss 节
,在这里程序头表的第二项会用 p_filesz 成员变量标注该段在文件占用的字节数(磁盘)并且同时用 p_memsz 标注在内存中占用的字节数,这样 Boot Loader 便会在从硬盘读 入第二段的同时为.bss节在内存中分配空间
。.comment 节没有被包含在 任意一段中,这表明它没有被装入内存