XV6源代碼閱讀-虛擬內存管理

Exercise1 源代碼閱讀

1.內存管理部分: kalloc.c vm.c 以及相關其餘文件代碼html

  • kalloc.c:char * kalloc(void)負責在須要的時候爲用戶空間、內核棧、頁表頁以及緩衝區分配物理內存,將物理地址轉爲虛擬地址返回,物理頁大小爲4k。void kfree(char * v)接收一個虛擬地址,找對對應的物理地址進行釋放。xv6使用空閒內存的前部分做爲指針域來指向下一頁空閒內存,物理內存管理是以頁(4K)爲單位進行分配的。物理內存空間上空閒的每一頁,都有一個指針域(虛擬地址)指向下一個空閒頁,最後一個空閒頁爲NULL ,經過這種方式,kmem只須要保存着虛擬地址空間上的freelist地址便可;
// kalloc.c
// Physical memory allocator, intended to allocate
// memory for user processes, kernel stacks, page table pages,
// and pipe buffers. Allocates 4096-byte pages.

void freerange(void *vstart, void *vend);
extern char end[]; // first address after kernel loaded from ELF file

struct run {
  struct run *next;
};

struct {
  struct spinlock lock;
  int use_lock;
  struct run *freelist;
} kmem;
  • xv6讓每一個進程都有獨立的頁表結構,在切換進程時老是須要切換頁表,switchkvm設置cr3寄存器的值爲kpgdir首地址,kpgdir僅僅在scheduler內核線程中使用。頁表和內核棧都是每一個進程獨有的,xv6使用結構體proc將它們統一塊兒來,在進程切換的時候,他們也每每隨着進程切換而切換,內核中模擬出了一個內核線程,它獨佔內核棧和內核頁表kpgdir,它是全部進程調度的基礎。switchuvm經過傳入的proc結構負責切換相關的進程獨有的數據結構,其中包括TSS相關的操做,而後將進程特有的頁表載入cr3寄存器,完成設置進程相關的虛擬地址空間環境;
// vm.c

……

// Switch h/w page table register to the kernel-only page table,
// for when no process is running.
void
switchkvm(void)
{
  lcr3(v2p(kpgdir));   // switch to the kernel page table
}

// Switch TSS and h/w page table to correspond to process p.
void
switchuvm(struct proc *p)
{
  pushcli();
  cpu->gdt[SEG_TSS] = SEG16(STS_T32A, &cpu->ts, sizeof(cpu->ts)-1, 0);
  cpu->gdt[SEG_TSS].s = 0;
  cpu->ts.ss0 = SEG_KDATA << 3;
  cpu->ts.esp0 = (uint)proc->kstack + KSTACKSIZE;
  ltr(SEG_TSS << 3);
  if(p->pgdir == 0)
    panic("switchuvm: no pgdir");
  lcr3(v2p(p->pgdir));  // switch to new address space
  popcli();
}
  • 進程的頁表在使用前每每須要初始化,其中必須包含內核代碼的映射,這樣進程在進入內核時便不須要再次切換頁表,進程使用虛擬地址空間的低地址部分,高地址部分留給內核,主要接口:
    • pde_t * setupkvm(void)經過kalloc分配一頁內存做爲頁目錄,而後將按照kmap數據結構映射內核虛擬地址空間到物理地址空間,期間調用了工具函數mappages;
    • int allocuvm(pde_t * pgdir, uint oldsz, uint newsz)在設置頁表的同時分配虛擬地址oldsz到newsz的以頁爲單位的內存;
    • int deallocuvm(pde_t * pgdir, uint oldsz, uint newsz)則將newsz到oldsz對應的虛擬地址空間內存置爲空閒;
    • int loaduvm(pde_t * pgdir, char * addr, struct inode * ip, uint offset, uint sz)將文件系統上的i節點內容讀取載入到相應的地址上,經過allocuvm接口爲用戶進程分配內存和設置頁表,而後調用loaduvm接口將文件系統上的程序載入到內存,便可以爲exec系統調用提供接口,爲用戶進程的正式運行作準備;
    • 當進程銷燬須要回收內存時,調用void freevm(pde_t * pgdir)清除用戶進程相關的內存環境,其首先調用將0到KERNBASE的虛擬地址空間回收,而後銷燬整個進程的頁表;
    • pde_t * copyuvm(pde_t * pgdir, uint sz)負責複製一個新的頁表並分配新的內存,新的內存佈局和舊的徹底同樣,xv6使用這個函數做爲fork()底層實現。

Exercise2 帶着問題閱讀

