Bộ nhớ/Paging & page table — bản đồ dịch địa chỉ
16/26
Bài 16 / 26~18 phútBộ nhớ ảoMiễn phí lượt xem

Paging & page table — bản đồ dịch địa chỉ

Page table ánh xạ trang ảo sang khung vật lý. Trace từng bước dịch địa chỉ VPN+offset, hiểu PTE và vì sao trang 4 KB cố định tránh external fragmentation.

TL;DR: Bộ nhớ ảo không ánh xạ từng byte riêng lẻ — nó chia không gian địa chỉ ảo thành các trang (page) kích thước cố định (điển hình 4 KB) và RAM vật lý thành các khung (frame) cùng kích thước. Một page table lưu ánh xạ "trang ảo số N nằm tại khung vật lý số M". Mỗi lần chương trình đọc một địa chỉ ảo, phần cứng tách nó thành page numberoffset, tra page table lấy frame number, ghép lại ra địa chỉ vật lý. Offset đi thẳng qua không đổi — đây là chi tiết thường bị bỏ qua nhưng là chìa khoá để trace từng bước. Mỗi tiến trình có page table riêng, đó là cơ chế cách ly bộ nhớ đã gặp ở bài 01.

Bạn malloc(1 MB) và OS trả về địa chỉ ảo 0x7f3a0000. Bên dưới, OS đặt lên page table của tiến trình bạn một loạt ánh xạ "trang ảo 0x7f3a0 → khung vật lý 0x1c2", "trang 0x7f3a1 → khung 0x5a7"… Những khung đó rải rác khắp RAM, nhưng tiến trình thấy một vùng liền mạch hoàn hảo tại 0x7f3a0000.

Làm thế nào OS và phần cứng biến những khung rời rạc đó thành một dải địa chỉ liền mạch mà chương trình thấy? Đó là việc của paging và page table — bài này giải thích cơ chế từng bước.

1. Analogy — bảng ghế trong rạp chiếu phim

Hình dung rạp chiếu phim có 10.000 ghế đánh số thật từ 1 đến 10.000. Khi bạn mua vé, nhân viên không đưa bạn đi tới đúng ghế số thật — họ đưa cho bạn một phiếu ghế ảo ("hàng 3, ghế 5 của phòng bạn") và giữ một bảng ánh xạ phòng nào hàng nào ghế nào tương ứng ghế thật số mấy.

Nhờ đó, hai phòng khác nhau đều có "hàng 3, ghế 5" mà không đụng nhau. Khi bạn ngồi đúng ghế ảo, nhân viên tra bảng và dẫn bạn tới ghế thật. Offset là vị trí trong phòng (hàng + cột); page number là số hiệu phòng.

Rạp chiếu phimBộ nhớ máy tính
Phòng chiếuTrang (page) — 4 KB
Ghế ảo trong phòngĐịa chỉ ảo
Vị trí trong phòng (hàng, cột)Offset (12 bit thấp)
Số hiệu phòngVirtual page number (VPN)
Ghế thật trong rạpĐịa chỉ vật lý
Số hiệu khu ghế thậtPhysical frame number (PFN)
Bảng ánh xạ của nhân viênPage table
💡 Cách nhớ

Địa chỉ ảo = VPN + offset. Page table dịch VPN thành PFN. Offset giữ nguyên — nó chỉ vị trí bên trong trang, không thay đổi khi dịch. Địa chỉ vật lý = PFN + offset.

2. Chia trang — vì sao kích thước cố định?

Không gian địa chỉ ảo và RAM vật lý đều được chia thành các khối cùng kích thước:

  • Trang (page): đơn vị của không gian địa chỉ ảo. Điển hình 4 KB = 4096 byte.
  • Khung (frame): đơn vị của RAM vật lý. Cùng kích thước với trang.

Kích thước cố định giải quyết external fragmentation — vấn đề nan giải của segmentation và cấp phát liền khối:

Cấp phát liền khối (không paging)
RAM phân mảnh: có 200 MB trống nhưng rải thành 100 đoạn nhỏ — không cấp được cho process cần 150 MB liền.
Paging (khung cố định)
Mỗi trang ảo ánh xạ vào bất kỳ khung nào còn rảnh — không cần khung liền nhau. 200 MB = 51.200 khung rảnh, dù rải rác, đều dùng được.

Internal fragmentation vẫn tồn tại: trang cuối cùng của một tiến trình thường dùng không hết 4 KB (trung bình lãng phí 2 KB). Đây là cái giá của kích thước cố định — nhưng nhỏ hơn nhiều so với external fragmentation.

3. Địa chỉ ảo = VPN + offset

