xv6-rev6 読み

main 関数が呼び出されてからイベント待ちループ (?) に入るまでのナニを確認してみる、ということで当分進めてみる方向です。

main 手続き

先頭部分が以下。

// 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

どこまで確認できるのか。

kinit1 手続き

の前に渡してる end というシンボルなんですが直上で extern な参照宣言あり。

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

こちらですが、kernel.ld で定義されております。末端。

	PROVIDE(edata = .);

	.bss : {
		*(.bss)
	}

	PROVIDE(end = .);

これを前提に手続きの定義が以下です。

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

ええと、initlock という手続きは kmem という構造体変数の spinlock な属性の初期化なのかな。ちなみに kmem という変数の定義が kalloc.c で以下です。

struct {
  struct spinlock lock;
  int use_lock;
  struct run *freelist;
} kmem;

あるいは spinlock 構造体の定義が spinlock.h で以下。

// Mutual exclusion lock.
struct spinlock {
  uint locked;       // Is the lock held?
  
  // For debugging:
  char *name;        // Name of lock.
  struct cpu *cpu;   // The cpu holding the lock.
  uint pcs[10];      // The call stack (an array of program counters)
                     // that locked the lock.
};

initlock 手続きの定義が以下です。

void
initlock(struct spinlock *lk, char *name)
{
  lk->name = name;
  lk->locked = 0;
  lk->cpu = 0;
}

このあたりはこんなもんかな、という程度で。freerange という手続きは何なのかな。一応 kinint1 に渡されるのは使えるメモリの先頭から末端のアドレスのはず。この場合は end から P2V(4*1024*1024) ってことなので end から 4MB という理解で良いのかな。
ちなみに P2V というマクロは名前の通り phys 2 virt ということかな。

#define P2V(a) (((void *) (a)) + KERNBASE)

KERNBASE 加えてますのでそうだろうな。で、freerange 手続きは kalloc.c で定義されてて以下。

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

先頭を調整して vend までを kfree してます。脱出条件がちょっとアレですが、これは kfree 手続きの定義に依るのかな。
あ、freerange 手続きは knint1 手続きから呼びだされる場合、空きページなリストを作ってることになってるように見えるな。手続き定義は以下なんですが、今の所 kmem.use_lock が 0 なのは明白なのでスルーします。

//PAGEBREAK: 21
// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc().  (The exception is when
// initializing the allocator; see kinit above.)
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);
}

で、次に呼びだされてるのが kvmalloc 手続き。

  kvmalloc();      // kernel page table

微妙なコメントが付いてます。定義は vm.c で以下。

// Allocate one page table for the machine for the kernel address
// space for scheduler processes.
void
kvmalloc(void)
{
  kpgdir = setupkvm();
  switchkvm();
}

余談ですが vm.c には walkpgdir という非常に懐しい名前の手続きの定義がありますね。kfree で領域 1 で memset しといて walkpgdir で領域確保したナニに 0 で memset している模様。
kvmalloc 手続きでは kpgdir という変数が初期化されてますが定義は vm.c で以下。

pde_t *kpgdir;  // for use in scheduler()

static ナシの帯域変数です。で、左辺な setupkvm 手続きの定義も vm.c で以下。

// Set up kernel part of a page table.
pde_t*
setupkvm()
{
  pde_t *pgdir;
  struct kmap *k;

  if((pgdir = (pde_t*)kalloc()) == 0)
    return 0;
  memset(pgdir, 0, PGSIZE);
  if (p2v(PHYSTOP) > (void*)DEVSPACE)
    panic("PHYSTOP too high");
  for(k = kmap; k < &kmap[NELEM(kmap)]; k++)
    if(mappages(pgdir, k->virt, k->phys_end - k->phys_start, 
                (uint)k->phys_start, k->perm) < 0)
      return 0;
  return pgdir;
}

コメントの通りなのだろうな。おそらく kalloc という手続きは 4096 なナニを確保して戻してるはず。核心部分は末端の繰り返しなのかな。ちなみに kmap という配列の定義は vm.c で以下ですね。

// This table defines the kernel's mappings, which are present in
// every process's page table.
static struct kmap {
  void *virt;
  uint phys_start;
  uint phys_end;
  int perm;
} kmap[] = {
  { (void*) KERNBASE, 0,             EXTMEM,    PTE_W},  // I/O space
  { (void*) KERNLINK, V2P(KERNLINK), V2P(data), 0}, // kernel text+rodata
  { (void*) data,     V2P(data),     PHYSTOP,   PTE_W},  // kernel data, memory
  { (void*) DEVSPACE, DEVSPACE,      0,         PTE_W},  // more devices
};

mappages という手続きは何をしてるのかな。コメントのみ以下に引用。

// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned.

ちなみにこの手続きも vm.c で定義されている模様。これで上記の配列な page table entry を作ってるのかどうか。
ちょっと DEVSPACE 云々なあたりの理解が微妙ですがもう少しきちんと確認した方が良さげ。と言いつつ今日もこれでエントリ投入します。