xv6学习笔记 分页机制 和内存管理

XV6分页机制、内存管理

报告内容

0. mmu.h的阅读

mmu.h源码中给出了XV6虚拟地址的构成,及所代表的含义

mmu.h中还有页表的相关信息,每个页目录都与1024条记录,每一个页表中也有1024条记录,每一页的大小4096字节,也就是4kb。

// Page directory and page table constants.
#define NPDENTRIES      1024    // # directory entries per page directory
#define NPTENTRIES      1024    // # PTEs per page table
#define PGSIZE          4096    // bytes mapped by a page

1.xv6物理内存初始化

内核加载到内存后,在main函数中,首先调用kinit1(),由于这时候的虚拟地址 [KERNBASE, KERNBASE+4MB) 映射到 物理地址[0, 4MB),所以内核实际能用的虚拟地址空间显然是不足以完成正常工作的,所以初始化过程中需要重新设置页表。如图:

xv6在main函数中调用kinit1和kinit2来初始化物理内存。

kinit1初始化内核末尾到物理内存4M的物理内存空间为未使用。调用freerange将空闲内存加入到空闲内存链表中

void kinit1(void *vstart, void *vend) 
{ 
    initlock(&kmem.lock, "kmem"); 
    kmem.use_lock = 0; 
    freerange(vstart, vend); 
}
kinit1(end, P2V(4*1024*1024));

在内核构建了新页表后,能够完全访问内核的虚拟地址空间,然后kinit2初始化剩余内核空间到PHYSTOP为未使用,开始了锁机制保护空闲内存链表。

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

2.内核新页表的初始化

之前说调用kinit2之前需要构建新的页表,main函数通过调用kvmalloc函数来实现内核新页表的初始化。通过初始化,最后内存布局和地址空间如下:内核末尾物理地址到物理地址PHYSTOP的内存空间未使用 ;虚拟地址空间KERNBASE以上部分映射到物理内存低地址相应位置

void
kvmalloc(void)
{
  kpgdir = setupkvm();
  switchkvm();
}

在setupkvm中,调用mappages来建立内核所需的虚拟地址到物理地址映射。阅读mappages的源码,对于每一个待映射的虚拟地址,调用walkpgdir找到该地址对应的PTE地址,然后保存对应的物理地址、权限。

在说一下 walkpgdir,这个函数用来计算PTE(页表条目)的地址,他会根据va的前十位来先找到在页目录的条目。如果该条目不存在,说明该页表不存在;如果alloc参数被设置,walkpgdir会分配页表页并将其物理地址放到页目录中。最后用虚拟地址的接下来10位来找到其在页表中的 PTE地址。

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;
}
​
//mmu.h
#define PDX(va)         (((uint)(va) >> PDXSHIFT) & 0x3FF)
​
static pte_t *walkpgdir(pde_t *pgdir, const void *va, int alloc)
{
  pde_t *pde;
  pte_t *pgtab;
​
  pde = &pgdir[PDX(va)];
  if(*pde & PTE_P){
    pgtab = (pte_t*)p2v(PTE_ADDR(*pde));
  } else {
    if(!alloc || (pgtab = (pte_t*)kalloc()) == 0)
      return 0;
    // Make sure all those PTE_P bits are zero.
    memset(pgtab, 0, PGSIZE);
    // The permissions here are overly generous, but they can
    // be further restricted by the permissions in the page table 
    // entries, if necessary.
    *pde = v2p(pgtab) | PTE_P | PTE_W | PTE_U;
  }
  return &pgtab[PTX(va)];
}

另外,在setupkvm中,所有的映射,最后都存储在kmap这样一个结构体中,下面这是kmap的结构

// 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},     // kern text+rodata
 { (void*)data,     V2P(data),     PHYSTOP,   PTE_W}, // kern data+memory
 { (void*)DEVSPACE, DEVSPACE,      0,         PTE_W}, // more devices
};

3.物理内存管理

xv6对上层提供kalloc和kfree接口来管理物理内存,上层无需知道具体的细节,kalloc返回虚拟地址空间的地址,kfree以虚拟地址为参数,通过kalloc和kfree能够有效管理物理内存,让上层只需要考虑虚拟地址空间。

kalloc分配4kb(相当于一页的大小)的物理内存,并返回一个内核可以使用的指针,如果返回0说明这页无法被分配。

kfree以虚拟地址为参数,用来释放一页的内存空间

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);
}
​
// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
char* kalloc(void)
{
  struct run *r;
​
  if(kmem.use_lock)
    acquire(&kmem.lock);
  r = kmem.freelist;
  if(r)
    kmem.freelist = r->next;
  if(kmem.use_lock)
    release(&kmem.lock);
  return (char*)r;
}
​

xv6将未分配的内存组成一个链表,一个结构体

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

xv6实际上通过保存虚拟地址空间的freelist,然后通过页表找到物理地址。由此,层调用的只需要想着“虚拟地址a对应的一页释放为空闲页”“分配一页返回虚拟地址给我”即可。

猜你喜欢

转载自blog.csdn.net/qq_37702890/article/details/84945971
今日推荐