Với page 4 KB = 2^12 byte, cần 12 bit để đánh địa chỉ mọi byte trong một trang. Đó là 12 bit thấp nhất của địa chỉ ảo — gọi là offset. Các bit còn lại là virtual page number (VPN).

Dia chi ao 32-bit, page 4 KB (2^12):

  |<---- 20 bit VPN ---->|<-- 12 bit offset -->|
  [ bit 31 ... bit 12   ][ bit 11 ... bit 0   ]

Vi du: dia chi ao 0x00403A2C
  VPN    = 0x00403A2C >> 12 = 0x00403    (= trang ao so 1027)
  offset = 0x00403A2C & 0xFFF = 0xA2C   (= byte 2604 trong trang)

Tương tự cho địa chỉ vật lý: 12 bit thấp là offset (giống hệt), phần còn lại là physical frame number (PFN).

flowchart LR
  VA["Dia chi ao<br/>[VPN | offset]"] -->|tra page table| PT["Page table<br/>VPN -> PFN"]
  PT -->|ghep lai| PA["Dia chi vat ly<br/>[PFN | offset]"]

4. Page table và page table entry (PTE)

Page table là một mảng (per-tiến-trình) lưu trong bộ nhớ kernel. Mỗi phần tử là một page table entry (PTE) tương ứng với một trang ảo. PTE chứa:

PFN — physical frame number
Present/Valid bit — trang có trong RAM không? 0 → page fault (bài 04)
Dirty bit — trang đã bị ghi sau khi nạp vào RAM chưa? (cần ghi lại khi swap ra)
Accessed bit — trang đã được đọc/ghi gần đây? (OS dùng để chọn trang evict)
R/W, User/Supervisor — quyền truy cập: chỉ đọc? chỉ kernel? (bảo vệ, bài 01)

Khi CPU truy cập một địa chỉ ảo:

  1. Tách VPN và offset từ địa chỉ ảo.
  2. Dùng VPN làm chỉ số vào page table, đọc PTE.
  3. Kiểm tra present bit: nếu 0 → page fault, OS xử lý (bài 04).
  4. Nếu 1: lấy PFN từ PTE, ghép PFN + offset = địa chỉ vật lý.
  5. Truy cập RAM tại địa chỉ vật lý đó.

5. Worked example — dịch địa chỉ từng bước

Giả sử: page 4 KB, địa chỉ ảo 32-bit, page table sau (chỉ trích một phần):

VPN (trang ảo)PFN (khung vật lý)Present
051
121
20
371

Yêu cầu dịch địa chỉ ảo 0x00003A2C:

Buoc 1: tach VPN va offset
  Dia chi ao = 0x00003A2C
  VPN    = 0x00003A2C >> 12 = 3      (trang ao so 3)
  offset = 0x00003A2C & 0xFFF = 0xA2C

Buoc 2: tra page table voi VPN = 3
  PTE[3]: PFN = 7, present = 1 -> OK

Buoc 3: ghep dia chi vat ly
  Dia chi vat ly = (PFN << 12) | offset
                 = (7 << 12) | 0xA2C
                 = 0x00007000 | 0xA2C
                 = 0x00007A2C

Offset 0xA2C giữ nguyên — nó đi thẳng vào địa chỉ vật lý không đổi. Chỉ VPN được đổi thành PFN.

Thử với VPN = 2 (present = 0):

Buoc 1: VPN = 2, offset = bat ky
Buoc 2: PTE[2].present = 0 -> page fault!
  CPU raise exception -> OS handler chay -> nap trang tu swap vao RAM
  (xem bai 04)

6. Mỗi tiến trình — một page table riêng

Khi OS tạo một tiến trình mới, nó cấp phát một page table mới cho tiến trình đó. Khi OS switch từ tiến trình A sang B, nó nạp con trỏ page table của B vào thanh ghi CR3 (x86) — MMU tự động dùng page table mới.

Đây là lý do hai tiến trình cùng có code tại 0x400000 mà không đụng nhau (bài 01): VPN 0x400 của A trỏ tới khung vật lý khác VPN 0x400 của B, vì chúng có page table khác nhau.

flowchart TD
  subgraph A["Tien trinh A"]
    VA1["VA 0x400A2C<br/>VPN=0x400, off=0xA2C"]
  end
  subgraph B["Tien trinh B"]
    VA2["VA 0x400A2C<br/>VPN=0x400, off=0xA2C"]
  end
  VA1 -->|page table A| F1["Khung #512"]
  VA2 -->|page table B| F2["Khung #47"]
  F1 --> RAM["RAM vat ly"]
  F2 --> RAM

7. Pitfall tổng hợp

