# Управление физической памятью ## Содержание 1. [Адресация (PhysAddr/VirtAddr)](#physaddr--virtaddr) 2. [BitmapPMM — менеджер физических фреймов](#bitmappmm) 3. [BuddyAllocator — блочный аллокатор](#buddyallocator) 4. [Page Tables — страничные таблицы x86-64](#page-tables) --- ## 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` | `VirtAddr - hhdm_offset`, возвращает None если адрес ниже HHDM | | `as_ptr::() -> *const T` | Приведение к константному указателю | | `as_mut_ptr::() -> *mut T` | Приведение к mutable указателю | --- ## BitmapPMM **Файл**: `src/mem/pmm.rs` Первичный менеджер физической памяти через битовую карту + счётчики ссылок. ### Структура ```rust 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> = 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` **Двухпроходный сканер:** 1. Проход 1: от `last_byte` до конца bitmap 2. Проход 2: от 0 до `last_byte` Сканирует побайтово в поисках байта с нулевым битом (`!0xFF`), внутри байта через `trailing_ones`. Устанавливает бит, `ref_count = 1`, `used_pages++`, обновляет `last_byte`. ### `alloc_contiguous(count) -> Option` Линейный поиск `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`. ```rust order_for(1) = 0 // 2^0 = 1 order_for(3) = 2 // 2^2 = 4 >= 3 order_for(2048) = 11 // 2^11 = 2048 ``` ### Структура ```rust pub struct BuddyAllocator { free_lists: [Vec; 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` 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` 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` Walk, возвращает флаги PTE (с поддержкой huge pages). #### `update_flags(virt, flags, hhdm) -> Result<(), ()>` Walk, обновляет флаги, сохраняя физический адрес. `invlpg`. #### `activate(phys_addr)` `unsafe`: запись CR3 = phys_addr → полный сброс TLB. ### `pmm_alloc() -> Option` Обёртка над `PMM.lock().unwrap().alloc_frame()` — разрывает циклическую зависимость между paging и pmm.