XV6:bootmain - 加载内核 ELF header

XV6: bootmain - loading kernel ELF header

我一直在分析xv6内核中bootmain.c代码的代码:

void
bootmain(void)
{
  struct elfhdr *elf;
  struct proghdr *ph, *eph;
  void (*entry)(void);
  uchar* pa;

  elf = (struct elfhdr*)0x10000;  // scratch space

  // Read 1st page off disk
  readseg((uchar*)elf, 4096, 0);

  // Is this an ELF executable?
  if(elf->magic != ELF_MAGIC)
    return;  // let bootasm.S handle error

  // Load each program segment (ignores ph flags).
  ph = (struct proghdr*)((uchar*)elf + elf->phoff);
  eph = ph + elf->phnum;
  for(; ph < eph; ph++){
    pa = (uchar*)ph->paddr;
    readseg(pa, ph->filesz, ph->off);
    if(ph->memsz > ph->filesz)
      stosb(pa + ph->filesz, 0, ph->memsz - ph->filesz);
  }

  // Call the entry point from the ELF header.
  // Does not return!
  entry = (void(*)(void))(elf->entry);
  entry();
}

我明白下面一行

readseg((uchar*)elf, 4096, 0);

正在尝试将 elf header 从磁盘复制到 elf 地址的内存中,但我不明白 为什么要复制4kb 其中 elf header 本身的大小是 52 字节。

在 运行 readelf -h kernel 之后,我得到以下关于 elf headers 的信息:

Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         3

这意味着(如果我的数学对我有用的话)精灵 header + 程序 header table 不会超过 148 个字节。

您显示的代码是 xv6 bootloader.

C 代码

至少要读取 ELF header,才能知道程序 header 的数量。而不是从磁盘读取两次(一次用于 ELF header,然后一次用于程序 headers),他们只读取整个第一页 (4KiB)。他们假设组合程序 header 和 ELF header 不超过 4 KiB。

最好加载ELF header;判断程序大小headers,读入内存;然后读取与每个程序相关的片段 header。如果 ELF header 和所有其他 header 以某种方式超过 4 KiB,则此代码将中断。

阅读代码和一些 xv6 文档后,他们似乎简化了内核 ELF 文件的加载,因此生成的代码可以放入 512 字节的引导扇区。一次读取数据可能是设计决定,但老实说,他们可能不希望内核本身有大量 header。只需读取前 4KiB,既方便又简单。

感兴趣的可能是第 102 页上的 xv6 documentation,其中描述了必须进行简化以使引导加载程序代码(和 ELF 加载程序)适合 512 字节引导扇区的事实:

The boot loader described in this appendix compiles to around 470 bytes of machine code, depending on the optimizations used when compiling the C code. In order to fit in that small amount of space, the xv6 boot loader makes a major simplifying assumption, that the kernel has been written to the boot disk contiguously starting at sector 1. More commonly, kernels are stored in ordinary file systems, where they may not be contiguous, or are loaded over a network. These complications require the boot loader to be able to drive a variety of disk and network controllers and understand various file systems and network protocols. In other words, the boot loader itself must be a small operating system. Since such complicated boot loaders certainly won’t fit in 512 bytes, most PC operating systems use a two-step boot process. First, a simple boot loader like the one in this appendix loads a full-featured boot-loader from a known disk location, often relying on the less space-constrained BIOS for disk access rather than trying to drive the disk itself. Then the full loader, relieved of the 512-byte limit, can implement the complexity needed to locate, load, and execute the desired kernel.

我认为可以得出结论,在简化假设中,有关于引导加载程序中 ELF 加载程序设计的假设。