feat: docs & ARCH 2.2, 2.3, 2.4
This commit is contained in:
267
kernel/docs/memory/vmm.md
Normal file
267
kernel/docs/memory/vmm.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# 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 1–4094: пользовательские.
|
||||
|
||||
`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
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user