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

9.4 KiB
Raw Blame History

Управление физической памятью

Содержание

  1. Адресация (PhysAddr/VirtAddr)
  2. BitmapPMM — менеджер физических фреймов
  3. BuddyAllocator — блочный аллокатор
  4. Page Tables — страничные таблицы x86-64

PhysAddr / VirtAddr

Файл: src/mem/address.rs

Типы-обёртки над u64 с HHDM-трансляцией.

PhysAddr(pub u64)#[repr(transparent)]

Метод Описание
to_virt(hhdm_offset) -> VirtAddr PhysAddr + hhdm_offset — вход в HHDM
is_aligned() -> bool Проверка 4 KiB выравнивания
align_down() -> Self Выравнивание вниз до границы страницы
align_up() -> Self Выравнивание вверх до границы страницы

VirtAddr(pub u64)#[repr(transparent)]

Метод Описание
to_phys(hhdm_offset) -> Option<PhysAddr> VirtAddr - hhdm_offset, возвращает None если адрес ниже HHDM
as_ptr::<T>() -> *const T Приведение к константному указателю
as_mut_ptr::<T>() -> *mut T Приведение к mutable указателю

BitmapPMM

Файл: src/mem/pmm.rs

Первичный менеджер физической памяти через битовую карту + счётчики ссылок.

Структура

pub struct BitmapPMM {
    bitmap: &'static mut [u8],       // один бит на страницу (1 = занята)
    ref_counts: &'static mut [u16],  // счётчики ссылок для COW/shared
    total_pages: usize,              // всего физических страниц в системе
    used_pages: usize,               // количество выделенных страниц
    last_byte: usize,                // подсказка для следующего сканирования
}

Глобальная: pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None).

init(mmap, hhdm_offset)

Полный поток инициализации:

  1. Вычисляет макс. физический адрес из карты памяти Limine
  2. Рассчитывает размер bitmap и ref_counts
  3. Находит USABLE регион для хранения метаданных
  4. Зануляет bitmap, все биты = 1 (все заняты), ref_counts = 1
  5. Проходит по карте памяти: все USABLE → free_frame()
  6. Лочит страницы метаданных (чтобы не выдать повторно)
  7. Лочит страницу 0 (зарезервирована)
  8. Сохраняет в глобальную PMM

free_frame(addr)

  • Декрементит ref_count
  • Если ref_count == 0: очищает бит, декрементит used_pages, обновляет last_byte

lock_frame(addr)

  • Если страница свободна: бит = 1, ref_count = 1, used_pages++
  • Если страница занята и ref_count был 0: ref_count = 1 (восстановление)

inc_ref_frame(addr)

  • Инкрементит ref_count (с насыщением) — для COW

alloc_frame() -> Option<PhysAddr>

Двухпроходный сканер:

  1. Проход 1: от last_byte до конца bitmap
  2. Проход 2: от 0 до last_byte

Сканирует побайтово в поисках байта с нулевым битом (!0xFF), внутри байта через trailing_ones. Устанавливает бит, ref_count = 1, used_pages++, обновляет last_byte.

alloc_contiguous(count) -> Option<PhysAddr>

Линейный поиск count последовательных свободных страниц.

Функции-обёртки

  • alloc_frame(), alloc_contiguous(count), free_frame(addr), inc_ref_frame(addr) — захватывают PMM.lock()
  • get_stats() -> (usize, usize) — возвращает (used, total)

BuddyAllocator

Файл: src/mem/buddy.rs

Power-of-two блочный аллокатор для per-actor управления физической памятью.

Константы

  • MAX_ORDER = 11 — макс. порядок (2^11 = 2048 страниц = 8 MiB)

order_for(page_count) -> usize

Вычисляет минимальное k такое, что 2^k >= page_count.

order_for(1) = 0      // 2^0 = 1
order_for(3) = 2      // 2^2 = 4 >= 3
order_for(2048) = 11  // 2^11 = 2048

Структура

pub struct BuddyAllocator {
    free_lists: [Vec<usize>; MAX_ORDER + 1],  // списки блоков по порядкам
    total_pages: usize,                         // всего страниц
    free_pages: usize,                          // свободно страниц
}

