197 lines
7.7 KiB
Markdown
197 lines
7.7 KiB
Markdown
# Page Tables и отображение памяти: `paging.rs`
|
||
|
||
## Концептуальная модель
|
||
|
||
x86-64 использует **4-уровневую иерархию таблиц страниц**:
|
||
|
||
```
|
||
Виртуальный адрес (48 бит):
|
||
┌─────────┬─────────┬─────────┬─────────┬─────────┐
|
||
│ PML4 │ PDPT │ PD │ PT │ offset │
|
||
│ bits │ bits │ bits │ bits │ (12) │
|
||
│ 47:39 │ 38:30 │ 29:21 │ 20:12 │ 11:0 │
|
||
└────┬────┴────┬────┴────┬────┴────┬────┴─────────┘
|
||
│ │ │ │
|
||
▼ ▼ ▼ ▼
|
||
PML4 ──► PDPT ──► PD ──► PT ──► Физический фрейм
|
||
(512 ent) (512 ent) (512 ent) (512 ent) (4 KiB)
|
||
```
|
||
|
||
Каждая запись (PTE) = 8 байт:
|
||
```
|
||
Bit 63: NX (No Execute)
|
||
Bits 62:52: Ignored/AVL
|
||
Bits 51:12: Physical frame address (4KiB aligned)
|
||
Bits 11:9: Ignored/AVL
|
||
Bits 8: Global
|
||
Bits 7: PS (Page Size = 1 GiB для PDPT, 2 MiB для PD)
|
||
Bits 6: Dirty
|
||
Bits 5: Accessed
|
||
Bits 4: PCD (Cache Disable)
|
||
Bits 3: PWT (Write Through)
|
||
Bits 2: U/S (User/Supervisor)
|
||
Bits 1: R/W (Read/Write)
|
||
Bit 0: Present
|
||
```
|
||
|
||
## PageTable — представление таблицы
|
||
|
||
```rust
|
||
#[repr(C, align(4096))]
|
||
pub struct PageTable {
|
||
entries: [u64; 512],
|
||
}
|
||
```
|
||
|
||
- `512` entries × 8 байт = 4096 байт (ровно одна страница).
|
||
- `align(4096)` — гарантирует, что структура размещается на физической
|
||
странице, как требует аппаратура.
|
||
|
||
## PageTableFlags — битовые флаги
|
||
|
||
```rust
|
||
bitflags! {
|
||
pub struct PageTableFlags: u64 {
|
||
const PRESENT = 1 << 0;
|
||
const WRITABLE = 1 << 1;
|
||
const USER = 1 << 2;
|
||
const WRITE_THROUGH = 1 << 3;
|
||
const NO_CACHE = 1 << 4;
|
||
const ACCESSED = 1 << 5;
|
||
const DIRTY = 1 << 6;
|
||
const HUGE_PAGE = 1 << 7;
|
||
const GLOBAL = 1 << 8;
|
||
const COW = 1 << 9; // Elyz-specific: Copy-on-Write marker
|
||
const NO_EXECUTE = 1 << 63;
|
||
}
|
||
}
|
||
```
|
||
|
||
Флаг `COW` (бит 9) — Elyz-специфичный, используется в AddressSpace для
|
||
маркировки страниц, которые нужно копировать при записи.
|
||
|
||
## Основные операции PageTable
|
||
|
||
### map_page(virt, phys, flags) — отображение одной страницы
|
||
|
||
```rust
|
||
pub fn map_page(&mut self, virt: VirtAddr, phys: PhysAddr, flags: PageTableFlags) {
|
||
let p1 = self.walk_to_p1_mut(virt, true).expect("...");
|
||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||
p1.entries[p1_idx] = phys.0 | flags.bits();
|
||
asm!("invlpg [{}]", in(reg) virt.0);
|
||
}
|
||
```
|
||
|
||
**Процесс:**
|
||
1. `walk_to_p1_mut(virt, create=true)` проходит 4 уровня (P4→P3→P2→P1),
|
||
создавая недостающие таблицы через `pmm_alloc()`.
|
||
2. Записывает физический адрес с флагами в P1 entry.
|
||
3. `INVLPG` — сбрасывает TLB для этой страницы.
|
||
|
||
### map_region(virt, phys, size, flags) — отображение диапазона
|
||
|
||
Вызывает `map_page()` для каждой страницы в диапазоне.
|
||
`size.div_ceil(4096)` — количество страниц.
|
||
|
||
### unmap_page(virt) — удаление отображения
|
||
|
||
```rust
|
||
pub fn unmap_page(&mut self, virt: VirtAddr) {
|
||
// walk_to_p1_mut(virt, false) — не создаём, только ищем
|
||
let Some(p1) = self.walk_to_p1_mut(virt, false) else { return; };
|
||
p1.entries[p1_idx] = 0; // Clear PTE
|
||
asm!("invlpg [{}]", in(reg) virt.0);
|
||
}
|
||
```
|
||
|
||
**Важно:** не освобождает физический фрейм — это ответственность
|
||
Caller'а (VMM). Идемпотентна: если PTE нет, ничего не делает.
|
||
|
||
### translate(virt) — трансляция виртуального адреса
|
||
|
||
Проходит 4 уровня, проверяя PRESENT на каждом уровне.
|
||
Поддерживает huge pages (1 GiB и 2 MiB):
|
||
- Если P3 entry имеет HUGE_PAGE — 1 GiB страница.
|
||
- Если P2 entry имеет HUGE_PAGE — 2 MiB страница.
|
||
|
||
### get_flags(virt) / update_flags(virt) — чтение/запись флагов
|
||
|
||
`get_flags` возвращает флаги PTE для виртуального адреса.
|
||
|
||
`update_flags` меняет флаги, сохраняя физический адрес, и вызывает INVLPG.
|
||
|
||
### activate(phys_addr) — загрузка в CR3
|
||
|
||
```rust
|
||
pub unsafe fn activate(&self, phys_addr: PhysAddr) {
|
||
asm!("mov cr3, {0}", in(reg) phys_addr.0);
|
||
}
|
||
```
|
||
|
||
Полная замена таблиц страниц. Полный TLB flush (без PCID).
|
||
|
||
В `vmm.rs::AddressSpace::activate()` используется улучшенная версия
|
||
с PCID и битом NOFLUSH.
|
||
|
||
## walk_to_p1_mut — сердце навигации
|
||
|
||
```rust
|
||
fn walk_to_p1_mut(&mut self, virt: VirtAddr, create: bool) -> Option<&mut Self> {
|
||
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||
|
||
let p3 = self.get_or_create_next_table(p4_idx, create)?;
|
||
let p2 = p3.get_or_create_next_table(p3_idx, create)?;
|
||
p2.get_or_create_next_table(p2_idx, create)
|
||
}
|
||
```
|
||
|
||
### get_or_create_next_table(index, create) — следующий уровень
|
||
|
||
```rust
|
||
fn get_or_create_next_table(&mut self, index: usize, create: bool) -> Option<&mut Self> {
|
||
if entry & PRESENT == 0 {
|
||
if !create { return None; }
|
||
// Аллоцируем новый фрейм для таблицы
|
||
let pt_phys = pmm_alloc().expect("OOM");
|
||
// Обнуляем (все entry = 0 = not present)
|
||
core::ptr::write_bytes(pt_virt.as_mut_ptr::<u8>(), 0, 4096);
|
||
// Устанавливаем entry: PRESENT | WRITABLE | USER
|
||
self.entries[index] = pt_phys.0 | (PRESENT | WRITABLE | USER);
|
||
}
|
||
// Преобразуем физический адрес entry в ссылку на PageTable
|
||
let next_phys = PhysAddr(self.entries[index] & PTE_ADDR_MASK);
|
||
Some(unsafe { &mut *next_phys.to_virt().as_mut_ptr::<Self>() })
|
||
}
|
||
```
|
||
|
||
### pte_addr_mask — маска адреса
|
||
|
||
```rust
|
||
const PTE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
|
||
```
|
||
|
||
Биты 51:12 — физический адрес с выравниванием 4KiB.
|
||
(0x000F_FFFF_FFFF_F000 = 52 бита адреса, очищенные нижние 12 бит).
|
||
|
||
## pmm_alloc — шлюз к PMM
|
||
|
||
```rust
|
||
pub fn pmm_alloc() -> Option<PhysAddr> {
|
||
PMM.lock().as_mut()?.alloc_frame()
|
||
}
|
||
```
|
||
|
||
Тонкая обёртка, чтобы избежать циклической зависимости между paging и pmm.
|
||
|
||
## Ключевые инварианты
|
||
|
||
1. Каждая PageTable занимает ровно одну физическую страницу (4 KiB).
|
||
2. Физический адрес таблицы всегда 4KiB-выровнен.
|
||
3. Все intermediate таблицы создаются с флагом USER (чтобы и ядро,
|
||
и пользователь могли их использовать). Разделение на user/kernel
|
||
контролируется на уровне P1 entry.
|
||
4. `INVLPG` вызывается после каждого изменения PTE для консистентности TLB.
|