Files
Elyz/kernel/docs/memory/physical-memory.md
2026-07-07 16:40:41 +03:00

133 lines
6.2 KiB
Markdown
Raw Permalink 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.
# Глобальный менеджер физической памяти: `pmm.rs`
## Концептуальная модель
`BitmapPMM` — это **глобальный, единственный в системе**, распределитель
физических фреймов (4 KiB). Он отвечает на вопрос:
> Какой физический фрейм сейчас свободен?
### Структуры данных
PMM использует **трёхуровневую битовую карту**:
```
Уровень 1 (L1 bitmap): u64 слова
бит в L1 = 1 если соответствующее слово L0 не полностью занято
Уровень 0 (L0 bitmap): байты
каждый байт = 8 фреймов, каждый бит = 1 фрейм
бит = 1 → фрейм занят
бит = 0 → фрейм свободен
Ref-counts (счётчики ссылок): u16
для каждого фрейма — сколько раз он был захвачен
```
### Зачем L1 (ускорение)?
Наивный линейный поиск по битовой карте размером в мегабайты — O(N).
L1-битмап позволяет пропускать целые группы по 64 слова (4096 фреймов),
где нет свободных страниц.
**Поиск:**
1. Ищем `l1_bitmap[l1_idx]` с ненулевым значением (есть свободные).
2. Вычисляем `word_idx` по позиции бита внутри L1-слова.
3. Читаем `bitmap[word_idx]` как u64.
4. Если слово == `!0` (все заняты) — очищаем бит в L1, продолжаем.
5. Иначе — `(!word).trailing_zeros()` даёт номер свободного бита.
6. Вычисляем `page_idx` и возвращаем `PhysAddr`.
### Инициализация
```rust
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse) {
```
1. Находим максимальный физический адрес из всех entry карты памяти.
2. Вычисляем `total_pages = max_addr / PAGE_SIZE`.
3. Вычисляем размер метаданных:
- `bitmap_size = total_pages / 8` (1 бит на фрейм)
- `ref_counts_size = total_pages * 2` (u16 на фрейм)
- `l1_size = (total_pages / 64 / 64) * 8`
4. Ищем **первый USABLE регион** размером >= метаданные.
5. Размещаем метаданные в этом регионе: сначала bitmap, потом ref_counts,
потом L1.
6. **Заполняем bitmap = 0xFF** (все занято).
7. **Проходим по карте памяти**: для каждого USABLE entry очищаем биты
(free_frame).
8. **Блокируем фреймы метаданных** (они теперь заняты PMM).
9. **Блокируем фрейм 0** (традиционно reserved).
10. Сохраняем `PMM.lock() = Some(pmm)`.
### alloc_frame() — пошагово
```rust
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
```
1. Двухпроходный поиск: сначала от `last_word` до конца, потом от начала.
(`last_word` — хинт для локализации, даёт амортизированное O(1)).
2. Для каждого L1-слова: если != 0 — есть свободные.
3. `trailing_zeros()` даёт первое слово с хотя бы одним свободным битом.
4. Читаем слово как u64.
5. `(!word).trailing_zeros()` даёт свободный бит.
6. Вычисляем `page_idx`.
7. Устанавливаем бит = 1, ref_count = 1, used_pages += 1.
8. Если слово стало полностью занятым — очищаем бит в L1.
9. Обновляем `last_word`.
### free_frame() — пошагово
```rust
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
```
1. Если бит уже 1 (фрейм занят):
- Уменьшаем ref_count (saturating_sub).
- Если ref_count == 0: очищаем бит, used_pages -= 1, обновляем L1.
2. Если бит == 0 (уже свободен) — ничего не делаем (идемпотентность).
### lock_frame() — захват без освобождения
Используется для резервирования фреймов, которые не должны быть
освобождены (нулевой фрейм, фреймы метаданных PMM).
### inc_ref_frame() — увеличение счётчика
Используется COW (Copy-on-Write) при fork для учёта разделяемых страниц.
## Thread Safety
```rust
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
```
Locked<T> — примитивная spinlock-обёртка (см. `allocator.rs`).
Все операции PMM требуют захвата блокировки.
## Глобальные функции
В `pmm.rs` определены функции-обёртки для удобства:
| Функция | Вызывает |
|---------|----------|
| `alloc_frame()` | `PMM.lock().as_mut()?.alloc_frame()` |
| `alloc_contiguous(n)` | `PMM.lock().as_mut()?.alloc_contiguous(n)` |
| `free_frame(addr)` | `PMM.lock().as_mut()?.free_frame(addr)` |
| `inc_ref_frame(addr)` | `PMM.lock().as_mut()?.inc_ref_frame(addr)` |
| `get_stats()` | `PMM.lock().as_ref()?.used_pages/total_pages` |
## alloc_contiguous() — выделение непрерывной области
Линейный проход по всем страницам, поиск `count` последовательных
свободных фреймов. O(total_pages) — используется редко, только для
устройств без IOMMU.
## Ключевая инварианта
`BitmapPMM` не знает о buddy-аллокаторах и PMActors. Он просто
отвечает на запросы «дай фрейм» и «забери фрейм». PMActors получают
свои диапазоны из PMM на этапе инициализации и больше к нему не
обращаются.