iT邦幫忙

0

[6.1810][code] xv6 的 Memory (二)

  • 分享至 

  • xImage
  •  

系列文章 : [6.1810] 跟著 MIT 6.1810 學習基礎作業系統觀念

大綱

  • xv6-boot : ch3.6 Process address space
  • kernel/vm.c/uvmcreate
  • kernel/vm.c/uvmunmap
  • kernel/vm.c/uvmalloc
  • kernel/vm.c/uvmdealloc
  • kernel/vm.c/freewalk
  • kernel/vm.c/uvmfree
  • kernel/vm.c/uvmcopy
  • kernel/vm.c/uvmclear
  • kernel/vm.c/walkaddr
  • kernel/vm.c/ismapped
  • kernel/vm.c/vmfault
  • kernel/vm.c/copyout
  • kernel/vm.c/copyin
  • kernel/vm.c/copyinstr
  • Summary for all functions in kernel/vm.c

xv6-boot : ch3.6 Process address space

https://ithelp.ithome.com.tw/upload/images/20260315/20180992f7VBxN5VAb.jpg

每一個 user process 都有自己的 page table,並且當 xv6-riscv 進行 context-switch 的時候,satp 也會跟著切換 page table。

上圖可以看到一個 process 的 user address space。會從 0 開始,並到 MAXVA ( 0x4000000000 ) 結束,但實際上,真正會映射到 physical address 會是其中的一小部分。

一個 U-mode process 的 address space 會包含幾個部分

  • text of the program
    • PTE_R | PTE_X | PTE_U
  • pages that contain the pre-initialized data
    • PTE_R | PTE_W | PTE_U
  • a page for stack
    • PTE_R | PTE_W | PTE_U
  • pages for the heap
    • PTE_R | PTE_W | PTE_U

使用權限來管理 user process 的 address sapce 是很普遍的技巧。

假如 text of the program 被賦予了 PTE_W 權限,那一個 process 可以意外地修改自己的 program。一個簡單的範例是,假如我們意外取用了一個 null pointer,並且對這個指標所指向的地方賦值,那我們就會修改到 address 0 的程式,而不是發出 page fault

為了避免這個情旺, xv6-riscv 不給 text section PTE_W 權限,而硬體會拒絕執行這個 store 並發出一個 page fault。xv6-riscv 的 kernel 會結束這個 process,並印出資訊,讓 developer 可以分析這個問題。

相似地,存放著資料的區段 ( data section, stack, heap ) 則不會給予執行權限 ( PTE_X ),這讓 user program 沒辦法讓 pc value 跳到存放資料的地方,並把這些資料視為 instructions 來執行。這可以避免類似 shellcode 的攻擊。

  • 設定好每個 page 的權限,讓可以寫入的地方 ( PTE_W ) 不可以執行 ( PTE_X )
  • 或是其他技巧,例如讓 user process 的 memory layout 隨機 ( ASLR, KASLR )

這些方式都可以讓攻擊者更難發起攻擊。

為了抵禦 stack overflowing,xv6-riscv 會在 stack 的 page 後面,擺放一個不可以取用的 page 來當作 guard page。藉由清除 PTE_U flag,可以讓這個 page 不可以在 U-mode 被使用。當有讓何人使用這個 page,不論是有意還是無意,硬體都會發出 page fault exception。

而真實世界的作業系統,可能會在發生這個 page fault 的時候,自動 allocate 更多記憶體給這個 process 的 stack。

在這裡,我們看到幾的很好的 page table 的使用範例

  • 不同的 process 可以擁有不同的 page table。這讓不同的 user process 的相同 virtual address,可以指向不同的 physical address。這讓 user process 可以有自己的 private user memory。
  • 每一個 prcoess,在自己的視角裡,都擁有著從 0 開始連續的 virtual adress。然而他們所使用的 physical address 可以是不連續的。
    這讓每一個 user process 可以有相同的 memory layout,並透過 page table 轉換成不同的 physical address。大大簡化了系統軟體的開發難度。
  • kernel 映射了一個 page 在 user address space 的頂端,但是 user process 卻不能使用 ( 沒有 PTE_U ),這個 page 存放了 trampoline code
    當 trap 發生的時候,我們從 U-mode 轉成 S-mode,但這時候卻還沒有修改 satp 來切換 root page table,所以我們需要映射一小段 S-mode 程式碼在 user process ( U-mode ),讓 user process 的 page table 有一小段只有 S-mode 可以取用的部分。


kernel/vm.c/uvmcreate

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L179 }
使用 kalloc 要一個 page,這個 page 會作為該 user process 的 root page table。



kernel/vm.c/uvmunmap

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L192-L193 }
這個 function 可以在 pagetable 上,以 va 為起點,解除 npages 個 page 的 va→pa 映射關係。

