fix: bugs

This commit is contained in:
Faynot
2026-07-06 00:25:49 +03:00
parent 1b38c7f445
commit c092a81331
23 changed files with 2502 additions and 123 deletions

View File

@@ -0,0 +1,243 @@
# Управление физической памятью
## Содержание
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.