FPGA開発日記

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

テキストを読んで、xv6のブートプロセスを理解する(3: メモリの確保)

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

さて、xv6のmainに足を踏み入れると、以下のようなコードが記述されている。

github.com

extern char end[]; // first address after kernel loaded from ELF file

// Bootstrap processor starts running C code here.
// Allocate a real stack and switch to it, first
// doing some setup required for memory allocator to work.
int
main(void)
{
  kinit1(end, P2V(4*1024*1024)); // phys page allocator
  kvmalloc();      // kernel page table
  mpinit();        // collect info about this machine
  cprintf("\ncpu%d: starting xv6\n\n", cpu->id);
  picinit();       // interrupt controller
  consoleinit();   // I/O devices & their interrupts
  uartinit();      // serial port
  pinit();         // process table
  tvinit();        // trap vectors
  binit();         // buffer cache
  fileinit();      // file table
  iinit();         // inode cache
  ideinit();       // disk
  timerinit();   // uniprocessor timer, we don't support multicore for MIPS.
  kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
  userinit();      // first user process

  // Finish setting up this processor in mpmain.
  mpmain();
}

kinit1とkinit2の中身を見てみよう。kalloc.c に配置されている。

// Initialization happens in two phases.
// 1. main() calls kinit1() while still using entrypgdir to place just
// the pages mapped by entrypgdir on free list.
// 2. main() calls kinit2() with the rest of the physical pages
// after installing a full page table that maps them on all cores.
void
kinit1(void *vstart, void *vend)
{
  initlock(&kmem.lock, "kmem");
  kmem.use_lock = 0;
  freerange(vstart, vend);
}

void
kinit2(void *vstart, void *vend)
{
  freerange(vstart, vend);
  kmem.use_lock = 1;
}

void
freerange(void *vstart, void *vend)
{
  char *p;
  p = (char*)PGROUNDUP((uint)vstart);
  for(; p + PGSIZE <= (char*)vend; p += PGSIZE)
    kfree(p);
}

kinit1はxv6のプログラム領域の最後から、4MBまでの領域を初期化し、フリーアドレススペースに挿入するようになっている。 フリーアドレススペースに挿入する仕組み自体は、kfree(p)によって実現されている。

  kinit1(end, P2V(4*1024*1024)); // phys page allocator

...

void
kfree(char *v)
{
  struct run *r;

  if((uint)v % PGSIZE || v < end || v2p(v) >= PHYSTOP)
    panic("kfree");

  // Fill with junk to catch dangling refs.
  memset(v, 1, PGSIZE);

  if(kmem.use_lock)
    acquire(&kmem.lock);
  r = (struct run*)v;
  r->next = kmem.freelist;
  kmem.freelist = r;
  if(kmem.use_lock)
    release(&kmem.lock);
}

endはプログラム領域の最後として、kernel.ldに定義されている。

...

        PROVIDE(edata = .);

        .bss : {
                *(.bss)
        }

        PROVIDE(end = .);

        /DISCARD/ : {
                *(.eh_frame .note.GNU-stack)
        }

このプログラムはアドレス0から4MBの領域をユーザ領域として使うようだが、実際にはendまではプログラムが含まれているので、 endから4MBまでの領域を初期化するようになっている。 ところが、MIPSコンパイルしたコードを実行すると、MIPSのkernelは4MBを越えてしまい、初期化する領域が無くなってしまった。 これにより、カーネルがパニックを起こす。

kfree(char *v)
{
  struct run *r;

  if((uint)v % PGSIZE || v < end || v2p(v) >= PHYSTOP)
    panic("kfree");

開放する領域がendよりも小さい場所だと、パニックするような仕組みになっているのだ。 これを回避するために、今回の移植では、メモリとして確保する領域を8MBに増やした。

github.com

index 0166c0f..834a817 100644
--- a/main.c
+++ b/main.c
@@ -18,7 +18,7 @@ extern char end[]; // first address after kernel loaded from ELF file
 int
 main(void)
 {
-  kinit1(end, P2V(4*1024*1024)); // phys page allocator
+  kinit1(end, P2V(8*1024*1024)); // phys page allocator
   kvmalloc();      // kernel page table
   mpinit();        // collect info about this machine
   cprintf("\ncpu%d: starting xv6\n\n", cpu->id);
@@ -32,7 +32,7 @@ main(void)
   iinit();         // inode cache
   ideinit();       // disk
   timerinit();   // uniprocessor timer, we don't support multicore for MIPS.
-  kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
+  kinit2(P2V(8*1024*1024), P2V(PHYSTOP)); // must come after startothers()
   userinit();      // first user process

これを実行することで、無事にメモリ初期化の領域は通過するようになったが、別の部分で止まってしまった。 そしてかなりシミュレーションに時間がかかる。この辺りも改善していこう。

swimmer_mips --imgfile ~/xv6-mips/xv6.img --max 1000000000
Swimmer-RISCV
  Version 20150910 Revision d8cf377
  developed by Masayuki Kimura <masayuki.kimura.1986@gmail.com>
<Info: NewMemory Region 1fc00000 is defined.>
<Info: NewMemory Region 0050d000 is defined.>
<Info: NewMemory Region 0050e000 is defined.>
<Info: NewMemory Region 0050f000 is defined.>
<Info: NewMemory Region 00510000 is defined.>
<Info: NewMemory Region 00511000 is defined.>
<Info: NewMemory Region 00512000 is defined.>
<Info: NewMemory Region 00513000 is defined.>
<Info: NewMemory Region 00514000 is defined.>
<Info: NewMemory Region 00515000 is defined.>
<Error: CSR Address 018 is invalid.>
<Error: CSR Address 028 is invalid.>

xv6...
<Info: NewMemory Region 00000000 is defined.>
cpu0: panic: inituvm: more than a page