bootloader加载kernel

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的分段和分节

在上图中可以看到kernel可执行文件的第一段包含了 ELF 文件的.test 节、.rodata 节、.stab节以及.stabstr 节,而文件的第二段包含了.data 节以及在硬盘上不占用空间但在内存中占据 660 字节的.bss 节,在这里程序头表的第二项会用 p_filesz 成员变量标注该段在文件占用的字节数(磁盘)并且同时用 p_memsz 标注在内存中占用的字节数,这样 Boot Loader 便会在从硬盘读 入第二段的同时为.bss节在内存中分配空间。.comment 节没有被包含在 任意一段中,这表明它没有被装入内存