# Глобальный менеджер физической памяти: `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`. ### Инициализация ```rust 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() — пошагово ```rust pub fn alloc_frame(&mut self) -> Option { ``` 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() — пошагово ```rust 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 ```rust pub static PMM: Locked> = 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 на этапе инициализации и больше к нему не обращаются.