VM 記憶體管理與保護 (VM for Memory Management & Protection)
Overview Table
| 主題 | 核心機制 | 帶來的好處 | 書頁 |
|---|---|---|---|
| 每行程獨立位址空間 | OS 為每個 process 維護一份獨立的 page table | 行程間記憶體天然隔離;多個 VP 可映射到同一 PP 實現共享 | p.847 |
| 簡化 linking | 每個 process 的記憶體映像格式一致(code 固定起於 0x400000) |
linker 產出的執行檔與實體位址無關 | p.847-848 |
| 簡化 loading | loader 只建立 PTE 指向目的檔位置、標記 invalid,不複製資料 | 資料由 VM 系統首次參照時 demand paging 載入 | p.848 |
| 簡化 sharing | 不同 process 的 VP 映射到同一個實體頁 | kernel code、C 標準函式庫(如 printf)只需一份 |
p.848 |
| 簡化 memory allocation | malloc 要求 k 頁時,配置 k 個連續虛擬頁→ 映射到任意分散的實體頁 |
免除尋找連續實體記憶體的負擔 | p.848 |
| 記憶體保護 | PTE 加上 SUP / READ / WRITE 權限位元,每次位址轉譯時由硬體檢查 | 頁層級 (page-level) 存取控制;違規 → SIGSEGV | p.848-849 |
9.4 VM as a Tool for Memory Management (p.847-848)
VM 的價值不只在「用 DRAM 快取較大的虛擬位址空間」。歷史上如 DEC PDP-11/70 的虛擬位址空間甚至小於實體記憶體,VM 依然有用——因為它大幅簡化記憶體管理並提供自然的保護機制。關鍵轉折:先前假設整個系統只有一張 page table;實際上 OS 為每個行程提供一份獨立的 page table,也就是一個獨立的虛擬位址空間。
- 獨立位址空間 (separate address space):process i 與 process j 各有自己的 VP 0, VP 1, … 編號,由各自的 page table 轉譯。
- 共享 (sharing):多個虛擬頁(可跨行程)可映射到同一個共享實體頁;如下圖 i 的 VP 2 與 j 的 VP 1 都指向 PP 7。
- demand paging + 獨立位址空間的組合,深刻改變了系統中記憶體的使用與管理方式。
Virtual address spaces Address Physical memory
translation
0 ┌─────────┐ 0 ┌─────────┐
│ VP 1 │──────────────────────┐ │ │
Process i: │ VP 2 │────────┐ └───────▶│ PP 2 │
N-1 └─────────┘ │ │ │
└─────────────────────▶│ PP 7 │◀── shared
0 ┌─────────┐ ┌─────────────────────▶│ page │
│ VP 1 │────────┘ │ │
Process j: │ VP 2 │──────────────────────────────▶│ PP 10 │
N-1 └─────────┘ M-1 └─────────┘
(Figure 9.9:OS 為每個 process 維護獨立的 page table;不同 process 的 VP 可映射到相同的實體頁)
「一個 process = 一張 page table = 一個虛擬位址空間」。隔離是預設(映射到不相交的實體頁),共享是例外(刻意映射到同一實體頁)。
簡化 Linking (p.847-848)
獨立位址空間讓每個 process 使用相同的基本記憶體映像格式,與 code/data 實際落在實體記憶體何處無關(參見第 8 章 Fig 8.13 的 Linux process 記憶體佈局)。
- 64 位元位址空間中,code segment 一律起始於虛擬位址
0x400000。 - data segment 緊接 code segment 之後(隔一段對齊間隙 alignment gap)。
- stack 佔據使用者位址空間最高處,向下成長。
- 這種一致性大幅簡化 linker 的設計:可產生完全連結 (fully linked)、與實體位置無關的執行檔。
0x400000 是教科書中未啟用 ASLR 的傳統 Linux 佈局。現代系統常以 ASLR(位址空間配置隨機化)與 PIE 隨機化各區段起點,細節見 03-Machine-Level-Programs/06-Buffer-Overflow-and-Pointer-Safety。簡化 Loading (p.848)
載入執行檔 / 共享目的檔的 .text 與 .data 時,Linux loader 的動作是:
loader 載入流程(不搬資料!)
┌────────────────────────────────────────────────────────┐
│ 1. 為 code/data segment 配置虛擬頁 (allocate VPs) │
│ 2. 將這些頁標記為 invalid(未快取) │
│ 3. 讓 PTE 指向目的檔中對應的位置 │
└────────────────────────────────────────────────────────┘
│ (loader 到此結束,零複製)
▼
首次參照該頁(CPU 取指令或指令存取記憶體)
│
▼
page fault → VM 系統自動、on demand 換入該頁
- 重點:loader 從不實際把資料從 disk 複製進記憶體;資料在第一次被參照時由 VM 系統自動 demand paging 換入。
- 把「一組連續虛擬頁映射到任意檔案的任意位置」的概念稱為 memory mapping(記憶體映射)。
- Linux 提供
mmap系統呼叫,讓應用程式自行做 memory mapping(詳見 9.8 節 → 09-Virtual-Memory/04-Memory-Mapping)。
簡化 Sharing (p.848)
獨立位址空間給 OS 一個一致的機制管理 user process 之間、以及 user process 與 OS 之間的共享:
| 情境 | Page table 安排 |
|---|---|
| 私有的 code、data、heap、stack(預設) | 各 process 的 VP 映射到不相交 (disjoint) 的實體頁 |
共享 kernel code、C 標準函式庫(如 printf) |
不同 process 的適當 VP 映射到同一組實體頁,全系統只保留一份副本 |
- 若每個 process 都內含 kernel 與 libc 的獨立副本,將極度浪費;共享映射避免了這件事。
簡化 Memory Allocation (p.848)
當程式要求額外 heap 空間(例如呼叫 malloc):
- OS 配置 k 個「連續」的虛擬頁,並將它們映射到實體記憶體中任意位置的 k 個實體頁。
- 因為 page table 逐頁映射的特性,OS 不需要找 k 個連續的實體頁——實體頁可以隨機散佈在實體記憶體各處。
- 這消除了實體記憶體的外部碎片化 (external fragmentation) 壓力;應用層 heap 內部的碎片化管理見 09-Virtual-Memory/05-Dynamic-Memory-Allocation。
9.5 VM as a Tool for Memory Protection (p.848-849)
現代系統必須讓 OS 能控制對記憶體系統的存取。一個 user process 不應該能夠:
- 修改自己的唯讀 code section
- 讀取或修改 kernel 的任何 code 與資料結構
- 讀寫其他 process 的私有記憶體
- 修改與其他 process 共享的虛擬頁——除非所有參與方透過顯式的 IPC 系統呼叫明確允許
獨立位址空間已天然隔離各 process 的私有記憶體;而位址轉譯機制可自然延伸出更細緻的存取控制:因為位址轉譯硬體在 CPU 每次產生位址時都會讀取 PTE,只要在 PTE 中加入額外的權限位元 (permission bits),即可以「頁」為單位控制存取。
Page tables with permission bits
SUP READ WRITE Address Physical memory
┌─────┬─────┬─────┬─────────┐ ┌─────────┐ PP 0
VP 0: │ No │ Yes │ No │ PP 6 │─┐ ├─────────┤
Proc VP 1: │ No │ Yes │ Yes │ PP 4 │─┼──┐ ┌─▶│ PP 2 │
i VP 2: │ Yes │ Yes │ Yes │ PP 2 │─┼──┼──┘ ├─────────┤
└─────┴─────┴─────┴─────────┘ │ └────▶│ PP 4 │
│ ├─────────┤
┌─────┬─────┬─────┬─────────┐ └───────▶│ PP 6 │◀─┐
VP 0: │ No │ Yes │ No │ PP 9 │────┐ ├─────────┤ │
Proc VP 1: │ Yes │ Yes │ Yes │ PP 6 │────┼─────┼─────────┼──┘
j VP 2: │ No │ Yes │ Yes │ PP 11 │─┐ └────▶│ PP 9 │
└─────┴─────┴─────┴─────────┘ │ ├─────────┤
└───────▶│ PP 11 │
└─────────┘
(Figure 9.10:每個 PTE 增加三個權限位元,實現 page-level 保護)
三個權限位元的語意
| 位元 | 意義 | 檢查規則 |
|---|---|---|
| SUP | 是否須在 kernel (supervisor) mode 才能存取 | kernel mode 可存取任何頁;user mode 只能存取 SUP = No 的頁 |
| READ | 讀取權限 | 控制對該頁的讀取 |
| WRITE | 寫入權限 | 控制對該頁的寫入 |
以 Figure 9.10 為例:process i 在 user mode 執行時——
- 可讀 VP 0(READ=Yes, WRITE=No, SUP=No)
- 可讀寫 VP 1(READ=Yes, WRITE=Yes, SUP=No)
- 完全不可存取 VP 2(SUP=Yes,即使 READ/WRITE 皆 Yes)
違規時的處理流程
指令違反權限位元
│
▼
CPU 觸發 general protection fault(一種 fault 類例外)
│
▼
控制權轉移到 kernel 中的 exception handler
│
▼
kernel 對違規的 process 送出 SIGSEGV
│
▼
Linux shell 回報:"segmentation fault"
- 例外分類與 fault 語意見 08-Exceptional-Control-Flow/01-Exceptions;SIGSEGV 的訊號機制見 08-Exceptional-Control-Flow/05-Signals。
因為 MMU 硬體本來就要在每次記憶體存取時讀 PTE 做轉譯,權限檢查只是順路多看幾個位元——不需要額外的記憶體存取,也不需軟體介入(除非真的違規才觸發例外)。
兩者都由 MMU 讀 PTE 時發現、都以例外形式進入 kernel,但語意完全不同:page fault(valid bit = 0,頁不在 DRAM)由 kernel 換入頁後重新執行該指令,對程式透明;permission 違規(general protection fault)則導致 SIGSEGV,預設終止程式。前者見 09-Virtual-Memory/01-Address-Spaces-and-VM-Caching。
Linux 的 getrusage 函式可監看 process 的 page fault 次數與許多其他資源使用資訊。
Exam/Test Patterns
| 情境 / 關鍵字 | 答案 |
|---|---|
| 「VM 空間比實體記憶體小還有用嗎?」 | 有(如 DEC PDP-11/70):VM 的價值還包括簡化記憶體管理與保護,不只 caching (p.847) |
| 「每個 process 有幾張 page table?」 | 每個 process 一張獨立的 page table = 一個獨立虛擬位址空間 (p.847) |
| 「兩個 process 如何共享 libc / kernel code?」 | 各自的 VP 映射到同一個實體頁(單一副本)(p.848) |
| 「64 位元 Linux code segment 起始位址?」 | 虛擬位址 0x400000;data 在其後、stack 在最高處向下長 (p.847) |
「loader 載入 .text/.data 時複製了多少資料?」 |
零;只設定 PTE 指向目的檔並標 invalid,首次參照時才 demand paging 換入 (p.848) |
| 「把連續虛擬頁映射到檔案任意位置」叫什麼? | Memory mapping;應用層 API 為 mmap (p.848) |
「malloc 要 k 頁,OS 需要找連續實體頁嗎?」 |
不需要;k 個連續虛擬頁可映射到散佈各處的 k 個實體頁 (p.848) |
| 「PTE 的三個權限位元?」 | SUP(須 kernel mode)、READ、WRITE (p.849) |
| 「user mode 能存取 SUP=Yes 的頁嗎?」 | 不能;kernel mode 才可存取任何頁,user mode 只能存取 SUP=No 的頁 (p.849) |
| 「違反權限位元會發生什麼?」 | CPU 觸發 general protection fault → kernel handler → 送 SIGSEGV → shell 顯示 "segmentation fault" (p.849) |
| 「如何查看 process 的 page fault 次數?」 | Linux getrusage 函式 (p.847 Aside) |
| 給定權限位元表,問某 process 在 user mode 可否讀/寫某 VP | 先看 SUP(Yes 則 user mode 一律拒絕),再看 READ / WRITE 各自的 Yes/No |
Related Notes
- 09-Virtual-Memory/01-Address-Spaces-and-VM-Caching — 前置:VP/PP、page table、page fault 與 demand paging 基礎
- 09-Virtual-Memory/03-Address-Translation-and-TLB — 後續:MMU 如何逐次讀 PTE 完成轉譯(權限檢查即掛載於此)
- 09-Virtual-Memory/04-Memory-Mapping —
mmap與 memory mapping 的完整機制(9.8 節) - 09-Virtual-Memory/05-Dynamic-Memory-Allocation —
malloc在虛擬頁之上的 heap 管理 - 08-Exceptional-Control-Flow/01-Exceptions — fault 類例外與 general protection fault 的處理模型
- 08-Exceptional-Control-Flow/05-Signals — SIGSEGV 的產生與預設行為
- 07-Linking/03-Executable-Loading-and-Dynamic-Linking — loader、執行檔映像與共享函式庫載入