# Виртуальная память (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` | Аллокатор ASID/PCID | | `KERNEL_SPACE` | `Locked>` | Адресное пространство ядра | --- ## Типы ошибок: `VmError` - `OutOfMemory` — нет свободной физической памяти - `RegionOverlap` — VMA перекрывается с существующей - `RegionNotFound` — VMA не найдена - `InvalidAlignment` — неверное выравнивание - `InvalidRange` — некорректный диапазон - `PermissionDenied` — нет прав доступа - `UnexpectedFault` — page fault в не-lazy, не-COW регионе - `NonCanonical` — неканонический адрес - `AsidExhausted` — кончились ASID --- ## `VmaBacking` — типы подложки ```rust pub enum VmaBacking { Anonymous(Vec>), // 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` — двухпроходный с пропуском целых слов (амортизированное 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, // 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` 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>` 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` Создание дочернего адресного пространства (fork): - **Physical/Shared**: маппинг напрямую - **Anonymous**: **COW setup**: - Убирает WRITABLE из PTE родителя - Устанавливает COW флаг в обоих PTEs - `inc_ref_frame()` для всех фреймов ### `translate(virt) -> Option` Аппаратный 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.