Nhầm 1: Nghĩ page và cache line là một. Cache line là 64 byte — đơn vị trao đổi giữa cache và RAM, ở tầng phần cứng. Page là 4 KB — đơn vị quản lý bộ nhớ ảo, ở tầng OS. Hai tầng khác nhau, kích thước khác nhau; một page chứa 64 cache line.

Nhầm 2: Nghĩ page table phẳng với 48-bit address space nhỏ thôi. Thật ra: địa chỉ ảo 48-bit, page 4 KB → VPN 36 bit → 2^36 entry = 64 tỉ entry. Nếu mỗi PTE 8 byte → page table phẳng tốn 512 GB cho mỗi tiến trình. Đó là lý do cần multi-level page table (bài 03).

Nhầm 3: Nghĩ cấp phát ảo (malloc) tiêu ngay RAM vật lý. OS thường chỉ thêm entry vào page table với present = 0; RAM chỉ được gán khi trang bị chạm lần đầu (lazy allocation, bài 04).

8. Đào sâu (tuỳ chọn)

📚 Đào sâu (tuỳ chọn)

Multi-level page table: thay vì một mảng phẳng 2^36 entry, x86-64 dùng 4 cấp (PML4 → PDPT → PD → PT). Mỗi cấp dùng 9 bit của VPN làm chỉ số vào bảng 512 entry. Lợi: chỉ cấp phát các nhánh của cây thật sự dùng — process thưa có thể chỉ cần vài KB thay vì hàng trăm GB. Bài 03 đi sâu vào cấu trúc này.

Huge pages: thay page 4 KB bằng 2 MB hoặc 1 GB. Lợi ích: ít PTE hơn → tiết kiệm bộ nhớ cho page table, ít TLB entry hơn (coverage mỗi entry lớn hơn, bài 03). Chi phí: internal fragmentation lớn hơn, và kernel cần tìm được vùng RAM vật lý liền nhau kích thước đó.

Inverted page table: thay vì lập index theo VPN, lập index theo PFN — mỗi entry cho biết VPN nào đang chiếm khung này. Tổng kích thước tỉ lệ với số khung vật lý (không phải kích thước không gian ảo) → cố định dù không gian ảo rộng. Nhược: lookup chậm hơn (cần hash), context switch phức tạp hơn. Dùng trên IBM Power và một số kiến trúc cũ.

Segmentation so với paging: segmentation chia bộ nhớ thành các đoạn kích thước biến đổi (code, data, stack) — đơn giản để lập trình nhưng gây external fragmentation, khó hoán đổi. Paging dùng kích thước cố định tránh external fragmentation và hoán đổi từng trang linh hoạt. x86-64 vẫn có segmentation nhưng OS hiện đại không dùng (segment đặt base 0, limit max → segmentation "tắt" về mặt hiệu quả).

9. Liên hệ các bài khác

10. Tóm tắt

  • Paging chia không gian địa chỉ ảo và RAM thành các khối cố định kích thước bằng nhau (điển hình 4 KB), tránh external fragmentation của cấp phát liền khối.
  • Địa chỉ ảo = VPN (page number) + offset (vị trí trong trang). Với page 4 KB → offset 12 bit, VPN là phần còn lại.
  • Page table ánh xạ VPN → PFN (physical frame number). Dịch địa chỉ: tách VPN+offset → tra PTE → lấy PFN → ghép PFN+offset = địa chỉ vật lý. Offset không đổi.
  • PTE chứa PFN + các bit trạng thái: present (0 → page fault), dirty, accessed, R/W, User/Supervisor.
  • Mỗi tiến trình có page table riêng — đây là cơ sở cách ly: cùng VPN ở hai tiến trình trỏ tới PFN khác nhau.
  • Page table phẳng cho không gian 48-bit cần 2^36 entry — quá lớn → cần multi-level page table (bài 03).

11. Tự kiểm tra

