Loading

XV6学习笔记(2) :内存管理

XV6学习笔记(2) :内存管理

在学习笔记1中,完成了对于pc启动和加载的过程。目前已经可以开始在c语言代码中运行了,而当前已经开启了分页模式,不过是两个4mb的大的内存页,而没有开启小的内存页。接下来就可以从main.c的init函数开始

这里会和JOS做一个对比

首先看一下在执行main.c之前的物理内存分布

0x0000-0x7c00     引导程序的栈
0x7c00-0x7d00     引导程序的代码(512字节)
0x10000-0x11000   内核ELF文件头(4096字节)
0xA0000-0x100000  设备区
0x100000-0x400000 Xv6操作系统(未用满)

1. Kinit1函数

1.1 xv6中的kinit1函数

int
main(void)
{
  kinit1(end, P2V(4*1024*1024)); // phys page allocator
  kvmalloc();      // kernel page table
  //....
}

这是main函数的开始。所以我们先从kinit1开始

这里的end地址就是kernel从0x80100000开始。然后是内核的代码段 + 只读数据段+ stab段+ stabstr + 数据段 + .bss段这些之后的起始地址。如下图所示。

image-20210817213129000
void
kinit()
{
  initlock(&kmem.lock, "kmem");
  freerange(end, (void*)PHYSTOP);
}

  1. 这里的vstart就是end的地址而vend是KERNBASE + 128MB = 0x86400000
  2. 这里就是把[vstart, 0x86400000]的内存按页(4kb大小)进行free
  3. kree这里会把他插入到freelist中
void
freerange(void *vstart, void *vend)
{
  char *p;
  p = (char*)PGROUNDUP((uint)vstart);
  for(; p + PGSIZE <= (char*)vend; p += PGSIZE)
    kfree(p);
}
//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(void *pa)
{
  struct run *r;

  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");

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

  r = (struct run*)pa;

  acquire(&kmem.lock);
  r->next = kmem.freelist;
  kmem.freelist = r;
  release(&kmem.lock);
}

好了这里就可以完成整个free操作了

image-20210817213005747

1.2 jos的boot_alloc函数

和上面的是非常类似的

  1. 当第一次执行的时候nextfree是空这里会进行第一次分配。这里的起始地址也是end的地址
  2. 然后返回这一段分配的地址,并更新nextfree
static void *
boot_alloc(uint32_t n)
{
	static char *nextfree;	// virtual address of next byte of free memory
	char *result = NULL;

	// Initialize nextfree if this is the first time.
	// 'end' is a magic symbol automatically generated by the linker,
	// which points to the end of the kernel's bss segment:
	// the first virtual address that the linker did *not* assign
	// to any kernel code or global variables.
	if (!nextfree) {
		extern char end[];
		nextfree = ROUNDUP((char *) end + 1, PGSIZE);
	}

	// Allocate a chunk large enough to hold 'n' bytes, then update
	// nextfree.  Make sure nextfree is kept aligned
	// to a multiple of PGSIZE.
	//
	// LAB 2: Your code here.
	if (n > 0) {
		result = nextfree;
		nextfree = ROUNDUP(nextfree + n, PGSIZE);
	} else if (n == 0) {
		result = ROUNDUP(nextfree, PGSIZE);
	} else {
		panic("boot_alloc(n): n < 0\n");
	}

	cprintf("boot_alloc(): nextfree=%08x\n", nextfree);

	if ((uintptr_t) nextfree >= KERNBASE + PTSIZE) {
		panic("boot_alloc(): out of memory\n");
	}

	return result;
}

2. kvminit

// Initialize the one kernel_pagetable
void
kvminit(void)
{
  kernel_pagetable = kvmmake();
}

这里我们先看一下kvmmake()