// Remove npages of mappings starting from va. va must be
// page-aligned. It's OK if the mappings don't exist.
// Optionally free the physical memory.
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
  uint64 a;
  pte_t *pte;

  if((va % PGSIZE) != 0)
    panic("uvmunmap: not aligned");

  for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
    // 因為這邊在使用 `walk` function 的時候,alloc 參數是 0
    // 所以遇到任何 pte 不存在的情況,不會 alloc 一個 page table 給它
    // 而是會直接 return 0
    // 這一步表示在 walk 的途中,就遇到 pte 不存在的情況
    if((pte = walk(pagetable, a, 0)) == 0) // leaf page table entry allocated?
      continue;   
    // 這一步代表有 leaf page table ( level 0 page table ),但是 leaf pte 不存在
    if((*pte & PTE_V) == 0)  // has physical page been allocated?
      continue;

    // 假如 do_free == true
    // 會順便把這個 va 映射到的 pa 所代表的 page 給 free 掉
    // 把這個 page 還給 kernel/kalloc.c/kmem 
    if(do_free){
      uint64 pa = PTE2PA(*pte);
      kfree((void*)pa);
    }

    // 假如 pte 存在的話,就將這個 va → pa 的映射關係拿掉
    *pte = 0;
  }
}


kernel/vm.c/uvmalloc

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L192-L193 }

這個 function 可以在 pagetable 上,從 va==oldsz 一路配置記憶體到 va==newsz,並且這些記憶體的權限會是 xperm。

因為 xv6-riscv 預期執行檔的 .text 跟 .data 會從 0 開始往上增長 ( 可以參考 xv6-boot : ch3.6 Process address space 章節的 memory layout 圖示 ),所以 kexec 在使用這個 function 的時候也是從 0 開始配置 va → pa 的映射。

Q: 為什麼在 kernel/exec.c/kexec 不先遍尋 ELF 的各個 section,最後只需要呼叫一次的 uvmalloc ?

// Allocate PTEs and physical memory to grow a process from oldsz to
// newsz, which need not be page aligned.  Returns new size or 0 on error.
uint64
uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz, int xperm)
{
  char *mem;
  uint64 a;

  if(newsz < oldsz)
    return oldsz;

  oldsz = PGROUNDUP(oldsz);
  for(a = oldsz; a < newsz; a += PGSIZE){
    mem = kalloc();
    if(mem == 0){
      // 假如在配置記憶體的過程中,發覺記憶體不足,
      // 會再用 `uvmdealloc` 退回所有的記憶體配置
      uvmdealloc(pagetable, a, oldsz);
      return 0;
    }
    memset(mem, 0, PGSIZE);
    if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_R|PTE_U|xperm) != 0){
      kfree(mem);
      uvmdealloc(pagetable, a, oldsz);
      return 0;
    }
  }
  return newsz;
}


kernel/vm.c/uvmdealloc

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L246-L247 }

uint64
uvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz)

這個 function 會在 pagetable 上,把 newsz ~ oldsz 這個區間的 va →pa 的映射關係給解除。
預期 newsz < oldsz



kernel/vm.c/freewalk

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L262-L263 }

// Recursively free page-table pages.
// All leaf mappings must already have been removed.
void
freewalk(pagetable_t pagetable)
  • 以 xv6-riscv 的設計來說,只會對 leaf PTE 設定 PTE_R|PTE_W|PTE_X
  • 所以 (pte & PTE_V) 跟 ((pte & (PTE_R|PTE_W|PTE_X)) == 0) 同時成立的話,就代表這個不是 leaf PTE
  • 在呼叫 freewalk 的時候,已經預期所有 leaf PTE 已經解除 va→pa 的映射了。所以在遍尋 PTE 的途中,只要遇到 (pte & PTE_V) 的 leaf PTE,就會發出 panic。
    https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L274
  • 這個 function 會遍尋 pagetable 下的所有 not leaf PTE 以及所有的 page table,並將所有 page table 的 physical memory page 用 kfree 回收。


kernel/vm.c/uvmfree

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L282C1-L283C42 }

void
uvmfree(pagetable_t pagetable, uint64 sz)
  • 會先用 uvmumap 處理所有的 leaf PTE
  • 之後再用 freewalk 處理所有 non-leaf PTE


kernel/vm.c/uvmcopy

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L290-L297 }

這個 function 會複製一整個 page table,以及 page table 所指向的所有 physical memory。

// Given a parent process's page table, copy
// its memory into a child's page table.
// Copies both the page table and the
// physical memory.
// returns 0 on success, -1 on failure.
// frees any allocated pages on failure.
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags;
  char *mem;

  for(i = 0; i < sz; i += PGSIZE){
    if((pte = walk(old, i, 0)) == 0)
      continue;   // page table entry hasn't been allocated
    if((*pte & PTE_V) == 0)
      continue;   // physical page hasn't been allocated

    // 這個 pte 是 leaf pte
    // allocate a page,並且將 leaf pte 指向的 physical memory
    // 的資料複製過去
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    if((mem = kalloc()) == 0)
      goto err;
    memmove(mem, (char*)pa, PGSIZE);

    // mappages 會為新的 root page table 配置新的 page table
    // 以及相對應的 pte
    if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
      kfree(mem);
      goto err;
    }
  }
  return 0;

 err:
  // uvmunmap 只會釋放 leaf pte 的資料
  // 並且釋放 leaf pte 所指向的 physical memory
  uvmunmap(new, 0, i / PGSIZE, 1);
  return -1;
}


