feat: docs & ARCH 2.2, 2.3, 2.4

This commit is contained in:
Faynot
2026-07-07 16:40:41 +03:00
parent c092a81331
commit 1bfde637d0
50 changed files with 5113 additions and 1823 deletions

View File

@@ -0,0 +1,196 @@
# 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.