/ Make a direct-map page table for the kernel.
pagetable_t
kvmmake(void)
{
  pagetable_t kpgtbl;

  kpgtbl = (pagetable_t) kalloc();
  memset(kpgtbl, 0, PGSIZE);

  // uart registers
  kvmmap(kpgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W);

  // virtio mmio disk interface
  kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);

  // PLIC
  kvmmap(kpgtbl, PLIC, PLIC, 0x400000, PTE_R | PTE_W);

  // map kernel text executable and read-only.
  kvmmap(kpgtbl, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);

  // map kernel data and the physical RAM we'll make use of.
  kvmmap(kpgtbl, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);

  // map the trampoline for trap entry/exit to
  // the highest virtual address in the kernel.
  kvmmap(kpgtbl, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);

  // map kernel stacks
  proc_mapstacks(kpgtbl);
  
  return kpgtbl;
}

当然这里引出了很多函数

1. kalloc函数

从我们的空闲队列中获得一个指针。返回

void *
kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r)
    kmem.freelist = r->next;
  release(&kmem.lock);

  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}

2. kvmmap:

  1. add a mapping to the kernel page table.
  2. only used when booting
  3. does not flush TLB or enable paging.
void
kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm)
{
  if(mappages(kpgtbl, va, sz, pa, perm) != 0)
    panic("kvmmap");
}

3. mappages函数

  1. 给定虚拟地址va和物理地址pa
  2. 把[va , va + size]和 [pa, pa + size]进行映射。并且以perm位
static int
mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm)
{
  char *a, *last;
  pte_t *pte;

  a = (char*)PGROUNDDOWN((uint)va);
  last = (char*)PGROUNDDOWN(((uint)va) + size - 1);
  for(;;){
    if((pte = walkpgdir(pgdir, a, 1)) == 0)
      return -1;
    if(*pte & PTE_P)
      panic("remap");
    *pte = pa | perm | PTE_P;
    if(a == last)
      break;
    a += PGSIZE;
    pa += PGSIZE;
  }
  return 0;
}

4.walk函数

这里关于页的操作要补充一些关于页表操作的知识

在risc-v的xv6中利用39位的虚拟地址。整个原则如下

image-20210821174317183
// The risc-v Sv39 scheme has three levels of page-table
// pages. A page-table page contains 512 64-bit PTEs.
// A 64-bit virtual address is split into five fields:
//   39..63 -- must be zero.
//   30..38 -- 9 bits of level-2 index.
//   21..29 -- 9 bits of level-1 index.
//   12..20 -- 9 bits of level-0 index.
//    0..11 -- 12 bits of byte offset within the page.

// extract the three 9-bit page table indices from a virtual address.
#define PGSHIFT 12  // bits of offset within a page
#define PXMASK          0x1FF // 9 bits
#define PXSHIFT(level)  (PGSHIFT+(9*(level)))
#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK)
#define PTE2PA(pte) (((pte) >> 10) << 12)
  1. 这里的操作用到了上面的PX函数

    #假设 level = 2 
    va >> (12 + 9 * 2) & 0x1FF
    (va >> 30) & 0x1FF
    |EXT|L2| & 0x0001|1111|1111|
    # 得到的就是L2的地址
    
  2. 这里就是获取指定虚拟地址的pte也就是最后一层的PPN

pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
  if(va >= MAXVA)
    panic("walk");

  for(int level = 2; level > 0; level--) {
    pte_t *pte = &pagetable[PX(level, va)];
    if(*pte & PTE_V) {
      pagetable = (pagetable_t)PTE2PA(*pte);
    } else {
      if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
        return 0;
      memset(pagetable, 0, PGSIZE);
      *pte = PA2PTE(pagetable) | PTE_V;
    }
  }
  return &pagetable[PX(0, va)];
}

整个映射完的图如下

image-20210821211420668

3. kvminithart()函数

  1. 第一行设置了satp这样硬件就可以找到pgdir的地址了
// Switch h/w page table register to the kernel's page table,
// and enable paging.
void
kvminithart()
{
  w_satp(MAKE_SATP(kernel_pagetable));
  sfence_vma();
}

刷新当前cpu的tlb

// flush the TLB.
static inline void
sfence_vma()
{
  // the zero, zero means flush all TLB entries.
  asm volatile("sfence.vma zero, zero");
}
posted @ 2021-08-18 20:58  周小伦  阅读(793)  评论(0编辑  收藏  举报