Tự kiểm tra
Q1
Vì sao paging dùng kích thước trang cố định thay vì kích thước biến đổi như segmentation?
Kích thước cố định loại bỏ external fragmentation — vấn đề nan giải của phân đoạn kích thước biến đổi (sau nhiều lần cấp phát/giải phóng, RAM đầy các lỗ nhỏ không đủ cho yêu cầu lớn). Với paging, mọi trang đều vừa với mọi khung vật lý còn trống, nên OS có thể gán bất kỳ khung nào mà không cần khung liền nhau. Kích thước cố định cũng giúp OS quản lý danh sách khung rảnh đơn giản (free list, bitmap). Nhược điểm duy nhất là internal fragmentation: trang cuối của một vùng có thể dùng không hết 4 KB — trung bình lãng phí 2 KB/vùng. Đây là cái giá chấp nhận được so với external fragmentation.
Q2
Cho địa chỉ ảo 0x00005B80, page 4 KB. Tính VPN và offset. Nếu page table cho biết VPN này ánh xạ tới PFN = 9, địa chỉ vật lý là bao nhiêu?
Page 4 KB = 2^12 byte, nên offset chiếm 12 bit thấp và VPN là phần còn lại. VPN = 0x00005B80 >> 12 = 0x5 = 5. Offset = 0x00005B80 & 0xFFF = 0xB80. Tra page table: VPN 5 → PFN 9. Địa chỉ vật lý = (9 << 12) | 0xB80 = 0x9000 | 0xB80 = 0x9B80. Lưu ý offset 0xB80 giữ nguyên — nó chỉ vị trí trong trang, không thay đổi qua dịch.
Q3
Page table entry (PTE) chứa những thông tin gì ngoài PFN? Giải thích vai trò của present bit.
Ngoài PFN, PTE chứa các bit trạng thái: present/valid bit (trang có trong RAM không), dirty bit (trang đã bị ghi sau khi nạp, cần ghi lại khi swap ra), accessed bit (trang đã được đọc/ghi gần đây, OS dùng để chọn trang evict theo LRU), và các bit bảo vệ R/W (chỉ đọc hay có thể ghi), User/Supervisor (user code hay chỉ kernel được dùng). Present bit = 1 nghĩa là trang đang nằm trong RAM, dịch địa chỉ tiến hành bình thường. Present = 0 nghĩa là trang không có trong RAM (có thể chưa nạp hoặc đã swap ra đĩa) — CPU phát một page fault exception, chuyển quyền kiểm soát về OS để xử lý (nạp trang vào RAM rồi thử lại lệnh).
Q4
Vì sao mỗi tiến trình cần một page table riêng? Điều gì xảy ra khi OS chuyển ngữ cảnh (context switch)?
Mỗi tiến trình có không gian địa chỉ ảo riêng biệt — cùng một VPN ở hai tiến trình phải trỏ tới các khung vật lý khác nhau để đảm bảo cách ly. Nếu dùng chung page table, tiến trình A có thể tra VPN của B và đọc/ghi bộ nhớ B — phá vỡ bảo vệ. Page table riêng là hàng rào: tiến trình A chỉ có thể dịch địa chỉ trong page table của nó, không có đường sang vùng B. Khi OS thực hiện context switch từ A sang B, nó lưu con trỏ page table của A rồi nạp con trỏ page table B vào thanh ghi điều khiển MMU (trên x86: thanh ghi CR3). Từ thời điểm đó, mọi truy cập bộ nhớ của CPU dùng page table B. TLB cũng phải được flush (hoặc dùng ASID — bài 03).
Q5
Vì sao page table phẳng không khả thi với không gian địa chỉ 48-bit?
Với page 4 KB (2^12 byte), VPN cần 48 - 12 = 36 bit, tức có 2^36 = 64 tỉ trang ảo có thể có. Page table phẳng cần một mảng 64 tỉ PTE. Nếu mỗi PTE chiếm 8 byte, một page table phẳng tốn 2^36 × 8 = 512 GB — cho mỗi tiến trình. Hệ thống có 1.000 tiến trình cần 512 TB chỉ cho page table! Thực tế, hầu hết VPN trong một tiến trình không được ánh xạ (chỉ một phần nhỏ không gian ảo được dùng). Multi-level page table (bài 03) giải quyết: chỉ cấp phát các nhánh của cây page table thật sự được dùng — tiến trình thưa chỉ cần vài KB thay vì hàng trăm GB.
Q6
Offset giữ nguyên qua quá trình dịch địa chỉ. Vì sao điều này đúng về mặt logic?
Offset là vị trí bên trong một trang — khoảng cách từ đầu trang tới byte cần đọc. Dù trang ảo đó nằm ở khung vật lý nào, vị trí tương đối bên trong vẫn là cùng một byte đó. Ví dụ: trang ảo 5 byte thứ 0xA2C ánh xạ tới khung vật lý 9 — byte cần tìm vẫn là byte thứ 0xA2C kể từ đầu khung đó. Dịch địa chỉ chỉ thay đổi "trang số mấy" (VPN → PFN) nhưng giữ nguyên "byte thứ mấy trong trang" (offset). Về mặt bit: 12 bit thấp của địa chỉ ảo và vật lý là một — chỉ các bit cao (page number) thay đổi.

Bài tiếp theo: MMU & TLB — dịch địa chỉ mà không chậm

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên