fix: bugs
This commit is contained in:
243
kernel/docs/memory-management.md
Normal file
243
kernel/docs/memory-management.md
Normal 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.
|
||||
Reference in New Issue
Block a user