feat: docs & ARCH 2.2, 2.3, 2.4
This commit is contained in:
196
kernel/docs/memory/paging.md
Normal file
196
kernel/docs/memory/paging.md
Normal 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.
|
||||
Reference in New Issue
Block a user