Files
Elyz/kernel/docs/memory-management.md
2026-07-06 00:25:49 +03:00

244 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Управление физической памятью
## Содержание
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<PhysAddr>` | `VirtAddr - hhdm_offset`, возвращает None если адрес ниже HHDM |
| `as_ptr::<T>() -> *const T` | Приведение к константному указателю |
| `as_mut_ptr::<T>() -> *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<Option<BitmapPMM>> = 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<PhysAddr>`
**Двухпроходный сканер:**
1. Проход 1: от `last_byte` до конца bitmap
2. Проход 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`.
```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<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>`
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<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.