# 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 (сортирован по адресу)│ └─────────────────────────────────────────────┘ │ ▼ ┌───────────────┐ │ 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>), // ^ 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, size: u64, flags: VmaFlags, cap_token: u64) -> Result ``` - `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> = 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 } ```