fix: bugs

This commit is contained in:
Faynot
2026-07-06 00:25:49 +03:00
parent 1b38c7f445
commit c092a81331
23 changed files with 2502 additions and 123 deletions

212
kernel/docs/vmm.md Normal file
View File

@@ -0,0 +1,212 @@
# Виртуальная память (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.