Files
Elyz/kernel/docs/vmm.md
2026-07-06 00:25:49 +03:00

213 lines
7.7 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.
# Виртуальная память (VMM)
**Файл**: `src/mem/vmm.rs`
Управление адресными пространствами: PML4 + VMA списки, lazy demand paging, copy-on-write, shared mappings, ASID/PCID, TLB shootdown IPI.
---
## Глобальные статики
| Переменная | Тип | Назначение |
|---|---|---|
| `ACTIVE_CPUS_MASK` | `AtomicU64` | Битовая маска активных CPU (core 0 по умолчанию) |
| `SHOOTDOWN_LOCK` | `Locked<()>` | Блокировка TLB shootdown IPI |
| `SHOOTDOWN_ASID` | `AtomicU16` | ASID, подлежащий сбросу |
| `SHOOTDOWN_ACK` | `AtomicU64` | Битовая маска подтверждений удалённых ядер |
| `INVPCID_SUPPORTED` | `AtomicBool` | Поддержка инструкции INVPCID |
| `ASID_ALLOC` | `Locked<AsidAllocator>` | Аллокатор ASID/PCID |
| `KERNEL_SPACE` | `Locked<Option<AddressSpace>>` | Адресное пространство ядра |
---
## Типы ошибок: `VmError`
- `OutOfMemory` — нет свободной физической памяти
- `RegionOverlap` — VMA перекрывается с существующей
- `RegionNotFound` — VMA не найдена
- `InvalidAlignment` — неверное выравнивание
- `InvalidRange` — некорректный диапазон
- `PermissionDenied` — нет прав доступа
- `UnexpectedFault` — page fault в не-lazy, не-COW регионе
- `NonCanonical` — неканонический адрес
- `AsidExhausted` — кончились ASID
---
## `VmaBacking` — типы подложки
```rust
pub enum VmaBacking {
Anonymous(Vec<Option<PhysAddr>>), // per-page tracking: None = ещё не выделена
Physical(PhysAddr), // фиксированный физический диапазон (MMIO, DMA)
Shared { owner_cap: u64, phys_base: PhysAddr }, // zero-copy borrow
}
```
---
## `VmaFlags` (bitflags)
| Флаг | Бит | Описание |
|---|---|---|
| `READ` | 0 | Чтение |
| `WRITE` | 1 | Запись |
| `EXEC` | 2 | Исполнение |
| `USER` | 3 | Доступ из userspace |
| `LAZY` | 4 | Demand paging (фрейм при page fault) |
| `SHARED` | 5 | Разделяемая память |
| `PINNED` | 6 | Защита от revoke |
| `NOCACHE` | 7 | Отключение кэширования |
| `MMIO` | 8 | Memory-mapped I/O |
| `COW` | 9 | Copy-on-Write |
**`to_page_flags() -> PageTableFlags`**: преобразует VmaFlags в аппаратные флаги. PRESENT всегда включён, NO_EXECUTE если не EXEC.
---
## `AsidAllocator`
Битовая карта на 4096 ASID/PCID (128 × u32).
- ASID 0 (ядро) и ASID 4095 (зарезервирован) — заняты постоянно
- `alloc() -> Option<u16>` — двухпроходный с пропуском целых слов (амортизированное O(1))
- `free(asid)`сброс бита
---
## `VmaRegion`
```rust
pub struct VmaRegion {
virt_start: VirtAddr,
virt_end: VirtAddr,
flags: VmaFlags,
cap_token: u64,
backing: VmaBacking,
}
```
Методы: `contains(addr)`, `size()`, `pages()`.
---
## `AddressSpace`
```rust
pub struct AddressSpace {
asid: u16,
pml4_phys: PhysAddr,
regions: Vec<VmaRegion>, // sorted by virt_start, non-overlapping
hhdm: u64,
}
```
### Создание
| Метод | Описание |
|---|---|
| `new(hhdm)` | Выделяет PML4 (занулённый), аллоцирует ASID |
| `from_active(pml4_phys, asid, hhdm)` | Обёртка для существующей PML4 (ядро, ASID=0) |
### `map_region(virt, phys, size, flags, cap_token) -> Result<VirtAddr>`
1. Валидация выравнивания (4 KiB) и проверка перекрытий (`check_overlap`)
2. Создание `VmaBacking`:
- **LAZY** → `Anonymous(vec![None; page_count])`
- **Physical** (если `phys` не None и нет LAZY) → `Physical(phys)`
- **Eager anonymous**: цикл по страницам, `pmm_alloc() + zero + map`
3. Вставка в `regions` с сохранением сортировки (`insert_sorted`)
### `map_shared(virt, phys_base, page_count, flags, owner_cap)`
Eager маппинг физического диапазона без владения фреймами (не освобождаются при unmap).
### `handle_fault(fault_addr, write) -> Result<(), VmError>`
Обработка page fault:
1. `find_idx(fault_addr)` — бинарный поиск региона
2. Проверка прав: write fault в WRITE-регионе
3. **COW**: если флаг COW и пишем:
- Выделяет новый фрейм
- Копирует содержимое старого
- Обновляет PTE: убирает COW, добавляет WRITABLE
4. **LAZY**: если флаг LAZY:
- Выделяет фрейм, зануляет, маппит
- Обновляет `Vec<Option<PhysAddr>>`
5. Иначе: `Err(UnexpectedFault)`
### `unmap_region(virt) -> Result<(), VmError>`
1. `find_idx(virt)` → регион
2. `do_unmap(region)`: анмаппит PTE, освобождает фреймы если Anonymous
3. Удаляет регион из списка
4. `tlb_flush_asid(self.asid)`
### `revoke_by_token(cap_token)`
Анмаппит все VMA с совпадающим `cap_token` (кроме PINNED). Сливает TLB.
### `process_pending_revocations(&mut self)`
1. Дренирует `MMU_REVOCATION_QUEUE` (pop в цикле)
2. Для каждого токена → `revoke_by_token(token)`
3. TLB flush если была хотя бы одна ревокация
### `clone_for_fork(child_cap_token) -> Result<Self, VmError>`
Создание дочернего адресного пространства (fork):
- **Physical/Shared**: маппинг напрямую
- **Anonymous**: **COW setup**:
- Убирает WRITABLE из PTE родителя
- Устанавливает COW флаг в обоих PTEs
- `inc_ref_frame()` для всех фреймов
### `translate(virt) -> Option<PhysAddr>`
Аппаратный walk страничной таблицы (через `PageTable::translate`).
### `activate()`
`unsafe`: запись CR3 = `pml4_phys | asid | (1<<63)`. Бит 63 = NOFLUSH — сохраняет PCID-тегированные записи TLB.
### `Drop`
1. Анмаппит все регионы
2. Освобождает PML4 фрейм через `free_frame`
3. Возвращает ASID в пул
---
## TLB Management
### `local_tlb_flush_asid(asid)`
- INVPCID type 1 если поддерживается (single-context flush)
- Иначе `tlb_flush_all()`
### `tlb_flush_asid(asid)`
1. `local_tlb_flush_asid(asid)` — локальный сброс
2. Устанавливает глобальные статики: `SHOOTDOWN_ASID = asid`, `SHOOTDOWN_ACK = 0`
3. `broadcast_ipi_exclude_self(TLB_SHOOTDOWN_VECTOR)` — IPI всем остальным ядрам
4. Spin-loop: `while SHOOTDOWN_ACK != ACTIVE_CPUS_MASK { hlt(); }`
### `tlb_flush_all()`
Читает CR3 → перезаписывает без бита 63 → полный сброс всех non-global entries.
### `handle_tlb_shootdown_ipi()`
1. `local_tlb_flush_asid(SHOOTDOWN_ASID.load())`
2. Устанавливает свой бит в `SHOOTDOWN_ACK` (через `fetch_or`)
### `init_cpu_features()`
CPUID leaf 7, проверка EBX[10] (INVPCID). Сохраняет в `INVPCID_SUPPORTED`.
### `init_kernel_space(pml4_phys, hhdm)`
Регистрирует PML4 ядра как `KERNEL_SPACE` с ASID 0.