1.XV6初始化以後到執行main.c時,內存佈局是怎樣的(其中已有哪些內容)?node

  • 內核代碼存在於物理地址低地址的0x100000處,頁表爲main.c文件中的entrypgdir數組,其中虛擬地址低4M映射物理地址低4M,虛擬地址 [KERNBASE, KERNBASE+4MB) 映射到 物理地址[0, 4MB);git

  • 緊接着調用kinit1初始化內核末尾到物理內存4M的物理內存空間爲未使用,而後調用kinit2初始化剩餘內核空間到PHYSTOP爲未使用。kinit1調用前使用的仍是最初的頁表(也就是是上面的內存佈局),因此只能初始化4M,同時因爲後期再構建新頁表時也要使用頁錶轉換機制來找到實際存放頁表的物理內存空間,這就構成了自舉問題,xv6經過在main函數最開始處釋放內核末尾到4Mb的空間來分配頁表,因爲在最開始時多核CPU還未啓動,因此沒有設置鎖機制。kinit2在內核構建了新頁表後,可以徹底訪問內核的虛擬地址空間,因此在這裏初始化全部物理內存,並開始了鎖機制保護空閒內存鏈表;
  • 而後main函數經過調用void kvmalloc(void)函數來實現內核新頁表的初始化;
  • 最後內存佈局和地址空間以下:內核末尾物理地址到物理地址PHYSTOP的內存空間未使用,虛擬地址空間KERNBASE以上部分映射到物理內存低地址相應位置。github

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

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

2.XV6 的動態內存管理是如何完成的? 有一個kmem(鏈表),用於管理可分配的物理內存頁。(vend=0x00400000,也就是可分配的內存頁最大爲4Mb)
詳見「Exercise 1 源代碼閱讀」部分,已經做出完整解答。數組

3.XV6的虛擬內存是如何初始化的? 畫出XV6的虛擬內存佈局圖,請說出每一部分對應的內容是什麼。見memlayout.h和vm.c的kmap上的註釋?bash

  • main函數經過調用void kinit1(void * vstart, void * vend), void kinit2(void * vstart, void * vend), void kvmalloc(void)函數來實現內核新頁表的初始化。虛擬地址與物理地址的轉換接口:
// memlayout.h
// Memory layout

#define EXTMEM  0x100000            // Start of extended memory
#define PHYSTOP 0xE000000           // Top physical memory
#define DEVSPACE 0xFE000000         // Other devices are at high addresses

// Key addresses for address space layout (see kmap in vm.c for layout)
#define KERNBASE 0x80000000         // First kernel virtual address
#define KERNLINK (KERNBASE+EXTMEM)  // Address where kernel is linked

#ifndef __ASSEMBLER__

static inline uint v2p(void *a) { return ((uint) (a))  - KERNBASE; }
static inline void *p2v(uint a) { return (void *) ((a) + KERNBASE); }

#endif

#define V2P(a) (((uint) (a)) - KERNBASE)
#define P2V(a) (((void *) (a)) + KERNBASE)

#define V2P_WO(x) ((x) - KERNBASE)    // same as V2P, but without casts
#define P2V_WO(x) ((x) + KERNBASE)    // same as V2P, but without casts
  • 內存佈局:

4.關於XV6 的內存頁式管理。發生中斷時,用哪一個頁表? 一個內存頁是多大? 頁目錄有多少項? 頁表有多少項? 最大支持多大的內存? 畫出從虛擬地址到物理地址的轉換圖。在XV6中,是如何將虛擬地址與物理地址映射的(調用了哪些函數實現了哪些功能)?數據結構

  • 發生中斷時,將換入cpu的進程的頁表首地址存入cr3寄存器;一個內存頁爲4k;XV6頁表採用的二級目錄,一級目錄有\(2^{10}\)條,二級目錄有\(2^{10} * 2^{10}\)條;頁表項爲\(2^2\)Bytes,故頁表有\(2^{12} / 2^2 = 2^{10} = 1024\)項;最大支持4G內存;

  • 物理內存頁的申請與釋放,虛擬地址與物理地址如何映射等在「Exercise 1 源代碼閱讀」都已經詳述了,在此主要說下mappages接口,虛擬地址 * va與物理地址 * pa映射size個字節,同時賦予該頁的權限perm,以下:
// vm.c
……
// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned.
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;
}
……

參考文獻

[1] xv6虛擬內存-博客園
[2] xv6 virtual memory-hexo
[3] xv6內存管理-簡書
[4] xv6內存管理-CSDNhexo

相關文章
相關標籤/搜索