feat: docs & ARCH 2.2, 2.3, 2.4
This commit is contained in:
132
kernel/docs/memory/physical-memory.md
Normal file
132
kernel/docs/memory/physical-memory.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Глобальный менеджер физической памяти: `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 на этапе инициализации и больше к нему не
|
||||
обращаются.
|
||||
Reference in New Issue
Block a user