Files
Elyz/kernel/docs/memory/physical-memory.md
2026-07-07 16:40:41 +03:00

6.2 KiB
Raw Permalink Blame History

Глобальный менеджер физической памяти: pmm.rs

Концептуальная модель

BitmapPMM — это глобальный, единственный в системе, распределитель физических фреймов (4 KiB). Он отвечает на вопрос:

Какой физический фрейм сейчас свободен?

Структуры данных

PMM использует трёхуровневую битовую карту:

Уровень 1 (L1 bitmap): u64 слова
    бит в L1 = 1 если соответствующее слово L0 не полностью занято

Уровень 0 (L0 bitmap): байты
    каждый байт = 8 фреймов, каждый бит = 1 фрейм
    бит = 1 → фрейм занят
    бит = 0 → фрейм свободен

Ref-counts (счётчики ссылок): u16
    для каждого фрейма — сколько раз он был захвачен

Зачем L1 (ускорение)?

Наивный линейный поиск по битовой карте размером в мегабайты — O(N). L1-битмап позволяет пропускать целые группы по 64 слова (4096 фреймов), где нет свободных страниц.

Поиск:

  1. Ищем l1_bitmap[l1_idx] с ненулевым значением (есть свободные).
  2. Вычисляем word_idx по позиции бита внутри L1-слова.
  3. Читаем bitmap[word_idx] как u64.
  4. Если слово == !0 (все заняты) — очищаем бит в L1, продолжаем.
  5. Иначе — (!word).trailing_zeros() даёт номер свободного бита.
  6. Вычисляем page_idx и возвращаем PhysAddr.

Инициализация

pub unsafe fn init(mmap: &limine::response::MemoryMapResponse) {
  1. Находим максимальный физический адрес из всех entry карты памяти.
  2. Вычисляем total_pages = max_addr / PAGE_SIZE.
  3. Вычисляем размер метаданных:
    • bitmap_size = total_pages / 8 (1 бит на фрейм)
    • ref_counts_size = total_pages * 2 (u16 на фрейм)
    • l1_size = (total_pages / 64 / 64) * 8
  4. Ищем первый USABLE регион размером >= метаданные.
  5. Размещаем метаданные в этом регионе: сначала bitmap, потом ref_counts, потом L1.
  6. Заполняем bitmap = 0xFF (все занято).
  7. Проходим по карте памяти: для каждого USABLE entry очищаем биты (free_frame).
  8. Блокируем фреймы метаданных (они теперь заняты PMM).
  9. Блокируем фрейм 0 (традиционно reserved).
  10. Сохраняем PMM.lock() = Some(pmm).

alloc_frame() — пошагово

pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
  1. Двухпроходный поиск: сначала от last_word до конца, потом от начала. (last_word — хинт для локализации, даёт амортизированное O(1)).
  2. Для каждого L1-слова: если != 0 — есть свободные.
  3. trailing_zeros() даёт первое слово с хотя бы одним свободным битом.
  4. Читаем слово как u64.
  5. (!word).trailing_zeros() даёт свободный бит.
  6. Вычисляем page_idx.
  7. Устанавливаем бит = 1, ref_count = 1, used_pages += 1.
  8. Если слово стало полностью занятым — очищаем бит в L1.
  9. Обновляем last_word.

free_frame() — пошагово

pub fn free_frame(&mut self, phys_addr: PhysAddr) {
  1. Если бит уже 1 (фрейм занят):
    • Уменьшаем ref_count (saturating_sub).
    • Если ref_count == 0: очищаем бит, used_pages -= 1, обновляем L1.
  2. Если бит == 0 (уже свободен) — ничего не делаем (идемпотентность).

lock_frame() — захват без освобождения

Используется для резервирования фреймов, которые не должны быть освобождены (нулевой фрейм, фреймы метаданных PMM).

inc_ref_frame() — увеличение счётчика

Используется COW (Copy-on-Write) при fork для учёта разделяемых страниц.

Thread Safety

pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);

Locked — примитивная spinlock-обёртка (см. allocator.rs). Все операции PMM требуют захвата блокировки.

Глобальные функции

В pmm.rs определены функции-обёртки для удобства:

Функция Вызывает
alloc_frame() PMM.lock().as_mut()?.alloc_frame()
alloc_contiguous(n) PMM.lock().as_mut()?.alloc_contiguous(n)
free_frame(addr) PMM.lock().as_mut()?.free_frame(addr)
inc_ref_frame(addr) PMM.lock().as_mut()?.inc_ref_frame(addr)
get_stats() PMM.lock().as_ref()?.used_pages/total_pages

alloc_contiguous() — выделение непрерывной области

Линейный проход по всем страницам, поиск count последовательных свободных фреймов. O(total_pages) — используется редко, только для устройств без IOMMU.

Ключевая инварианта

BitmapPMM не знает о buddy-аллокаторах и PMActors. Он просто отвечает на запросы «дай фрейм» и «забери фрейм». PMActors получают свои диапазоны из PMM на этапе инициализации и больше к нему не обращаются.