6.2 KiB
Глобальный менеджер физической памяти: 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 фреймов), где нет свободных страниц.
Поиск:
- Ищем
l1_bitmap[l1_idx]с ненулевым значением (есть свободные). - Вычисляем
word_idxпо позиции бита внутри L1-слова. - Читаем
bitmap[word_idx]как u64. - Если слово ==
!0(все заняты) — очищаем бит в L1, продолжаем. - Иначе —
(!word).trailing_zeros()даёт номер свободного бита. - Вычисляем
page_idxи возвращаемPhysAddr.
Инициализация
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse) {
- Находим максимальный физический адрес из всех entry карты памяти.
- Вычисляем
total_pages = max_addr / PAGE_SIZE. - Вычисляем размер метаданных:
bitmap_size = total_pages / 8(1 бит на фрейм)ref_counts_size = total_pages * 2(u16 на фрейм)l1_size = (total_pages / 64 / 64) * 8
- Ищем первый USABLE регион размером >= метаданные.
- Размещаем метаданные в этом регионе: сначала bitmap, потом ref_counts, потом L1.
- Заполняем bitmap = 0xFF (все занято).
- Проходим по карте памяти: для каждого USABLE entry очищаем биты (free_frame).
- Блокируем фреймы метаданных (они теперь заняты PMM).
- Блокируем фрейм 0 (традиционно reserved).
- Сохраняем
PMM.lock() = Some(pmm).
alloc_frame() — пошагово
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
- Двухпроходный поиск: сначала от
last_wordдо конца, потом от начала. (last_word— хинт для локализации, даёт амортизированное O(1)). - Для каждого L1-слова: если != 0 — есть свободные.
trailing_zeros()даёт первое слово с хотя бы одним свободным битом.- Читаем слово как u64.
(!word).trailing_zeros()даёт свободный бит.- Вычисляем
page_idx. - Устанавливаем бит = 1, ref_count = 1, used_pages += 1.
- Если слово стало полностью занятым — очищаем бит в L1.
- Обновляем
last_word.
free_frame() — пошагово
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
- Если бит уже 1 (фрейм занят):
- Уменьшаем ref_count (saturating_sub).
- Если ref_count == 0: очищаем бит, used_pages -= 1, обновляем L1.
- Если бит == 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 на этапе инициализации и больше к нему не
обращаются.