kernel/vm.c/uvmclear

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L327 }

// mark a PTE invalid for user access.
// used by exec for the user stack guard page.
void
uvmclear(pagetable_t pagetable, uint64 va)

針對特定的 pagetable,特定的 va,讓這個 va 沒辦法在 U-mode 使用。
可以用來製作 user stack 的 guard page。



kernel/vm.c/walkaddr

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L121 }

這個 function 可以查找特定 page table 上的 virtual address,會對應到哪一個 physical address。要注意,這個 function 只能查到該 va 為 U-mode 可碰觸的情況。

// Look up a virtual address, return the physical address,
// or 0 if not mapped.
// Can only be used to look up user pages.
uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
  pte_t *pte;
  uint64 pa;

  if(va >= MAXVA)
    return 0;

  // 透過 `walk` 嘗試拿到 leaf PTE
  pte = walk(pagetable, va, 0);

  if(pte == 0)
    return 0;
  if((*pte & PTE_V) == 0)
    return 0;

  // 該 leaf PTE 必須為 U-mode 可碰觸
  if((*pte & PTE_U) == 0)
    return 0;

  // 拿出該 pte 指向的 physical memory
  pa = PTE2PA(*pte);
  return pa;
}


kernel/vm.c/ismapped

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L475-L476 }

int
ismapped(pagetable_t pagetable, uint64 va)

查看這個 va 有沒有映射到 pa。



kernel/vm.c/vmfault

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L448-L454 }

// allocate and map user memory if process is referencing a page
// that was lazily allocated in sys_sbrk().
// returns 0 if va is invalid or already mapped, or if
// out of physical memory, and physical address if successful.
uint64
vmfault(pagetable_t pagetable, uint64 va, int read)
  • 假如這個 va 是非法的, return 0
  • 假如這個 va 已經有映射到 pa 了 ( ismapped ),return 0
  • 假如上述情況都沒發生,則 allocate 一塊 physical memory,並新增 va 對此 pa 的映射


kernel/vm.c/copyout

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L343 }

把資料從 kernel space ( S-mode ) 搬到 user space ( U-mode )。

  • dstva : 目標的 user process 的 virutal address
  • pagetable : 目標的 user process 的 root page table
  • src : kernel space 的 source pointer
  • len : 想要複製的長度

整理一下會用到的 function

  • walkaddr
    • 從特定的 pagetable,理解這個 virtual address 對應到什麼 physical address,這個 function 只能處理相對應的 pte 有 PTE_U 的情況。
  • vmfault
    • 假如這個 va 是非法的, return 0
    • 假如這個 va 已經有映射到 pa 了 ( ismapped ),return 0
    • 假如上述情況都沒發生,則 allocate 一塊 physical memory,並新增 va 對此 pa 的映射
  • walk
    • 從特定的 pagetable,將 virtual address 轉成對應的 leaf PTE

這個 function 的流程

  • 給予 user process 的 pagetable 以及 virtual address,找出相對應的 physical address
  • 把 kernel space 的資料搬到 user space

Q: 可能不需要執行一次 walkaddr 拿 physical address,然後再執行一次 walk 拿 pte 嗎 ?
應該只需要執行一次 walk 就該能一次拿到 pte 跟 physical address 了。



kernel/vm.c/copyin

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L377-L381 }

// Copy from user to kernel.
// Copy len bytes to dst from virtual address srcva in a given page table.
// Return 0 on success, -1 on error.
int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)

這個 function 的流程

  • 給予 user process 的 pagetable 以及 virtual address,找出相對應的 physical address
  • 把 user space 的資料搬到 kernel space


kernel/vm.c/copyinstr

{ https://github.com/TommyWu-fdgkhdkgh/xv6-riscv/blob/xv6-riscv-rev5/kernel/vm.c#L405-L410 }

// Copy a null-terminated string from user to kernel.
// Copy bytes to dst from virtual address srcva in a given page table,
// until a '\0', or max.
// Return 0 on success, -1 on error.
int
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)

這個 function 跟 copyin 很像,差別在於,當遇到 \x00 的時候就停止搬運資料。



Summary for all functions in kernel/vm.c

  • 各個 kvm 開頭的 function
    • kernel/vm.c/kvmmap
    • kernel/vm.c/kvmmake
    • kernel/vm.c/kvminit
    • kernel/vm.c/kvminithart
  • 各個 uvm 開頭的 function
    • kernel/vm.c/uvmcreate
    • kernel/vm.c/uvmunmap
    • kernel/vm.c/uvmalloc
    • kernel/vm.c/uvmdealloc
    • kernel/vm.c/uvmfree
    • kernel/vm.c/uvmcopy
    • kernel/vm.c/uvmclear
  • 各個不是 kvm* 也不是 uvm* 的 function
    • kernel/vm.c/walk
    • kernel/vm.c/mappages
    • kernel/vm.c/freewalk
    • kernel/vm.c/walkaddr
    • kernel/vm.c/ismapped
    • kernel/vm.c/vmfault
    • kernel/vm.c/copyout
    • kernel/vm.c/copyin
    • kernel/vm.c/copyinstr


Reference


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言