9.4 KiB
Управление физической памятью
Содержание
- Адресация (PhysAddr/VirtAddr)
- BitmapPMM — менеджер физических фреймов
- BuddyAllocator — блочный аллокатор
- 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)
Полный поток инициализации:
- Вычисляет макс. физический адрес из карты памяти Limine
- Рассчитывает размер bitmap и ref_counts
- Находит USABLE регион для хранения метаданных
- Зануляет bitmap, все биты = 1 (все заняты), ref_counts = 1
- Проходит по карте памяти: все USABLE →
free_frame() - Лочит страницы метаданных (чтобы не выдать повторно)
- Лочит страницу 0 (зарезервирована)
- Сохраняет в глобальную
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: от
last_byteдо конца bitmap - Проход 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>
- Находит наименьший
k >= orderс непустымfree_list[k] - Pop с конца (O(1))
- Разбивает до нужного порядка: при каждом разбиении buddy кладётся в список порядком ниже
- Возвращает индекс
alloc_pages(page_count) -> Option<(usize, usize)>
Округляет page_count до степени двойки, вызывает alloc(). Возвращает (index, order).
free(block_idx, order)
- Добавляет блок в
free_list[order] - 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)
- Walk/create P4 → P3 → P2 → P1 (создаёт отсутствующие)
- Записывает PTE =
phys | flags 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.