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

7.7 KiB
Raw Blame History

Виртуальная память (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 — типы подложки

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

pub struct VmaRegion {
    virt_start: VirtAddr,
    virt_end: VirtAddr,
    flags: VmaFlags,
    cap_token: u64,
    backing: VmaBacking,
}

Методы: contains(addr), size(), pages().


AddressSpace

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:
    • LAZYAnonymous(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.