Files
Elyz/kernel/docs/memory/vmm.md
2026-07-07 16:40:41 +03:00

268 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Virtual Memory Manager: `vmm.rs`
## Концептуальная модель
**AddressSpace** — это представление виртуального адресного пространства
процесса. Оно состоит из:
1. **Аппаратной части**: PML4 (Page Map Level 4) — корневая таблица страниц.
2. **Программной части**: VMA (Virtual Memory Area) список — отсортированный
перечень отображённых регионов с метаданными.
```
AddressSpace
┌─────────────────────────────────────────────┐
│ asid: u16 (PCID = Process Context ID) │
│ pml4_phys: PhysAddr (адрес PML4) │
│ regions: Vec<VmaRegion> (сортирован по адресу)│
└─────────────────────────────────────────────┘
┌───────────────┐
│ PML4 (P4) │ ◄── CR3
├───────────────┤
│ PDE (P3) │
├───────────────┤
│ PTE (P2) │
├───────────────┤
│ PTE (P1) │ ◄── 4 KiB страницы
└───────────────┘
```
## VmaRegion — регион виртуальной памяти
```rust
pub struct VmaRegion {
pub virt_start: VirtAddr, // начало (выровнено на 4k)
pub virt_end: VirtAddr, // конец (эксклюзивный)
pub flags: VmaFlags, // READ, WRITE, EXEC, USER, LAZY, SHARED, COW...
pub cap_token: u64, // токен capability (для revoke)
pub backing: VmaBacking, // откуда берутся физические страницы
}
```
### VmaBacking — источник физических страниц
```rust
pub enum VmaBacking {
Anonymous(Vec<Option<PhysAddr>>),
// ^ Owned: каждая страница выделена из PMM под этот VMA.
// Option: None = lazy (страница ещё не выделена).
Physical(PhysAddr),
// ^ Не-owned: всё отображение — непрерывный физический диапазон.
// Frame не освобождаются при unmap.
Shared { owner_cap: u64, phys_base: PhysAddr },
// ^ Zero-copy shared mapping: чужие физические страницы.
// Frame не освобождаются при unmap.
// При revoke owner_cap — все Shared VMA с этим токеном удаляются.
}
```
### VmaFlags
```rust
pub struct VmaFlags: u32 {
const READ = 1 << 0;
const WRITE = 1 << 1;
const EXEC = 1 << 2;
const USER = 1 << 3;
const LAZY = 1 << 4; // Demand-paging: page fault = alloc frame
const SHARED = 1 << 5; // Zero-copy shared mapping
const PINNED = 1 << 6; // Защищён от revoke
const NOCACHE = 1 << 7; // Cache disabled (для MMIO)
const MMIO = 1 << 8; // Memory-mapped I/O
const COW = 1 << 9; // Copy-on-Write active
}
```
`to_page_flags()` транслирует VmaFlags в PageTableFlags:
- PRESENT всегда
- WRITE → WRITABLE
- EXEC → NO_EXECUTE (инвертировано!)
- LAZY → PRESENT без фрейма (fault отлавливается)
- COW → COW (кастомный бит 9)
## ASID Allocator — выделение PCID
x86-64 PCID (Process Context Identifier) — 12-битный идентификатор (0-4095).
```rust
struct AsidAllocator {
bitmap: [u32; 128], // 4096 бит
next_hint: u16, // хинт для амортизированного O(1)
}
```
- ASID 0: kernel (no PCID tagging) — всегда занят.
- ASID 4095: reserved by Intel spec — всегда занят.
- ASID 14094: пользовательские.
`alloc()` — Two-pass: от hint до 4094, затем от 1 до hint.
Пропускает полностью занятые 32-битные слова.
## Основные операции AddressSpace
### `new()` — создание пустого пространства
1. Аллоцирует фрейм из PMM для PML4.
2. Обнуляет его (все entry = not present).
3. Выделяет ASID из ASID_ALLOC.
4. Пустой список regions.
### `from_active(pml4_phys, asid)` — обёртка существующего PML4
Используется для kernel space (ASID 0).
### `map_region(virt, phys, size, flags, cap_token)` — отображение
```rust
pub fn map_region(&mut self, virt: VirtAddr, phys: Option<PhysAddr>,
size: u64, flags: VmaFlags, cap_token: u64) -> Result<VirtAddr, VmError>
```
- `phys = Some(base)`: Physical mapping — непрерывный диапазон.
- `phys = None + LAZY`: Demand-paging — фреймы выделяются при fault'е.
- `phys = None + !LAZY`: Eager alloc — все фреймы выделяются сейчас.
Создаёт VmaRegion, вставляет в отсортированный список.
### `map_shared(virt, phys_base, page_count, flags, owner_cap)` — zero-copy
Как map_region, но:
- Не копирует фреймы (не владеет ими).
- Всегда eagerly отображает (адреса известны).
- Записывает `cap_token = owner_cap` для revoke.
### `unmap_region(virt)` — удаление VMA
1. Находит VMA по адресу.
2. Удаляет из списка.
3. Вызывает `do_unmap(pml4, region)`:
- unmap_page для каждой страницы.
- Если backing owns_frames (Anonymous) — free_frame.
4. TLB flush.
### `handle_fault(fault_addr, write)` — Page Fault handler
```rust
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError>
```
1. Находит VMA, содержащий fault_addr.
2. Проверяет права (write + !WRITE = PermissionDenied).
3. Проверяет COW:
- Если write + COW: аллоцирует новый фрейм, копирует данные,
заменяет PTE, освобождает старый фрейм.
4. Проверяет LAZY:
- Если LAZY + page не выделена: alloc_frame, обнуление, map_page.
5. Иначе: UnexpectedFault → panic.
### `clone_for_fork(child_cap_token)` — fork адресного пространства
1. Создаёт пустое AddressSpace::new().
2. Для каждого VMA родителя:
- Physical/Shared: просто копирует PTE (та же физическая память).
- Anonymous (owned): COW.
* Устанавливает COW флаг на родительские PTE.
* Убирает WRITABLE с родительских PTE.
* Отображает те же фреймы в дочернее пространство (без WRITABLE).
* При записи → handle_fault → copy-on-write.
### `revoke_by_token(cap_token)` / `do_revoke_by_token(cap_token)` — отзыв
Удаляет все нем-PINNED VMA с заданным cap_token.
- Собирает индексы регионов, соответствующих токену.
- Удаляет в обратном порядке (чтобы индексы не сдвигались).
- TLB flush.
### `process_pending_revocations()` — обработка очереди отзыва
Дренирует глобальную `MMU_REVOCATION_QUEUE` и revoke-ит каждый токен.
Вызывается перед handle_fault при page fault'е, чтобы избежать deadlock
с capability subsystem.
## TLB Management
### `local_tlb_flush_asid(asid)` — сброс TLB для одного ASID
```rust
fn local_tlb_flush_asid(asid: u16) {
if INVPCID_SUPPORTED {
// INVPCID type 1 (single-context): сбрасывает только
// TLB entries, помеченные данным PCID.
asm!("invpcid {ty}, [{desc}]", ...);
} else {
// Fallback: полный сброс TLB (все контексты)
tlb_flush_all();
}
}
```
### `tlb_flush_asid(asid)` — TLB shootdown (SMP)
```rust
pub fn tlb_flush_asid(asid: u16) {
local_tlb_flush_asid(asid); // всегда сбрасываем локальный
let target_mask = active_cpus & !(1 << current_core);
if target_mask == 0 { return; }
// Lock SHOOTDOWN_LOCK → записываем ASID → broadcast IPI → wait ACK
SHOOTDOWN_ASID.store(asid);
SHOOTDOWN_ACK.store(0);
lapic::broadcast_ipi_exclude_self(TLB_SHOOTDOWN_VECTOR);
while SHOOTDOWN_ACK.load() & target_mask != target_mask {
spin_loop();
}
}
```
### `tlb_flush_all()` — полный сброс
Перезапись CR3 без PCID NOFLUSH бита.
## AddressSpace::activate() — контекстный переключатель
```rust
pub unsafe fn activate(&self) {
let cr3 = self.pml4_phys.0 | u64::from(self.asid) | (1u64 << 63);
// ^ Биты 11:0 = ASID, бит 63 = NOFLUSH (не сбрасывать TLB)
asm!("mov cr3, {0}", in(reg) cr3);
}
```
## Глобальный kernel space
```rust
pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
```
Инициализируется в `init_kernel_space(pml4_phys)` после создания
первичных page tables.
## Drop для AddressSpace
При Drop:
1. unmap всех регионов (с освобождением owned frames).
2. free_frame(pml4_phys).
3. free_asid(asid).
## VmError — типы ошибок
```rust
pub enum VmError {
OutOfMemory, // Нет фреймов в PMM
RegionOverlap, // VMA пересекается с существующим
RegionNotFound, // Нет VMA по адресу
InvalidAlignment, // Адрес/размер не выровнены на 4k
InvalidRange, // Нулевой размер или переполнение
PermissionDenied, // Нет прав на запись
UnexpectedFault, // Fault в не-lazy, не-COW регионе
NonCanonical, // Некорректный адрес
AsidExhausted, // Закончились ASID
}
```