File: vm.c
分頁硬體#
- PTE:Page table entry,包含 20-bit PPN 及 flags
- PPN:Physical Page number
- x86的頁表:$2^{20}$ 條 PTE。
目標#
分頁硬體使用虛擬位址找到對應的 PTE,接著把高 20-bit 替換為 PTE 的 PPN,低 12-bit 直接沿用,即完成轉譯的動作。
XV6 頁表#
- 一個頁表在物理記憶體中為一顆兩層的樹
- 樹根為一個 4096 字節的目錄(page dir),包含 1024 個類 PTE,分別指向不同的頁表頁(page table page)。
- 每頁包含 1024 個 32-bit PTE。
- 轉譯過程
- 分頁硬體用虛擬地址的高 10-bit 找到指定的頁。
- 如果指向的頁存在的話,繼續使用接著的 10-bit 來找到指定的 PTE。
- 不存在的話,拋出錯誤。
flags#
flags | name | 為 1 時 | 為 0 時 |
---|---|---|---|
P | Present | 表示頁存在 | 不存在 |
W | Writable | 可以寫入 | 只能讀/取 |
U | User | user 能使用此頁 | 只有 kernel 能使用 |
WT | - | Write-through | Write-back |
CD | Cache Disable | 不會對此頁進行 cache | 進行 cache |
A | Accessed | 為 0 時被存取, 處理器會將此位設為 1 | - |
D | Dirty | 為 0 時寫入此頁, 處理器會將此位設為 1 | - |
AVL | Available for system use | - | - |
只有軟體可以將 A、D 清 0。[1]
名詞解釋#
- 物理記憶體:DRAM
- 物理地址:DRAM 的位址
Process address space#
main
呼叫kvmalloc
跳到新的頁表,重新映射至記憶體。
- 每個 process 都有自己的頁表,在切換 process 時也會切換頁表。
- process 的頁表從 0 開始,最多至
KERNBASE
,限制 process 最多使用 2GB。 - 如果需要更多記憶體時:
- XV6 先找到一個空的頁
- 將對應的 PTE 加入 process 的頁表裡
- 每個 process 的頁表都有包含對應的 kernel 映射(
KERNBASE
之上),這樣當發生中斷時就不需要切換頁表。 KERNBASE
之上的頁對應的 PTE,PTE_U 均設為 0。
Code: 建立 address space#
main
呼叫kvmalloc
來建立KERNBASE
之上的頁表
kvmalloc#
功能 | 回傳值 |
---|---|
建立 kernel page | void |
146 | // Allocate one page table for the machine for the kernel address |
- 建立頁表的工作由
setupkvm
完成
setupkvm#
功能 | 回傳值 |
---|---|
設定 kernel page | PDE |
155 | // Set up kernel part of a page table. |
- 首先分配一頁來存放目錄
164 | memset(pgdir, 0, PGSIZE); |
- 接著呼叫
mappages
來建立 kernel 所需的映射。 - 映射存放在 kmap 裡
113 | // This table defines the kernel's mappings, which are present in |
- kmap 包含 kernel 的資料及指令、
PHYTOP
以下的物理記憶體、及 I/O 設備的記憶體。 - 這裡不會建立有關 user 的映射
mappages#
功能 | 回傳值 |
---|---|
設定 PTE | 0 (ok) / -1 (err) |
67 | // Create PTEs for virtual addresses starting at va that refer to |
- 首先呼叫
walkpgdir
來找到對應的 PTE
81 | if(*pte & PTE_P) |
- 接著確認 PTE_P flags
83 | *pte = pa | perm | PTE_P; |
- 最後初始化 PTE。
- 問題:如何初始化
功能 | 回傳值 |
---|---|
從目錄尋找對應的 PTE | PTE |
*pgdir |
*va |
alloc |
---|---|---|
目標目錄 | 目標虛擬地址 | 是否有 alloc |
42 | // Return the address of the PTE in page table pgdir |
功能 | 回傳值 |
---|---|
切換至 kernel 頁 | void |
155 | void |
分配物理記憶體#
kernel 在運行時須為以下物件分配物理記憶體:
- Page table
- Process 的 user 記憶體
- kernel stack
- Pipe buffers
Code: 物理記憶體分配器#
File: kalloc.c
- 分配器為一個可分配的記憶體頁所構成的 free list
main
呼叫kinit1(end, P2V(4*1024*1024))
及kinit2(P2V(4*1024*1024), P2V(PHYSTOP))
初始化分配器
kinit1 / 2#
功能 | 回傳值 |
---|---|
初始化物理記憶體分配器 | void |
*vstart |
*vend |
---|---|
起始位址 | 結束位址 |
25 | void |
功能 | 回傳值 |
---|---|
初始化物理記憶體分配器 | void |
*vstart |
*vend |
---|---|
起始位址 | 結束位址 |
38 | void |
kinit1 / 2
呼叫freerange
將記憶體加入 free list
freerange#
功能 | 回傳值 |
---|---|
釋放一段記憶體 | void |
*vstart |
*vend |
---|---|
起始位址 | 結束位址 |
45 | void |
freerange
呼叫kfree
來完成工作
kfree#
功能 | 回傳值 | *v |
---|---|---|
釋放記憶體 | void | 欲 free 的虛擬地址 |
54 | //PAGEBREAK: 21 |
- 首先將每個字節設為 1
69 | if(kmem.use_lock) |
- 接著把 v 轉為
struct run
的指標,插在 free list 的第一顆。
User part of an address space#
Code: sbrk#
File: sysproc.c
功能 | 回傳值 |
---|---|
增長/收縮 process 的記憶體 | 記憶體大小(結果) |
44 | int |
sbrk
透過呼叫growproc
來完成工作。
File: proc.c
功能 | 回傳值 | n |
---|---|---|
增長/收縮 process 的記憶體 | 0 (ok) / -1 (err) | 增長/收縮大小 |
105 | // Grow current process's memory by n bytes. |
- 如果 n>0:
allocuvm
- 如果 n<0:
deallocuvm
allocuvm、deallocuvm#
File: vm.c
功能 | 回傳值 |
---|---|
增長記憶體 | 記憶體大小(結果) |
*pgdir |
oldsz |
newsz |
---|---|---|
從該目錄尋找可用記憶體 | 舊的大小 | 新的大小 |
218 | // Allocate page tables and physical memory to grow process from oldsz to |
- 首先檢查是否有要超過大小,及動作是否合法。
231 | a = PGROUNDUP(oldsz); |
- 接著透過
kalloc()
來要記憶體,並將要到的記憶體清空 - 最後回傳 process 目前總共的大小
功能 | 回傳值 |
---|---|
縮減記憶體 | 記憶體大小(結果) |
*pgdir |
oldsz |
newsz |
---|---|---|
從該目錄釋放記憶體 | 舊的大小 | 新的大小 |
245 | // Deallocate user pages to bring the process size from oldsz to |
- 一樣先檢查動作是否合法
258 | a = PGROUNDUP(newsz); |
- 接著一個一個 pte free,先將 flags 歸0,再透過
kfree
完成工作。
Code: exec#
- 功用:創建 user part address space
- 概觀:打開及讀取 ELF 文件來初始化 user part
struct elfhdr#
File: elf.h
5 | // File header |
- 一個 ELF 文件包含一個 elfhdr、program setion hdr(struct proghdr)
struct proghdr#
24 | // Program section header |
- 一個 proghdr 描述了須載入至記憶體的 program section
XV6 的 program 只有一個 section,其他 OS 可能會有多個。
File: exec.c#
1 |
|
- 用
namei
打開二進制文件(ch6 會說明)
23 | ilock(ip); |
- 接著確認 ELF 是否正確(藉由 ELF_magic)
31 | if((pgdir = setupkvm()) == 0) |
setupkvm
分配一個沒有 user part 的頁allocuvm
分配給每個 ELF 的 program section 記憶體。loaduvm
將 section 載入至記憶體
50 | // Allocate two pages at the next page boundary. |
Reference
- 1.記憶體管理/分頁架構 ↩