213 lines
7.7 KiB
Markdown
213 lines
7.7 KiB
Markdown
# Виртуальная память (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.
|