FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

ページテーブルを有効にするまでのブートローダの起動のしくみ

※ この記事はまだ勉強中のため、いろいろ間違いがあるかもしれません。

xv6のbootmain.cなどのブートローダは、まだセグメントやページテーブルが有効な状態としては利用されない。 実際にOSを動かすとなれば、ページテーブルが必須となるが、これはどのようにして設定しているのだろうか。

X86番のxv6では、bootmainからentryに飛ぶ直前に、エントリを設定してセグメントを有効にしている。 ここでは、0x0010_0000番地にロードされたデータが、全て0x8010_0000として読み込まれるようにセグメントを設定することで、entryから先は0x8010_0000にアクセスすることで、プログラムにアクセスできるようになっている。

ここで、kernel.ldを見てみよう。

SECTIONS
{
        /* Link the kernel at this address: "." means the current address */
        /* Must be equal to KERNLINK */
        . = 0x80100000;

        .text : AT(0x100000) {
                *(.text .stub .text.* .gnu.linkonce.t.*)
        }

見て分かるとおり、プログラムがロードされるのは0x8010_0000だが、実行されるのは0x0010_0000からフェッチされることを想定している。 セグメントが有効になった場合に、0x0010_0000からフェッチが開始される訳だ。

bootmain.cでは、ディスクイメージからカーネルをメモリにロードした後、entryを見つけて、そこからプログラムを開始するようにしている。

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();
}

そして、entryはentry.Sに記述されている。

# Entering xv6 on boot processor, with paging off.
.globl entry
entry:
  # Turn on page size extension for 4Mbyte pages
  movl    %cr4, %eax
  orl     $(CR4_PSE), %eax
  movl    %eax, %cr4
  # Set page directory
  movl    $(V2P_WO(entrypgdir)), %eax
  movl    %eax, %cr3
  # Turn on paging.
  movl    %cr0, %eax
  orl     $(CR0_PG|CR0_WP), %eax
  movl    %eax, %cr0

  # Set up the stack pointer.
  movl $(stack + KSTACKSIZE), %esp

  # Jump to main(), and switch to executing at
  # high addresses. The indirect call is needed because
  # the assembler produces a PC-relative instruction
  # for a direct jump.
  mov $main, %eax
  jmp *%eax

ここで、ページテーブルを有効にして、やっとmainに飛ぶ訳だ。 さて、今度は、これをMIPSに移植していこう。