133 lines
6.2 KiB
Markdown
133 lines
6.2 KiB
Markdown
# Глобальный менеджер физической памяти: `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 на этапе инициализации и больше к нему не
|
||
обращаются.
|