Инвариант: free_pages == Σ 2^k * len(free_lists[k]).

new(total_pages) -> Self

Жадное разложение диапазона на блоки максимального размера:

  • Итерация по диапазону
  • На каждой позиции вычисляет макс. порядок (ограничен alignment через trailing_zeros и остатком)
  • Добавляет блок в соответствующий free_list

Пример: 6 страниц → блок 4 (order 2) + блок 2 (order 1).

alloc(order) -> Option<usize>

  1. Находит наименьший k >= order с непустым free_list[k]
  2. Pop с конца (O(1))
  3. Разбивает до нужного порядка: при каждом разбиении buddy кладётся в список порядком ниже
  4. Возвращает индекс

alloc_pages(page_count) -> Option<(usize, usize)>

Округляет page_count до степени двойки, вызывает alloc(). Возвращает (index, order).

free(block_idx, order)

  1. Добавляет блок в free_list[order]
  2. Coalescing на каждом уровне:
    • buddy_idx = block_idx ^ (1 << order) (XOR)
    • Ищет buddy в free_list[order] (линейно, swap_remove)
    • Если найден: удаляет его, объединяет, переходит на уровень выше
    • Если не найден или вне диапазона: стоп

Интроспекция

  • free_pages(), total_pages(), is_exhausted(), stats() (per-order counts)

Page Tables

Файл: src/mem/paging.rs

4-уровневая страничная таблица x86-64: P4 → P3 → P2 → P1 (512 entry × 8 байт = 4 KiB каждая).

PageTableFlags (bitflags)

Флаг Бит Описание
PRESENT 0 Страница в памяти
WRITABLE 1 Разрешена запись
USER 2 Доступ из ring 3
WRITE_THROUGH 3 Write-through кэширование
NO_CACHE 4 Отключение кэширования
ACCESSED 5 Было обращение
DIRTY 6 Была запись
HUGE_PAGE 7 2 MiB (P2) или 1 GiB (P3)
GLOBAL 8 Не сбрасывается при перезагрузке CR3
COW 9 Кастомный — Copy-on-Write
NO_EXECUTE 63 Запрет исполнения (NX bit)

PageTable#[repr(C, align(4096))]

Поле: entries: [u64; 512]

Методы

map_page(virt, phys, flags, hhdm)

  1. Walk/create P4 → P3 → P2 → P1 (создаёт отсутствующие)
  2. Записывает PTE = phys | flags
  3. invlpg

get_or_create_next_table(index, hhdm, create) -> Option<&mut PageTable>

  • Читает PTE, проверяет PRESENT
  • Если есть: &mut *(hhdm + phys_addr) (HHDM dereference)
  • Если нет и create == true: pmm_alloc(), зануляет, ставит entry с PRESENT|WRITABLE|USER, возвращает
  • Если нет и create == false: None

walk_to_p1_mut(virt, hhdm, create) -> Option<&mut PageTable>

Из virtual address вычисляет индексы P4[47:39], P3[38:30], P2[29:21], P1[20:12], проходит цепочку.

unmap_page(virt, hhdm)

Walk (без create), зануляет PTE. invlpg. Идемпотентен.

translate(virt, hhdm) -> Option<PhysAddr>

Walk 4 уровней. Поддерживает huge pages (1 GiB на P3, 2 MiB на P2). Возвращает PhysAddr + page offset.

map_region(virt, phys, size, flags, hhdm)

Цикл по 4 KiB страницам, каждая → map_page().

get_flags(virt, hhdm) -> Option<PageTableFlags>

Walk, возвращает флаги PTE (с поддержкой huge pages).

update_flags(virt, flags, hhdm) -> Result<(), ()>

Walk, обновляет флаги, сохраняя физический адрес. invlpg.

activate(phys_addr)

unsafe: запись CR3 = phys_addr → полный сброс TLB.

pmm_alloc() -> Option<PhysAddr>

Обёртка над PMM.lock().unwrap().alloc_frame() — разрывает циклическую зависимость между paging и pmm.