Compare commits
2 Commits
1b38c7f445
...
1bfde637d0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bfde637d0 | ||
|
|
c092a81331 |
626
PLAN.md
Normal file
626
PLAN.md
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
# Elyz (LISA) Microkernel — Полный план оптимизации и полировки
|
||||||
|
|
||||||
|
> Анализ кода по состоянию на июнь 2026.
|
||||||
|
> Составлен на основе исходников: `kernel/src/`, linker-скрипты, Cargo.toml, Makefile.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Содержание
|
||||||
|
|
||||||
|
1. [Условные обозначения](#условные-обозначения)
|
||||||
|
2. [Критические баги (не работает)](#ф-1-критические-баги-фаза-1)
|
||||||
|
3. [Средние баги (UB / утечки)](#ф-1-критические-баги-фаза-1)
|
||||||
|
4. [Проблемы архитектуры](#ф-2-архитектурный-рефакторинг-фаза-2)
|
||||||
|
5. [Code Quality & Naming](#ф-3-code-quality--naming-фаза-3)
|
||||||
|
6. [Производительность](#ф-4-производительность-фаза-4)
|
||||||
|
7. [Инфраструктура](#ф-5-инфраструктура-фаза-5)
|
||||||
|
8. [Полный пошаговый план (сортированный)](#полный-пошаговый-план-сортированный-по-приоритету)
|
||||||
|
9. [Roadmap](#roadmap)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Условные обозначения
|
||||||
|
|
||||||
|
| Метка | Значение |
|
||||||
|
|-------|----------|
|
||||||
|
| 🔴 CRIT | Системный баг — крах, silent corruption, UB |
|
||||||
|
| 🟡 HIGH | Утечка памяти, логическая ошибка |
|
||||||
|
| 🟢 MED | Неоптимальность, технический долг |
|
||||||
|
| 🔵 LOW | Косметика, нейминг, кодстайл |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 1: Критические баги (Фаза 1 — CRIT 1.1–1.6 ✅ исправлены)
|
||||||
|
|
||||||
|
### 🔴 CRIT 1.6 — `tlb_flush_asid` не работает с >64 ядрами ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/vmm.rs:794-802`
|
||||||
|
|
||||||
|
**Проблема**: `1u64 << current_core` при `current_core >= 64` — UB (panic в debug, wrap в release).
|
||||||
|
|
||||||
|
**Решение**: Добавлена проверка `current_core < 64` перед shift. Для core >= 64 self-exclusion пропускается (core не входит в нижние 64 бита маски). В `handle_tlb_shootdown_ipi` аналогичная проверка предотвращает запись в маску по некорректному смещению.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let target_mask = if current_core < 64 {
|
||||||
|
active_cpus & !(1u64 << current_core)
|
||||||
|
} else {
|
||||||
|
active_cpus
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**TODO**: При реализации SMP с >64 ядер — перейти на `AtomicU128` или динамический список APIC ID.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 1.5: Средние баги (Фаза 1.5 — MED 1.7–1.10 ✅ исправлены)
|
||||||
|
|
||||||
|
### 🟡 MED 1.7 — `unwrap_unchecked()` на Option без full fence ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pm_router.rs:70-82`
|
||||||
|
|
||||||
|
**Проблема**: TOCTOU race между check `is_ready` и `unwrap_unchecked()`. Хотя `is_ready` никогда не сбрасывается, code smell.
|
||||||
|
|
||||||
|
**Решение**: Добавлен комментарий-инвариант, объясняющий почему TOCTOU безопасен (is_ready set-once, never cleared). Panic path вынесен в `#[cold]` функцию:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[cold]
|
||||||
|
fn not_initialized() -> ! {
|
||||||
|
panic!("FATAL: PMRouter is accessed before initialization!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟡 MED 1.8 — `process_messages` не имеет квоты ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pm_manages.rs:328-354`
|
||||||
|
|
||||||
|
**Проблема**: Дренирует всю очередь за один вызов. Если 10 000 сообщений, заблокирует ядро на неопределённое время.
|
||||||
|
|
||||||
|
**Решение**: Добавлен лимит `MAX_MESSAGES_PER_CALL = 64`. После обработки 64 сообщений вызов возвращает управление, позволяя другим компонентам ядра получить процессорное время.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟡 MED 1.9 — Bitmap PMM инициализирует `ref_counts` в 1, потом маркирует used-фреймы ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pmm.rs:49-76`
|
||||||
|
|
||||||
|
**Проблема**: `fill(0xFF)` + `fill(1)` для всех страниц, затем итерация free по USABLE. Лишняя работа.
|
||||||
|
|
||||||
|
**Решение**: Инвертирована логика: `fill(0)` для bitmap и ref_counts, затем итерация по **не-USABLE** записям для mark_used. Убрана лишняя запись в резервированные страницы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟡 MED 1.10 — Page fault handler берёт `KERNEL_SPACE.lock()` каждый раз ✅ исправлено
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/cpu/interrupts.rs:109-148`
|
||||||
|
|
||||||
|
**Проблема**: Двойной захват `KERNEL_SPACE.lock()` — в `process_deferred_mmu_events()` и в `rust_page_fault_handler()`. На SMP каждый PF будет сериализован.
|
||||||
|
|
||||||
|
**Решение**: `process_deferred_mmu_events()` встроен в `rust_page_fault_handler` — блокировка захватывается один раз и удерживается для обеих операций. Добавлен TODO о переходе на `RwLock` для SMP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 2: Архитектурный рефакторинг (Фаза 2)
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.1 — HHDM offset передаётся параметром в каждую функцию
|
||||||
|
|
||||||
|
**Проблема**: `hhdm: u64` тащится через все вызовы:
|
||||||
|
```rust
|
||||||
|
p4.map_region(virt, phys, size, flags, hhdm);
|
||||||
|
p4.map_page(virt, frame, flags, hhdm);
|
||||||
|
```
|
||||||
|
|
||||||
|
Загрязняет сигнатуры и не нужно (HHDM offset — константа после boot).
|
||||||
|
|
||||||
|
**Решение**: Глобальный `static HHDM_OFFSET: AtomicU64`:
|
||||||
|
```rust
|
||||||
|
pub fn init(offset: u64) { HHDM_OFFSET.store(offset, Ordering::Release); }
|
||||||
|
pub fn get() -> u64 { HHDM_OFFSET.load(Ordering::Relaxed) }
|
||||||
|
```
|
||||||
|
|
||||||
|
Убрать `hhdm` из параметров всех функций paging/vmm.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.2 — Buddy allocator: O(n) поиск при free()
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/buddy.rs:246`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if let Some(pos) = self.free_lists[order].iter().position(|&b| b == buddy_idx) {
|
||||||
|
```
|
||||||
|
|
||||||
|
Линейный поиск по `Vec<usize>` — O(количество блоков этого порядка). При 10 000 блоков порядка 0, free() занимает тысячи итераций.
|
||||||
|
|
||||||
|
**Решение**: Перейти на **intrusive linked list** через сами страницы: первые 8 байт неиспользуемой страницы хранят `next` указатель. Даёт O(1) удаление.
|
||||||
|
|
||||||
|
Или хеш-таблицу: `HashMap<usize, ()>` (buddy_idx → exists).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.3 — PMM Bitmap: O(n) allocation scan
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pmm.rs:128-158`
|
||||||
|
|
||||||
|
Побайтовый (побитовый) scan всего bitmap в поиске нулевого бита. На системе с 64 GB RAM → 64M бит → до 8M итераций на аллокацию.
|
||||||
|
|
||||||
|
**Решение**: Использовать **tree bitmap** (buddy-style bitmap). Хранить дополнительный уровень с `u64` словами, где каждый бит = `all_ones(word)`. Позволяет за O(1) найти свободный блок через `ctz` / `clz`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.4 — Channel `wait_for_response` — busy-wait
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pm_router.rs:130-137`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
100% CPU consumption в ожидании. Для реального асинхронного рантайма нужен yield-to-scheduler или waker.
|
||||||
|
|
||||||
|
**Решение**: Добавить `Waker` registration: поток регистрирует свой `Waker` в канале, а `route_responses` вызывает `wake()` при записи результата. Пока планировщика нет — хотя бы HLT-based ожидание.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.5 — Capability revocation: O(n) рекурсия
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/cap/mod.rs:97-137`
|
||||||
|
|
||||||
|
`revoke_internal()` сканирует **весь CNode** на каждом уровне рекурсии. С 256+ слотами и глубиной 10 → 2560 итераций.
|
||||||
|
|
||||||
|
**Решение**: Держать per-slot список children:
|
||||||
|
```rust
|
||||||
|
struct CNodeSlot {
|
||||||
|
cap: Capability,
|
||||||
|
parent: Option<usize>,
|
||||||
|
children: Vec<usize>, // явный список наследников
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Отзыв — проход по дереву без сканирования всей таблицы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.6 — Нет SMP startup кода
|
||||||
|
|
||||||
|
`ACTIVE_CPUS_MASK = 1` (только BSP). TLB shootdown кидает IPI в пустоту. Нет:
|
||||||
|
- SIPI trampoline page
|
||||||
|
- Startup sequence для AP
|
||||||
|
- Per-CPU data areas
|
||||||
|
- Stack allocation для AP
|
||||||
|
|
||||||
|
**Решение**: Реализовать SIPI-последовательность:
|
||||||
|
1. Выделить страницу по адресу <1MB (real mode trampoline)
|
||||||
|
2. Записать startup код (выход из real mode → long mode → setup stack → jump to rust)
|
||||||
|
3. Отправить SIPI с вектором на эту страницу
|
||||||
|
4. AP просыпается, инициализирует LAPIC, регистрируется в `ACTIVE_CPUS_MASK`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ ARCH 2.7 — Вся память в HHDM identity-map при старте
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/main.rs:194-201`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
for entry in mmap_res.entries() {
|
||||||
|
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
|
||||||
|
if entry.entry_type != RESERVED {
|
||||||
|
p4.map_region(VirtAddr(entry.base), phys, entry.length, flags, hhdm_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Все *не-reserved* регионы identity-map'ятся по физическому адресу (second mapping). На системе с 64GB RAM и 4KB страницами = 16M calls к `map_page`. Каждый call:
|
||||||
|
- Аллоцирует до 3 страниц таблиц (P3, P2, P1)
|
||||||
|
- INVLPG на каждую 4KB страницу
|
||||||
|
- Время: ~часы при эмуляции, минуты при KVM
|
||||||
|
|
||||||
|
**Решение**: Использовать **2MB huge pages** (`PageTableFlags::HUGE_PAGE`) для identity map. 64GB → 32K entries вместо 16M. INVLPG заменяется на CR3 reload.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 3: Code Quality & Naming (Фаза 3)
|
||||||
|
|
||||||
|
### 🧹 CQ 3.1 — Dead code: FramebufferDisplay + Console (main.rs)
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/main.rs:42-144`
|
||||||
|
|
||||||
|
Две структуры:
|
||||||
|
- `FramebufferDisplay` (реализует `DrawTarget` для `embedded-graphics`)
|
||||||
|
- `Console` (обёртка над `FramebufferDisplay` + `embedded-graphics`)
|
||||||
|
|
||||||
|
Никто не используется. Настоящий консольный вывод — `tty::Console`. При этом `embedded-graphics` висит в Cargo.toml как зависимость.
|
||||||
|
|
||||||
|
**Решение**: Удалить об struct + реализацию, убрать `embedded-graphics` из зависимостей.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.2 — Unused imports в main.rs
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/main.rs:6-16`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use core::fmt::{self, Write};
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use crate::mem::paging::{PageTable, PageTableFlags};
|
||||||
|
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||||
|
use crate::cap::{Relation, CapRights, Capability, CapObject};
|
||||||
|
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
|
||||||
|
```
|
||||||
|
|
||||||
|
Часть из них используется, часть — нет (например `fmt::self`, `PageTable`). После удаления dead code (3.1) ещё часть станет не нужна.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.3 — `#[allow(dead_code)]` на весь mod vmm
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/vmm.rs:27`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![allow(dead_code)]
|
||||||
|
```
|
||||||
|
|
||||||
|
Отключает warning для ВСЕГО модуля. 800+ строк кода с отключённой проверкой мёртвого кода. Означает что значительная часть VMM API не используется сейчас — надо либо использовать, либо убрать, либо маркировать `#[allow(dead_code)]` на конкретные единицы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.4 — Имя проекта: LISA / Elyz / kernel
|
||||||
|
|
||||||
|
| Где | Что написано |
|
||||||
|
|-----|-------------|
|
||||||
|
| `Cargo.toml` | `name = "LISA"` |
|
||||||
|
| `README.md` | "Elyz" |
|
||||||
|
| `limine.conf` | `Elyz (LISA KERNEL)` |
|
||||||
|
| `GNUmakefile` | `elyz-x86_64.iso` |
|
||||||
|
| `main.rs` | `LIS4` (LISA 4?) |
|
||||||
|
|
||||||
|
Три разных публичных имени.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.5 — Странное имя файла: `pm_manages.rs`
|
||||||
|
|
||||||
|
Грамматически некорректно (глагол в имени файла). Основная структура — `PMActor`, файл должен называться `pm_actor.rs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.6 — Мёртвые linker-скрипты
|
||||||
|
|
||||||
|
- `linker-aarch64.ld`
|
||||||
|
- `linker-riscv64.ld`
|
||||||
|
- `linker-loongarch64.ld`
|
||||||
|
|
||||||
|
Не используются (build.rs хардкодит `linker-x86_64.ld`). Ни один из этих таргетов не подключен в `rust-toolchain.toml`. Dead weight.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.7 — Locked SpinLock без backoff
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/allocator.rs:27`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
while self.lock.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
|
||||||
|
core::hint::spin_loop();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`spin_loop()` на x86 — это `REP NOP` (PAUSE). Это правильно, но при длительном ожидании нужно экспоненциально увеличивать задержку.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧹 CQ 3.8 — `log!` максрос не lock-free
|
||||||
|
|
||||||
|
При вызове `info!(...)` внутри макроса:
|
||||||
|
1. Вызывается `SerialPort::init()` (см. CRIT 1.3)
|
||||||
|
2. Пишет на экран через framebuffer
|
||||||
|
3. Пишет в serial
|
||||||
|
|
||||||
|
Если в PF handler вызвать `info!`, и PF произошёл из-за framebuffer DMA или serial IRQ — будет reentrancy. Надо маркировать как `#[inline(never)]` или добавить reentrancy guard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 4: Производительность (Фаза 4)
|
||||||
|
|
||||||
|
### ⚡ PERF 4.1 — Bitmap PMM: word-level skip
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/pmm.rs:139`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if self.bitmap[byte_idx] == 0xFF { continue; }
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка на уровне байта — можно на уровне `u64` слова: `if word == u64::MAX { skip 8 bytes }`. В 8x меньше итераций.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ PERF 4.2 — Buddy free: O(n) → O(1)
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/buddy.rs:246`
|
||||||
|
|
||||||
|
См. ARCH 2.2. Замена `Vec` на intrusive list — ключевой перф-фикс.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ PERF 4.4 — INVLPG в bulk_map: batch flush
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/paging.rs:119-121`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
unsafe {
|
||||||
|
asm!("invlpg [{}]", in(reg) virt.0, ...);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`map_region` вызывает `map_page` в цикле — каждая итерация делает INVLPG. 16M страниц = 16M INVLPG. CPU заливает TLBs и потом их же сбрасывает.
|
||||||
|
|
||||||
|
**Решение**: Убрать INVLPG из `map_page` (оставить вызывающему), сделать отдельную функцию `flush_range(virt, pages)` или вызов `invpcid` type-2 (flush-by-range).
|
||||||
|
|
||||||
|
**Но осторожно**: INVLPG нужен при модификации активной таблицы. Если identity-map ещё не загружена — можно без INVLPG.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ PERF 4.5 — TLB shootdown: busy-wait → IPI + HLT
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/mem/vmm.rs:809-811`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
while SHOOTDOWN_ACK.load(Ordering::Acquire) & target_mask != target_mask {
|
||||||
|
spin_loop();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Пока нет AP — код мёртвый. Когда AP появятся, надо чтобы AP висели в `HLT` и просыпались от IPI с TLB_SHOOTDOWN_VECTOR.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ PERF 4.6 — Page fault handler: lock-free VMA tree
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/cpu/interrupts.rs:110`
|
||||||
|
|
||||||
|
`KERNEL_SPACE.lock()` — каждый PF сериализуется. На одноядерной системе — OK, на SMP — bottleneck.
|
||||||
|
|
||||||
|
**Решение**: `RWLock` на VMA region или lock-free RB-tree для VMA поиска.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 5: Инфраструктура (Фаза 5)
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.1 — SMP AP Startup Code
|
||||||
|
|
||||||
|
Отсутствует. Нужен:
|
||||||
|
- Startup trampoline в real mode (<1MB)
|
||||||
|
- Переход в protected → long mode
|
||||||
|
- Настройка GDT/IDT для AP
|
||||||
|
- Per-CPU stack
|
||||||
|
- Регистрация в `ACTIVE_CPUS_MASK`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.2 — Мультиархитектурность
|
||||||
|
|
||||||
|
Есть linker-скрипты под aarch64, riscv64, loongarch64, но:
|
||||||
|
- `build.rs` хардкодит `linker-x86_64.ld`
|
||||||
|
- ASM в interrupts.rs только x86
|
||||||
|
- LAPIC — x86-specific
|
||||||
|
- Port IO — x86-specific
|
||||||
|
- IDT — x86-specific
|
||||||
|
|
||||||
|
Для портирования нужна архитектурная абстракция — traits:
|
||||||
|
```rust
|
||||||
|
trait Arch {
|
||||||
|
type PteFlags;
|
||||||
|
fn init_interrupts();
|
||||||
|
fn send_ipi(core_id: u32, vector: u8);
|
||||||
|
fn read_cycle_counter() -> u64;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.3 — Unit-тесты для Buddy Allocator
|
||||||
|
|
||||||
|
Buddy allocator — идеальный кандидат для `#[cfg(test)]`:
|
||||||
|
- Проверка инвариантов после alloc/free sequence
|
||||||
|
- Fuzzing случайных порядков
|
||||||
|
- Проверка coalescing (граничные случаи)
|
||||||
|
- Проверка alignment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.4 — Утилита для бинарного патчинга
|
||||||
|
|
||||||
|
Сейчас результирующий ELF надо руками objcopy'ить. Можно добавить Cargo alias или build.rs скрипт для автоматизации.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ INFRA 5.5 — GDB/QEMU debugging support
|
||||||
|
|
||||||
|
Добавить `.gdbinit` / `gdb.py` с:
|
||||||
|
- `target remote localhost:1234`
|
||||||
|
- Разгрузка символов из ELF
|
||||||
|
- Макросы для просмотра page table, capability, VMA regions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ф 6: Безопасность (сквозная)
|
||||||
|
|
||||||
|
### 🛡️ SEC 6.1 — Capability minting не проверяет `Relation` parent
|
||||||
|
|
||||||
|
**Файл**: `kernel/src/cap/mod.rs:64-77`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if !src_slot.cap.rights.contains(CapRights::GRANT) {
|
||||||
|
return Err("Insufficient rights to mint (Missing GRANT flag)");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверяет только GRANT флаг. Но не проверяет `relation`: если source — `Borrow`, можно ли mint? В текущей реализации — можно. Должен быть check:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if src_slot.cap.relation == Relation::Borrow {
|
||||||
|
return Err("Cannot mint from borrowed capability");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛡️ SEC 6.2 — CapGuardToken не реализован
|
||||||
|
|
||||||
|
В дескрипторе есть упоминание `guards: Vec<CapGuardToken>`, `Borrow` relation и guard-токены, логика самих guard'ов не реализована. Если есть borrow, mint не должен позволять дальнейшее распространение — но это не проверяется.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛡️ SEC 6.3 — Нет проверки прав на операции с PMM через capabilities
|
||||||
|
|
||||||
|
`BitmapPMM` полностью глобальный: любой код может вызвать `alloc_frame()`, `free_frame()` без проверки capability. Капы пока хранят токены и права, но PMM их не проверяет.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Полный пошаговый план (сортированный по приоритету)
|
||||||
|
|
||||||
|
### Этап 1: Исправить критические баги (сейчас — не работает)
|
||||||
|
|
||||||
|
```
|
||||||
|
[X] 1. ✅ HIGH — Исправить target_mask для >64 core
|
||||||
|
Добавлена проверка current_core < 64 перед shift.
|
||||||
|
Estimate: ~10 мин
|
||||||
|
|
||||||
|
[ ] 2. 🔥 HIGH — Исправить ref_counts инициализацию
|
||||||
|
Вместо fill(1) + free, сделать fill(0) + mark_used.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 2: Архитектурный рефакторинг
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 3. 🏗️ ARCH — Глобальный HHDM_OFFSET
|
||||||
|
AtomicU64 static, убрать hhdm из сигнатур.
|
||||||
|
Estimate: ~1 ч
|
||||||
|
|
||||||
|
[ ] 4. 🏗️ ARCH — Intrusive free-list в buddy allocator
|
||||||
|
O(1) free вместо O(n).
|
||||||
|
Estimate: ~2 ч
|
||||||
|
|
||||||
|
[ ] 5. 🏗️ ARCH — Tree bitmap для PMM
|
||||||
|
word-level scan с ctz.
|
||||||
|
Estimate: ~1.5 ч
|
||||||
|
|
||||||
|
[ ] 6. 🏗️ ARCH — HLT-based wait в router channel
|
||||||
|
Пока нет планировщика — HLT вместо spin.
|
||||||
|
Estimate: ~30 мин
|
||||||
|
|
||||||
|
[ ] 7. 🏗️ ARCH — Capability revoke без O(n) scan
|
||||||
|
Children list в слоте.
|
||||||
|
Estimate: ~1 ч
|
||||||
|
|
||||||
|
[ ] 8. 🏗️ ARCH — Identity-map через 2MB huge pages
|
||||||
|
map_region с HUGE_PAGE флагом для identity.
|
||||||
|
Estimate: ~1 ч
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 3: Code Quality
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 9. 🧹 CQ — Удалить dead code (FramebufferDisplay, Console, embedded-graphics)
|
||||||
|
+ неиспользуемые импорты.
|
||||||
|
Estimate: ~20 мин
|
||||||
|
|
||||||
|
[ ] 10. 🧹 CQ — Убрать #![allow(dead_code)] из vmm.rs
|
||||||
|
Маркировать конкретные единицы.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
|
||||||
|
[ ] 11. 🧹 CQ — Удалить мёртвые linker-скрипты
|
||||||
|
aarch64/riscv64/loongarch64.ld.
|
||||||
|
Estimate: ~5 мин
|
||||||
|
|
||||||
|
[ ] 12. 🧹 CQ — Переименовать pm_manages.rs → pm_actor.rs
|
||||||
|
+ все референсы.
|
||||||
|
Estimate: ~10 мин
|
||||||
|
|
||||||
|
[ ] 13. 🧹 CQ — Вынести panic.rs из main.rs
|
||||||
|
Отдельный файл для panic_handler.
|
||||||
|
Estimate: ~10 мин
|
||||||
|
|
||||||
|
[ ] 14. 🧹 CQ — Синхронизировать имена: LISA → Elyz
|
||||||
|
Cargo.toml, main.rs ("LIS4"), README.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
|
||||||
|
[ ] 15. 🧹 CQ — Locked spinlock с exponential backoff
|
||||||
|
PAUSE + цикл задержки.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 4: Производительность
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 16. ⚡ PERF — Bitmap scan: word-level (u64) вместо byte-level
|
||||||
|
+ ctzl для поиска бита.
|
||||||
|
Estimate: ~30 мин
|
||||||
|
|
||||||
|
[ ] 17. ⚡ PERF — INVLPG-free bulk map
|
||||||
|
Убрать INVLPG из map_page, вызов flush_range после.
|
||||||
|
Estimate: ~30 мин
|
||||||
|
|
||||||
|
[ ] 18. ⚡ PERF — process_messages quota
|
||||||
|
Max N сообщений за вызов.
|
||||||
|
Estimate: ~15 мин
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 5: Инфраструктура
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 19. 🛠️ INFRA — SMP AP startup code
|
||||||
|
SIPI → trampoline → long mode → Rust.
|
||||||
|
Estimate: ~1 неделя
|
||||||
|
|
||||||
|
[ ] 20. 🛠️ INFRA — Unit tests для buddy allocator
|
||||||
|
#[cfg(test)] модуль.
|
||||||
|
Estimate: ~1 ч
|
||||||
|
|
||||||
|
[ ] 21. 🛠️ INFRA — GDB script + QEMU -s
|
||||||
|
.gdbinit для отладки.
|
||||||
|
Estimate: ~30 мин
|
||||||
|
|
||||||
|
[ ] 22. 🛠️ INFRA — Arch trait для x86_64
|
||||||
|
Подготовка к портированию.
|
||||||
|
Estimate: ~3-4 ч
|
||||||
|
```
|
||||||
|
|
||||||
|
### Этап 6: Безопасность
|
||||||
|
|
||||||
|
```
|
||||||
|
[ ] 23. 🛡️ SEC — Проверка Relation при mint
|
||||||
|
Запретить mint из Borrow.
|
||||||
|
Estimate: ~10 мин
|
||||||
|
|
||||||
|
[ ] 24. 🛡️ SEC — CapGuard реализация
|
||||||
|
Привязка guard-токенов к source slot при mint.
|
||||||
|
Estimate: ~45 мин
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Roadmap (визуально)
|
||||||
|
|
||||||
|
```
|
||||||
|
Неделя 1 Неделя 2 Неделя 3
|
||||||
|
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
|
||||||
|
│ ✅ CRIT 1–6 готово │▸▸ │ ARCH 3 HHDM global │▸▸ │ PERF 16 word-level PMM │
|
||||||
|
│ ✅ MED 1.7–1.10 │ │ ARCH 4 intrusive list │ │ PERF 17 INVLPG batch │
|
||||||
|
│ HIGH 2 ref_counts │ │ ARCH 5 tree bitmap │ │ INFRA 19 SMP startup │
|
||||||
|
│ │ │ CQ 9 clean dead code │ │ INFRA 20 unit tests │
|
||||||
|
│ │ │ CQ 11-15 rename/refact│ │ SEC 23-24 caps │
|
||||||
|
└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘
|
||||||
|
↓ ↓ ↓
|
||||||
|
Kernel bootable и Код читаемый, Производительность:
|
||||||
|
логирует без крашей архитектура чище SMP + оптимизации
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Документ создан 29 июня 2026 на основе анализа исходного кода проекта Elyz.*
|
||||||
152
kernel/docs/boot/introduction.md
Normal file
152
kernel/docs/boot/introduction.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Процесс загрузки: концептуальная модель
|
||||||
|
|
||||||
|
## Цепочка загрузки
|
||||||
|
|
||||||
|
```
|
||||||
|
Питание включено
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
CPU reset vector (0xFFFFFFF0)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
UEFI firmware / BIOS
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Limine bootloader ─────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ 1. Переводит CPU в 64-bit long mode │
|
||||||
|
│ 2. Настраивает page tables (identity map) │
|
||||||
|
│ 3. Загружает ядро по физическому адресу │
|
||||||
|
│ 4. Настраивает HHDM │
|
||||||
|
│ 5. Заполняет Limine requests │
|
||||||
|
│ 6. Передаёт управление на kmain │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
kmain() (точка входа) │
|
||||||
|
│ │
|
||||||
|
... инициализация ... │
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
HCF (останов CPU) ─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limine Boot Protocol
|
||||||
|
|
||||||
|
Elyz использует [Limine](https://github.com/limine-bootloader/limine) —
|
||||||
|
современный bootloader с открытым исходным кодом.
|
||||||
|
|
||||||
|
Статические `Limine requests` сообщают bootloader'у, что нужно ядру:
|
||||||
|
|
||||||
|
| Request | Назначение |
|
||||||
|
|---------|------------|
|
||||||
|
| `FramebufferRequest` | Получить framebuffer для графического вывода |
|
||||||
|
| `MemoryMapRequest` | Получить карту физической памяти |
|
||||||
|
| `HhdmRequest` | Получить HHDM offset |
|
||||||
|
| `ExecutableAddressRequest` | Получить физический/виртуальный адрес ядра |
|
||||||
|
| `BaseRevision` | Проверить совместимость с Limine |
|
||||||
|
|
||||||
|
### Размещение в секциях
|
||||||
|
|
||||||
|
Requests размещаются между специальными маркерами в секции `.data`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[used]
|
||||||
|
#[unsafe(link_section = ".requests_start_marker")]
|
||||||
|
static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
|
||||||
|
|
||||||
|
// ... все requests ...
|
||||||
|
|
||||||
|
#[used]
|
||||||
|
#[unsafe(link_section = ".requests_end_marker")]
|
||||||
|
static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new();
|
||||||
|
```
|
||||||
|
|
||||||
|
Линкер скрипт сохраняет эти секции:
|
||||||
|
```ld
|
||||||
|
.data : {
|
||||||
|
*(.data .data.*)
|
||||||
|
KEEP(*(.requests_start_marker))
|
||||||
|
KEEP(*(.requests))
|
||||||
|
KEEP(*(.requests_end_marker))
|
||||||
|
} :data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Точка входа — kmain
|
||||||
|
|
||||||
|
Линкер скрипт: `ENTRY(kmain)`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
unsafe extern "C" fn kmain() -> ! { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
- `no_mangle` — сохраняет имя `kmain` (линкер ищет именно его).
|
||||||
|
- `extern "C"` — C ABI (Linux x86-64 calling convention: RDI, RSI, ...).
|
||||||
|
- `unsafe` — на этапе инициализации все операции потенциально опасны.
|
||||||
|
- `-> !` — kmain никогда не возвращается (HALT).
|
||||||
|
|
||||||
|
## Build system
|
||||||
|
|
||||||
|
### GNUmakefile
|
||||||
|
|
||||||
|
```makefile
|
||||||
|
KARCH ?= x86_64
|
||||||
|
RUST_TARGET ?= $(KARCH)-unknown-none
|
||||||
|
RUST_PROFILE ?= dev
|
||||||
|
|
||||||
|
all:
|
||||||
|
RUSTFLAGS="-C relocation-model=static" \
|
||||||
|
cargo build --target $(RUST_TARGET) --profile $(RUST_PROFILE)
|
||||||
|
cp target/$(RUST_TARGET)/$(RUST_PROFILE_SUBDIR)/kernel .
|
||||||
|
```
|
||||||
|
|
||||||
|
- `relocation-model=static`: ядро не использует динамическую перелокацию.
|
||||||
|
- `--target x86_64-unknown-none`: bare-metal target (без ОС).
|
||||||
|
- Результат копируется в `kernel/` (корень).
|
||||||
|
|
||||||
|
### build.rs — linker script
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||||
|
println!("cargo:rustc-link-arg=-Tlinker-{arch}.ld");
|
||||||
|
println!("cargo:rerun-if-changed=linker-{arch}.ld");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Подставляет правильный linker script для архитектуры.
|
||||||
|
|
||||||
|
### rust-toolchain.toml
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
targets = ["x86_64-unknown-none"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Требуется nightly Rust из-за:
|
||||||
|
- `#![no_std]`, `#![no_main]`.
|
||||||
|
- `core::arch::global_asm!`, `core::arch::asm!`.
|
||||||
|
- `const { ... }` в инициализации констант.
|
||||||
|
|
||||||
|
## Память: расположение после загрузки
|
||||||
|
|
||||||
|
```
|
||||||
|
Физическая память:
|
||||||
|
┌───────────────────────┐ 0x0
|
||||||
|
│ Reserved │
|
||||||
|
├───────────────────────┤
|
||||||
|
│ Usable (free) │ ← входит в mmap entries
|
||||||
|
├───────────────────────┤
|
||||||
|
│ Kernel image │ ← загружен Limine
|
||||||
|
├───────────────────────┤
|
||||||
|
│ PMM metadata │ ← bitmap + ref_counts + L1
|
||||||
|
├───────────────────────┤
|
||||||
|
│ ... (usable) │
|
||||||
|
└───────────────────────┘ max_addr
|
||||||
|
|
||||||
|
Виртуальная память (Higher Half):
|
||||||
|
0xFFFF_8000_0000_0000 ─── HHDM (вся физическая память 1:1)
|
||||||
|
0xFFFF_9000_0000_0000 ─── Kernel heap (8 MiB)
|
||||||
|
```
|
||||||
151
kernel/docs/boot/linker.md
Normal file
151
kernel/docs/boot/linker.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Линкер-скрипты: архитектурные детали
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Линкер-скрипты управляют расположением секций ELF-образа ядра.
|
||||||
|
Для каждой архитектуры — свой скрипт, но все они следуют одной схеме.
|
||||||
|
|
||||||
|
## Общая структура
|
||||||
|
|
||||||
|
```
|
||||||
|
OUTPUT_FORMAT(...) ← формат ELF (зависит от архитектуры)
|
||||||
|
ENTRY(kmain) ← точка входа
|
||||||
|
|
||||||
|
PHDRS ← сегменты (program headers)
|
||||||
|
{
|
||||||
|
text PT_LOAD;
|
||||||
|
rodata PT_LOAD;
|
||||||
|
data PT_LOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTIONS ← расположение секций
|
||||||
|
{
|
||||||
|
. = 0xffffffff80000000; ← база higher half
|
||||||
|
|
||||||
|
.text : { *(.text .text.*) } :text
|
||||||
|
|
||||||
|
. = ALIGN(MAXPAGESIZE);
|
||||||
|
.rodata : { *(.rodata .rodata.*) } :rodata
|
||||||
|
|
||||||
|
. = ALIGN(MAXPAGESIZE);
|
||||||
|
.data : {
|
||||||
|
*(.data .data.*)
|
||||||
|
KEEP(*(.requests_start_marker))
|
||||||
|
KEEP(*(.requests))
|
||||||
|
KEEP(*(.requests_end_marker))
|
||||||
|
} :data
|
||||||
|
|
||||||
|
.bss : { *(.bss .bss.*) *(COMMON) } :data
|
||||||
|
|
||||||
|
/DISCARD/ : { *(.eh_frame*) *(.note .note.*) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Детали
|
||||||
|
|
||||||
|
### OUTPUT_FORMAT
|
||||||
|
|
||||||
|
| Архитектура | Формат |
|
||||||
|
|-------------|--------|
|
||||||
|
| x86_64 | `elf64-x86-64` |
|
||||||
|
| aarch64 | `elf64-littleaarch64` |
|
||||||
|
| riscv64 | `elf64-littleriscv` |
|
||||||
|
| loongarch64 | `elf64-loongarch` |
|
||||||
|
|
||||||
|
### Базовый адрес: 0xFFFFFFFF80000000
|
||||||
|
|
||||||
|
Ядро размещается в **higher half** — верхней 2 GiB виртуального
|
||||||
|
адресного пространства. Это стандартная практика для x86-64:
|
||||||
|
|
||||||
|
```
|
||||||
|
0x0000_0000_0000_00000 ─── user space (не используется ядром)
|
||||||
|
0xFFFF_8000_0000_00000 ─── kernel space (higher half)
|
||||||
|
0xFFFF_FFFF_FFFF_FFFF ─── конец
|
||||||
|
```
|
||||||
|
|
||||||
|
Любой адрес в области 0xFFFF800000000000 — 0xFFFFFFFFFFFFFFFF корректен;
|
||||||
|
0xFFFFFFFF80000000 выбран как начало typical higher half региона.
|
||||||
|
|
||||||
|
### PHDRS: PT_LOAD сегменты
|
||||||
|
|
||||||
|
Bootloader загружает только PT_LOAD сегменты. Их три:
|
||||||
|
|
||||||
|
1. **text**: код + inline-константы.
|
||||||
|
2. **rodata**: неизменяемые данные (строки, таблицы).
|
||||||
|
3. **data**: изменяемые данные + BSS.
|
||||||
|
|
||||||
|
### Section alignment
|
||||||
|
|
||||||
|
```ld
|
||||||
|
. = ALIGN(CONSTANT(MAXPAGESIZE));
|
||||||
|
```
|
||||||
|
|
||||||
|
MAXPAGESIZE = 0x1000 (4 KiB). Каждая секция начинается с новой страницы,
|
||||||
|
что даёт bootloader'у правильные MMU permissionы:
|
||||||
|
- text = read + execute (no write)
|
||||||
|
- rodata = read (no write, no execute)
|
||||||
|
- data = read + write (no execute)
|
||||||
|
|
||||||
|
### Limine Requests в .data
|
||||||
|
|
||||||
|
```ld
|
||||||
|
KEEP(*(.requests_start_marker))
|
||||||
|
KEEP(*(.requests))
|
||||||
|
KEEP(*(.requests_end_marker))
|
||||||
|
```
|
||||||
|
|
||||||
|
- `KEEP` — запрещает линкеру выбрасывать эти секции (dead code elimination).
|
||||||
|
- `.requests_start_marker` и `.requests_end_marker` — маркеры границ.
|
||||||
|
- Bootloader сканирует память между ними, чтобы найти requests.
|
||||||
|
|
||||||
|
### BSS
|
||||||
|
|
||||||
|
```ld
|
||||||
|
.bss : {
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(COMMON)
|
||||||
|
} :data
|
||||||
|
```
|
||||||
|
|
||||||
|
- BSS — неинициализированные глобальные переменные.
|
||||||
|
- Занимает место в виртуальной памяти, но не в ELF-файле.
|
||||||
|
- Bootloader обнуляет BSS перед передачей управления.
|
||||||
|
|
||||||
|
### DISCARD
|
||||||
|
|
||||||
|
```ld
|
||||||
|
/DISCARD/ : {
|
||||||
|
*(.eh_frame*)
|
||||||
|
*(.note .note.*)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `.eh_frame*` — исключительные фреймы C++/Rust unwinding.
|
||||||
|
- `.note.*` — ELF notes.
|
||||||
|
- Не нужны bare-metal ядру, могут вызвать проблемы.
|
||||||
|
|
||||||
|
## Архитектурные различия
|
||||||
|
|
||||||
|
### RISC-V
|
||||||
|
|
||||||
|
```ld
|
||||||
|
.data : {
|
||||||
|
*(.data .data.*)
|
||||||
|
KEEP(*(.requests_start_marker))
|
||||||
|
KEEP(*(.requests))
|
||||||
|
KEEP(*(.requests_end_marker))
|
||||||
|
*(.sdata .sdata.*) ← RISC-V: small data
|
||||||
|
} :data
|
||||||
|
|
||||||
|
.bss : {
|
||||||
|
*(.sbss .sbss.*) ← RISC-V: small BSS
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(COMMON)
|
||||||
|
} :data
|
||||||
|
```
|
||||||
|
|
||||||
|
RISC-V имеет `.sdata`/`.sbss` секции для small data (GP-relative addressing).
|
||||||
|
|
||||||
|
### AArch64 и LoongArch64
|
||||||
|
|
||||||
|
Идентичны x86_64, за исключением OUTPUT_FORMAT.
|
||||||
146
kernel/docs/capability/cnode.md
Normal file
146
kernel/docs/capability/cnode.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# CNode: таблица capability: `mod.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**CNode** (Capability Node) — это массив слотов, каждый из которых
|
||||||
|
может хранить один capability. Это аналог файловой таблицы в Unix,
|
||||||
|
но для capabilities.
|
||||||
|
|
||||||
|
```
|
||||||
|
CNode {
|
||||||
|
slots: Vec<Locked<CNodeSlot>>
|
||||||
|
}
|
||||||
|
|
||||||
|
CNodeSlot {
|
||||||
|
cap: Capability,
|
||||||
|
parent_idx: Option<usize>, // индекс родительского слота
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Инициализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn new(size: usize) -> Self {
|
||||||
|
let mut slots = Vec::with_capacity(size);
|
||||||
|
for _ in 0..size {
|
||||||
|
slots.push(Locked::new(CNodeSlot {
|
||||||
|
cap: Capability::empty(),
|
||||||
|
parent_idx: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Self { slots }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Все слоты изначально пустые (`Capability::empty()`).
|
||||||
|
|
||||||
|
## Операции
|
||||||
|
|
||||||
|
### insert(slot, cap) — вставка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn insert(&self, slot: usize, cap: Capability) -> Result<(), &'static str> {
|
||||||
|
if slot >= self.slots.len() { return Err("Index out of bounds"); }
|
||||||
|
let mut s = self.slots[slot].lock();
|
||||||
|
s.cap = cap;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Простая вставка без проверки (перезаписывает существующий).
|
||||||
|
|
||||||
|
### mint(src, dest, relation, rights) — создание потомка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn mint(&self, src: usize, dest: usize, relation: Relation, rights: CapRights) -> Result<(), &'static str>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Валидация:**
|
||||||
|
- src и dest в пределах массива.
|
||||||
|
- src != dest (дедлок не имеет смысла, но блокировка была бы корректна).
|
||||||
|
- src содержит валидный capability.
|
||||||
|
- src имеет GRANT.
|
||||||
|
|
||||||
|
**Процесс:**
|
||||||
|
1. Захват блокировок в порядке возрастания индекса (lock ranking).
|
||||||
|
2. Вычисление `final_rights = src.rights & rights`.
|
||||||
|
3. Копирование Capability в dest с новыми правами и relation.
|
||||||
|
4. Установка `parent_idx = Some(src)`.
|
||||||
|
|
||||||
|
### revoke(slot_idx) — отзыв
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn revoke(&self, slot_idx: usize) -> Result<(), &'static str>
|
||||||
|
```
|
||||||
|
|
||||||
|
Каскадное удаление:
|
||||||
|
|
||||||
|
```
|
||||||
|
revoke_internal(slot_idx):
|
||||||
|
│
|
||||||
|
├── 1. Поиск потомков:
|
||||||
|
│ for i in 0..slots.len():
|
||||||
|
│ if slots[i].parent_idx == Some(slot_idx):
|
||||||
|
│ revoke_internal(i) ← рекурсивно!
|
||||||
|
│
|
||||||
|
├── 2. Уничтожение себя:
|
||||||
|
│ cap = Capability::empty()
|
||||||
|
│ parent_idx = None
|
||||||
|
│ token = old_token_sig
|
||||||
|
│
|
||||||
|
└── 3. Отправка token в очередь:
|
||||||
|
if token != 0 && token != 0xDEAD_BEEF:
|
||||||
|
MMU_REVOCATION_QUEUE.push(token)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Почему нет блокировок при рекурсии?**
|
||||||
|
- На каждом шаге проверка `is_child` захватывает и отпускает блокировку.
|
||||||
|
- Рекурсивный вызов происходит **после** освобождения блокировки.
|
||||||
|
- Это предотвращает взаимоблокировки.
|
||||||
|
|
||||||
|
**Фильтр токенов:**
|
||||||
|
- `token == 0`: пустой/невалидный capability.
|
||||||
|
- `token == 0xDEAD_BEEF`: сырой Untyped (для тестов/отладки).
|
||||||
|
- Эти токены не отправляются в очередь (бессмысленно).
|
||||||
|
|
||||||
|
### get_cap(slot) — чтение
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn get_cap(&self, slot: usize) -> Option<Capability> {
|
||||||
|
self.slots.get(slot).map(|s| s.lock().cap)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lock Ranking — детали
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let (src_slot, dest_slot) = if src < dest {
|
||||||
|
_guard_low = self.slots[src].lock(); // меньший → первый
|
||||||
|
_guard_high = self.slots[dest].lock(); // больший → второй
|
||||||
|
} else {
|
||||||
|
_guard_low = self.slots[dest].lock();
|
||||||
|
_guard_high = self.slots[src].lock();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Причина:** если поток A делает mint(5, 10), а поток B делает mint(10, 5),
|
||||||
|
без lock ranking они могут взаимно заблокироваться:
|
||||||
|
- A: lock(5) → ждёт lock(10)
|
||||||
|
- B: lock(10) → ждёт lock(5)
|
||||||
|
|
||||||
|
С lock ranking:
|
||||||
|
- A: lock(5) → lock(10)
|
||||||
|
- B: lock(5) → ждёт... → дождался → lock(10)
|
||||||
|
|
||||||
|
## Интеграция с событиями
|
||||||
|
|
||||||
|
После `revoke()`, токен попадает в `MMU_REVOCATION_QUEUE` (см.
|
||||||
|
`events.rs`). VMM обрабатывает очередь в `process_pending_revocations()`.
|
||||||
|
|
||||||
|
## Особенности реализации
|
||||||
|
|
||||||
|
1. **Vec<Locked<CNodeSlot>>**: каждый слот — отдельная spinlock-ячейка.
|
||||||
|
Это позволяет параллельно читать разные слоты.
|
||||||
|
2. **`pub slots`**: прямой доступ к слоту возможен (для тестов).
|
||||||
|
3. **panic на overflow**: если очередь отзыва переполнена — паника.
|
||||||
|
Это критический сбой подсистемы ресурсов.
|
||||||
115
kernel/docs/capability/descriptors.md
Normal file
115
kernel/docs/capability/descriptors.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Дескрипторы capability: `descriptor.rs`
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Файл определяет базовые типы capability-системы:
|
||||||
|
что такое capability, какие бывают объекты, права и отношения.
|
||||||
|
|
||||||
|
## CapObject — что представляет capability
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum CapObject {
|
||||||
|
Empty, // Пустой слот
|
||||||
|
Memory { phys: PhysAddr, size_pages: usize }, // Фрейм памяти
|
||||||
|
CNode { phys: PhysAddr, slots: usize }, // Другой CNode
|
||||||
|
PMActor { id: u64 }, // PM Actor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory
|
||||||
|
|
||||||
|
Capability на физическую память:
|
||||||
|
- `phys` — физический адрес начала.
|
||||||
|
- `size_pages` — размер в страницах.
|
||||||
|
|
||||||
|
### CNode
|
||||||
|
|
||||||
|
Capability на другой CNode:
|
||||||
|
- `phys` — физический адрес CNode.
|
||||||
|
- `slots` — количество слотов.
|
||||||
|
|
||||||
|
### PMActor
|
||||||
|
|
||||||
|
Capability на PMActor:
|
||||||
|
- `id` — уникальный идентификатор актора.
|
||||||
|
|
||||||
|
### Empty
|
||||||
|
|
||||||
|
Слот пуст. `is_valid()` возвращает `false`.
|
||||||
|
|
||||||
|
## CapRights — права доступа
|
||||||
|
|
||||||
|
```rust
|
||||||
|
bitflags! {
|
||||||
|
pub struct CapRights: u8 {
|
||||||
|
const READ = 1 << 0; // 0x01 — чтение
|
||||||
|
const WRITE = 1 << 1; // 0x02 — запись
|
||||||
|
const EXECUTE = 1 << 2; // 0x04 — исполнение
|
||||||
|
const GRANT = 1 << 3; // 0x08 — разрешение на mint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Правила:**
|
||||||
|
- Права дочернего capability = `родительские_права & запрошенные_права`.
|
||||||
|
- Нельзя расширить права: если родитель не имеет GRANT, mint невозможен.
|
||||||
|
- `CapRights::all()` = R | W | X | G = 0x0F.
|
||||||
|
|
||||||
|
## Relation — тип связи
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum Relation {
|
||||||
|
Strong, // Владелец — сильная ссылка (объект жив, пока есть Strong)
|
||||||
|
Borrow, // Заёмщик — временный доступ
|
||||||
|
Transfer, // Передача — владение переходит без возможности отзыва
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Strong**: capability владеет объектом. При revoke, объект может
|
||||||
|
быть уничтожен или возвращён пулу.
|
||||||
|
- **Borrow**: capability предоставляет временный доступ.
|
||||||
|
При revoke родителя, borrow-потомки тоже отзываются.
|
||||||
|
- **Transfer**: полная передача владения. Используется при IPC.
|
||||||
|
|
||||||
|
## Capability — полный дескриптор
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Capability {
|
||||||
|
pub object: CapObject, // Целевой объект
|
||||||
|
pub rights: CapRights, // Права доступа
|
||||||
|
pub relation: Relation, // Тип связи
|
||||||
|
pub token_sig: u64, // Уникальный подписывающий токен
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### token_sig — назначение
|
||||||
|
|
||||||
|
- Уникальный 64-битный идентификатор capability.
|
||||||
|
- Используется для:
|
||||||
|
1. **Отзыва**: при revoke, token_sig отправляется в MMU_REVOCATION_QUEUE.
|
||||||
|
2. **Идентификации в VMM**: VMA хранят `cap_token == token_sig`.
|
||||||
|
3. **Отладки**: каждый capability можно однозначно отследить.
|
||||||
|
|
||||||
|
### Методы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl Capability {
|
||||||
|
pub const fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
object: CapObject::Empty,
|
||||||
|
rights: CapRights::empty(),
|
||||||
|
relation: Relation::Borrow,
|
||||||
|
token_sig: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
!matches!(self.object, CapObject::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`token_sig = 0` зарезервирован для пустых/невалидных capability.
|
||||||
|
`token_sig = 0xDEAD_BEEF` используется для сырых Untyped
|
||||||
|
(не отправляется в MMU_REVOCATION_QUEUE, см. `cap/mod.rs`).
|
||||||
126
kernel/docs/capability/introduction.md
Normal file
126
kernel/docs/capability/introduction.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Capability-система: концептуальная модель
|
||||||
|
|
||||||
|
## Философия
|
||||||
|
|
||||||
|
**Capability** (дескриптор возможности) — это **неподделываемый токен**,
|
||||||
|
дающий право выполнить определённую операцию над определённым объектом.
|
||||||
|
|
||||||
|
В традиционных ОС (Linux, Windows) доступ контролируется через:
|
||||||
|
- PID + UID/GID + проверка при каждом системном вызове.
|
||||||
|
- MMU: page tables определяют, что отображено, но не кто отобразил.
|
||||||
|
|
||||||
|
В модели capabilities:
|
||||||
|
- **Если у вас нет capability — у вас нет доступа.**
|
||||||
|
- Capability хранятся в CNode — защищённой таблице, доступной только ядру.
|
||||||
|
- Capability можно создавать только от родительского capability
|
||||||
|
(иерархия наследования).
|
||||||
|
- Права можно только **урезать** (mint), но не расширить.
|
||||||
|
- Capability можно **отозвать** (revoke), что уничтожает его
|
||||||
|
и всех его потомков.
|
||||||
|
|
||||||
|
## Основные понятия
|
||||||
|
|
||||||
|
```
|
||||||
|
Capability {
|
||||||
|
object: CapObject, // на что указывает (Memory, CNode, PMActor...)
|
||||||
|
rights: CapRights, // права (R, W, X, G)
|
||||||
|
relation: Relation, // Strong (владеет), Borrow (заём), Transfer
|
||||||
|
token_sig: u64, // уникальный идентификатор (для revoke)
|
||||||
|
}
|
||||||
|
|
||||||
|
Relation {
|
||||||
|
Strong: владеет объектом (capability владеет памятью)
|
||||||
|
Borrow: заём — временный доступ без права распоряжаться
|
||||||
|
Transfer: передача — владение переходит получателю
|
||||||
|
}
|
||||||
|
|
||||||
|
CapRights {
|
||||||
|
READ = 0x1,
|
||||||
|
WRITE = 0x2,
|
||||||
|
EXECUTE = 0x4,
|
||||||
|
GRANT = 0x8, // разрешение создавать дочерние capability
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Иерархия и отзыв
|
||||||
|
|
||||||
|
```
|
||||||
|
CNode (массив слотов)
|
||||||
|
┌────────┬────────┬────────┬────────┐
|
||||||
|
│ slot 0 │ slot 1 │ slot 2 │ slot 3 │ ...
|
||||||
|
├────────┼────────┼────────┼────────┤
|
||||||
|
│ cap │ cap │ cap │ cap │
|
||||||
|
│ parent:│ parent:│ parent:│ parent:│
|
||||||
|
│ None │ Some(0)│ Some(0)│ Some(1)│
|
||||||
|
└────────┴────────┴────────┴────────┘
|
||||||
|
│
|
||||||
|
┌────────┴────────┐
|
||||||
|
▼ ▼
|
||||||
|
slot 1 slot 2
|
||||||
|
(mint from 0) (mint from 0)
|
||||||
|
|
||||||
|
revoke(0) → slot 0 уничтожается
|
||||||
|
→ рекурсивно: slot 1, slot 2 тоже уничтожаются
|
||||||
|
→ token_sig slot 0 отправляется в MMU_REVOCATION_QUEUE
|
||||||
|
→ VMM обработает отзыв при следующем page fault
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mint — создание дочернего capability
|
||||||
|
|
||||||
|
```rust
|
||||||
|
cnode.mint(src, dest, relation, rights)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `src` — исходный слот (должен иметь GRANT).
|
||||||
|
- `dest` — целевой слот (должен быть пустым).
|
||||||
|
- `relation` — как наследник связан с родителем.
|
||||||
|
- `rights` — права наследника (∩ с правами родителя).
|
||||||
|
|
||||||
|
### Revoke — отзыв capability
|
||||||
|
|
||||||
|
```rust
|
||||||
|
cnode.revoke(slot_idx)
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Рекурсивно находит всех потомков и уничтожает их.
|
||||||
|
2. Уничтожает сам capability.
|
||||||
|
3. Отправляет `token_sig` в глобальную `MMU_REVOCATION_QUEUE`.
|
||||||
|
4. VMM при следующем page fault обрабатывает все накопленные отзывы.
|
||||||
|
|
||||||
|
## Lock Ranking — предотвращение дедлоков
|
||||||
|
|
||||||
|
В `mint()` захватываются две блокировки (src и dest). Чтобы избежать
|
||||||
|
инверсии блокировок, используется строгий порядок:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let (src_slot, dest_slot) = if src < dest {
|
||||||
|
// Захватываем меньший индекс первым
|
||||||
|
_guard_low = self.slots[src].lock();
|
||||||
|
_guard_high = self.slots[dest].lock();
|
||||||
|
} else {
|
||||||
|
_guard_low = self.slots[dest].lock();
|
||||||
|
_guard_high = self.slots[src].lock();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Интеграция с VMM
|
||||||
|
|
||||||
|
При отзыве capability, VMM должна аннулировать все VMA, связанные
|
||||||
|
с отозванным токеном. Для этого:
|
||||||
|
|
||||||
|
1. `revoke()` пушит `token_sig` в lock-free очередь.
|
||||||
|
2. При page fault: `process_pending_revocations()` дренирует очередь.
|
||||||
|
3. VMA с `cap_token == token_sig` удаляются из AddressSpace.
|
||||||
|
4. TLB flush для синхронизации MMU.
|
||||||
|
|
||||||
|
## Использование в kmain()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let root_cnode = CNode::new(256);
|
||||||
|
// Вставка capability на фрейм
|
||||||
|
root_cnode.insert(0, mem_cap).unwrap();
|
||||||
|
// Mint с урезанными правами
|
||||||
|
root_cnode.mint(0, 10, Relation::Borrow, R | W).unwrap();
|
||||||
|
// Revoke — отзыв всех потомков
|
||||||
|
root_cnode.revoke(0);
|
||||||
|
```
|
||||||
73
kernel/docs/capability/objects.md
Normal file
73
kernel/docs/capability/objects.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Ядерные объекты: `object.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
`KernelObject` — это ref-counted представление объекта ядра.
|
||||||
|
В отличие от `Capability` (которая указывает на объект), `KernelObject`
|
||||||
|
— это сам объект с подсчётом ссылок.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct KernelObject {
|
||||||
|
pub phys_addr: PhysAddr, // физический адрес объекта
|
||||||
|
pub size_bits: u8, // размер в битах (2^size_bits)
|
||||||
|
pub obj_type: ObjectType, // тип объекта
|
||||||
|
pub ref_count: AtomicUsize, // счётчик ссылок
|
||||||
|
pub owner_id: u64, // ID владельца
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ObjectType — классификация объектов
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum ObjectType {
|
||||||
|
Untyped, // Сырая память без типа
|
||||||
|
Frame, // Фрейм (4KiB страница)
|
||||||
|
CNode, // Capability Node
|
||||||
|
ThreadBlock, // Блок управления потоком (TCB)
|
||||||
|
PageTable, // Таблица страниц
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ref-counting
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl KernelObject {
|
||||||
|
pub fn add_ref(&self) {
|
||||||
|
self.ref_count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
// ^ Relaxed: нас не волнует порядок других операций
|
||||||
|
// при увеличении счётчика. Только атомарность.
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(&self) -> bool {
|
||||||
|
self.ref_count.fetch_sub(1, Ordering::Release) == 1
|
||||||
|
// ^ Release: все операции до release видны тому,
|
||||||
|
// кто Acquire-читает ref_count.
|
||||||
|
// Возвращает true, если это была последняя ссылка
|
||||||
|
// (объект должен быть уничтожен).
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Текущее состояние
|
||||||
|
|
||||||
|
`KernelObject` определён, но пока не используется активно.
|
||||||
|
Capability-система в текущей версии работает напрямую с `CapObject`
|
||||||
|
и `PhysAddr`, без обёртки в `KernelObject`.
|
||||||
|
|
||||||
|
Планируемое использование:
|
||||||
|
- При выделении памяти через PMActor: создаётся KernelObject.
|
||||||
|
- Capability ссылается на KernelObject через ID/индекс.
|
||||||
|
- Когда последняя Strong capability удалена → KernelObject
|
||||||
|
уничтожается → память возвращается.
|
||||||
|
|
||||||
|
## Отличие CapObject vs KernelObject
|
||||||
|
|
||||||
|
| Характеристика | CapObject | KernelObject |
|
||||||
|
|---------------|-----------|--------------|
|
||||||
|
| Роль | Что capability представляет | Сам объект в памяти ядра |
|
||||||
|
| Ref-count | Нет | AtomicUsize |
|
||||||
|
| Хранение | В CNode slot | В отдельной таблице |
|
||||||
|
| Типы | Memory, CNode, PMActor | Untyped, Frame, CNode, ThreadBlock, PageTable |
|
||||||
|
| Связь | CapObject.Memory.phys = KernelObject.phys_addr | — |
|
||||||
115
kernel/docs/cpu/idt.md
Normal file
115
kernel/docs/cpu/idt.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Interrupt Descriptor Table: `idt.rs`
|
||||||
|
|
||||||
|
## Аппаратная модель
|
||||||
|
|
||||||
|
IDT (Interrupt Descriptor Table) — это таблица из 256 entry (по 16 байт каждая),
|
||||||
|
которая сообщает CPU, куда передавать управление при прерываниях и исключениях.
|
||||||
|
|
||||||
|
```
|
||||||
|
IDT:
|
||||||
|
┌──────┬──────────────────────────────────────────────────────┐
|
||||||
|
│ 0 │ #DE — Divide Error │
|
||||||
|
│ 1 │ #DB — Debug │
|
||||||
|
│ 2 │ #NMI — Non-Maskable Interrupt │
|
||||||
|
│ 3 │ #BP — Breakpoint │
|
||||||
|
│ 4 │ #OF — Overflow │
|
||||||
|
│ 5 │ #BR — Bound Range Exceeded │
|
||||||
|
│ 6 │ #UD — Undefined Opcode │
|
||||||
|
│ 7 │ #NM — Device Not Available │
|
||||||
|
│ 8 │ #DF — Double Fault │
|
||||||
|
│ 9 │ #MF — Coprocessor Segment Overrun │
|
||||||
|
│ 10 │ #TS — Invalid TSS │
|
||||||
|
│ 11 │ #NP — Segment Not Present │
|
||||||
|
│ 12 │ #SS — Stack-Segment Fault │
|
||||||
|
│ 13 │ #GP — General Protection Fault │
|
||||||
|
│ 14 │ #PF — Page Fault │
|
||||||
|
│ 15-31│ Reserved / CPU exceptions │
|
||||||
|
│ 32-255│ User-defined (hardware interrupts) │
|
||||||
|
└──────┴──────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Загрузка IDT: инструкция `lidt [idtr_ptr]`, где `idtr_ptr` — это
|
||||||
|
6-байтовая структура `IdtPtr`:
|
||||||
|
```
|
||||||
|
IdtPtr:
|
||||||
|
┌──────────┬──────────┐
|
||||||
|
│ limit:16 │ base:48 │
|
||||||
|
└──────────┴──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## IdtEntry — 16-байтовая запись
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[repr(C, packed)]
|
||||||
|
pub struct IdtEntry {
|
||||||
|
offset_low: u16, // Бит 0:15 адреса обработчика
|
||||||
|
selector: u16, // Селектор сегмента кода (0x28 для ядра)
|
||||||
|
ist: u8, // Interrupt Stack Table
|
||||||
|
type_attr: u8, // Тип вентиля + флаги
|
||||||
|
offset_mid: u16, // Бит 16:31 адреса обработчика
|
||||||
|
offset_high: u32, // Бит 32:63 адреса обработчика
|
||||||
|
ignore: u32, // Зарезервировано
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**type_attr:**
|
||||||
|
- Бит 7: Present (должен быть 1)
|
||||||
|
- Бит 6-5: DPL (Descriptor Privilege Level)
|
||||||
|
- Бит 4: Reserved (0)
|
||||||
|
- Бит 3-0: Gate Type (0xE = Interrupt Gate, 0xF = Trap Gate)
|
||||||
|
|
||||||
|
`set_handler(handler, selector, flags)`:
|
||||||
|
```rust
|
||||||
|
pub fn set_handler(&mut self, handler: u64, selector: u16, flags: u8) {
|
||||||
|
self.offset_low = handler as u16;
|
||||||
|
self.selector = selector;
|
||||||
|
self.ist = 0;
|
||||||
|
self.type_attr = flags | 0x80; // Present bit forced on
|
||||||
|
self.offset_mid = (handler >> 16) as u16;
|
||||||
|
self.offset_high = (handler >> 32) as u32;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## InterruptDescriptorTable — 256 entry
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct InterruptDescriptorTable {
|
||||||
|
entries: [IdtEntry; 256], // 256 × 16 = 4096 байт
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Методы:**
|
||||||
|
- `set_handler(vector, handler)` — устанавливает обработчик с
|
||||||
|
селектором 0x28 (GDT code segment) и type_attr 0x8E
|
||||||
|
(Interrupt Gate, Ring 0, Present).
|
||||||
|
- `load()` — `lidt` инструкция.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub unsafe fn load(&'static self) {
|
||||||
|
let ptr = IdtPtr {
|
||||||
|
limit: (size_of::<Self>() - 1) as u16, // 4095
|
||||||
|
base: self as *const _ as u64,
|
||||||
|
};
|
||||||
|
asm!("lidt [{}]", in(reg) &ptr);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальная IDT
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
|
||||||
|
```
|
||||||
|
|
||||||
|
`static mut` — потому что IDT модифицируется в ранней инициализации,
|
||||||
|
до включения прерываний. Потенциально может быть заменён на `static`
|
||||||
|
с `UnsafeCell`.
|
||||||
|
|
||||||
|
## Детали конфигурации
|
||||||
|
|
||||||
|
- **Selector**: `0x28` — это GDT entry для ring 0 code segment
|
||||||
|
(дескриптор 5, 5 × 8 = 0x28). Селектор сегмента кода в long mode.
|
||||||
|
- **Type 0x8E**: `1000_1110` = бит 7 (Present) + бит 3:0 = 1110
|
||||||
|
(Interrupt Gate, 32-bit). В 64-bit режиме все вентили — 64-bit,
|
||||||
|
флаг 0xE остаётся корректным.
|
||||||
|
- **IST**: 0 — не используем Interrupt Stack Table (один стек для
|
||||||
|
всех обработчиков).
|
||||||
200
kernel/docs/cpu/interrupts.md
Normal file
200
kernel/docs/cpu/interrupts.md
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
# Обработчики прерываний и исключений: `interrupts.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
Файл объединяет **обработчики исключений** (таблицу IDT) и
|
||||||
|
**межпроцессорные прерывания** (TLB shootdown). Это связующий слой
|
||||||
|
между аппаратурой (CPU exceptions, APIC) и программными подсистемами
|
||||||
|
(VMM, Memory).
|
||||||
|
|
||||||
|
## Структура обработчика (stub + Rust handler)
|
||||||
|
|
||||||
|
Каждый обработчик состоит из двух частей:
|
||||||
|
|
||||||
|
1. **Сборочный stub** (global_asm): сохраняет контекст, вызывает
|
||||||
|
Rust-функцию, восстанавливает контекст, iretq.
|
||||||
|
2. **Rust handler**: собственно обработка.
|
||||||
|
|
||||||
|
```
|
||||||
|
Пример: Page Fault
|
||||||
|
[stack]
|
||||||
|
page_fault_stub: error_code ← CPU пушет
|
||||||
|
push rax ...регистры...
|
||||||
|
push rcx
|
||||||
|
...
|
||||||
|
mov rdi, [rsp + 15*8] ← error_code как аргумент
|
||||||
|
call rust_page_fault_handler ← вызов Rust
|
||||||
|
pop r15 восстановление
|
||||||
|
...
|
||||||
|
pop rax
|
||||||
|
add rsp, 8 ← убираем error_code
|
||||||
|
iretq ← возврат
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exception stubs (макрос)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
macro_rules! exception_stub {
|
||||||
|
($name:ident, $handler:ident) => {
|
||||||
|
concat!(
|
||||||
|
".global ", stringify!($name), "\n",
|
||||||
|
stringify!($name), ":\n",
|
||||||
|
"push rax\npush rcx\n...push r15\n", // сохранение
|
||||||
|
"mov rdi, [rsp + 15*8]\n", // error_code
|
||||||
|
"call ", stringify!($handler), "\n",
|
||||||
|
"pop r15\n...pop rax\n", // восстановление
|
||||||
|
"add rsp, 8\n", // очистка error_code
|
||||||
|
"iretq\n",
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Используется для:
|
||||||
|
- `page_fault_stub` → `rust_page_fault_handler` (вектор 14)
|
||||||
|
- `gpf_stub` → `rust_gpf_handler` (вектор 13)
|
||||||
|
- `double_fault_stub` → `rust_double_fault_handler` (вектор 8)
|
||||||
|
|
||||||
|
## TLB shootdown stub
|
||||||
|
|
||||||
|
```rust
|
||||||
|
global_asm!(
|
||||||
|
"tlb_shootdown_stub:",
|
||||||
|
"push rax\npush rcx\n...", // сохранение
|
||||||
|
"call rust_tlb_shootdown_handler",
|
||||||
|
"pop r15\n...pop rax\n", // восстановление
|
||||||
|
"iretq"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Early handlers — для начальной загрузки
|
||||||
|
|
||||||
|
До того как VMM и slab allocator готовы, ядро не может обрабатывать
|
||||||
|
сложные исключения. Для векторов 0-31 генерируются ранние заглушки.
|
||||||
|
|
||||||
|
### Генерация (макрос `.altmacro`)
|
||||||
|
|
||||||
|
```asm
|
||||||
|
.macro early_stub vec
|
||||||
|
.globl early_handler_\vec
|
||||||
|
.balign 16
|
||||||
|
early_handler_\vec:
|
||||||
|
push 0 /* dummy error code (если нет аппаратного) */
|
||||||
|
push \vec /* номер вектора */
|
||||||
|
jmp early_common
|
||||||
|
.endm
|
||||||
|
```
|
||||||
|
|
||||||
|
`early_stub 0`..`early_stub 31` генерирует 32 обработчика.
|
||||||
|
|
||||||
|
### `early_common`
|
||||||
|
|
||||||
|
```asm
|
||||||
|
early_common:
|
||||||
|
push rax\npush rcx\n... // сохранение всех GP-регистров
|
||||||
|
mov rdi, [rsp + 15*8] // vector number
|
||||||
|
mov rsi, [rsp + 16*8] // error code (или dummy 0)
|
||||||
|
call rust_early_exception_handler
|
||||||
|
// never returns
|
||||||
|
```
|
||||||
|
|
||||||
|
### `rust_early_exception_handler(vector, error_code)`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub extern "C" fn rust_early_exception_handler(vector: u64, _error_code: u64) -> ! {
|
||||||
|
serial::write_global(format_args!(
|
||||||
|
"\n!!! EARLY EXCEPTION !!! vector={} error_code={:#x}\nCPU halted.\n",
|
||||||
|
vector, _error_code
|
||||||
|
));
|
||||||
|
loop { asm!("cli; hlt"); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Всегда HALT — раннее исключение фатально.
|
||||||
|
|
||||||
|
## init_early_exceptions() — настройка IDT для ранней загрузки
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn init_early_exceptions() {
|
||||||
|
let idt = addr_of_mut!(IDT);
|
||||||
|
// Устанавливаем early_handler_N для векторов 0-31
|
||||||
|
for (v, &handler) in early_handlers.iter().enumerate() {
|
||||||
|
(*idt).set_handler(v as u8, handler);
|
||||||
|
}
|
||||||
|
// Переопределяем критические:
|
||||||
|
(*idt).set_handler(8, double_fault_stub); // #DF
|
||||||
|
(*idt).set_handler(13, gpf_stub); // #GPF
|
||||||
|
(*idt).set_handler(14, page_fault_stub); // #PF
|
||||||
|
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub);
|
||||||
|
|
||||||
|
let ptr: &'static IDT = &*addr_of!(IDT);
|
||||||
|
ptr.load(); // lidt
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## init_idt() — перезагрузка после полной инициализации
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn init_idt() {
|
||||||
|
// Переустанавливаем только Page Fault и TLB Shootdown
|
||||||
|
// (их обработчики уже переключились на VMM-aware версии)
|
||||||
|
let idt = addr_of_mut!(IDT);
|
||||||
|
(*idt).set_handler(14, page_fault_stub);
|
||||||
|
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub);
|
||||||
|
let ptr: &'static IDT = &*addr_of!(IDT);
|
||||||
|
ptr.load();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обработчики исключений
|
||||||
|
|
||||||
|
### `rust_page_fault_handler(error_code)`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub extern "C" fn rust_page_fault_handler(error_code: u64) {
|
||||||
|
let fault_addr: u64;
|
||||||
|
asm!("mov {}, cr2", out(reg) fault_addr); // читаем CR2
|
||||||
|
|
||||||
|
let write = (error_code & 0x2) != 0; // fault на запись?
|
||||||
|
let present = (error_code & 0x1) != 0; // PTE был PRESENT?
|
||||||
|
|
||||||
|
let mut vmm_guard = KERNEL_SPACE.lock();
|
||||||
|
if let Some(space) = vmm_guard.as_mut() {
|
||||||
|
space.process_pending_revocations(); // обрабатываем отзывы
|
||||||
|
match space.handle_fault(fault_addr, write) {
|
||||||
|
Ok(_) => {} // обработано: iretq retry
|
||||||
|
Err(e) => panic!(...), // необработанный fault
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Page fault before KERNEL_SPACE!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `rust_gpf_handler(error_code)` и `rust_double_fault_handler(error_code)`
|
||||||
|
|
||||||
|
Оба — HALT с сообщением.
|
||||||
|
|
||||||
|
## TLB Shootdown обработчик
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub extern "C" fn rust_tlb_shootdown_handler() {
|
||||||
|
crate::mem::vmm::handle_tlb_shootdown_ipi();
|
||||||
|
crate::cpu::lapic::send_eoi(); // подтверждаем LAPIC прерывание
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тайминги и безопасность
|
||||||
|
|
||||||
|
- TLB shootdown — IPI, требует минимальной задержки.
|
||||||
|
- Все операции в `handle_tlb_shootdown_ipi` — простые и быстрые.
|
||||||
|
- LAPIC EOI отправляется сразу после локального TLB flush.
|
||||||
|
|
||||||
|
## Константы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;
|
||||||
|
```
|
||||||
|
|
||||||
|
Вектор 0xFD (253) — в диапазоне пользовательских прерываний (32-255),
|
||||||
|
намеренно далеко от системных векторов 0-31.
|
||||||
106
kernel/docs/cpu/introduction.md
Normal file
106
kernel/docs/cpu/introduction.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# CPU подсистема: концептуальная модель
|
||||||
|
|
||||||
|
## Состав и ответственность
|
||||||
|
|
||||||
|
CPU подсистема отвечает за:
|
||||||
|
|
||||||
|
1. **Обработку прерываний и исключений** — IDT, обработчики.
|
||||||
|
2. **Исключения ранней загрузки** — пока ядро ещё не полностью инициализировано.
|
||||||
|
3. **TLB Shootdown** — межпроцессорное прерывание для синхронизации TLB.
|
||||||
|
4. **Local APIC** — программируемый контроллер прерываний.
|
||||||
|
|
||||||
|
```
|
||||||
|
CPU Subsystem
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ CPU Core #0 │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────┐ ┌─────────────────────────┐ │
|
||||||
|
│ │ LAPIC │ │ IDT │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ ICR ────────┼────────► 0: #DE (Divide Error) │ │
|
||||||
|
│ │ EOI │ │ 1: #DB (Debug) │ │
|
||||||
|
│ │ (timer) │ │ ... │ │
|
||||||
|
│ │ │ │ 8: #DF (Double Fault) │ │
|
||||||
|
│ │ │ │ 13: #GP (GPF) │ │
|
||||||
|
│ │ │ │ 14: #PF (Page Fault) │ │
|
||||||
|
│ │ │ │ ... │ │
|
||||||
|
│ │ │ │ 0xFD: TLB Shootdown │ │
|
||||||
|
│ └──────────────┘ └─────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Модули
|
||||||
|
|
||||||
|
| Файл | Компонент | Функция |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| `idt.rs` | IDT структуры | Определение IdtEntry, IdtPtr, InterruptDescriptorTable |
|
||||||
|
| `interrupts.rs` | Обработчики | Early handlers, Page Fault, Double Fault, GPF, TLB shootdown |
|
||||||
|
| `lapic.rs` | Local APIC | Инициализация LAPIC, EOI, broadcast IPI |
|
||||||
|
|
||||||
|
## Порядок инициализации
|
||||||
|
|
||||||
|
```
|
||||||
|
kmain()
|
||||||
|
│
|
||||||
|
├── cpu::interrupts::init_early_exceptions()
|
||||||
|
│ └── IDT для векторов 0-31 + TLB shootdown (0xFD)
|
||||||
|
│
|
||||||
|
├── ... (PMM, LAPIC, Page tables) ...
|
||||||
|
│
|
||||||
|
├── cpu::interrupts::init_idt()
|
||||||
|
│ └── Перезагрузка IDT с полными обработчиками
|
||||||
|
│
|
||||||
|
├── STI (разрешение прерываний)
|
||||||
|
│
|
||||||
|
└── ... (работа с прерываниями)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обработка исключений: два этапа
|
||||||
|
|
||||||
|
### Этап 1: Early (до VMM)
|
||||||
|
|
||||||
|
На раннем этапе загрузки (до настройки page tables и heap) ядро
|
||||||
|
не может обрабатывать сложные исключения. Для векторов 0-31
|
||||||
|
устанавливаются `early_handler_N`, которые:
|
||||||
|
|
||||||
|
1. Пушат вектор и (возможно) dummy error code.
|
||||||
|
2. Переходят в `early_common`.
|
||||||
|
3. Сохраняют все регистры.
|
||||||
|
4. Вызывают `rust_early_exception_handler(vector, error_code)`.
|
||||||
|
5. Паникуют (HALT).
|
||||||
|
|
||||||
|
### Этап 2: Полный (после VMM)
|
||||||
|
|
||||||
|
После инициализации VMM три критических исключения получают
|
||||||
|
полноценные обработчики:
|
||||||
|
|
||||||
|
- **Page Fault (#PF, вектор 14)**: попытка обработать (COW, lazy).
|
||||||
|
- **Double Fault (#DF, вектор 8)**: HALT с сообщением.
|
||||||
|
- **General Protection Fault (#GPF, вектор 13)**: HALT с сообщением.
|
||||||
|
- **TLB Shootdown (вектор 0xFD)**: межпроцессорный TLB сброс.
|
||||||
|
|
||||||
|
## TLB Shootdown: модель
|
||||||
|
|
||||||
|
При изменении page tables на одном ядре, TLB других ядер устаревает.
|
||||||
|
Протокол:
|
||||||
|
|
||||||
|
```
|
||||||
|
CPU 0 (инициатор) CPU 1 (мишень)
|
||||||
|
│ │
|
||||||
|
├── local_tlb_flush_asid() │
|
||||||
|
├── SHOOTDOWN_LOCK.lock() │
|
||||||
|
├── SHOOTDOWN_ASID = asid │
|
||||||
|
├── SHOOTDOWN_ACK = 0 │
|
||||||
|
├── LAPIC: broadcast IPI │
|
||||||
|
│ (вектор TLB_SHOOTDOWN_VECTOR) ───► прерывание!
|
||||||
|
│ ├── handle_tlb_shootdown_ipi()
|
||||||
|
│ ├── local_tlb_flush_asid()
|
||||||
|
│ ├── SHOOTDOWN_ACK |= 1 << core
|
||||||
|
│ ├── LAPIC::send_eoi()
|
||||||
|
│ └── iretq
|
||||||
|
│ │
|
||||||
|
├── spin_loop() ◄────────────────────┤
|
||||||
|
│ (ждёт ACK от всех ядер) │
|
||||||
|
├── SHOOTDOWN_LOCK.unlock() │
|
||||||
|
└── continue │
|
||||||
|
```
|
||||||
115
kernel/docs/cpu/lapic.md
Normal file
115
kernel/docs/cpu/lapic.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Local APIC: `lapic.rs`
|
||||||
|
|
||||||
|
## Аппаратная модель
|
||||||
|
|
||||||
|
**Local APIC** (Advanced Programmable Interrupt Controller) — это
|
||||||
|
встроенный в каждое ядро x86-64 контроллер прерываний.
|
||||||
|
|
||||||
|
```
|
||||||
|
Local APIC (MMIO, начиная с 0xFEE00_000)
|
||||||
|
┌──────────────────────────────┐
|
||||||
|
│ 0x020: IRR (In-Service Reg) │
|
||||||
|
│ 0x030: TMR (Trigger Mode) │
|
||||||
|
│ 0x080: EOI │ ← запись 0 = подтверждение прерывания
|
||||||
|
│ 0x0B0: LINT0/LINT1 │
|
||||||
|
│ 0x0E0: Timer │
|
||||||
|
│ 0x200: LVT Error │
|
||||||
|
│ 0x300: ICR (Interrupt Cmd) │ ← отправка межпроцессорного прерывания
|
||||||
|
│ 0x310: ICR_HIGH (APIC ID) │
|
||||||
|
└──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Регистры
|
||||||
|
|
||||||
|
| Смещение | Регистр | Назначение |
|
||||||
|
|----------|---------|------------|
|
||||||
|
| 0x0B0 | EOI | End Of Interrupt — подтверждение обработки |
|
||||||
|
| 0x300 | ICR (low) | Interrupt Command Register — отправка IPI |
|
||||||
|
| 0x310 | ICR (high) | Destination APIC ID |
|
||||||
|
|
||||||
|
## Инициализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
// LAPIC отображён bootloader'ом в HHDM по адресу 0xFEE00_000
|
||||||
|
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + get_hhdm(), Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
LAPIC расположен по физическому адресу `0xFEE0_0000`. Ядро получает
|
||||||
|
виртуальный базовый адрес, добавляя HHDM offset.
|
||||||
|
|
||||||
|
## Доступ к регистрам
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[inline(always)]
|
||||||
|
fn write_lapic_reg(offset: u64, value: u32) {
|
||||||
|
let base = LAPIC_VIRT_BASE.load(Ordering::Relaxed);
|
||||||
|
if base == 0 { return; } // LAPIC ещё не инициализирован
|
||||||
|
unsafe { core::ptr::write_volatile((base + offset) as *mut u32, value) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `write_volatile` — запрещает компилятору оптимизировать обращение
|
||||||
|
(регистры MMIO).
|
||||||
|
- `Relaxed` ordering — для отладки/инициализации достаточно.
|
||||||
|
|
||||||
|
## Операции
|
||||||
|
|
||||||
|
### send_eoi() — конец прерывания
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn send_eoi() {
|
||||||
|
write_lapic_reg(LAPIC_EOI, 0); // 0x0B0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### broadcast_ipi_exclude_self(vector) — IPI всем, кроме себя
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn broadcast_ipi_exclude_self(vector: u8) {
|
||||||
|
// ICR[19:18] = 10b (All Excluding Self)
|
||||||
|
// ICR[14] = 1 (Assert)
|
||||||
|
// ICR[7:0] = vector
|
||||||
|
let icr_low = (2 << 18) | (1 << 14) | (vector as u32);
|
||||||
|
write_lapic_reg(LAPIC_ICR_LOW, icr_low);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Используется для TLB shootdown — необходимо разослать всем ядрам
|
||||||
|
(кроме текущего) IPI с вектором `TLB_SHOOTDOWN_VECTOR`.
|
||||||
|
|
||||||
|
### current_core_id() — определение текущего ядра
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn current_core_id() -> u32 {
|
||||||
|
// CPUID leaf 1: EBX[31:24] = Local APIC ID
|
||||||
|
let mut ebx: u32;
|
||||||
|
asm!(
|
||||||
|
"mov {tmp:r}, rbx", // спрятать rbx (резерв LLVM)
|
||||||
|
"mov eax, 1", "cpuid",
|
||||||
|
"mov {out:e}, ebx", // сохранить EBX
|
||||||
|
"mov rbx, {tmp:r}", // восстановить rbx
|
||||||
|
tmp = out(reg) _,
|
||||||
|
out = out(reg) ebx,
|
||||||
|
...
|
||||||
|
);
|
||||||
|
ebx >> 24
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**RBX проблема:** LLVM резервирует RBX, поэтому его нужно сохранять
|
||||||
|
и восстанавливать вручную вокруг CPUID инструкции.
|
||||||
|
|
||||||
|
## Тонкости
|
||||||
|
|
||||||
|
1. **MMIO vs MSR**: LAPIC можно программировать через MSR (IA32_APIC_BASE)
|
||||||
|
и через MMIO. Bootloader (Limine) настраивает MMIO mapping в HHDM.
|
||||||
|
2. **Инициализация**: LAPIC уже включён bootloader'ом. `init()` просто
|
||||||
|
сохраняет виртуальный адрес.
|
||||||
|
3. **x2APIC**: не используется (в текущей версии — MMIO xAPIC).
|
||||||
|
4. **EOI**: обязателен после каждого прерывания от LAPIC (включая IPI).
|
||||||
|
5. **ICR запись**: после записи в ICR Low, шина APIC доставляет
|
||||||
|
прерывание. Запись блокирующая (ждёт готовности шины).
|
||||||
88
kernel/docs/debug/introduction.md
Normal file
88
kernel/docs/debug/introduction.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Подсистема отладки: концептуальная модель
|
||||||
|
|
||||||
|
## Два канала вывода
|
||||||
|
|
||||||
|
Ядро имеет два параллельных канала для отладки:
|
||||||
|
|
||||||
|
1. **Экранный (framebuffer console)** — через `tty::Console`.
|
||||||
|
- Использует PSF2-шрифты.
|
||||||
|
- Цветной вывод (зелёный/жёлтый/красный для Info/Warn/Error).
|
||||||
|
- Медленнее, но визуально нагляднее.
|
||||||
|
|
||||||
|
2. **Serial port (COM1)** — через `debug::serial`.
|
||||||
|
- Текстовый вывод с ANSI escape codes.
|
||||||
|
- Работает через QEMU/KVM serial console.
|
||||||
|
- Быстрее, может быть перенаправлен в файл.
|
||||||
|
|
||||||
|
## LogLevel — уровни логирования
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum LogLevel {
|
||||||
|
Info, // Зелёный на экране, зелёный в serial
|
||||||
|
Warn, // Жёлтый
|
||||||
|
Error, // Красный
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый уровень имеет:
|
||||||
|
- `serial_color_code()` — ANSI escape code для serial.
|
||||||
|
- `console_color()` — RGB значение для framebuffer.
|
||||||
|
|
||||||
|
## Макросы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Основной макрос
|
||||||
|
log!(console, level, module, format_args...)
|
||||||
|
|
||||||
|
// Специализированные
|
||||||
|
info!(console, module, format_args...)
|
||||||
|
warn!(console, module, format_args...)
|
||||||
|
error!(console, module, format_args...)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат вывода на экран:**
|
||||||
|
```
|
||||||
|
[ LOG ] <module> | <message>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат в serial:**
|
||||||
|
```
|
||||||
|
GREEN[ LOG] RESET <module> | <message>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Цветовое кодирование
|
||||||
|
|
||||||
|
| Уровень | Экран (RGB) | Serial (ANSI) |
|
||||||
|
|---------|-------------|---------------|
|
||||||
|
| Info | 0x00FF00 | `\x1b[32m` (green) |
|
||||||
|
| Warn | 0xFFFF00 | `\x1b[33m` (yellow) |
|
||||||
|
| Error | 0xFF0000 | `\x1b[31m` (red) |
|
||||||
|
| Текст | 0xFFFFFF (white) | `\x1b[0m` (reset) |
|
||||||
|
|
||||||
|
## Использование в kmain()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// После инициализации serial
|
||||||
|
debug::serial::init_global();
|
||||||
|
|
||||||
|
// После инициализации console
|
||||||
|
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
||||||
|
info!(console, "MEM", "BitmapPMM initialized.");
|
||||||
|
info!(console, "LAPIC", "Local APIC initialized.");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ ┌───────────────────┐
|
||||||
|
│ kmain() │────►│ log!() macro │
|
||||||
|
└──────────────┘ └────────┬──────────┘
|
||||||
|
│
|
||||||
|
┌──────────────┼──────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||||
|
│ Экран │ │ Serial │ │ Паника │
|
||||||
|
│ Console │ │ COM1 │ │ Handler │
|
||||||
|
│ (tty.rs) │ │(serial.rs)│ │(main.rs) │
|
||||||
|
└──────────┘ └──────────┘ └──────────┘
|
||||||
|
```
|
||||||
110
kernel/docs/debug/serial.md
Normal file
110
kernel/docs/debug/serial.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Serial Port драйвер: `serial.rs`
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Драйвер последовательного порта (UART 16550, COM1) для отладочного
|
||||||
|
вывода. Позволяет видеть сообщения ядра через QEMU serial console,
|
||||||
|
minicom, screen и т.д.
|
||||||
|
|
||||||
|
## Аппаратная модель
|
||||||
|
|
||||||
|
COM1 расположен по портам ввода-вывода `0x3F8`-`0x3FF`:
|
||||||
|
|
||||||
|
| Порт | Регистр | Назначение |
|
||||||
|
|------|---------|------------|
|
||||||
|
| 0x3F8 | DATA | Чтение/запись данных |
|
||||||
|
| 0x3F9 | IER | Interrupt Enable |
|
||||||
|
| 0x3FA | IIR/FCR | Interrupt ID / FIFO Control |
|
||||||
|
| 0x3FB | LCR | Line Control |
|
||||||
|
| 0x3FC | MCR | Modem Control |
|
||||||
|
| 0x3FD | LSR | Line Status |
|
||||||
|
| 0x3FE | MSR | Modem Status |
|
||||||
|
|
||||||
|
## Инициализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub unsafe fn init() -> Self {
|
||||||
|
let port = Self::COM1; // 0x3F8
|
||||||
|
outb(port + 1, 0x00); // IER = 0 (disable interrupts)
|
||||||
|
outb(port + 3, 0x80); // LCR DLAB=1 (enable baud rate programming)
|
||||||
|
outb(port + 0, 0x03); // Divisor LSB = 3 (38400 baud)
|
||||||
|
outb(port + 1, 0x00); // Divisor MSB = 0
|
||||||
|
outb(port + 3, 0x03); // LCR = 8N1 (8 bits, No parity, 1 stop)
|
||||||
|
outb(port + 2, 0xC7); // FCR = enable FIFO, clear, 14-byte threshold
|
||||||
|
outb(port + 4, 0x0B); // MCR = DTR+RTS+OUT2 (enable IRQ + handshake)
|
||||||
|
SerialPort(port)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Детали конфигурации
|
||||||
|
|
||||||
|
1. **IER = 0**: отключаем прерывания UART (TODO: включить для RX).
|
||||||
|
2. **DLAB = 1**: разрешаем программирование делителя бода.
|
||||||
|
3. **Divisor = 3**: при тактовой 1.8432 MHz → 115200 / 3 = 38400 бод.
|
||||||
|
4. **LCR = 0x03**: 8N1 — 8 бит данных, нет чётности, 1 стоп-бит.
|
||||||
|
5. **FCR = 0xC7**: enable FIFO, clear both FIFOs, trigger at 14 bytes.
|
||||||
|
6. **MCR = 0x0B**: DTR=1, RTS=1, OUT2=1 (необходимо для IRQ на ISA шине).
|
||||||
|
|
||||||
|
## Отправка байта
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn is_transmit_empty(&self) -> bool {
|
||||||
|
unsafe { (inb(self.0 + 5) & 0x20) != 0 } // LSR bit 5 = Transmitter Holding Register Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self, data: u8) {
|
||||||
|
while !self.is_transmit_empty() {} // Ждём, пока UART готов
|
||||||
|
unsafe { outb(self.0, data); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## fmt::Write реализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl core::fmt::Write for SerialPort {
|
||||||
|
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||||
|
for byte in s.bytes() { self.send(byte); }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальный экземпляр
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static SERIAL_PORT: Locked<Option<SerialPort>> = Locked::new(None);
|
||||||
|
|
||||||
|
pub fn init_global() {
|
||||||
|
let mut guard = SERIAL_PORT.lock();
|
||||||
|
*guard = Some(unsafe { SerialPort::init() });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_global(args: core::fmt::Arguments) {
|
||||||
|
let mut guard = SERIAL_PORT.lock();
|
||||||
|
if let Some(ref mut sp) = *guard {
|
||||||
|
let _ = sp.write_fmt(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`init_global()` вызывается из `kmain()` после инициализации TTY.
|
||||||
|
`write_global()` используется:
|
||||||
|
- В `log!()` макросе (через debug.rs).
|
||||||
|
- В `rust_panic()` обработчике.
|
||||||
|
- В обработчиках исключений (GPF, Double Fault, Early Exception).
|
||||||
|
|
||||||
|
## Низкоуровневый I/O
|
||||||
|
|
||||||
|
```rust
|
||||||
|
unsafe fn outb(port: u16, val: u8) {
|
||||||
|
asm!("out dx, al", in("dx") port, in("al") val,
|
||||||
|
options(nomem, nostack, preserves_flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn inb(port: u16) -> u8 {
|
||||||
|
let res: u8;
|
||||||
|
asm!("in al, dx", out("al") res, in("dx") port,
|
||||||
|
options(nomem, nostack, preserves_flags));
|
||||||
|
res
|
||||||
|
}
|
||||||
|
```
|
||||||
128
kernel/docs/events/introduction.md
Normal file
128
kernel/docs/events/introduction.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Очередь отзыва RevocationQueue: `events.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**RevocationQueue** — это глобальная lock-free кольцевая очередь,
|
||||||
|
которая соединяет capability-систему (CNode) с виртуальной памятью (VMM).
|
||||||
|
|
||||||
|
### Назначение
|
||||||
|
|
||||||
|
Когда capability отзывается, VMM должна аннулировать все VMA,
|
||||||
|
связанные с этим capability. Но:
|
||||||
|
- CNode.revoke() не может напрямую обратиться к VMM (разные подсистемы,
|
||||||
|
разное время жизни блокировок).
|
||||||
|
- VMM может быть в обработке page fault'а.
|
||||||
|
- Отзыв может быть каскадным и рекурсивным.
|
||||||
|
|
||||||
|
Решение: **асинхронная очередь**.
|
||||||
|
|
||||||
|
```
|
||||||
|
CNode.revoke(token_1)
|
||||||
|
│
|
||||||
|
└──→ push(token_1) ───→ MMU_REVOCATION_QUEUE ──→ pop() ──→ VMM
|
||||||
|
│
|
||||||
|
CNode.revoke(token_2) │
|
||||||
|
│ │
|
||||||
|
└──→ push(token_2) ─────────────────────────────────────────┤
|
||||||
|
│
|
||||||
|
Page Fault handler │
|
||||||
|
│ │
|
||||||
|
└──→ process_pending_revocations() ←────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## RevocationQueue — детали
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct RevocationQueue {
|
||||||
|
buffer: [AtomicU64; QUEUE_SIZE], // 1024 entry
|
||||||
|
_pad0: [u8; 64], // padding: кэш-линия
|
||||||
|
head: AtomicUsize, // читатель (VMM)
|
||||||
|
_pad1: [u8; 64], // padding: кэш-линия
|
||||||
|
tail: AtomicUsize, // писатели (CNode revoke)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Тот же паттерн, что и в `PMActorQueue`: разделение head и tail
|
||||||
|
по разным кэш-линиям предотвращает false sharing.
|
||||||
|
|
||||||
|
### push(token_sig)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn push(&self, token_sig: u64) -> Result<(), &'static str> {
|
||||||
|
let mut tail = self.tail.load(Ordering::Relaxed);
|
||||||
|
loop {
|
||||||
|
let head = self.head.load(Ordering::Acquire);
|
||||||
|
if tail.wrapping_sub(head) >= QUEUE_SIZE {
|
||||||
|
return Err("Revocation queue overflow");
|
||||||
|
}
|
||||||
|
match self.tail.compare_exchange_weak(tail, tail+1, AcqRel, Relaxed) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.buffer[tail & QUEUE_MASK].store(token_sig, Release);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(actual) => tail = actual,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### pop()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn pop(&self) -> Option<u64> {
|
||||||
|
let head = self.head.load(Ordering::Relaxed);
|
||||||
|
let tail = self.tail.load(Ordering::Acquire);
|
||||||
|
if head == tail { return None; }
|
||||||
|
let token = self.buffer[head & QUEUE_MASK].load(Ordering::Acquire);
|
||||||
|
self.head.store(head.wrapping_add(1), Ordering::Release);
|
||||||
|
Some(token)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальный экземпляр
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub static MMU_REVOCATION_QUEUE: RevocationQueue = RevocationQueue::new();
|
||||||
|
```
|
||||||
|
|
||||||
|
## VMM: обработка отзывов
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// В interrupts.rs: rust_page_fault_handler()
|
||||||
|
let mut vmm_guard = KERNEL_SPACE.lock();
|
||||||
|
if let Some(space) = vmm_guard.as_mut() {
|
||||||
|
space.process_pending_revocations(); // ← дренирует очередь
|
||||||
|
match space.handle_fault(fault_addr, write) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// В vmm.rs: AddressSpace
|
||||||
|
pub fn process_pending_revocations(&mut self) {
|
||||||
|
let mut needs_flush = false;
|
||||||
|
while let Some(token) = MMU_REVOCATION_QUEUE.pop() {
|
||||||
|
self.do_revoke_by_token(token);
|
||||||
|
needs_flush = true;
|
||||||
|
}
|
||||||
|
if needs_flush {
|
||||||
|
tlb_flush_asid(self.asid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Почему именно так?
|
||||||
|
|
||||||
|
1. **Lock-free**: отзыв capability не блокируется VMM.
|
||||||
|
2. **Batch processing**: VMM обрабатывает сразу все накопленные отзывы
|
||||||
|
при следующем page fault, экономя TLB flush'ы.
|
||||||
|
3. **Deadlock prevention**: process_pending_revocations вызывается
|
||||||
|
**до** handle_fault, избегая циклических зависимостей между
|
||||||
|
блокировками capability и VMM.
|
||||||
|
4. **Простота**: кольцевой буфер с CAS — проверенная временем структура.
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
- Размер: 1024 entry. Если очередь переполняется — **паника**.
|
||||||
|
Это проектный выбор: переполнение означает, что VMM не успевает
|
||||||
|
обрабатывать отзывы быстрее, чем CNode генерирует новые.
|
||||||
|
- Однопоточный pop (только page fault handler дренирует очередь).
|
||||||
1055
kernel/docs/glossary/terms.md
Normal file
1055
kernel/docs/glossary/terms.md
Normal file
File diff suppressed because it is too large
Load Diff
70
kernel/docs/index.md
Normal file
70
kernel/docs/index.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Документация ядра Elyz (LISA)
|
||||||
|
|
||||||
|
## Структура документации
|
||||||
|
|
||||||
|
```
|
||||||
|
kernel/docs/
|
||||||
|
├── index.md ← этот файл: оглавление
|
||||||
|
│
|
||||||
|
├── overview/
|
||||||
|
│ ├── introduction.md ← Концептуальная модель (7 принципов)
|
||||||
|
│ └── architecture.md ← Архитектура, схема, порядок инициализации
|
||||||
|
│
|
||||||
|
├── memory/
|
||||||
|
│ ├── introduction.md ← Трёхуровневая модель памяти
|
||||||
|
│ ├── address-translation.md ← PhysAddr, VirtAddr, HHDM
|
||||||
|
│ ├── physical-memory.md ← BitmapPMM (глобальный)
|
||||||
|
│ ├── paging.md ← PageTable, 4-level walk
|
||||||
|
│ ├── buddy.md ← BuddyAllocator (intrusive list)
|
||||||
|
│ ├── pm-actor.md ← PMActor + MPSC очередь
|
||||||
|
│ ├── pm-router.md ← PMRouter (65536 каналов)
|
||||||
|
│ ├── allocator.md ← Slab-аллокатор (куча)
|
||||||
|
│ └── vmm.md ← AddressSpace, VMA, COW, TLB
|
||||||
|
│
|
||||||
|
├── cpu/
|
||||||
|
│ ├── introduction.md ← CPU подсистема, 2 этапа обработки
|
||||||
|
│ ├── idt.md ← IDT entry, InterruptDescriptorTable
|
||||||
|
│ ├── interrupts.md ← Обработчики, stubs, early exceptions
|
||||||
|
│ └── lapic.md ← Local APIC, IPI, EOI
|
||||||
|
│
|
||||||
|
├── capability/
|
||||||
|
│ ├── introduction.md ← Capability model, mint, revoke
|
||||||
|
│ ├── descriptors.md ← Capability, CapObject, CapRights
|
||||||
|
│ ├── cnode.md ← CNode, lock ranking
|
||||||
|
│ └── objects.md ← KernelObject (ref-counted)
|
||||||
|
│
|
||||||
|
├── debug/
|
||||||
|
│ ├── introduction.md ← Отладка: экран + serial
|
||||||
|
│ └── serial.md ← COM1 UART 16550 драйвер
|
||||||
|
│
|
||||||
|
├── boot/
|
||||||
|
│ ├── introduction.md ← Процесс загрузки, Limine
|
||||||
|
│ └── linker.md ← Линкер-скрипты
|
||||||
|
│
|
||||||
|
└── events/
|
||||||
|
└── introduction.md ← RevocationQueue (lock-free)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Быстрый старт по файлам
|
||||||
|
|
||||||
|
| Файл ядра | Что делает | Документация |
|
||||||
|
|-----------|-----------|--------------|
|
||||||
|
| `src/main.rs` | Точка входа, инициализация, тесты | `overview/architecture.md` (раздел порядка инициализации) |
|
||||||
|
| `src/mem/pmm.rs` | Глобальный PMM (Bitmap + L1) | `memory/physical-memory.md` |
|
||||||
|
| `src/mem/buddy.rs` | Buddy-аллокатор | `memory/buddy.md` |
|
||||||
|
| `src/mem/paging.rs` | 4-уровневые page tables | `memory/paging.md` |
|
||||||
|
| `src/mem/pm_manages.rs` | PMActor + очередь | `memory/pm-actor.md` |
|
||||||
|
| `src/mem/pm_router.rs` | PM Router каналы | `memory/pm-router.md` |
|
||||||
|
| `src/mem/vmm.rs` | AddressSpace, COW, TLB | `memory/vmm.md` |
|
||||||
|
| `src/mem/allocator.rs` | Slab-аллокатор | `memory/allocator.md` |
|
||||||
|
| `src/mem/address.rs` | Физические/виртуальные адреса | `memory/address-translation.md` |
|
||||||
|
| `src/cpu/idt.rs` | IDT структуры | `cpu/idt.md` |
|
||||||
|
| `src/cpu/interrupts.rs` | Обработчики исключений | `cpu/interrupts.md` |
|
||||||
|
| `src/cpu/lapic.rs` | Local APIC | `cpu/lapic.md` |
|
||||||
|
| `src/cap/mod.rs` | CNode | `capability/cnode.md` |
|
||||||
|
| `src/cap/descriptor.rs` | Capability типы | `capability/descriptors.md` |
|
||||||
|
| `src/cap/object.rs` | KernelObject | `capability/objects.md` |
|
||||||
|
| `src/debug/mod.rs` | Log macros | `debug/introduction.md` |
|
||||||
|
| `src/debug/serial.rs` | Serial driver | `debug/serial.md` |
|
||||||
|
| `src/events.rs` | Очередь отзыва | `events/introduction.md` |
|
||||||
|
| `src/tty.rs` | Framebuffer console | `tty.md` |
|
||||||
103
kernel/docs/memory/address-translation.md
Normal file
103
kernel/docs/memory/address-translation.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# Адресация и трансляция: `address.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
В x86-64 MMU транслирует виртуальные адреса (48 бит, канонические)
|
||||||
|
в физические (до 52 бит) через 4-уровневую иерархию page tables.
|
||||||
|
|
||||||
|
Ядро использует **Higher-Half Direct Map (HHDM)** — область виртуальной
|
||||||
|
памяти, где вся физическая память отображена 1:1 с фиксированным смещением.
|
||||||
|
|
||||||
|
```
|
||||||
|
Физический адрес 0x0
|
||||||
|
│
|
||||||
|
│ + HHDM_OFFSET (например, 0xFFFF8000_0000_0000)
|
||||||
|
▼
|
||||||
|
Виртуальный адрес 0xFFFF8000_0000_0000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Физический адрес: `PhysAddr(pub u64)`
|
||||||
|
|
||||||
|
Представляет собой физический адрес. Это **newtype** над `u64` — строгая
|
||||||
|
типизация предотвращает случайное смешивание физических и виртуальных адресов.
|
||||||
|
|
||||||
|
**Методы:**
|
||||||
|
- `to_virt()` — преобразует в виртуальный адрес через HHDM:
|
||||||
|
`VirtAddr(self.0 + HHDM_OFFSET)`
|
||||||
|
- `is_aligned()` — проверка выравнивания на 4KiB
|
||||||
|
- `align_down()` / `align_up()` — выравнивание вниз/вверх до границы 4KiB
|
||||||
|
|
||||||
|
## Виртуальный адрес: `VirtAddr(pub u64)`
|
||||||
|
|
||||||
|
**Методы:**
|
||||||
|
- `to_phys()` — обратное преобразование: вычитает HHDM, возвращает `None`
|
||||||
|
если адрес ниже HHDM (не канонический для физической памяти)
|
||||||
|
- `as_ptr<T>()` — получить `*const T` (для чтения)
|
||||||
|
- `as_mut_ptr<T>()` — получить `*mut T` (для записи)
|
||||||
|
|
||||||
|
## HHDM Offset: глобальное состояние
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static HHDM_OFFSET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
```
|
||||||
|
|
||||||
|
- `init_hhdm(offset: u64)` — вызывается в `kmain()` сразу после получения
|
||||||
|
ответа от bootloader'а. Сохраняет offset через `Ordering::Release`.
|
||||||
|
- `get_hhdm() -> u64` — читает offset через `Ordering::Relaxed` (высокая
|
||||||
|
производительность, на этой архитектуре атомарность гарантирована).
|
||||||
|
|
||||||
|
### Почему Atomic?
|
||||||
|
|
||||||
|
HHDM инициализируется однократно до включения прерываний и создания
|
||||||
|
дополнительных потоков. Atomic гарантирует, что даже если в будущем
|
||||||
|
инструкции будут переупорядочены, значение будет видно всем ядрам.
|
||||||
|
|
||||||
|
## Код и абстракция
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// address.rs (полный код)
|
||||||
|
static HHDM_OFFSET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
pub fn init_hhdm(offset: u64) {
|
||||||
|
HHDM_OFFSET.store(offset, Ordering::Release);
|
||||||
|
// ^ Release: все предыдущие записи становятся видимы для
|
||||||
|
// всех acquire-операций на HHDM_OFFSET
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_hhdm() -> u64 {
|
||||||
|
HHDM_OFFSET.load(Ordering::Relaxed)
|
||||||
|
// ^ Relaxed: в однопоточном контексте и при инициализации
|
||||||
|
// до включения прерываний это безопасно и максимально быстро
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct PhysAddr(pub u64);
|
||||||
|
// ^ repr(transparent): гарантирует, что PhysAddr имеет то же
|
||||||
|
// представление в памяти, что и u64 — важно для FFI и передачи
|
||||||
|
// в ассемблерные инструкции (mov cr3, rax)
|
||||||
|
|
||||||
|
impl PhysAddr {
|
||||||
|
pub fn to_virt(self) -> VirtAddr {
|
||||||
|
VirtAddr(self.0 + get_hhdm())
|
||||||
|
// ^ Концептуально: физический адрес + HHDM смещение даёт
|
||||||
|
// виртуальный адрес, по которому этот физический фрейм
|
||||||
|
// отображён в higher half
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtAddr {
|
||||||
|
pub fn as_mut_ptr<T>(self) -> *mut T {
|
||||||
|
self.0 as *mut T
|
||||||
|
// ^ Прямая интерпретация числа как указателя.
|
||||||
|
// Безопасность на стороне вызывающего кода.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Где используется
|
||||||
|
|
||||||
|
- `PhysAddr::to_virt()` — в `paging.rs` для доступа к Page Table entries,
|
||||||
|
в `pmm.rs` для обнуления фреймов, в `buddy.rs` для intrusive list.
|
||||||
|
- `VirtAddr::as_mut_ptr()` — в `paging.rs` для создания ссылок на PTE.
|
||||||
|
- `init_hhdm()` — в `kmain()` при старте.
|
||||||
|
- `get_hhdm()` — в `lapic.rs` для вычисления LAPIC_base.
|
||||||
211
kernel/docs/memory/allocator.md
Normal file
211
kernel/docs/memory/allocator.md
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
# Slab-аллокатор: куча ядра
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**SlabAllocator** — это глобальный аллокатор кучи для ядра.
|
||||||
|
Rust-программы используют `alloc::vec::Vec`, `alloc::boxed::Box` и т.д. —
|
||||||
|
все они в конечном счёте вызывают `GlobalAlloc::alloc()`.
|
||||||
|
|
||||||
|
### Стратегия
|
||||||
|
|
||||||
|
Для маленьких блоков (≤ 2048 байт) — **slab lists**:
|
||||||
|
предварительно нарезанные блоки фиксированного размера.
|
||||||
|
|
||||||
|
Для больших блоков (> 2048 байт) — **freelist больших блоков**:
|
||||||
|
освобождённые блоки переиспользуются.
|
||||||
|
|
||||||
|
Если ни там, ни там нет — **bump allocation**:
|
||||||
|
последовательная раздача из заранее выделенного региона.
|
||||||
|
|
||||||
|
```
|
||||||
|
Запрос alloc(32 байта):
|
||||||
|
1. list_index(32) = 2 (BLOCK_SIZES[2] = 32)
|
||||||
|
2. list_heads[2] есть свободный блок?
|
||||||
|
- Да: отдаём его, заменяем голову списка
|
||||||
|
- Нет: bump-аллокация блока размером 32 (fallback_alloc)
|
||||||
|
|
||||||
|
Запрос alloc(4096 байт):
|
||||||
|
1. list_index(4096) = None (максимум 2048)
|
||||||
|
2. large_block_free есть блок ≥ 4096?
|
||||||
|
- Да: отдаём
|
||||||
|
- Нет: bump-аллокация
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структуры данных
|
||||||
|
|
||||||
|
### Slab списки — `list_heads`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
||||||
|
|
||||||
|
struct ListNode {
|
||||||
|
next: Option<&'static mut ListNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SlabAllocator {
|
||||||
|
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()], // 9 списков
|
||||||
|
large_block_free: Option<&'static mut LargeBlockNode>,
|
||||||
|
heap_start: usize,
|
||||||
|
heap_end: usize,
|
||||||
|
next_bump: usize,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### LargeBlockNode — для блоков > 2048 байт
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct LargeBlockNode {
|
||||||
|
size: usize,
|
||||||
|
next: Option<&'static mut LargeBlockNode>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Инициализация: `init(start, size)`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn init(&mut self, start: usize, size: usize) {
|
||||||
|
self.heap_start = start;
|
||||||
|
self.next_bump = start;
|
||||||
|
self.heap_end = start + size;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Вызывается в kmain() после настройки page table для области `0xFFFF_9000_0000_0000`.
|
||||||
|
|
||||||
|
## GlobalAlloc — реализация
|
||||||
|
|
||||||
|
### alloc(layout)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||||
|
let mut allocator = self.lock();
|
||||||
|
match SlabAllocator::list_index(&layout) {
|
||||||
|
Some(index) => {
|
||||||
|
// 1. Пробуем slab list
|
||||||
|
match allocator.list_heads[index].take() {
|
||||||
|
Some(node) => {
|
||||||
|
allocator.list_heads[index] = node.next.take();
|
||||||
|
node as *mut ListNode as *mut u8
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// 2. Нет в slab — bump alloc целого блока
|
||||||
|
let block_size = BLOCK_SIZES[index];
|
||||||
|
allocator.fallback_alloc(
|
||||||
|
Layout::from_size_align(block_size, block_size).unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => allocator.fallback_alloc(layout) // > 2048
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### fallback_alloc(layout)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
|
||||||
|
let size = layout.size().max(layout.align());
|
||||||
|
|
||||||
|
// 1. Пробуем large block free list (для > 2048)
|
||||||
|
if size > 2048 {
|
||||||
|
// поиск по large_block_free
|
||||||
|
while let Some(ref mut node) = *field {
|
||||||
|
if node.size >= size {
|
||||||
|
// отдаём, удаляем из списка
|
||||||
|
return node as *mut u8;
|
||||||
|
}
|
||||||
|
field = &mut node.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Bump alloc
|
||||||
|
let alloc_start = (self.next_bump + layout.align() - 1) & !(layout.align() - 1);
|
||||||
|
let alloc_end = alloc_start.checked_add(layout.size())?;
|
||||||
|
if alloc_end > self.heap_end {
|
||||||
|
null_mut() // OOM
|
||||||
|
} else {
|
||||||
|
self.next_bump = alloc_end;
|
||||||
|
alloc_start as *mut u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### dealloc(ptr, layout)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||||
|
let mut allocator = self.lock();
|
||||||
|
match SlabAllocator::list_index(&layout) {
|
||||||
|
Some(index) => {
|
||||||
|
// Добавляем в slab list (переиспользование)
|
||||||
|
let new_node = ListNode { next: allocator.list_heads[index].take() };
|
||||||
|
let new_node_ptr = ptr as *mut ListNode;
|
||||||
|
unsafe { new_node_ptr.write(new_node); }
|
||||||
|
allocator.list_heads[index] = Some(&mut *new_node_ptr);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Добавляем в large block free list
|
||||||
|
let new_node = LargeBlockNode {
|
||||||
|
size: layout.size().max(layout.align()),
|
||||||
|
next: allocator.large_block_free.take(),
|
||||||
|
};
|
||||||
|
let new_node_ptr = ptr as *mut LargeBlockNode;
|
||||||
|
unsafe { new_node_ptr.write(new_node); }
|
||||||
|
allocator.large_block_free = Some(&mut *new_node_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Важно:** `layout.size()` должен быть ≥ `size_of::<ListNode>()`, чтобы
|
||||||
|
освобождённый блок мог хранить указатели списка. Гарантируется, потому
|
||||||
|
что наименьший BLOCK_SIZE (8) ≥ `size_of::<Option<&'static mut ListNode>>`
|
||||||
|
(8 байт на 64-bit).
|
||||||
|
|
||||||
|
## Locked<T> — примитивная spinlock-обёртка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct Locked<A> {
|
||||||
|
inner: UnsafeCell<A>,
|
||||||
|
lock: AtomicBool,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `lock()` — spin-wait с CAS + `hint::spin_loop()`.
|
||||||
|
- `LockedGuard` — RAII guard, при Drop отпускает блокировку.
|
||||||
|
- `unsafe impl Sync` — потому что `lock()` гарантирует взаимное исключение.
|
||||||
|
|
||||||
|
Используется не только для аллокатора, но и для PMM, serial port,
|
||||||
|
VMM KERNEL_SPACE и других глобальных структур.
|
||||||
|
|
||||||
|
## Глобальный аллокатор
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[global_allocator]
|
||||||
|
pub static ALLOCATOR: Locked<SlabAllocator> = Locked::new(SlabAllocator::new());
|
||||||
|
```
|
||||||
|
|
||||||
|
Этот `static` перехватывает все вызовы `alloc::alloc::alloc()`,
|
||||||
|
делая возможным использование `Vec`, `Box`, `String` и т.д. в ядре.
|
||||||
|
|
||||||
|
## Heap init в kmain()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let heap_start = 0xFFFF_9000_0000_0000;
|
||||||
|
let heap_size = 8 * 1024 * 1024; // 8 MiB
|
||||||
|
// Предварительно map'им все страницы кучи
|
||||||
|
for i in (0..heap_size).step_by(4096) {
|
||||||
|
let frame = mem::pmm::alloc_frame().expect("OOM");
|
||||||
|
p4.map_page(VirtAddr(heap_start + i), frame, flags);
|
||||||
|
}
|
||||||
|
// Инициализируем аллокатор
|
||||||
|
allocator::ALLOCATOR.lock().init(heap_start as usize, heap_size);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Почему slab?
|
||||||
|
|
||||||
|
1. **Скорость**: alloc/dealloc — O(1) для малых блоков.
|
||||||
|
2. **Нет фрагментации**: блоки фиксированного размера.
|
||||||
|
3. **Локальность**: блоки одного размера рядом в памяти.
|
||||||
|
4. **Простота**: ~180 строк кода.
|
||||||
267
kernel/docs/memory/buddy.md
Normal file
267
kernel/docs/memory/buddy.md
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
# Buddy Allocator: `buddy.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
Buddy-аллокатор — это алгоритм управления памятью, который:
|
||||||
|
- Делит память на блоки размером `2^order` страниц.
|
||||||
|
- Каждый блок может быть либо свободен, либо занят.
|
||||||
|
- При освобождении блок объединяется (coalesce) с соседом (buddy),
|
||||||
|
если тот тоже свободен, образуя блок вдвое большего размера.
|
||||||
|
|
||||||
|
```
|
||||||
|
Пример: порядок 0 (1 страница), порядок 1 (2 страницы), порядок 2 (4 страницы)
|
||||||
|
|
||||||
|
Order 2: [ 0-3 ] [ 4-7 ] [ 8-11 ]
|
||||||
|
Order 1: [ 0-1 ][ 2-3 ] [ 4-5 ][ 6-7 ] [ 8-9 ][ 10-11 ]
|
||||||
|
Order 0: [0][1][2][3] [4][5][6][7] [8][9][10][11] [12][13][14][15]
|
||||||
|
^
|
||||||
|
buddy-пара: (0,1), (2,3), (4,5)...
|
||||||
|
buddy(i) = i XOR (1 << order)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Intrusive List (список в самих страницах)
|
||||||
|
|
||||||
|
Вместо отдельной структуры данных для списков свободных блоков,
|
||||||
|
Elyz использует **intrusive linked list** — указатели хранятся прямо
|
||||||
|
внутри свободных физических страниц:
|
||||||
|
|
||||||
|
```
|
||||||
|
Страница (4 KiB):
|
||||||
|
┌──────────────┐
|
||||||
|
│ next: usize │ ← указатель на следующую свободную страницу
|
||||||
|
├──────────────┤
|
||||||
|
│ prev: usize │ ← указатель на предыдущую свободную страницу
|
||||||
|
├──────────────┤
|
||||||
|
│ │
|
||||||
|
│ (не занято) │
|
||||||
|
│ │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Для доступа к странице по индексу используется HHDM:
|
||||||
|
```rust
|
||||||
|
fn page_virt(&self, idx: usize) -> VirtAddr {
|
||||||
|
PhysAddr(self.base_phys + idx as u64 * 4096).to_virt()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура BuddyAllocator
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct BuddyAllocator {
|
||||||
|
free_heads: [usize; MAX_ORDER + 1], // головы списков для каждого порядка
|
||||||
|
total_pages: usize, // всего страниц в управлении
|
||||||
|
free_pages: usize, // свободно страниц
|
||||||
|
base_phys: u64, // физический адрес начала
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `MAX_ORDER = 11` — максимальный порядок (2^11 = 2048 страниц = 8 MiB).
|
||||||
|
- `NEXT_SENTINEL = usize::MAX` — маркер конца списка.
|
||||||
|
- `free_heads[order]` — индекс первой свободной страницы порядка `order`.
|
||||||
|
|
||||||
|
## Операции со списком
|
||||||
|
|
||||||
|
### flist_push(order, idx) — добавить в голову
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn flist_push(&mut self, order: usize, idx: usize) {
|
||||||
|
let head = self.free_heads[order];
|
||||||
|
// Устанавливаем: node.next = head, node.prev = SENTINEL
|
||||||
|
self.write_node(idx, head, NEXT_SENTINEL);
|
||||||
|
if head != NEXT_SENTINEL {
|
||||||
|
// head.prev = idx
|
||||||
|
self.write_node(head, self.read_next(head), idx);
|
||||||
|
}
|
||||||
|
self.free_heads[order] = idx;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### flist_remove(order, idx) — удалить из списка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn flist_remove(&mut self, order: usize, idx: usize) {
|
||||||
|
let next = self.read_next(idx);
|
||||||
|
let prev = self.read_prev(idx);
|
||||||
|
// Очищаем указатели удаляемого узла
|
||||||
|
self.write_node(idx, NEXT_SENTINEL, NEXT_SENTINEL);
|
||||||
|
if prev != NEXT_SENTINEL {
|
||||||
|
self.write_node(prev, next, self.read_prev(prev));
|
||||||
|
} else {
|
||||||
|
self.free_heads[order] = next; // удалили голову
|
||||||
|
}
|
||||||
|
if next != NEXT_SENTINEL {
|
||||||
|
self.write_node(next, self.read_next(next), prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### flist_pop(order) — извлечь из головы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn flist_pop(&mut self, order: usize) -> Option<usize> {
|
||||||
|
let head = self.free_heads[order];
|
||||||
|
if head == NEXT_SENTINEL { return None; }
|
||||||
|
self.flist_remove(order, head);
|
||||||
|
Some(head)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Инициализация: `new(total_pages, base_phys)`
|
||||||
|
|
||||||
|
Стратегия: разбить весь диапазон на максимально возможные блоки.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn new(total_pages: usize, base_phys: u64) -> Self {
|
||||||
|
let mut this = Self {
|
||||||
|
free_heads: [NEXT_SENTINEL; MAX_ORDER + 1],
|
||||||
|
total_pages, free_pages: 0, base_phys,
|
||||||
|
};
|
||||||
|
if total_pages == 0 { return this; }
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
while idx < total_pages {
|
||||||
|
let remaining = total_pages - idx;
|
||||||
|
// Максимальный порядок с учётом выравнивания и остатка
|
||||||
|
let align_order = /* макс порядок по выравниванию idx */;
|
||||||
|
let size_order = /* макс порядок по remaining */;
|
||||||
|
let order = MAX_ORDER.min(align_order).min(size_order);
|
||||||
|
let block_size = 1usize << order;
|
||||||
|
this.flist_push(order, idx);
|
||||||
|
this.free_pages += block_size;
|
||||||
|
idx += block_size;
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Как определяются align_order и size_order
|
||||||
|
|
||||||
|
- `align_order`: если `idx == 0` — `MAX_ORDER`; иначе `(idx.trailing_zeros()).min(MAX_ORDER)`.
|
||||||
|
Чем больше нулевых битов в idx, тем выше порядок выравнивания.
|
||||||
|
- `size_order`: `(64 - 1) - remaining.leading_zeros()` — максимальная степень двойки
|
||||||
|
≤ remaining.
|
||||||
|
|
||||||
|
## Аллокация: `alloc(order)`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn alloc(&mut self, order: usize) -> Option<usize> {
|
||||||
|
// 1. Ищем первый непустой список начиная с order
|
||||||
|
let found_order = (order..=MAX_ORDER)
|
||||||
|
.find(|&o| self.free_heads[o] != NEXT_SENTINEL)?;
|
||||||
|
|
||||||
|
// 2. Извлекаем блок из found_order
|
||||||
|
let block_idx = self.flist_pop(found_order);
|
||||||
|
self.free_pages -= 1 << found_order;
|
||||||
|
|
||||||
|
// 3. Разбиваем до нужного порядка (split)
|
||||||
|
let mut cur_order = found_order;
|
||||||
|
while cur_order > order {
|
||||||
|
cur_order -= 1;
|
||||||
|
let buddy_idx = block_idx + (1 << cur_order);
|
||||||
|
self.flist_push(cur_order, buddy_idx);
|
||||||
|
self.free_pages += 1 << cur_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(block_idx)
|
||||||
|
// Возвращается индекс первого блока
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример split
|
||||||
|
|
||||||
|
```
|
||||||
|
Запрос: order 1 (2 страницы)
|
||||||
|
Найден: order 3 (8 страниц, блок [0-7])
|
||||||
|
|
||||||
|
Шаг 1: cur_order = 3 → 2
|
||||||
|
buddy = 0 + 4 = 4
|
||||||
|
push(order=2, idx=4) — блок [4-7] в order 2
|
||||||
|
Шаг 2: cur_order = 2 → 1
|
||||||
|
buddy = 0 + 2 = 2
|
||||||
|
push(order=1, idx=2) — блок [2-3] в order 1
|
||||||
|
Результат: order=1, idx=0 — блок [0-1]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Освобождение: `free(block_idx, order)` + coalesce
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn free(&mut self, mut block_idx: usize, mut order: usize) {
|
||||||
|
// Пытаемся объединить с buddy
|
||||||
|
while order < MAX_ORDER {
|
||||||
|
let buddy_idx = block_idx ^ (1 << order);
|
||||||
|
|
||||||
|
// Проверка: buddy в пределах памяти?
|
||||||
|
let buddy_end = buddy_idx.checked_add(1 << order)?;
|
||||||
|
if buddy_end > self.total_pages { break; }
|
||||||
|
|
||||||
|
// Buddy свободен?
|
||||||
|
if self.flist_contains(order, buddy_idx) {
|
||||||
|
// Удаляем buddy из его списка
|
||||||
|
self.flist_remove(order, buddy_idx);
|
||||||
|
self.free_pages -= 1 << order;
|
||||||
|
// Объединяем: block_idx = min(block_idx, buddy_idx)
|
||||||
|
block_idx = block_idx.min(buddy_idx);
|
||||||
|
order += 1;
|
||||||
|
} else {
|
||||||
|
break; // buddy занят, не можем объединить
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем объединённый блок в список
|
||||||
|
self.free_pages += 1 << order;
|
||||||
|
self.flist_push(order, block_idx);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример coalesce
|
||||||
|
|
||||||
|
```
|
||||||
|
Освобождаем: order 0, idx = 0
|
||||||
|
Блок [0] возвращается в список order 0.
|
||||||
|
|
||||||
|
Проверка: buddy(0, order 0) = 0 XOR 1 = 1
|
||||||
|
Если idx=1 тоже свободен в order 0:
|
||||||
|
- Удаляем idx=1 из order 0
|
||||||
|
- block_idx = min(0, 1) = 0
|
||||||
|
- order = 1
|
||||||
|
- Проверка: buddy(0, order 1) = 0 XOR 2 = 2
|
||||||
|
Если idx=2 свободен в order 1:
|
||||||
|
- Удаляем idx=2 из order 1
|
||||||
|
- order = 2
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## alloc_pages(page_count) — универсальная аллокация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn alloc_pages(&mut self, page_count: usize) -> Option<(usize, usize)> {
|
||||||
|
let order = order_for(page_count);
|
||||||
|
self.alloc(order).map(|idx| (idx, order))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`order_for()` вычисляет `ceil(log2(page_count))`.
|
||||||
|
|
||||||
|
## Вспомогательные функции
|
||||||
|
|
||||||
|
- `free_pages()` / `total_pages()` — геттеры.
|
||||||
|
- `is_exhausted()` — `free_pages == 0`.
|
||||||
|
- `stats()` — возвращает `[(order, count)]` для каждого порядка (количество
|
||||||
|
свободных блоков). Используется для отладки.
|
||||||
|
- `flist_contains(order, idx)` — проверка, есть ли блок в списке
|
||||||
|
(проверяет `prev != NEXT_SENTINEL` или `head == idx`).
|
||||||
|
|
||||||
|
## Почему Intrusive List?
|
||||||
|
|
||||||
|
1. **Zero overhead**: не нужно отдельной аллокации для списков.
|
||||||
|
2. **O(1)**: push/pop/remove — все O(1) операции.
|
||||||
|
3. **Константная память**: аллокатор не потребляет дополнительной памяти
|
||||||
|
сверх управляемых страниц.
|
||||||
|
4. **Кэш-дружественность**: доступ к next/prev обращается к той же странице,
|
||||||
|
которая будет выделена.
|
||||||
|
|
||||||
|
## Где используется
|
||||||
|
|
||||||
|
- `PMActor` содержит `BuddyAllocator` для управления своим диапазоном.
|
||||||
|
- Прямые тесты в `kmain()` демонстрируют all/alloc/free/coalesce/exhaustion.
|
||||||
82
kernel/docs/memory/introduction.md
Normal file
82
kernel/docs/memory/introduction.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Управление памятью: концептуальная модель
|
||||||
|
|
||||||
|
## Архитектурная философия
|
||||||
|
|
||||||
|
Управление памятью в Elyz построено как **трёхуровневая иерархия**,
|
||||||
|
где каждый уровень решает свою задачу и взаимодействует с соседними
|
||||||
|
через строго определённые интерфейсы.
|
||||||
|
|
||||||
|
```
|
||||||
|
Уровень 1: BitmapPMM (глобальный, физический)
|
||||||
|
│
|
||||||
|
│ предоставляет сырые фреймы
|
||||||
|
▼
|
||||||
|
Уровень 2: PMActor + BuddyAllocator (распределённый, физический)
|
||||||
|
│
|
||||||
|
│ управляет диапазонами, выдаёт под-диапазоны
|
||||||
|
▼
|
||||||
|
Уровень 3: AddressSpace (VMM) (виртуальный)
|
||||||
|
│
|
||||||
|
│ отображает физические фреймы в виртуальные адреса
|
||||||
|
▼
|
||||||
|
CPU (MMU, page tables)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Зачем три уровня?
|
||||||
|
|
||||||
|
- **BitmapPMM** — глобальный аллокатор физических фреймов. Простой,
|
||||||
|
надёжный, но неэффективный для частых alloc/free маленьких блоков.
|
||||||
|
Используется для начальной загрузки и для Page Table страниц.
|
||||||
|
|
||||||
|
- **BuddyAllocator + PMActor** — распределённая модель. Каждый актор
|
||||||
|
управляет своим диапазоном физической памяти через buddy-алгоритм.
|
||||||
|
Это даёт: (1) изоляцию — актор A не может истощить память актора B;
|
||||||
|
(2) масштабирование — акторы могут работать параллельно;
|
||||||
|
(3) предсказуемость — каждый актор знает свой лимит.
|
||||||
|
|
||||||
|
- **AddressSpace (VMM)** — виртуальные адресные пространства. PML4,
|
||||||
|
VMA-деревья, copy-on-write, lazy mapping. Использует PMM для
|
||||||
|
аллокации Page Table страниц (через `pmm_alloc` в `paging.rs`).
|
||||||
|
|
||||||
|
### Разделение ответственности по файлам
|
||||||
|
|
||||||
|
| Файл | Компонент | Роль в абстракции |
|
||||||
|
|------|-----------|-------------------|
|
||||||
|
| `address.rs` | PhysAddr / VirtAddr / HHDM | Базовые типы для адресов |
|
||||||
|
| `pmm.rs` | BitmapPMM | Глобальный менеджер физических фреймов |
|
||||||
|
| `buddy.rs` | BuddyAllocator | O(1) buddy allocator (intrusive list) |
|
||||||
|
| `pm_manages.rs` | PMActor + PMActorQueue | Актёр физической памяти + MPSC очередь |
|
||||||
|
| `pm_router.rs` | PMRouter | Lock-free маршрутизация запросов/ответов |
|
||||||
|
| `paging.rs` | PageTable | Аппаратные 4-уровневые page tables |
|
||||||
|
| `vmm.rs` | AddressSpace + VMA | Виртуальные адресные пространства |
|
||||||
|
| `allocator.rs` | SlabAllocator | Кучевой аллокатор (global_allocator) |
|
||||||
|
| `mod.rs` | Экспорт | Фасад подсистемы |
|
||||||
|
|
||||||
|
### Поток данных: типичная аллокация
|
||||||
|
|
||||||
|
```
|
||||||
|
Процесс A хочет 16 страниц:
|
||||||
|
1. Код пользователя отправляет PMRequest::Allocate в PMActor через
|
||||||
|
PMRouter::alloc_channel() + actor.submit_request()
|
||||||
|
2. Когда актор получает CPU, process_messages() вызывает
|
||||||
|
buddy.alloc_pages(16)
|
||||||
|
3. BuddyAllocator находит блок порядка 4 (2^4 = 16) или больше,
|
||||||
|
разбивает его при необходимости
|
||||||
|
4. PMActor создаёт Capability с CapObject::Memory { phys, size_pages }
|
||||||
|
5. Ответ (PMResponse::Allocated) отправляется через PMRouter
|
||||||
|
6. Получатель может отобразить фреймы в своё AddressSpace через
|
||||||
|
map_region() или map_shared()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Поток данных: page fault
|
||||||
|
|
||||||
|
```
|
||||||
|
CPU ловит #PF (page fault):
|
||||||
|
1. interrupt.rs: rust_page_fault_handler() читает CR2
|
||||||
|
2. Получает блокировку KERNEL_SPACE
|
||||||
|
3. process_pending_revocations() — обрабатывает накопленные отзывы
|
||||||
|
4. handle_fault() проверяет: это COW? это lazy region?
|
||||||
|
5. Если COW — копируем страницу (copy-on-write)
|
||||||
|
6. Если lazy — alloc_frame() из PMM + map_page()
|
||||||
|
7. Если нераспознанный fault — KERNEL PANIC
|
||||||
|
```
|
||||||
196
kernel/docs/memory/paging.md
Normal file
196
kernel/docs/memory/paging.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Page Tables и отображение памяти: `paging.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
x86-64 использует **4-уровневую иерархию таблиц страниц**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Виртуальный адрес (48 бит):
|
||||||
|
┌─────────┬─────────┬─────────┬─────────┬─────────┐
|
||||||
|
│ PML4 │ PDPT │ PD │ PT │ offset │
|
||||||
|
│ bits │ bits │ bits │ bits │ (12) │
|
||||||
|
│ 47:39 │ 38:30 │ 29:21 │ 20:12 │ 11:0 │
|
||||||
|
└────┬────┴────┬────┴────┬────┴────┬────┴─────────┘
|
||||||
|
│ │ │ │
|
||||||
|
▼ ▼ ▼ ▼
|
||||||
|
PML4 ──► PDPT ──► PD ──► PT ──► Физический фрейм
|
||||||
|
(512 ent) (512 ent) (512 ent) (512 ent) (4 KiB)
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждая запись (PTE) = 8 байт:
|
||||||
|
```
|
||||||
|
Bit 63: NX (No Execute)
|
||||||
|
Bits 62:52: Ignored/AVL
|
||||||
|
Bits 51:12: Physical frame address (4KiB aligned)
|
||||||
|
Bits 11:9: Ignored/AVL
|
||||||
|
Bits 8: Global
|
||||||
|
Bits 7: PS (Page Size = 1 GiB для PDPT, 2 MiB для PD)
|
||||||
|
Bits 6: Dirty
|
||||||
|
Bits 5: Accessed
|
||||||
|
Bits 4: PCD (Cache Disable)
|
||||||
|
Bits 3: PWT (Write Through)
|
||||||
|
Bits 2: U/S (User/Supervisor)
|
||||||
|
Bits 1: R/W (Read/Write)
|
||||||
|
Bit 0: Present
|
||||||
|
```
|
||||||
|
|
||||||
|
## PageTable — представление таблицы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[repr(C, align(4096))]
|
||||||
|
pub struct PageTable {
|
||||||
|
entries: [u64; 512],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `512` entries × 8 байт = 4096 байт (ровно одна страница).
|
||||||
|
- `align(4096)` — гарантирует, что структура размещается на физической
|
||||||
|
странице, как требует аппаратура.
|
||||||
|
|
||||||
|
## PageTableFlags — битовые флаги
|
||||||
|
|
||||||
|
```rust
|
||||||
|
bitflags! {
|
||||||
|
pub struct PageTableFlags: u64 {
|
||||||
|
const PRESENT = 1 << 0;
|
||||||
|
const WRITABLE = 1 << 1;
|
||||||
|
const USER = 1 << 2;
|
||||||
|
const WRITE_THROUGH = 1 << 3;
|
||||||
|
const NO_CACHE = 1 << 4;
|
||||||
|
const ACCESSED = 1 << 5;
|
||||||
|
const DIRTY = 1 << 6;
|
||||||
|
const HUGE_PAGE = 1 << 7;
|
||||||
|
const GLOBAL = 1 << 8;
|
||||||
|
const COW = 1 << 9; // Elyz-specific: Copy-on-Write marker
|
||||||
|
const NO_EXECUTE = 1 << 63;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Флаг `COW` (бит 9) — Elyz-специфичный, используется в AddressSpace для
|
||||||
|
маркировки страниц, которые нужно копировать при записи.
|
||||||
|
|
||||||
|
## Основные операции PageTable
|
||||||
|
|
||||||
|
### map_page(virt, phys, flags) — отображение одной страницы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn map_page(&mut self, virt: VirtAddr, phys: PhysAddr, flags: PageTableFlags) {
|
||||||
|
let p1 = self.walk_to_p1_mut(virt, true).expect("...");
|
||||||
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
|
p1.entries[p1_idx] = phys.0 | flags.bits();
|
||||||
|
asm!("invlpg [{}]", in(reg) virt.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Процесс:**
|
||||||
|
1. `walk_to_p1_mut(virt, create=true)` проходит 4 уровня (P4→P3→P2→P1),
|
||||||
|
создавая недостающие таблицы через `pmm_alloc()`.
|
||||||
|
2. Записывает физический адрес с флагами в P1 entry.
|
||||||
|
3. `INVLPG` — сбрасывает TLB для этой страницы.
|
||||||
|
|
||||||
|
### map_region(virt, phys, size, flags) — отображение диапазона
|
||||||
|
|
||||||
|
Вызывает `map_page()` для каждой страницы в диапазоне.
|
||||||
|
`size.div_ceil(4096)` — количество страниц.
|
||||||
|
|
||||||
|
### unmap_page(virt) — удаление отображения
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn unmap_page(&mut self, virt: VirtAddr) {
|
||||||
|
// walk_to_p1_mut(virt, false) — не создаём, только ищем
|
||||||
|
let Some(p1) = self.walk_to_p1_mut(virt, false) else { return; };
|
||||||
|
p1.entries[p1_idx] = 0; // Clear PTE
|
||||||
|
asm!("invlpg [{}]", in(reg) virt.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Важно:** не освобождает физический фрейм — это ответственность
|
||||||
|
Caller'а (VMM). Идемпотентна: если PTE нет, ничего не делает.
|
||||||
|
|
||||||
|
### translate(virt) — трансляция виртуального адреса
|
||||||
|
|
||||||
|
Проходит 4 уровня, проверяя PRESENT на каждом уровне.
|
||||||
|
Поддерживает huge pages (1 GiB и 2 MiB):
|
||||||
|
- Если P3 entry имеет HUGE_PAGE — 1 GiB страница.
|
||||||
|
- Если P2 entry имеет HUGE_PAGE — 2 MiB страница.
|
||||||
|
|
||||||
|
### get_flags(virt) / update_flags(virt) — чтение/запись флагов
|
||||||
|
|
||||||
|
`get_flags` возвращает флаги PTE для виртуального адреса.
|
||||||
|
|
||||||
|
`update_flags` меняет флаги, сохраняя физический адрес, и вызывает INVLPG.
|
||||||
|
|
||||||
|
### activate(phys_addr) — загрузка в CR3
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub unsafe fn activate(&self, phys_addr: PhysAddr) {
|
||||||
|
asm!("mov cr3, {0}", in(reg) phys_addr.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Полная замена таблиц страниц. Полный TLB flush (без PCID).
|
||||||
|
|
||||||
|
В `vmm.rs::AddressSpace::activate()` используется улучшенная версия
|
||||||
|
с PCID и битом NOFLUSH.
|
||||||
|
|
||||||
|
## walk_to_p1_mut — сердце навигации
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn walk_to_p1_mut(&mut self, virt: VirtAddr, create: bool) -> Option<&mut Self> {
|
||||||
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
|
|
||||||
|
let p3 = self.get_or_create_next_table(p4_idx, create)?;
|
||||||
|
let p2 = p3.get_or_create_next_table(p3_idx, create)?;
|
||||||
|
p2.get_or_create_next_table(p2_idx, create)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### get_or_create_next_table(index, create) — следующий уровень
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn get_or_create_next_table(&mut self, index: usize, create: bool) -> Option<&mut Self> {
|
||||||
|
if entry & PRESENT == 0 {
|
||||||
|
if !create { return None; }
|
||||||
|
// Аллоцируем новый фрейм для таблицы
|
||||||
|
let pt_phys = pmm_alloc().expect("OOM");
|
||||||
|
// Обнуляем (все entry = 0 = not present)
|
||||||
|
core::ptr::write_bytes(pt_virt.as_mut_ptr::<u8>(), 0, 4096);
|
||||||
|
// Устанавливаем entry: PRESENT | WRITABLE | USER
|
||||||
|
self.entries[index] = pt_phys.0 | (PRESENT | WRITABLE | USER);
|
||||||
|
}
|
||||||
|
// Преобразуем физический адрес entry в ссылку на PageTable
|
||||||
|
let next_phys = PhysAddr(self.entries[index] & PTE_ADDR_MASK);
|
||||||
|
Some(unsafe { &mut *next_phys.to_virt().as_mut_ptr::<Self>() })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### pte_addr_mask — маска адреса
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const PTE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
|
||||||
|
```
|
||||||
|
|
||||||
|
Биты 51:12 — физический адрес с выравниванием 4KiB.
|
||||||
|
(0x000F_FFFF_FFFF_F000 = 52 бита адреса, очищенные нижние 12 бит).
|
||||||
|
|
||||||
|
## pmm_alloc — шлюз к PMM
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn pmm_alloc() -> Option<PhysAddr> {
|
||||||
|
PMM.lock().as_mut()?.alloc_frame()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Тонкая обёртка, чтобы избежать циклической зависимости между paging и pmm.
|
||||||
|
|
||||||
|
## Ключевые инварианты
|
||||||
|
|
||||||
|
1. Каждая PageTable занимает ровно одну физическую страницу (4 KiB).
|
||||||
|
2. Физический адрес таблицы всегда 4KiB-выровнен.
|
||||||
|
3. Все intermediate таблицы создаются с флагом USER (чтобы и ядро,
|
||||||
|
и пользователь могли их использовать). Разделение на user/kernel
|
||||||
|
контролируется на уровне P1 entry.
|
||||||
|
4. `INVLPG` вызывается после каждого изменения PTE для консистентности TLB.
|
||||||
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 на этапе инициализации и больше к нему не
|
||||||
|
обращаются.
|
||||||
258
kernel/docs/memory/pm-actor.md
Normal file
258
kernel/docs/memory/pm-actor.md
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
# PM Actor: распределённый менеджер физической памяти
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**PMActor** (Physical Memory Actor) — это изолированный агент, который
|
||||||
|
владеет фиксированным диапазоном физической памяти и управляет им через
|
||||||
|
BuddyAllocator. Он следует **модели акторов**:
|
||||||
|
|
||||||
|
- У каждого актора есть **почтовый ящик** (MPSC queue).
|
||||||
|
- Другие компоненты **отправляют ему сообщения** (Allocate, Free, Carve).
|
||||||
|
- Актёр **обрабатывает сообщения**, когда получает CPU time.
|
||||||
|
- Актёр **отправляет ответы** через PM Router.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────┐
|
||||||
|
│ PMActor #1 │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────┐ │
|
||||||
|
│ │ MPSC Inbox (PMActorQueue) │ │
|
||||||
|
│ │ [Alloc] [Free] [Carve] [Alloc] [Free] ... │ │
|
||||||
|
│ └──────────────────────┬───────────────────────┘ │
|
||||||
|
│ │ pop │
|
||||||
|
│ ┌──────────────────────▼───────────────────────┐ │
|
||||||
|
│ │ process_messages() │ │
|
||||||
|
│ │ ┌────────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||||
|
│ │ │handle_alloc│ │handle_free│ │handle_carve│ │ │
|
||||||
|
│ │ └──────┬─────┘ └────┬─────┘ └─────┬────┘ │ │
|
||||||
|
│ │ ▼ ▼ ▼ │ │
|
||||||
|
│ │ ┌────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ BuddyAllocator │ │ │
|
||||||
|
│ │ │ [диапазон: 0x4000_0000 — 0x4000_xxxx] │ │ │
|
||||||
|
│ │ └────────────────────────────────────────┘ │ │
|
||||||
|
│ └────────────────────────┬──────────────────────┘ │
|
||||||
|
│ │ Vec<PMResponse> │
|
||||||
|
│ ▼ │
|
||||||
|
│ Через PMRouter.route_responses() │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура PMActor
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMActor {
|
||||||
|
pub actor_id: u64, // Уникальный ID
|
||||||
|
pub root_untyped: Capability, // Capability на весь диапазон
|
||||||
|
pub managed_range: (PhysAddr, PhysAddr), // [start, end)
|
||||||
|
queue: PMActorQueue, // MPSC inbox
|
||||||
|
buddy: BuddyAllocator, // Buddy-аллокатор
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `actor_id` — используется в `token_sig` для уникальной идентификации.
|
||||||
|
- `root_untyped` — Strong capability на весь диапазон (корень дерева).
|
||||||
|
- `managed_range` — физические границы (начало, конец).
|
||||||
|
- `queue` — lock-free MPSC кольцевой буфер.
|
||||||
|
- `buddy` — аллокатор для этого диапазона.
|
||||||
|
|
||||||
|
## PMActorQueue — lock-free MPSC кольцевой буфер
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMActorQueue {
|
||||||
|
buffer: [AtomicU64; QUEUE_SIZE], // 1024 entry
|
||||||
|
_pad0: [u8; 64],
|
||||||
|
head: AtomicUsize, // читатель (актор)
|
||||||
|
_pad1: [u8; 64],
|
||||||
|
tail: AtomicUsize, // писатели (производители)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Зачем padding?
|
||||||
|
|
||||||
|
`_pad0` и `_pad1` размером 64 байта — это размер кэш-линии x86-64.
|
||||||
|
Разделение head и tail по разным кэш-линиям предотвращает **false sharing**
|
||||||
|
(когда два ядра пишут в разные переменные, но они на одной кэш-линии).
|
||||||
|
|
||||||
|
### send(req) — Multi-Producer
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn send(&self, req: PMRequest) -> Result<(), &'static str> {
|
||||||
|
let packed = req.pack();
|
||||||
|
let mut tail = self.tail.load(Ordering::Relaxed);
|
||||||
|
loop {
|
||||||
|
let head = self.head.load(Ordering::Acquire);
|
||||||
|
if tail.wrapping_sub(head) >= QUEUE_SIZE {
|
||||||
|
return Err("Queue overflow");
|
||||||
|
}
|
||||||
|
// CAS: пытаемся захватить слот
|
||||||
|
match self.tail.compare_exchange_weak(tail, tail+1, AcqRel, Relaxed) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.buffer[tail & QUEUE_MASK].store(packed, Release);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(actual) => tail = actual,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `wrapping_sub`: кольцевой буфер, переполнение при разнице >= QUEUE_SIZE.
|
||||||
|
- `compare_exchange_weak`: позволяет нескольким продюсерам соревноваться.
|
||||||
|
- Store с `Release`: гарантирует, что данные видны потребителю.
|
||||||
|
|
||||||
|
### pop() — Single-Consumer
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn pop(&self) -> Option<PMRequest> {
|
||||||
|
let head = self.head.load(Ordering::Relaxed);
|
||||||
|
let tail = self.tail.load(Ordering::Acquire);
|
||||||
|
if head == tail { return None; }
|
||||||
|
let packed = self.buffer[head & QUEUE_MASK].load(Ordering::Acquire);
|
||||||
|
self.head.store(head + 1, Ordering::Release);
|
||||||
|
Some(PMRequest::unpack(packed))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Только актор вызывает pop — никакой конкуренции.
|
||||||
|
- Acquire на tail: видим последнюю запись продюсера.
|
||||||
|
|
||||||
|
## Message Packing
|
||||||
|
|
||||||
|
Каждое сообщение упаковывается в одно `u64`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Bit 63:56 OPCODE (8 bits) 1=Alloc, 2=Free, 3=Carve
|
||||||
|
Bit 55:40 CHANNEL_ID (16 bits) маршрутизация ответа; 0 = discard
|
||||||
|
Bit 39:20 ARG1 (20 bits) size_pages/local_frame_idx/offset_pages
|
||||||
|
Bit 19: 0 ARG2 (20 bits) token_sig (low 20 bits) / size_pages / order
|
||||||
|
```
|
||||||
|
|
||||||
|
Максимум: 2^20 = 1 048 576 страниц = 4 GiB на одну аллокацию.
|
||||||
|
|
||||||
|
## Типы сообщений (PMRequest)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum PMRequest {
|
||||||
|
Allocate {
|
||||||
|
size_pages: usize, // сколько страниц
|
||||||
|
token_sig: u32, // тег capability
|
||||||
|
channel_id: u16, // куда отправить ответ
|
||||||
|
},
|
||||||
|
Free {
|
||||||
|
local_frame_idx: usize, // относительный индекс
|
||||||
|
order: usize, // buddy order
|
||||||
|
},
|
||||||
|
Carve {
|
||||||
|
offset_pages: usize, // смещение от начала
|
||||||
|
size_pages: usize, // размер
|
||||||
|
channel_id: u16, // куда отправить ответ
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## PMResponse — результат
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMResponse {
|
||||||
|
pub channel_id: u16,
|
||||||
|
pub result: PMResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PMResult {
|
||||||
|
Allocated { cap: Capability, order: usize },
|
||||||
|
OutOfMemory { size_pages: usize },
|
||||||
|
Carved { cap: Capability },
|
||||||
|
Freed { pages_returned: usize },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обработка сообщений
|
||||||
|
|
||||||
|
### handle_allocate(size_pages, token_sig, channel_id)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn handle_allocate(&mut self, size_pages, token_sig, channel_id) -> Option<PMResponse> {
|
||||||
|
match self.buddy.alloc_pages(size_pages) {
|
||||||
|
Some((rel_idx, order)) => {
|
||||||
|
let phys = PhysAddr(self.managed_range.0.0 + rel_idx * PAGE_SIZE);
|
||||||
|
let cap = Capability {
|
||||||
|
object: CapObject::Memory { phys, size_pages: 1 << order },
|
||||||
|
rights: CapRights::READ | WRITE | GRANT,
|
||||||
|
relation: Relation::Strong,
|
||||||
|
token_sig: (token_sig as u64) ^ self.actor_id ^ (rel_idx as u64),
|
||||||
|
};
|
||||||
|
Some(PMResponse { channel_id, result: Allocated { cap, order } })
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
Some(PMResponse { channel_id, result: OutOfMemory { size_pages } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Детали:**
|
||||||
|
- `rel_idx` — относительный индекс в buddy (от 0 до total_pages).
|
||||||
|
- Физический адрес: `managed_range.start + rel_idx * 4096`.
|
||||||
|
- `token_sig` — XOR от token_sig запроса, actor_id и rel_idx для уникальности.
|
||||||
|
- Размер в capability: `1 << order` (округляется до степени двойки).
|
||||||
|
|
||||||
|
### handle_free(local_frame_idx, order)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn handle_free(&mut self, local_frame_idx, order) -> Option<PMResponse> {
|
||||||
|
// Валидация границ
|
||||||
|
let block_end = local_frame_idx.saturating_add(1usize << order);
|
||||||
|
if block_end > self.buddy.total_pages() {
|
||||||
|
debug_assert!(false, "Out of range");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.buddy.free(local_frame_idx, order);
|
||||||
|
None // Free не требует ответа
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### handle_carve(offset_pages, size_pages, channel_id)
|
||||||
|
|
||||||
|
Carve — выделение под-диапазона без участия buddy-аллокатора.
|
||||||
|
Используется для статических размещений (framebuffer alias).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn handle_carve(&mut self, offset_pages, size_pages, channel_id) -> Option<PMResponse> {
|
||||||
|
let range_pages = self.buddy.total_pages();
|
||||||
|
// Проверка границ
|
||||||
|
if offset_pages >= range_pages || size_pages == 0 || ... {
|
||||||
|
return Some(PMResponse { channel_id, result: OutOfMemory { size_pages } });
|
||||||
|
}
|
||||||
|
let phys = PhysAddr(self.managed_range.0.0 + offset_pages * PAGE_SIZE);
|
||||||
|
let cap = Capability {
|
||||||
|
object: CapObject::Memory { phys, size_pages },
|
||||||
|
rights: CapRights::READ | WRITE | GRANT,
|
||||||
|
relation: Relation::Strong,
|
||||||
|
token_sig: self.actor_id ^ offset_pages,
|
||||||
|
};
|
||||||
|
Some(PMResponse { channel_id, result: Carved { cap } })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ballooning hook (TODO)
|
||||||
|
|
||||||
|
Когда `BuddyAllocator::alloc_pages` возвращает `None`, актор генерирует
|
||||||
|
`OutOfMemory`. Будущая подсистема ballooning будет перехватывать этот
|
||||||
|
ответ до того, как он дойдёт до запросившего, и договариваться о передаче
|
||||||
|
памяти от соседнего актора.
|
||||||
|
|
||||||
|
## Владение и capabilities
|
||||||
|
|
||||||
|
- PMActor держит Strong capability на весь свой диапазон.
|
||||||
|
- Все выделенные или вырезанные под-диапазоны — тоже Strong (дочерние).
|
||||||
|
- При освобождении через Free buddy-дерево объединяет блоки.
|
||||||
|
- Актёр **никогда** не обращается к глобальному BitmapPMM.
|
||||||
|
|
||||||
|
## Максимальное количество сообщений за вызов
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const MAX_MESSAGES_PER_CALL: usize = 64;
|
||||||
|
```
|
||||||
|
|
||||||
|
Ограничение предотвращает голодание ядра, когда inbox глубок.
|
||||||
189
kernel/docs/memory/pm-router.md
Normal file
189
kernel/docs/memory/pm-router.md
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# PM Router: Lock-Free маршрутизация ответов
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**PM Router** — это центральный коммутатор, соединяющий исполнителей
|
||||||
|
запросов (PMActors) с потребителями результатов (процессы/потоки).
|
||||||
|
|
||||||
|
- Он предоставляет **65536 каналов** для асинхронной коммуникации.
|
||||||
|
- Каналы **lock-free**: alloc/release канала — CAS на атомарных счётчиках.
|
||||||
|
- Исключает блокировки между продюсером и консьюмером.
|
||||||
|
|
||||||
|
```
|
||||||
|
Процесс A PMActor #1
|
||||||
|
│ │
|
||||||
|
│ alloc_channel() │
|
||||||
|
│ ─────────► router ──────► │
|
||||||
|
│ │
|
||||||
|
│ submit_request(Alloc) │
|
||||||
|
│ ────────────────────────► │
|
||||||
|
│ │ process_messages()
|
||||||
|
│ │ ──────► buddy.alloc()
|
||||||
|
│ │
|
||||||
|
│ ◄─────────────── │
|
||||||
|
│ route_responses([Resp]) │
|
||||||
|
│ router │
|
||||||
|
│ │
|
||||||
|
│ wait_for_response(ch) │
|
||||||
|
│ ◄═══ RESULT ════ │
|
||||||
|
│ │
|
||||||
|
│ free_channel (неявно) │
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура канала
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[repr(align(64))]
|
||||||
|
pub struct Channel {
|
||||||
|
state: AtomicU8, // FREE=0, PENDING=1, READY=2
|
||||||
|
next_free: AtomicU16, // указатель в стеке свободных каналов
|
||||||
|
result: UnsafeCell<Option<PMResult>>, // ячейка результата
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `align(64)` — каждая кэш-линия содержит ровно один канал.
|
||||||
|
- `state` — конечный автомат: FREE → PENDING → READY → FREE.
|
||||||
|
- `UnsafeCell` — потому что write происходит из route_responses,
|
||||||
|
read — из wait_for_response. Синхронизация через state.
|
||||||
|
|
||||||
|
## Структура PMRouter
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMRouter {
|
||||||
|
channels: Box<[Channel]>, // 65536 каналов, Box<[T]> в куче
|
||||||
|
free_head: AtomicU16, // стек свободных каналов
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Стек свободных каналов
|
||||||
|
|
||||||
|
Изначально все каналы свободны, `free_head = 1` (канал 0 зарезервирован
|
||||||
|
как «discard» — ответы на канал 0 игнорируются).
|
||||||
|
|
||||||
|
```
|
||||||
|
free_head ──► channel[1].next_free = 2
|
||||||
|
channel[2].next_free = 3
|
||||||
|
channel[3].next_free = 4
|
||||||
|
...
|
||||||
|
channel[65535].next_free = 0 (NULL)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Операции
|
||||||
|
|
||||||
|
### alloc_channel() — выделить канал
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn alloc_channel(&self) -> Option<u16> {
|
||||||
|
let mut head = self.free_head.load(Acquire);
|
||||||
|
loop {
|
||||||
|
if head == 0 { return None; } // нет свободных
|
||||||
|
let next = self.channels[head].next_free.load(Relaxed);
|
||||||
|
// CAS: free_head = head → next
|
||||||
|
match self.free_head.compare_exchange_weak(head, next, AcqRel, Acquire) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.channels[head].state.store(STATE_PENDING, Release);
|
||||||
|
return Some(head);
|
||||||
|
}
|
||||||
|
Err(new) => head = new,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Lock-free: CAS на `free_head` позволяет нескольким продюсерам
|
||||||
|
конкурировать без блокировок.
|
||||||
|
|
||||||
|
### route_responses(responses) — запись результатов
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn route_responses(&self, responses: Vec<PMResponse>) {
|
||||||
|
for resp in responses {
|
||||||
|
if resp.channel_id == 0 { continue; }
|
||||||
|
let channel = &self.channels[resp.channel_id as usize];
|
||||||
|
unsafe { *channel.result.get() = Some(resp.result); }
|
||||||
|
channel.state.store(STATE_READY, Release);
|
||||||
|
// TODO: Focus Mode — пробуждение ожидающего потока
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### wait_for_response(id) — ожидание результата
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn wait_for_response(&self, id: u16) -> PMResult {
|
||||||
|
let channel = &self.channels[id as usize];
|
||||||
|
// busy-wait с HLT
|
||||||
|
while channel.state.load(Acquire) != STATE_READY {
|
||||||
|
unsafe { asm!("hlt") }; // CPU остановка до прерывания
|
||||||
|
}
|
||||||
|
let result = unsafe { (*channel.result.get()).take().unwrap() };
|
||||||
|
channel.state.store(STATE_FREE, Release);
|
||||||
|
|
||||||
|
// Возвращаем канал в стек свободных
|
||||||
|
let mut head = self.free_head.load(Relaxed);
|
||||||
|
loop {
|
||||||
|
channel.next_free.store(head, Relaxed);
|
||||||
|
match self.free_head.compare_exchange_weak(head, id, Release, Relaxed) {
|
||||||
|
Ok(_) => break,
|
||||||
|
Err(new) => head = new,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### request_and_wait — синхронная обёртка
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn request_and_wait<F>(actor: &PMActor, req_builder: F) -> PMResult
|
||||||
|
where
|
||||||
|
F: FnOnce(u16) -> PMRequest
|
||||||
|
{
|
||||||
|
let router = get_router();
|
||||||
|
let channel_id = router.alloc_channel().expect("OOM");
|
||||||
|
let req = req_builder(channel_id);
|
||||||
|
actor.submit_request(req).expect("Inbox full");
|
||||||
|
router.wait_for_response(channel_id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальный экземпляр
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static ROUTER: GlobalRouter = GlobalRouter {
|
||||||
|
is_ready: AtomicBool::new(false),
|
||||||
|
inner: UnsafeCell::new(None),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Double-checked initialization:
|
||||||
|
1. `init()` — сетап всех каналов, `is_ready = true`.
|
||||||
|
2. `get_router()` — проверяет `is_ready`, затем `unwrap_unchecked()`.
|
||||||
|
|
||||||
|
**Безопасность**: `is_ready` устанавливается один раз и никогда не
|
||||||
|
очищается, поэтому TOCTOU между проверкой и unwrap — безопасен.
|
||||||
|
|
||||||
|
## dispatch() — утилита
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn dispatch(responses: Vec<PMResponse>) {
|
||||||
|
get_router().route_responses(responses);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ключевые свойства
|
||||||
|
|
||||||
|
| Свойство | Значение |
|
||||||
|
|----------|----------|
|
||||||
|
| Количество каналов | 65536 |
|
||||||
|
| Размер канала | 64 байта (1 cache line) |
|
||||||
|
| alloc_channel | Lock-free, O(1) |
|
||||||
|
| route_responses | O(N), N = количество ответов |
|
||||||
|
| wait_for_response | Lock-free + HLT |
|
||||||
|
| Состояния | FREE → PENDING → READY → FREE |
|
||||||
|
|
||||||
|
## Где используется
|
||||||
|
|
||||||
|
- В `kmain()`: тестирование PMActor через Router.
|
||||||
|
- Будущие IPC и процесс-менеджер будут использовать Router для
|
||||||
|
асинхронного взаимодействия с PMActors.
|
||||||
267
kernel/docs/memory/vmm.md
Normal file
267
kernel/docs/memory/vmm.md
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
# Virtual Memory Manager: `vmm.rs`
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
**AddressSpace** — это представление виртуального адресного пространства
|
||||||
|
процесса. Оно состоит из:
|
||||||
|
|
||||||
|
1. **Аппаратной части**: PML4 (Page Map Level 4) — корневая таблица страниц.
|
||||||
|
2. **Программной части**: VMA (Virtual Memory Area) список — отсортированный
|
||||||
|
перечень отображённых регионов с метаданными.
|
||||||
|
|
||||||
|
```
|
||||||
|
AddressSpace
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ asid: u16 (PCID = Process Context ID) │
|
||||||
|
│ pml4_phys: PhysAddr (адрес PML4) │
|
||||||
|
│ regions: Vec<VmaRegion> (сортирован по адресу)│
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌───────────────┐
|
||||||
|
│ PML4 (P4) │ ◄── CR3
|
||||||
|
├───────────────┤
|
||||||
|
│ PDE (P3) │
|
||||||
|
├───────────────┤
|
||||||
|
│ PTE (P2) │
|
||||||
|
├───────────────┤
|
||||||
|
│ PTE (P1) │ ◄── 4 KiB страницы
|
||||||
|
└───────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## VmaRegion — регион виртуальной памяти
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct VmaRegion {
|
||||||
|
pub virt_start: VirtAddr, // начало (выровнено на 4k)
|
||||||
|
pub virt_end: VirtAddr, // конец (эксклюзивный)
|
||||||
|
pub flags: VmaFlags, // READ, WRITE, EXEC, USER, LAZY, SHARED, COW...
|
||||||
|
pub cap_token: u64, // токен capability (для revoke)
|
||||||
|
pub backing: VmaBacking, // откуда берутся физические страницы
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VmaBacking — источник физических страниц
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum VmaBacking {
|
||||||
|
Anonymous(Vec<Option<PhysAddr>>),
|
||||||
|
// ^ Owned: каждая страница выделена из PMM под этот VMA.
|
||||||
|
// Option: None = lazy (страница ещё не выделена).
|
||||||
|
|
||||||
|
Physical(PhysAddr),
|
||||||
|
// ^ Не-owned: всё отображение — непрерывный физический диапазон.
|
||||||
|
// Frame не освобождаются при unmap.
|
||||||
|
|
||||||
|
Shared { owner_cap: u64, phys_base: PhysAddr },
|
||||||
|
// ^ Zero-copy shared mapping: чужие физические страницы.
|
||||||
|
// Frame не освобождаются при unmap.
|
||||||
|
// При revoke owner_cap — все Shared VMA с этим токеном удаляются.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VmaFlags
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct VmaFlags: u32 {
|
||||||
|
const READ = 1 << 0;
|
||||||
|
const WRITE = 1 << 1;
|
||||||
|
const EXEC = 1 << 2;
|
||||||
|
const USER = 1 << 3;
|
||||||
|
const LAZY = 1 << 4; // Demand-paging: page fault = alloc frame
|
||||||
|
const SHARED = 1 << 5; // Zero-copy shared mapping
|
||||||
|
const PINNED = 1 << 6; // Защищён от revoke
|
||||||
|
const NOCACHE = 1 << 7; // Cache disabled (для MMIO)
|
||||||
|
const MMIO = 1 << 8; // Memory-mapped I/O
|
||||||
|
const COW = 1 << 9; // Copy-on-Write active
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`to_page_flags()` транслирует VmaFlags в PageTableFlags:
|
||||||
|
- PRESENT всегда
|
||||||
|
- WRITE → WRITABLE
|
||||||
|
- EXEC → NO_EXECUTE (инвертировано!)
|
||||||
|
- LAZY → PRESENT без фрейма (fault отлавливается)
|
||||||
|
- COW → COW (кастомный бит 9)
|
||||||
|
|
||||||
|
## ASID Allocator — выделение PCID
|
||||||
|
|
||||||
|
x86-64 PCID (Process Context Identifier) — 12-битный идентификатор (0-4095).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct AsidAllocator {
|
||||||
|
bitmap: [u32; 128], // 4096 бит
|
||||||
|
next_hint: u16, // хинт для амортизированного O(1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- ASID 0: kernel (no PCID tagging) — всегда занят.
|
||||||
|
- ASID 4095: reserved by Intel spec — всегда занят.
|
||||||
|
- ASID 1–4094: пользовательские.
|
||||||
|
|
||||||
|
`alloc()` — Two-pass: от hint до 4094, затем от 1 до hint.
|
||||||
|
Пропускает полностью занятые 32-битные слова.
|
||||||
|
|
||||||
|
## Основные операции AddressSpace
|
||||||
|
|
||||||
|
### `new()` — создание пустого пространства
|
||||||
|
|
||||||
|
1. Аллоцирует фрейм из PMM для PML4.
|
||||||
|
2. Обнуляет его (все entry = not present).
|
||||||
|
3. Выделяет ASID из ASID_ALLOC.
|
||||||
|
4. Пустой список regions.
|
||||||
|
|
||||||
|
### `from_active(pml4_phys, asid)` — обёртка существующего PML4
|
||||||
|
|
||||||
|
Используется для kernel space (ASID 0).
|
||||||
|
|
||||||
|
### `map_region(virt, phys, size, flags, cap_token)` — отображение
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn map_region(&mut self, virt: VirtAddr, phys: Option<PhysAddr>,
|
||||||
|
size: u64, flags: VmaFlags, cap_token: u64) -> Result<VirtAddr, VmError>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `phys = Some(base)`: Physical mapping — непрерывный диапазон.
|
||||||
|
- `phys = None + LAZY`: Demand-paging — фреймы выделяются при fault'е.
|
||||||
|
- `phys = None + !LAZY`: Eager alloc — все фреймы выделяются сейчас.
|
||||||
|
|
||||||
|
Создаёт VmaRegion, вставляет в отсортированный список.
|
||||||
|
|
||||||
|
### `map_shared(virt, phys_base, page_count, flags, owner_cap)` — zero-copy
|
||||||
|
|
||||||
|
Как map_region, но:
|
||||||
|
- Не копирует фреймы (не владеет ими).
|
||||||
|
- Всегда eagerly отображает (адреса известны).
|
||||||
|
- Записывает `cap_token = owner_cap` для revoke.
|
||||||
|
|
||||||
|
### `unmap_region(virt)` — удаление VMA
|
||||||
|
|
||||||
|
1. Находит VMA по адресу.
|
||||||
|
2. Удаляет из списка.
|
||||||
|
3. Вызывает `do_unmap(pml4, region)`:
|
||||||
|
- unmap_page для каждой страницы.
|
||||||
|
- Если backing owns_frames (Anonymous) — free_frame.
|
||||||
|
4. TLB flush.
|
||||||
|
|
||||||
|
### `handle_fault(fault_addr, write)` — Page Fault handler
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError>
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Находит VMA, содержащий fault_addr.
|
||||||
|
2. Проверяет права (write + !WRITE = PermissionDenied).
|
||||||
|
3. Проверяет COW:
|
||||||
|
- Если write + COW: аллоцирует новый фрейм, копирует данные,
|
||||||
|
заменяет PTE, освобождает старый фрейм.
|
||||||
|
4. Проверяет LAZY:
|
||||||
|
- Если LAZY + page не выделена: alloc_frame, обнуление, map_page.
|
||||||
|
5. Иначе: UnexpectedFault → panic.
|
||||||
|
|
||||||
|
### `clone_for_fork(child_cap_token)` — fork адресного пространства
|
||||||
|
|
||||||
|
1. Создаёт пустое AddressSpace::new().
|
||||||
|
2. Для каждого VMA родителя:
|
||||||
|
- Physical/Shared: просто копирует PTE (та же физическая память).
|
||||||
|
- Anonymous (owned): COW.
|
||||||
|
* Устанавливает COW флаг на родительские PTE.
|
||||||
|
* Убирает WRITABLE с родительских PTE.
|
||||||
|
* Отображает те же фреймы в дочернее пространство (без WRITABLE).
|
||||||
|
* При записи → handle_fault → copy-on-write.
|
||||||
|
|
||||||
|
### `revoke_by_token(cap_token)` / `do_revoke_by_token(cap_token)` — отзыв
|
||||||
|
|
||||||
|
Удаляет все нем-PINNED VMA с заданным cap_token.
|
||||||
|
- Собирает индексы регионов, соответствующих токену.
|
||||||
|
- Удаляет в обратном порядке (чтобы индексы не сдвигались).
|
||||||
|
- TLB flush.
|
||||||
|
|
||||||
|
### `process_pending_revocations()` — обработка очереди отзыва
|
||||||
|
|
||||||
|
Дренирует глобальную `MMU_REVOCATION_QUEUE` и revoke-ит каждый токен.
|
||||||
|
Вызывается перед handle_fault при page fault'е, чтобы избежать deadlock
|
||||||
|
с capability subsystem.
|
||||||
|
|
||||||
|
## TLB Management
|
||||||
|
|
||||||
|
### `local_tlb_flush_asid(asid)` — сброс TLB для одного ASID
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn local_tlb_flush_asid(asid: u16) {
|
||||||
|
if INVPCID_SUPPORTED {
|
||||||
|
// INVPCID type 1 (single-context): сбрасывает только
|
||||||
|
// TLB entries, помеченные данным PCID.
|
||||||
|
asm!("invpcid {ty}, [{desc}]", ...);
|
||||||
|
} else {
|
||||||
|
// Fallback: полный сброс TLB (все контексты)
|
||||||
|
tlb_flush_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `tlb_flush_asid(asid)` — TLB shootdown (SMP)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn tlb_flush_asid(asid: u16) {
|
||||||
|
local_tlb_flush_asid(asid); // всегда сбрасываем локальный
|
||||||
|
|
||||||
|
let target_mask = active_cpus & !(1 << current_core);
|
||||||
|
if target_mask == 0 { return; }
|
||||||
|
|
||||||
|
// Lock SHOOTDOWN_LOCK → записываем ASID → broadcast IPI → wait ACK
|
||||||
|
SHOOTDOWN_ASID.store(asid);
|
||||||
|
SHOOTDOWN_ACK.store(0);
|
||||||
|
lapic::broadcast_ipi_exclude_self(TLB_SHOOTDOWN_VECTOR);
|
||||||
|
|
||||||
|
while SHOOTDOWN_ACK.load() & target_mask != target_mask {
|
||||||
|
spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `tlb_flush_all()` — полный сброс
|
||||||
|
|
||||||
|
Перезапись CR3 без PCID NOFLUSH бита.
|
||||||
|
|
||||||
|
## AddressSpace::activate() — контекстный переключатель
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub unsafe fn activate(&self) {
|
||||||
|
let cr3 = self.pml4_phys.0 | u64::from(self.asid) | (1u64 << 63);
|
||||||
|
// ^ Биты 11:0 = ASID, бит 63 = NOFLUSH (не сбрасывать TLB)
|
||||||
|
asm!("mov cr3, {0}", in(reg) cr3);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Глобальный kernel space
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
|
||||||
|
```
|
||||||
|
|
||||||
|
Инициализируется в `init_kernel_space(pml4_phys)` после создания
|
||||||
|
первичных page tables.
|
||||||
|
|
||||||
|
## Drop для AddressSpace
|
||||||
|
|
||||||
|
При Drop:
|
||||||
|
1. unmap всех регионов (с освобождением owned frames).
|
||||||
|
2. free_frame(pml4_phys).
|
||||||
|
3. free_asid(asid).
|
||||||
|
|
||||||
|
## VmError — типы ошибок
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum VmError {
|
||||||
|
OutOfMemory, // Нет фреймов в PMM
|
||||||
|
RegionOverlap, // VMA пересекается с существующим
|
||||||
|
RegionNotFound, // Нет VMA по адресу
|
||||||
|
InvalidAlignment, // Адрес/размер не выровнены на 4k
|
||||||
|
InvalidRange, // Нулевой размер или переполнение
|
||||||
|
PermissionDenied, // Нет прав на запись
|
||||||
|
UnexpectedFault, // Fault в не-lazy, не-COW регионе
|
||||||
|
NonCanonical, // Некорректный адрес
|
||||||
|
AsidExhausted, // Закончились ASID
|
||||||
|
}
|
||||||
|
```
|
||||||
105
kernel/docs/overview/architecture.md
Normal file
105
kernel/docs/overview/architecture.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Архитектура ядра Elyz
|
||||||
|
|
||||||
|
## Высокоуровневая схема
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ kmain() — точка входа │
|
||||||
|
│ (инициализация: Limine, PMM, LAPIC, IDT, VMM, PM Router, Caps) │
|
||||||
|
└──────────────┬──────────────────────────────────────┬───────────────┘
|
||||||
|
│ │
|
||||||
|
┌──────────▼──────────┐ ┌─────────▼──────────────┐
|
||||||
|
│ memory subsystem │ │ capability system │
|
||||||
|
│ │ │ │
|
||||||
|
│ ┌───────────────┐ │ │ ┌──────────────────┐ │
|
||||||
|
│ │ BitmapPMM │ │ │ │ CapDescriptor │ │
|
||||||
|
│ │ (глобальный) │ │ │ │ (права + объект) │ │
|
||||||
|
│ └───────┬───────┘ │ │ └────────┬─────────┘ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ ┌───────▼───────┐ │ │ ┌────────▼─────────┐ │
|
||||||
|
│ │ PM Actor + │ │ │ │ CNode │ │
|
||||||
|
│ │ BuddyAlloc │ │ │ │ (таблица caps) │ │
|
||||||
|
│ └───────┬───────┘ │ │ └──────────────────┘ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ┌───────▼───────┐ │ └────────────────────────┘
|
||||||
|
│ │ PM Router │ │
|
||||||
|
│ │ (каналы) │ │
|
||||||
|
│ └───────┬───────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌───────▼───────┐ │
|
||||||
|
│ │ AddressSpace │ │
|
||||||
|
│ │ (VMM, PML4) │ │
|
||||||
|
│ └───────────────┘ │
|
||||||
|
└─────────────────────┘
|
||||||
|
|
||||||
|
┌──────────────────────┐ ┌──────────────────────┐
|
||||||
|
│ cpu subsystem │ │ debug subsystem │
|
||||||
|
│ ┌────────────────┐ │ │ ┌─────────────────┐ │
|
||||||
|
│ │ IDT + Interrupt│ │ │ │ Serial port │ │
|
||||||
|
│ │ Handlers │ │ │ │ (COM1) │ │
|
||||||
|
│ └────────────────┘ │ │ └─────────────────┘ │
|
||||||
|
│ ┌────────────────┐ │ │ ┌─────────────────┐ │
|
||||||
|
│ │ Local APIC │ │ │ │ Log macros │ │
|
||||||
|
│ └────────────────┘ │ │ │ (info!/warn!) │ │
|
||||||
|
└──────────────────────┘ │ └─────────────────┘ │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Порядок инициализации в `kmain()`
|
||||||
|
|
||||||
|
1. **Limine requests** — статические структуры, сообщающие bootloader'у,
|
||||||
|
что ядру нужны: framebuffer, memory map, HHDM offset, адрес ядра.
|
||||||
|
2. **BASE_REVISION проверка** — убеждаемся, что Limine совместим.
|
||||||
|
3. **HHDM инициализация** — сохраняем Higher-Half Direct Map offset.
|
||||||
|
4. **Framebuffer/TTY** — создаём консоль для вывода на экран.
|
||||||
|
5. **Serial port** — инициализируем COM1 для отладки.
|
||||||
|
6. **Early IDT** — загружаем базовые обработчики исключений (0–31).
|
||||||
|
7. **BitmapPMM::init()** — сканируем карту памяти, строим битовую карту.
|
||||||
|
8. **LAPIC::init()** — настраиваем Local APIC.
|
||||||
|
9. **Создание P4** — аллоцируем фрейм для корневой таблицы страниц.
|
||||||
|
10. **Identity map** — отображаем всю физическую память в HHDM
|
||||||
|
и (для non-reserved) в идентичное отображение.
|
||||||
|
11. **Map kernel** — отображаем образ ядра.
|
||||||
|
12. **Activate P4** — загружаем новую таблицу страниц в CR3.
|
||||||
|
13. **Heap init** — резервируем 8 MiB для slab-аллокатора.
|
||||||
|
14. **CPU features** — проверяем INVPCID.
|
||||||
|
15. **VMM init** — регистрируем AddressSpace ядра.
|
||||||
|
16. **IDT final** — перезагружаем IDT с Page Fault и TLB shootdown.
|
||||||
|
17. **PM Router init** — создаём 65536 каналов.
|
||||||
|
18. **STI** — разрешаем прерывания.
|
||||||
|
19. **Capability тесты** — тестируем CNode, mint, revoke.
|
||||||
|
20. **PMActor тесты** — создаём актор, тестируем Alloc/Free/Carve/OOM.
|
||||||
|
21. **Buddy тесты** — прямое тестирование аллокатора.
|
||||||
|
22. **PMM stress test** — alloc/free 64 фреймов.
|
||||||
|
23. **HCF** — гасим CPU.
|
||||||
|
|
||||||
|
## Модульная структура
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main.rs — точка входа, инициализация, тесты
|
||||||
|
├── cap/ — capability система
|
||||||
|
│ ├── mod.rs — CNode (таблица дескрипторов)
|
||||||
|
│ ├── descriptor.rs — типы Capability, CapObject, CapRights, Relation
|
||||||
|
│ └── object.rs — KernelObject (ref-counted)
|
||||||
|
├── cpu/ — подсистема CPU
|
||||||
|
│ ├── mod.rs — реэкспорт модулей
|
||||||
|
│ ├── idt.rs — IDT entry/table/ptr
|
||||||
|
│ ├── interrupts.rs — обработчики исключений, TLB shootdown
|
||||||
|
│ └── lapic.rs — Local APIC драйвер
|
||||||
|
├── debug/ — подсистема отладки
|
||||||
|
│ ├── mod.rs — LogLevel, log!/info!/warn! макросы
|
||||||
|
│ └── serial.rs — COM1 serial port драйвер
|
||||||
|
├── events.rs — RevocationQueue (lock-free кольцевой буфер)
|
||||||
|
├── tty.rs — Framebuffer console (PSF2 шрифты)
|
||||||
|
├── mem/ — подсистема памяти
|
||||||
|
│ ├── mod.rs — реэкспорт модулей
|
||||||
|
│ ├── address.rs — PhysAddr, VirtAddr, HHDM offset
|
||||||
|
│ ├── allocator.rs — SlabAllocator + Locked<T> + глобальный аллокатор
|
||||||
|
│ ├── buddy.rs — BuddyAllocator (intrusive list, O(1))
|
||||||
|
│ ├── paging.rs — PageTable, 4-level page walk
|
||||||
|
│ ├── pmm.rs — BitmapPMM (tree bitmap с L1 ускорением)
|
||||||
|
│ ├── pm_manages.rs — PMActor + PMActorQueue (MPSC)
|
||||||
|
│ ├── pm_router.rs — PMRouter (65536 lock-free каналов)
|
||||||
|
│ └── vmm.rs — AddressSpace, VMA, COW, fork, TLB shootdown
|
||||||
|
```
|
||||||
72
kernel/docs/overview/introduction.md
Normal file
72
kernel/docs/overview/introduction.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Введение в ядро Elyz (LISA)
|
||||||
|
|
||||||
|
## Концептуальная модель
|
||||||
|
|
||||||
|
Elyz (LISA) — это модульное, capability-ориентированное ядро для x86-64,
|
||||||
|
спроектированное вокруг следующих архитектурных принципов:
|
||||||
|
|
||||||
|
### 1. Capability-ориентированная безопасность
|
||||||
|
|
||||||
|
Вместо традиционной модели «всё или ничего» (ring 0 vs ring 3), Elyz
|
||||||
|
использует систему **capabilities** (дескрипторов прав). Каждый дескриптор
|
||||||
|
представляет собой *неподделываемый токен*, дающий доступ к конкретному
|
||||||
|
объекту ядра (фрейму памяти, CNode, PMActor) с определёнными правами.
|
||||||
|
|
||||||
|
- Capability — это не просто число; это структура с полями `object`, `rights`,
|
||||||
|
`relation`, `token_sig`.
|
||||||
|
- Capability можно *создавать* (mint) с урезанными правами от родительского
|
||||||
|
дескриптора.
|
||||||
|
- Capability можно *отозвать* (revoke), что каскадно уничтожает всех потомков.
|
||||||
|
- Система гарантирует, что access — это всегда наличие capability.
|
||||||
|
|
||||||
|
### 2. Многоуровневое управление памятью
|
||||||
|
|
||||||
|
Управление физической памятью разделено на три уровня:
|
||||||
|
|
||||||
|
| Уровень | Компонент | Ответственность |
|
||||||
|
|---------|-----------|-----------------|
|
||||||
|
| 1 (глобальный) | `BitmapPMM` | Владение всей физической памятью, аллокация/освобождение по 4KiB фреймам. Инициализируется из карты памяти bootloader'а. |
|
||||||
|
| 2 (распределённый) | `PMActor` | Актёр физической памяти — владеет *диапазоном* физической памяти, использует `BuddyAllocator` для аллокации внутри этого диапазона. Каждый PMActor — изолированный аллокатор. |
|
||||||
|
| 3 (виртуальный) | `AddressSpace` (VMM) | Управляет виртуальными адресными пространствами (PML4 + VMA-деревья). Использует PMM для аллокации PT-страниц и lazy demand-paging. |
|
||||||
|
|
||||||
|
Эти уровни связывает **PM Router** — lock-free система каналов для асинхронной
|
||||||
|
пересылки запросов и ответов между исполнителями.
|
||||||
|
|
||||||
|
### 3. Асинхронная модель акторов
|
||||||
|
|
||||||
|
PMActor следует модели CSP (Communicating Sequential Processes):
|
||||||
|
|
||||||
|
- Каждый актор имеет lock-free MPSC-очередь входящих сообщений.
|
||||||
|
- Потоки/процессы отправляют запросы (Alloc/Free/Carve) в очередь актора.
|
||||||
|
- Когда актор получает CPU time, он вызывает `process_messages()` и
|
||||||
|
отправляет ответы через PM Router.
|
||||||
|
- PM Router использует 65536 предварительно выделенных каналов для
|
||||||
|
lock-free маршрутизации результатов.
|
||||||
|
|
||||||
|
### 4. Виртуальная память с Copy-on-Write
|
||||||
|
|
||||||
|
`AddressSpace` поддерживает:
|
||||||
|
- **Lazy demand-paging**: физическая страница выделяется только при
|
||||||
|
page fault'е.
|
||||||
|
- **Copy-on-Write (COW)**: при fork'е адресного пространства страницы
|
||||||
|
разделяются между родителем и потомком; копирование происходит
|
||||||
|
при первой записи.
|
||||||
|
- **Zero-copy shared mappings**: физические страницы, принадлежащие
|
||||||
|
capability, могут быть отображены в другое адресное пространство
|
||||||
|
без копирования. Владелец страниц — capability, а не VMA.
|
||||||
|
|
||||||
|
### 5. Отзыв (Revocation) через lock-free очередь
|
||||||
|
|
||||||
|
Система отзыва дескрипторов использует глобальную lock-free кольцевую
|
||||||
|
очередь `MMU_REVOCATION_QUEUE`. При отзыве capability:
|
||||||
|
1. Токен capability помещается в очередь.
|
||||||
|
2. При следующем page fault'е VMM обрабатывает накопленные отзывы.
|
||||||
|
3. Все VMA, связанные с отозванным токеном, аннулируются.
|
||||||
|
|
||||||
|
### 6. Минималистичный дизайн
|
||||||
|
|
||||||
|
- `#![no_std]` — никакой стандартной библиотеки.
|
||||||
|
- `#![no_main]` — точка входа `kmain`.
|
||||||
|
- Все структуры данных, аллокаторы и драйверы написаны с нуля.
|
||||||
|
- Единственные внешние зависимости: `limine` (bootloader протокол),
|
||||||
|
`embedded-graphics` (шрифты), `bitflags`.
|
||||||
150
kernel/docs/tty.md
Normal file
150
kernel/docs/tty.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# Framebuffer Console: `tty.rs`
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Драйвер графического вывода текста на framebuffer. Использует
|
||||||
|
PSF2 (PC Screen Font 2) шрифты для отрисовки символов.
|
||||||
|
|
||||||
|
Является основным визуальным каналом вывода ядра (через info! макрос).
|
||||||
|
|
||||||
|
## Структура Console
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct Console<'a> {
|
||||||
|
framebuffer: &'a Framebuffer<'a>, // ссылка на Limine framebuffer
|
||||||
|
font: &'static [u8], // PSF2 шрифт
|
||||||
|
pub x: usize, // текущая позиция X (колонка)
|
||||||
|
pub y: usize, // текущая позиция Y (строка)
|
||||||
|
pub fg_color: u32, // цвет текста (RGB)
|
||||||
|
pub bg_color: u32, // цвет фона (RGB)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## PSF2 Header
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[repr(C, packed)]
|
||||||
|
struct Psf2Header {
|
||||||
|
magic: u32, // 0x864AB572 (PSF2 magic)
|
||||||
|
version: u32,
|
||||||
|
header_size: u32, // размер заголовка
|
||||||
|
flags: u32,
|
||||||
|
num_glyphs: u32, // количество глифов
|
||||||
|
bytes_per_glyph: u32, // байт на глиф
|
||||||
|
height: u32, // высота глифа в пикселях
|
||||||
|
width: u32, // ширина глифа в пикселях
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Шрифт — `font.psf` (8×16, встроен через `include_bytes!`).
|
||||||
|
|
||||||
|
## Операции
|
||||||
|
|
||||||
|
### clear()
|
||||||
|
|
||||||
|
Заполняет весь framebuffer нулями. Сбрасывает позицию курсора в (0, 0).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
core::ptr::write_bytes(fb.addr(), 0, (fb.pitch() * fb.height()) as usize);
|
||||||
|
}
|
||||||
|
self.x = 0;
|
||||||
|
self.y = 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### scroll()
|
||||||
|
|
||||||
|
Сдвигает содержимое framebuffer вверх на высоту шрифта.
|
||||||
|
Новые строки заполняются нулями.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn scroll(&mut self) {
|
||||||
|
let shift = font_height * pitch; // смещение на 1 строку
|
||||||
|
let size = pitch * (height - font_height); // сколько копировать
|
||||||
|
unsafe {
|
||||||
|
ptr::copy(addr.add(shift), addr, size);
|
||||||
|
ptr::write_bytes(addr.add(size), 0, shift);
|
||||||
|
}
|
||||||
|
self.y -= font_height;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### draw_glyph(glyph_index, x, y)
|
||||||
|
|
||||||
|
Рисует глиф (символ) в заданной позиции.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn draw_glyph(&mut self, glyph_index: u32, x: usize, y: usize) {
|
||||||
|
let bytes_per_line = (width + 7) / 8; // пиксели → байты
|
||||||
|
let glyph_offset = header_size + (glyph_index * bytes_per_glyph);
|
||||||
|
|
||||||
|
for cy in 0..height {
|
||||||
|
let glyph_row = self.font[glyph_offset + cy * bytes_per_line];
|
||||||
|
for cx in 0..width {
|
||||||
|
if (glyph_row & (0x80 >> cx)) != 0 {
|
||||||
|
// Пиксель включён — рисуем fg_color
|
||||||
|
let offset = ((y + cy) * fb_pitch) + ((x + cx) * 4);
|
||||||
|
unsafe {
|
||||||
|
fb_addr.add(offset).cast::<u32>().write_volatile(self.fg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Если пиксель выключен — фон остаётся (нет очистки)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `width=8`, `bytes_per_line=1`: каждый ряд глифа = 1 байт.
|
||||||
|
- `(0x80 >> cx)`: проверка бита слева направо (MSB first).
|
||||||
|
- `write_volatile`: запрещает компилятору оптимизировать запись в MMIO.
|
||||||
|
|
||||||
|
### write_char(c)
|
||||||
|
|
||||||
|
Печатает символ:
|
||||||
|
- `\n` → переход на новую строку.
|
||||||
|
- Другие символы → draw_glyph. Если символ > num_glyphs → глиф 0.
|
||||||
|
- Перенос строки по ширине экрана.
|
||||||
|
- Скроллинг при достижении низа.
|
||||||
|
|
||||||
|
## fmt::Write реализация
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl fmt::Write for Console<'_> {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
for c in s.chars() {
|
||||||
|
self.write_char(c);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Использование в kmain()
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let fb = fb_res.framebuffers().next().expect("No framebuffer");
|
||||||
|
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
||||||
|
console.clear();
|
||||||
|
|
||||||
|
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
||||||
|
// ... остальные логи
|
||||||
|
|
||||||
|
let _ = writeln!(console, "{}", logo); // ASCII art при загрузке
|
||||||
|
```
|
||||||
|
|
||||||
|
## Детали реализации
|
||||||
|
|
||||||
|
- **Цвета**: write_volatile для 32-битного пикселя. Формат — RGBA?
|
||||||
|
(зависит от режима framebuffer).
|
||||||
|
- **Фон**: не перерисовывается. Только пиксели текста.
|
||||||
|
Для полной очистки — clear().
|
||||||
|
- **PSF2 magic**: `0x864AB572` — проверяется в заголовке.
|
||||||
|
- **Производительность**: draw_glyph простая, без буферизации.
|
||||||
|
Для скроллинга — memcpy всего framebuffer.
|
||||||
|
|
||||||
|
## Зависимости
|
||||||
|
|
||||||
|
- `KERNEL_FONT: &[u8]` — встроен в main.rs через `include_bytes!("font.psf")`.
|
||||||
|
- `limine::framebuffer::Framebuffer` — через HHDM отображённый framebuffer.
|
||||||
@@ -7,25 +7,29 @@ pub static mut IDT: crate::cpu::idt::InterruptDescriptorTable = crate::cpu::idt:
|
|||||||
|
|
||||||
pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;
|
pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;
|
||||||
|
|
||||||
global_asm!(
|
macro_rules! exception_stub {
|
||||||
".global page_fault_stub",
|
($name:ident, $handler:ident) => {
|
||||||
"page_fault_stub:",
|
concat!(
|
||||||
"push rax",
|
".global ", stringify!($name), "\n",
|
||||||
"push rcx",
|
stringify!($name), ":\n",
|
||||||
"push rdx",
|
"push rax\npush rcx\npush rdx\npush rbx\npush rbp\n",
|
||||||
"push rbx",
|
"push rsi\npush rdi\npush r8\npush r9\npush r10\n",
|
||||||
"push rbp",
|
"push r11\npush r12\npush r13\npush r14\npush r15\n",
|
||||||
"push rsi",
|
"mov rdi, [rsp + 15*8]\n",
|
||||||
"push rdi",
|
"call ", stringify!($handler), "\n",
|
||||||
"push r8",
|
"pop r15\npop r14\npop r13\npop r12\npop r11\n",
|
||||||
"push r9",
|
"pop r10\npop r9\npop r8\npop rdi\npop rsi\n",
|
||||||
"push r10",
|
"pop rbp\npop rbx\npop rdx\npop rcx\npop rax\n",
|
||||||
"push r11",
|
"add rsp, 8\n",
|
||||||
"push r12",
|
"iretq\n",
|
||||||
"push r13",
|
)
|
||||||
"push r14",
|
};
|
||||||
"push r15",
|
}
|
||||||
|
|
||||||
|
global_asm!(
|
||||||
|
exception_stub!(page_fault_stub, rust_page_fault_handler),
|
||||||
|
exception_stub!(gpf_stub, rust_gpf_handler),
|
||||||
|
exception_stub!(double_fault_stub, rust_double_fault_handler),
|
||||||
".global tlb_shootdown_stub",
|
".global tlb_shootdown_stub",
|
||||||
"tlb_shootdown_stub:",
|
"tlb_shootdown_stub:",
|
||||||
"push rax",
|
"push rax",
|
||||||
@@ -43,12 +47,7 @@ global_asm!(
|
|||||||
"push r13",
|
"push r13",
|
||||||
"push r14",
|
"push r14",
|
||||||
"push r15",
|
"push r15",
|
||||||
|
|
||||||
"call rust_tlb_shootdown_handler",
|
"call rust_tlb_shootdown_handler",
|
||||||
|
|
||||||
"mov rdi, [rsp + 15*8]",
|
|
||||||
"call rust_page_fault_handler",
|
|
||||||
|
|
||||||
"pop r15",
|
"pop r15",
|
||||||
"pop r14",
|
"pop r14",
|
||||||
"pop r13",
|
"pop r13",
|
||||||
@@ -64,36 +63,202 @@ global_asm!(
|
|||||||
"pop rdx",
|
"pop rdx",
|
||||||
"pop rcx",
|
"pop rcx",
|
||||||
"pop rax",
|
"pop rax",
|
||||||
|
|
||||||
"add rsp, 8",
|
|
||||||
"iretq"
|
"iretq"
|
||||||
);
|
);
|
||||||
|
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
fn page_fault_stub();
|
fn page_fault_stub();
|
||||||
|
fn gpf_stub();
|
||||||
|
fn double_fault_stub();
|
||||||
fn tlb_shootdown_stub();
|
fn tlb_shootdown_stub();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_idt() {
|
pub fn init_early_exceptions() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let idt_mut_ptr = core::ptr::addr_of_mut!(IDT);
|
let idt = core::ptr::addr_of_mut!(IDT);
|
||||||
(*idt_mut_ptr).set_handler(14, page_fault_stub as u64);
|
let early: [u64; 32] = [
|
||||||
|
early_handler_0 as u64, early_handler_1 as u64,
|
||||||
|
early_handler_2 as u64, early_handler_3 as u64,
|
||||||
|
early_handler_4 as u64, early_handler_5 as u64,
|
||||||
|
early_handler_6 as u64, early_handler_7 as u64,
|
||||||
|
early_handler_8 as u64, early_handler_9 as u64,
|
||||||
|
early_handler_10 as u64, early_handler_11 as u64,
|
||||||
|
early_handler_12 as u64, early_handler_13 as u64,
|
||||||
|
early_handler_14 as u64, early_handler_15 as u64,
|
||||||
|
early_handler_16 as u64, early_handler_17 as u64,
|
||||||
|
early_handler_18 as u64, early_handler_19 as u64,
|
||||||
|
early_handler_20 as u64, early_handler_21 as u64,
|
||||||
|
early_handler_22 as u64, early_handler_23 as u64,
|
||||||
|
early_handler_24 as u64, early_handler_25 as u64,
|
||||||
|
early_handler_26 as u64, early_handler_27 as u64,
|
||||||
|
early_handler_28 as u64, early_handler_29 as u64,
|
||||||
|
early_handler_30 as u64, early_handler_31 as u64,
|
||||||
|
];
|
||||||
|
for (v, &handler) in early.iter().enumerate() {
|
||||||
|
(*idt).set_handler(v as u8, handler);
|
||||||
|
}
|
||||||
|
// Override with proper handlers for vectors 8, 13, 14 and TLB IPI.
|
||||||
|
(*idt).set_handler(8, double_fault_stub as u64);
|
||||||
|
(*idt).set_handler(13, gpf_stub as u64);
|
||||||
|
(*idt).set_handler(14, page_fault_stub as u64);
|
||||||
|
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
|
||||||
|
|
||||||
(*idt_mut_ptr).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
|
let ptr: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
|
||||||
|
ptr.load();
|
||||||
let idt_static_ref: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
|
|
||||||
idt_static_ref.load();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init_idt() {
|
||||||
pub fn process_deferred_mmu_events() {
|
// init_early_exceptions already loaded the IDT; this just overrides
|
||||||
let mut vmm_guard = KERNEL_SPACE.lock();
|
// vectors that the full kernel needs. Calling lidt again is harmless.
|
||||||
if let Some(space) = vmm_guard.as_mut() {
|
unsafe {
|
||||||
space.process_pending_revocations();
|
let idt = core::ptr::addr_of_mut!(IDT);
|
||||||
|
(*idt).set_handler(14, page_fault_stub as u64);
|
||||||
|
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub as u64);
|
||||||
|
let ptr: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT);
|
||||||
|
ptr.load();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn rust_gpf_handler(error_code: u64) -> ! {
|
||||||
|
crate::debug::serial::write_global(format_args!(
|
||||||
|
"\n!!! GENERAL PROTECTION FAULT !!! error_code={:#x}\n\
|
||||||
|
CPU halted.\n",
|
||||||
|
error_code
|
||||||
|
));
|
||||||
|
loop {
|
||||||
|
unsafe { asm!("cli; hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn rust_double_fault_handler(error_code: u64) -> ! {
|
||||||
|
crate::debug::serial::write_global(format_args!(
|
||||||
|
"\n!!! DOUBLE FAULT !!! error_code={:#x}\n\
|
||||||
|
CPU halted.\n",
|
||||||
|
error_code
|
||||||
|
));
|
||||||
|
loop {
|
||||||
|
unsafe { asm!("cli; hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global_asm!(r#"
|
||||||
|
.altmacro
|
||||||
|
|
||||||
|
.macro early_stub vec
|
||||||
|
.globl early_handler_\vec
|
||||||
|
.balign 16
|
||||||
|
early_handler_\vec:
|
||||||
|
push 0 /* dummy error code */
|
||||||
|
push \vec /* vector number */
|
||||||
|
jmp early_common
|
||||||
|
.endm
|
||||||
|
|
||||||
|
early_stub 0
|
||||||
|
early_stub 1
|
||||||
|
early_stub 2
|
||||||
|
early_stub 3
|
||||||
|
early_stub 4
|
||||||
|
early_stub 5
|
||||||
|
early_stub 6
|
||||||
|
early_stub 7
|
||||||
|
early_stub 8
|
||||||
|
early_stub 9
|
||||||
|
early_stub 10
|
||||||
|
early_stub 11
|
||||||
|
early_stub 12
|
||||||
|
early_stub 13
|
||||||
|
early_stub 14
|
||||||
|
early_stub 15
|
||||||
|
early_stub 16
|
||||||
|
early_stub 17
|
||||||
|
early_stub 18
|
||||||
|
early_stub 19
|
||||||
|
early_stub 20
|
||||||
|
early_stub 21
|
||||||
|
early_stub 22
|
||||||
|
early_stub 23
|
||||||
|
early_stub 24
|
||||||
|
early_stub 25
|
||||||
|
early_stub 26
|
||||||
|
early_stub 27
|
||||||
|
early_stub 28
|
||||||
|
early_stub 29
|
||||||
|
early_stub 30
|
||||||
|
early_stub 31
|
||||||
|
|
||||||
|
early_common:
|
||||||
|
push rax
|
||||||
|
push rcx
|
||||||
|
push rdx
|
||||||
|
push rbx
|
||||||
|
push rbp
|
||||||
|
push rsi
|
||||||
|
push rdi
|
||||||
|
push r8
|
||||||
|
push r9
|
||||||
|
push r10
|
||||||
|
push r11
|
||||||
|
push r12
|
||||||
|
push r13
|
||||||
|
push r14
|
||||||
|
push r15
|
||||||
|
mov rdi, [rsp + 15*8] /* vector number */
|
||||||
|
mov rsi, [rsp + 16*8] /* error code (or dummy 0) */
|
||||||
|
call rust_early_exception_handler
|
||||||
|
/* never returns */
|
||||||
|
"#);
|
||||||
|
|
||||||
|
unsafe extern "C" {
|
||||||
|
fn early_handler_0();
|
||||||
|
fn early_handler_1();
|
||||||
|
fn early_handler_2();
|
||||||
|
fn early_handler_3();
|
||||||
|
fn early_handler_4();
|
||||||
|
fn early_handler_5();
|
||||||
|
fn early_handler_6();
|
||||||
|
fn early_handler_7();
|
||||||
|
fn early_handler_8();
|
||||||
|
fn early_handler_9();
|
||||||
|
fn early_handler_10();
|
||||||
|
fn early_handler_11();
|
||||||
|
fn early_handler_12();
|
||||||
|
fn early_handler_13();
|
||||||
|
fn early_handler_14();
|
||||||
|
fn early_handler_15();
|
||||||
|
fn early_handler_16();
|
||||||
|
fn early_handler_17();
|
||||||
|
fn early_handler_18();
|
||||||
|
fn early_handler_19();
|
||||||
|
fn early_handler_20();
|
||||||
|
fn early_handler_21();
|
||||||
|
fn early_handler_22();
|
||||||
|
fn early_handler_23();
|
||||||
|
fn early_handler_24();
|
||||||
|
fn early_handler_25();
|
||||||
|
fn early_handler_26();
|
||||||
|
fn early_handler_27();
|
||||||
|
fn early_handler_28();
|
||||||
|
fn early_handler_29();
|
||||||
|
fn early_handler_30();
|
||||||
|
fn early_handler_31();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn rust_early_exception_handler(vector: u64, _error_code: u64) -> ! {
|
||||||
|
crate::debug::serial::write_global(format_args!(
|
||||||
|
"\n!!! EARLY EXCEPTION !!! vector={} error_code={:#x}\n\
|
||||||
|
CPU halted.\n",
|
||||||
|
vector, _error_code
|
||||||
|
));
|
||||||
|
loop {
|
||||||
|
unsafe { asm!("cli; hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn rust_page_fault_handler(error_code: u64) {
|
pub extern "C" fn rust_page_fault_handler(error_code: u64) {
|
||||||
let fault_addr: u64;
|
let fault_addr: u64;
|
||||||
@@ -105,13 +270,18 @@ pub extern "C" fn rust_page_fault_handler(error_code: u64) {
|
|||||||
let present = (error_code & 0x1) != 0;
|
let present = (error_code & 0x1) != 0;
|
||||||
let virt_addr = VirtAddr(fault_addr);
|
let virt_addr = VirtAddr(fault_addr);
|
||||||
|
|
||||||
process_deferred_mmu_events();
|
// Acquire KERNEL_SPACE lock once for both deferred events and fault handling.
|
||||||
|
// TODO SMP: switch to RwLock so concurrent read-only faults are not
|
||||||
|
// serialised. `handle_fault` is read-only for COW faults but mutates
|
||||||
|
// the VMA tree on demand-paging — a lock-free VMA tree or per-region
|
||||||
|
// locks would be ideal.
|
||||||
let mut vmm_guard = KERNEL_SPACE.lock();
|
let mut vmm_guard = KERNEL_SPACE.lock();
|
||||||
|
|
||||||
if let Some(space) = vmm_guard.as_mut() {
|
if let Some(space) = vmm_guard.as_mut() {
|
||||||
|
space.process_pending_revocations();
|
||||||
|
|
||||||
match space.handle_fault(virt_addr, write) {
|
match space.handle_fault(virt_addr, write) {
|
||||||
Ok(_) => return,
|
Ok(_) => {},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
panic!(
|
panic!(
|
||||||
"KERNEL PANIC: Unprocessed failure of virtual memory (Page Fault)!\n\
|
"KERNEL PANIC: Unprocessed failure of virtual memory (Page Fault)!\n\
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// src/cpu/lapic.rs
|
// src/cpu/lapic.rs
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicU64, Ordering};
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use crate::mem::address::get_hhdm;
|
||||||
|
|
||||||
pub const LAPIC_DEFAULT_BASE: u64 = 0xFEE00_000;
|
pub const LAPIC_DEFAULT_BASE: u64 = 0xFEE00_000;
|
||||||
const LAPIC_EOI: u64 = 0x0B0;
|
const LAPIC_EOI: u64 = 0x0B0;
|
||||||
@@ -8,8 +9,8 @@ const LAPIC_ICR_LOW: u64 = 0x300;
|
|||||||
|
|
||||||
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
pub fn init(hhdm_offset: u64) {
|
pub fn init() {
|
||||||
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + hhdm_offset, Ordering::SeqCst);
|
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + get_hhdm(), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|||||||
@@ -36,15 +36,13 @@ macro_rules! log {
|
|||||||
let _ = writeln!($console, $($arg)*);
|
let _ = writeln!($console, $($arg)*);
|
||||||
|
|
||||||
// Serial debug output
|
// Serial debug output
|
||||||
let mut sp = unsafe { $crate::debug::serial::SerialPort::init() };
|
$crate::debug::serial::write_global(format_args!(
|
||||||
let _ = writeln!(
|
"{}[{:>5}]\x1b[0m {:<8} | {}\n",
|
||||||
sp,
|
|
||||||
"{}[{:>5}]\x1b[0m {:<8} | {}",
|
|
||||||
$level.serial_color_code(),
|
$level.serial_color_code(),
|
||||||
"LOG",
|
"LOG",
|
||||||
$module,
|
$module,
|
||||||
format_args!($($arg)*)
|
format_args!($($arg)*)
|
||||||
);
|
));
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
|
use core::fmt::Write;
|
||||||
|
use crate::mem::allocator::Locked;
|
||||||
|
|
||||||
pub struct SerialPort(u16);
|
pub struct SerialPort(u16);
|
||||||
|
|
||||||
@@ -7,16 +9,14 @@ impl SerialPort {
|
|||||||
|
|
||||||
pub unsafe fn init() -> Self {
|
pub unsafe fn init() -> Self {
|
||||||
let port = Self::COM1;
|
let port = Self::COM1;
|
||||||
// В новых версиях Rust даже внутри unsafe fn
|
|
||||||
// вызовы других unsafe функций требуют явного блока
|
|
||||||
unsafe {
|
unsafe {
|
||||||
outb(port + 1, 0x00); // Disable interrupts
|
|
||||||
outb(port + 3, 0x80); // Enable DLAB
|
|
||||||
outb(port + 0, 0x03); // Divisor 3 (38400 baud)
|
|
||||||
outb(port + 1, 0x00);
|
outb(port + 1, 0x00);
|
||||||
outb(port + 3, 0x03); // 8 bits, no parity, 1 stop bit
|
outb(port + 3, 0x80);
|
||||||
outb(port + 2, 0xC7); // Enable FIFO
|
outb(port + 0, 0x03);
|
||||||
outb(port + 4, 0x0B); // IRQs enabled, RTS/DSR set
|
outb(port + 1, 0x00);
|
||||||
|
outb(port + 3, 0x03);
|
||||||
|
outb(port + 2, 0xC7);
|
||||||
|
outb(port + 4, 0x0B);
|
||||||
}
|
}
|
||||||
SerialPort(port)
|
SerialPort(port)
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,20 @@ impl core::fmt::Write for SerialPort {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SERIAL_PORT: Locked<Option<SerialPort>> = Locked::new(None);
|
||||||
|
|
||||||
|
pub fn init_global() {
|
||||||
|
let mut guard = SERIAL_PORT.lock();
|
||||||
|
*guard = Some(unsafe { SerialPort::init() });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_global(args: core::fmt::Arguments) {
|
||||||
|
let mut guard = SERIAL_PORT.lock();
|
||||||
|
if let Some(ref mut sp) = *guard {
|
||||||
|
let _ = sp.write_fmt(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn outb(port: u16, val: u8) {
|
unsafe fn outb(port: u16, val: u8) {
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("out dx, al", in("dx") port, in("al") val, options(nomem, nostack, preserves_flags));
|
asm!("out dx, al", in("dx") port, in("al") val, options(nomem, nostack, preserves_flags));
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use crate::mem::paging::{PageTable, PageTableFlags};
|
|||||||
use crate::mem::address::{PhysAddr, VirtAddr};
|
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||||
use crate::cap::{Relation, CapRights, Capability, CapObject};
|
use crate::cap::{Relation, CapRights, Capability, CapObject};
|
||||||
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
|
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
|
||||||
|
use crate::mem::buddy::BuddyAllocator;
|
||||||
|
|
||||||
pub mod cap;
|
pub mod cap;
|
||||||
pub mod cpu;
|
pub mod cpu;
|
||||||
@@ -175,28 +176,42 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
let hhdm_res = HHDM_REQUEST.get_response().expect("Limine: No HHDM");
|
let hhdm_res = HHDM_REQUEST.get_response().expect("Limine: No HHDM");
|
||||||
let kaddr_res = KERNEL_ADDR_REQUEST.get_response().expect("Limine: No Kernel Address");
|
let kaddr_res = KERNEL_ADDR_REQUEST.get_response().expect("Limine: No Kernel Address");
|
||||||
let hhdm_offset = hhdm_res.offset();
|
let hhdm_offset = hhdm_res.offset();
|
||||||
|
crate::mem::address::init_hhdm(hhdm_offset);
|
||||||
|
|
||||||
let fb = fb_res.framebuffers().next().expect("Limine: No active framebuffer found");
|
let fb = fb_res.framebuffers().next().expect("Limine: No active framebuffer found");
|
||||||
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
||||||
console.clear();
|
console.clear();
|
||||||
|
|
||||||
|
debug::serial::init_global();
|
||||||
|
|
||||||
|
cpu::interrupts::init_early_exceptions();
|
||||||
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
||||||
|
|
||||||
unsafe { mem::pmm::BitmapPMM::init(&mmap_res, hhdm_offset); }
|
unsafe { mem::pmm::BitmapPMM::init(&mmap_res); }
|
||||||
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
|
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
|
||||||
|
|
||||||
|
cpu::lapic::init();
|
||||||
|
info!(console, "LAPIC", "Local APIC initialized.");
|
||||||
|
|
||||||
|
// debug: locate the free page
|
||||||
|
info!(console, "BOOT", "alloc_frame...");
|
||||||
let p4_phys = mem::pmm::alloc_frame().expect("OOM: Failed to allocate P4 table");
|
let p4_phys = mem::pmm::alloc_frame().expect("OOM: Failed to allocate P4 table");
|
||||||
let p4 = unsafe { &mut *p4_phys.to_virt(hhdm_offset).as_mut_ptr::<PageTable>() };
|
info!(console, "BOOT", "alloc_frame ok: phys=0x{:x}", p4_phys.0);
|
||||||
|
let virt = p4_phys.to_virt();
|
||||||
|
info!(console, "BOOT", "virt=0x{:x}", virt.0);
|
||||||
|
let p4 = unsafe { &mut *virt.as_mut_ptr::<PageTable>() };
|
||||||
|
info!(console, "BOOT", "zeroing page...");
|
||||||
unsafe { core::ptr::write_bytes(p4 as *mut _ as *mut u8, 0, 4096); }
|
unsafe { core::ptr::write_bytes(p4 as *mut _ as *mut u8, 0, 4096); }
|
||||||
|
info!(console, "BOOT", "zero done");
|
||||||
|
|
||||||
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
|
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
|
||||||
|
|
||||||
for entry in mmap_res.entries() {
|
for (i, entry) in mmap_res.entries().iter().enumerate() {
|
||||||
let phys = PhysAddr(entry.base);
|
let phys = PhysAddr(entry.base);
|
||||||
let virt_hhdm = phys.to_virt(hhdm_offset);
|
let virt_hhdm = phys.to_virt();
|
||||||
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
|
p4.map_region(virt_hhdm, phys, entry.length, flags);
|
||||||
if entry.entry_type != limine::memory_map::EntryType::RESERVED {
|
if entry.entry_type != limine::memory_map::EntryType::RESERVED {
|
||||||
p4.map_region(VirtAddr(entry.base), phys, entry.length, flags, hhdm_offset);
|
p4.map_region(VirtAddr(entry.base), phys, entry.length, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +220,6 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
PhysAddr(kaddr_res.physical_base()),
|
PhysAddr(kaddr_res.physical_base()),
|
||||||
0x1000 * 1024,
|
0x1000 * 1024,
|
||||||
flags,
|
flags,
|
||||||
hhdm_offset
|
|
||||||
);
|
);
|
||||||
|
|
||||||
info!(console, "MMU", "Activating Kernel Page Tables...");
|
info!(console, "MMU", "Activating Kernel Page Tables...");
|
||||||
@@ -215,7 +229,7 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
let heap_size = 8 * 1024 * 1024;
|
let heap_size = 8 * 1024 * 1024;
|
||||||
for i in (0..heap_size).step_by(4096) {
|
for i in (0..heap_size).step_by(4096) {
|
||||||
let frame = mem::pmm::alloc_frame().expect("OOM: Heap allocation failed");
|
let frame = mem::pmm::alloc_frame().expect("OOM: Heap allocation failed");
|
||||||
p4.map_page(VirtAddr(heap_start + i as u64), frame, flags, hhdm_offset);
|
p4.map_page(VirtAddr(heap_start + i as u64), frame, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -225,7 +239,7 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
info!(console, "HEAP", "Kernel Slab Allocator is online.");
|
info!(console, "HEAP", "Kernel Slab Allocator is online.");
|
||||||
|
|
||||||
mem::init_cpu_features();
|
mem::init_cpu_features();
|
||||||
mem::vmm::init_kernel_space(p4_phys, hhdm_offset);
|
mem::vmm::init_kernel_space(p4_phys);
|
||||||
info!(console, "VMM", "Kernel Address Space registered successfully.");
|
info!(console, "VMM", "Kernel Address Space registered successfully.");
|
||||||
|
|
||||||
cpu::interrupts::init_idt();
|
cpu::interrupts::init_idt();
|
||||||
@@ -354,6 +368,74 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
panic!("CRITICAL STATE LOSS: Memory leak detected inside PMActor context!");
|
panic!("CRITICAL STATE LOSS: Memory leak detected inside PMActor context!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === ARCH 2.2: Buddy Allocator Intrusive List — Direct Test ===
|
||||||
|
info!(console, "PM", "=-= Buddy Allocator Direct Test (Intrusive List) =-=");
|
||||||
|
|
||||||
|
let buddy_test_pages = 128;
|
||||||
|
let buddy_test_base = PhysAddr(0x5000_0000);
|
||||||
|
let mut buddy = BuddyAllocator::new(buddy_test_pages, buddy_test_base.0);
|
||||||
|
|
||||||
|
let b1 = buddy.alloc(0).expect("buddy: alloc order 0");
|
||||||
|
let b2 = buddy.alloc(0).expect("buddy: alloc order 0 #2");
|
||||||
|
buddy.free(b1, 0);
|
||||||
|
buddy.free(b2, 0);
|
||||||
|
assert_eq!(buddy.free_pages(), buddy_test_pages, "buddy: pages not fully recovered after simple alloc/free");
|
||||||
|
info!(console, "PM", " [OK] Simple alloc/free");
|
||||||
|
|
||||||
|
let b3 = buddy.alloc(1).expect("buddy: alloc order 1");
|
||||||
|
assert_eq!(b3, 0, "buddy: first order 1 block at 0");
|
||||||
|
let b4 = buddy.alloc(1).expect("buddy: alloc order 1 #2");
|
||||||
|
assert_eq!(b4, 2, "buddy: second order 1 block at 2");
|
||||||
|
buddy.free(b3, 1);
|
||||||
|
buddy.free(b4, 1);
|
||||||
|
let b5 = buddy.alloc(2).expect("buddy: order 2 after coalescing");
|
||||||
|
assert_eq!(b5, 0, "buddy: coalesced block at 0");
|
||||||
|
buddy.free(b5, 2);
|
||||||
|
info!(console, "PM", " [OK] Coalescing across orders (O(1) intrusive list)");
|
||||||
|
|
||||||
|
let mut allocs = Vec::new();
|
||||||
|
loop {
|
||||||
|
match buddy.alloc(0) {
|
||||||
|
Some(idx) => allocs.push(idx),
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(buddy.is_exhausted(), "buddy: should be exhausted");
|
||||||
|
info!(console, "PM", " [OK] Exhaustion after {} allocs", allocs.len());
|
||||||
|
|
||||||
|
for (i, idx) in allocs.iter().enumerate() {
|
||||||
|
buddy.free(*idx, 0);
|
||||||
|
}
|
||||||
|
assert_eq!(buddy.free_pages(), buddy_test_pages, "buddy: not fully recovered");
|
||||||
|
info!(console, "PM", " [OK] Full recovery after freeing {} pages", allocs.len());
|
||||||
|
|
||||||
|
info!(console, "PM", "~) BUDDY DIRECT TEST PASSED (~");
|
||||||
|
|
||||||
|
// === ARCH 2.3: PMM Tree Bitmap — alloc/free stress test ===
|
||||||
|
info!(console, "PM", "=-= PMM Tree Bitmap Alloc/Free Stress Test =-=");
|
||||||
|
|
||||||
|
const PMM_TEST_COUNT: usize = 64;
|
||||||
|
let mut frames = [PhysAddr(0); PMM_TEST_COUNT];
|
||||||
|
for i in 0..PMM_TEST_COUNT {
|
||||||
|
frames[i] = mem::pmm::alloc_frame().expect("PMM tree: OOM during alloc test");
|
||||||
|
}
|
||||||
|
info!(console, "PM", " [OK] Allocated {} frames", PMM_TEST_COUNT);
|
||||||
|
|
||||||
|
for f in &frames {
|
||||||
|
mem::pmm::free_frame(*f);
|
||||||
|
}
|
||||||
|
info!(console, "PM", " [OK] Freed {} frames back", PMM_TEST_COUNT);
|
||||||
|
|
||||||
|
for i in 0..PMM_TEST_COUNT {
|
||||||
|
frames[i] = mem::pmm::alloc_frame().expect("PMM tree: OOM after free cycle");
|
||||||
|
}
|
||||||
|
info!(console, "PM", " [OK] Re-allocated {} frames after free cycle", PMM_TEST_COUNT);
|
||||||
|
|
||||||
|
for f in &frames {
|
||||||
|
mem::pmm::free_frame(*f);
|
||||||
|
}
|
||||||
|
info!(console, "PM", "~) PMM TREE BITMAP TEST PASSED (~");
|
||||||
|
|
||||||
let logo = r#"
|
let logo = r#"
|
||||||
###########
|
###########
|
||||||
##################
|
##################
|
||||||
@@ -379,6 +461,7 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hcf() -> ! {
|
fn hcf() -> ! {
|
||||||
|
unsafe { asm!("cli", options(nomem, nostack, preserves_flags)); }
|
||||||
loop {
|
loop {
|
||||||
unsafe { asm!("hlt", options(nomem, nostack, preserves_flags)); }
|
unsafe { asm!("hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
}
|
}
|
||||||
@@ -386,7 +469,6 @@ fn hcf() -> ! {
|
|||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn rust_panic(info: &core::panic::PanicInfo) -> ! {
|
fn rust_panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
let mut sp = unsafe { debug::serial::SerialPort::init() };
|
debug::serial::write_global(format_args!("KERNEL PANIC: {:?}\n", info));
|
||||||
let _ = writeln!(sp, "KERNEL PANIC: {:?}", info);
|
|
||||||
hcf();
|
hcf();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
static HHDM_OFFSET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
/// Store the Higher-Half Direct Map offset obtained from the bootloader.
|
||||||
|
/// Must be called once during early boot, before any address translation.
|
||||||
|
pub fn init_hhdm(offset: u64) {
|
||||||
|
HHDM_OFFSET.store(offset, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the HHDM offset (kernel virtual base for physical memory).
|
||||||
|
#[inline]
|
||||||
|
pub fn get_hhdm() -> u64 {
|
||||||
|
HHDM_OFFSET.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct PhysAddr(pub u64);
|
pub struct PhysAddr(pub u64);
|
||||||
@@ -8,8 +24,8 @@ pub struct VirtAddr(pub u64);
|
|||||||
|
|
||||||
impl PhysAddr {
|
impl PhysAddr {
|
||||||
/// Convert physical address to virtual via HHDM offset
|
/// Convert physical address to virtual via HHDM offset
|
||||||
pub fn to_virt(self, hhdm_offset: u64) -> VirtAddr {
|
pub fn to_virt(self) -> VirtAddr {
|
||||||
VirtAddr(self.0 + hhdm_offset)
|
VirtAddr(self.0 + get_hhdm())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -24,9 +40,10 @@ impl PhysAddr {
|
|||||||
|
|
||||||
impl VirtAddr {
|
impl VirtAddr {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn to_phys(self, hhdm_offset: u64) -> Option<PhysAddr> {
|
pub fn to_phys(self) -> Option<PhysAddr> {
|
||||||
if self.0 < hhdm_offset { return None; }
|
let hhdm = get_hhdm();
|
||||||
Some(PhysAddr(self.0 - hhdm_offset))
|
if self.0 < hhdm { return None; }
|
||||||
|
Some(PhysAddr(self.0 - hhdm))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@@ -55,8 +55,14 @@ struct ListNode {
|
|||||||
|
|
||||||
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
||||||
|
|
||||||
|
struct LargeBlockNode {
|
||||||
|
size: usize,
|
||||||
|
next: Option<&'static mut LargeBlockNode>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SlabAllocator {
|
pub struct SlabAllocator {
|
||||||
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()],
|
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()],
|
||||||
|
large_block_free: Option<&'static mut LargeBlockNode>,
|
||||||
heap_start: usize,
|
heap_start: usize,
|
||||||
heap_end: usize,
|
heap_end: usize,
|
||||||
next_bump: usize,
|
next_bump: usize,
|
||||||
@@ -66,6 +72,7 @@ impl SlabAllocator {
|
|||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
list_heads: [None, None, None, None, None, None, None, None, None],
|
list_heads: [None, None, None, None, None, None, None, None, None],
|
||||||
|
large_block_free: None,
|
||||||
heap_start: 0,
|
heap_start: 0,
|
||||||
heap_end: 0,
|
heap_end: 0,
|
||||||
next_bump: 0,
|
next_bump: 0,
|
||||||
@@ -84,11 +91,28 @@ impl SlabAllocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
|
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
|
||||||
|
let size = layout.size().max(layout.align());
|
||||||
|
|
||||||
|
if size > 2048 {
|
||||||
|
let mut field: *mut Option<&'static mut LargeBlockNode> = &mut self.large_block_free;
|
||||||
|
unsafe {
|
||||||
|
while let Some(ref mut node) = *field {
|
||||||
|
if node.size >= size {
|
||||||
|
let node_ptr: *mut LargeBlockNode = *node;
|
||||||
|
let next = node.next.take();
|
||||||
|
*field = next;
|
||||||
|
return node_ptr as *mut u8;
|
||||||
|
}
|
||||||
|
field = &mut node.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let alloc_start = (self.next_bump + layout.align() - 1) & !(layout.align() - 1);
|
let alloc_start = (self.next_bump + layout.align() - 1) & !(layout.align() - 1);
|
||||||
let alloc_end = alloc_start.checked_add(layout.size()).unwrap_or(self.heap_end + 1);
|
let alloc_end = alloc_start.checked_add(layout.size()).unwrap_or(self.heap_end + 1);
|
||||||
|
|
||||||
if alloc_end > self.heap_end {
|
if alloc_end > self.heap_end {
|
||||||
null_mut() // Out of memory
|
null_mut()
|
||||||
} else {
|
} else {
|
||||||
self.next_bump = alloc_end;
|
self.next_bump = alloc_end;
|
||||||
alloc_start as *mut u8
|
alloc_start as *mut u8
|
||||||
@@ -139,6 +163,17 @@ unsafe impl GlobalAlloc for Locked<SlabAllocator> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
// Large block: add to free list for reuse
|
||||||
|
let size = layout.size().max(layout.align());
|
||||||
|
let new_node = LargeBlockNode {
|
||||||
|
size,
|
||||||
|
next: allocator.large_block_free.take(),
|
||||||
|
};
|
||||||
|
let new_node_ptr = ptr as *mut LargeBlockNode;
|
||||||
|
unsafe {
|
||||||
|
new_node_ptr.write(new_node);
|
||||||
|
allocator.large_block_free = Some(&mut *new_node_ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,105 +1,99 @@
|
|||||||
//! # Per-Actor Buddy Allocator (`src/mem/buddy.rs`)
|
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||||
//!
|
|
||||||
//! A power-of-two page-block allocator for a fixed, pre-committed physical
|
|
||||||
//! region. Designed for **single-consumer** use inside a [`crate::mem::pm_manages::PMActor`]:
|
|
||||||
//! the owning actor is the only entity that ever mutates its buddy allocator, so
|
|
||||||
//! no locking is needed — the entire subsystem is inherently lock-free from the
|
|
||||||
//! consumer's perspective.
|
|
||||||
//!
|
|
||||||
//! ## Block Layout
|
|
||||||
//!
|
|
||||||
//! The region is subdivided into blocks of `2^order` pages (order 0 … MAX_ORDER).
|
|
||||||
//! Every block at order *k* is aligned to `2^k` pages within the region.
|
|
||||||
//!
|
|
||||||
//! ```text
|
|
||||||
//! order 0 → 1 page = 4 KiB
|
|
||||||
//! order 1 → 2 pages = 8 KiB
|
|
||||||
//! …
|
|
||||||
//! order 11 → 2 048 pages = 8 MiB (MAX_ORDER)
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ## Complexity
|
|
||||||
//!
|
|
||||||
//! | Operation | Amortised | Worst-case |
|
|
||||||
//! |-----------|-----------|------------|
|
|
||||||
//! | `alloc` | O(log N) | O(MAX_ORDER · Lₖ) |
|
|
||||||
//! | `free` | O(log N) | O(MAX_ORDER · Lₖ) |
|
|
||||||
//!
|
|
||||||
//! where Lₖ = `free_lists[k].len()` ≤ `total_pages / 2^k`.
|
|
||||||
//!
|
|
||||||
//! ## Production note
|
|
||||||
//!
|
|
||||||
//! For managed ranges > 1 GiB (> 256 K pages), replace the per-order `Vec<usize>`
|
|
||||||
//! free lists with a radix tree or interval tree to bound Lₖ. For current
|
|
||||||
//! PMActor capital sizes (typically ≤ 128 MiB = 32 K pages at order 0), the
|
|
||||||
//! `Vec`-based implementation is fully adequate.
|
|
||||||
|
|
||||||
extern crate alloc;
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
|
|
||||||
// Constants & helpers
|
|
||||||
/// Maximum allocation order.
|
|
||||||
/// `2^11 × 4 096 bytes = 8 MiB` per single allocation.
|
|
||||||
pub const MAX_ORDER: usize = 11;
|
pub const MAX_ORDER: usize = 11;
|
||||||
|
|
||||||
/// ⌈log2(n)⌉ — the minimum order whose block size covers `page_count` pages.
|
const NEXT_SENTINEL: usize = usize::MAX;
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// order_for(1) = 0 (2^0 = 1)
|
|
||||||
/// order_for(2) = 1 (2^1 = 2)
|
|
||||||
/// order_for(3) = 2 (2^2 = 4 ≥ 3)
|
|
||||||
/// order_for(2048) = 11
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn order_for(page_count: usize) -> usize {
|
pub fn order_for(page_count: usize) -> usize {
|
||||||
if page_count <= 1 {
|
if page_count <= 1 {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
// Number of bits needed to represent (page_count - 1).
|
|
||||||
usize::BITS as usize - (page_count - 1).leading_zeros() as usize
|
usize::BITS as usize - (page_count - 1).leading_zeros() as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuddyAllocator
|
|
||||||
/// Buddy allocator over a contiguous, pre-committed range of physical pages.
|
|
||||||
///
|
|
||||||
/// All page indices stored in free lists are **relative to the start of the
|
|
||||||
/// managed range**. The owner (`PMActor`) translates to absolute `PhysAddr`
|
|
||||||
/// by adding `managed_range.0`.
|
|
||||||
///
|
|
||||||
/// # Invariants
|
|
||||||
/// - `free_pages ≤ total_pages` at all times.
|
|
||||||
/// - Every block in `free_lists[k]` is aligned to `2^k` pages (i.e.
|
|
||||||
/// `idx % (1 << k) == 0`).
|
|
||||||
/// - No block appears in more than one order's free list simultaneously.
|
|
||||||
pub struct BuddyAllocator {
|
pub struct BuddyAllocator {
|
||||||
/// `free_lists[k]` = relative page indices of free 2^k-page blocks.
|
free_heads: [usize; MAX_ORDER + 1],
|
||||||
/// Ordering within each list is irrelevant; `pop()` / `swap_remove()` are used.
|
|
||||||
free_lists: [Vec<usize>; MAX_ORDER + 1],
|
|
||||||
/// Total pages in the managed range (need not be a power of two).
|
|
||||||
total_pages: usize,
|
total_pages: usize,
|
||||||
/// Running count of free pages. Always equals `Σ (2^k × free_lists[k].len())`.
|
|
||||||
free_pages: usize,
|
free_pages: usize,
|
||||||
|
base_phys: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuddyAllocator {
|
impl BuddyAllocator {
|
||||||
//Construction
|
#[inline]
|
||||||
/// Create a new allocator over `total_pages` pages, **all initially free**.
|
fn page_virt(&self, idx: usize) -> VirtAddr {
|
||||||
///
|
PhysAddr(self.base_phys + idx as u64 * 4096).to_virt()
|
||||||
/// Uses a greedy largest-first decomposition to build the initial free lists
|
}
|
||||||
/// in O(total_pages / 2^MAX_ORDER) iterations — essentially O(1) for
|
|
||||||
/// power-of-two sizes.
|
|
||||||
///
|
|
||||||
/// Example: 7 pages → blocks [4, 2, 1] → free_lists[2]=[0], [1]=[4], [0]=[6].
|
|
||||||
pub fn new(total_pages: usize) -> Self {
|
|
||||||
// core::array::from_fn is the idiomatic way to init a non-Copy array.
|
|
||||||
let free_lists: [Vec<usize>; MAX_ORDER + 1] =
|
|
||||||
core::array::from_fn(|_| Vec::new());
|
|
||||||
|
|
||||||
|
unsafe fn write_node(&self, idx: usize, next: usize, prev: usize) {
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.page_virt(idx).as_mut_ptr::<usize>();
|
||||||
|
ptr.write(next);
|
||||||
|
ptr.add(1).write(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_next(&self, idx: usize) -> usize {
|
||||||
|
unsafe { self.page_virt(idx).as_ptr::<usize>().read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_prev(&self, idx: usize) -> usize {
|
||||||
|
unsafe { self.page_virt(idx).as_ptr::<usize>().add(1).read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flist_push(&mut self, order: usize, idx: usize) {
|
||||||
|
let head = self.free_heads[order];
|
||||||
|
unsafe {
|
||||||
|
self.write_node(idx, head, NEXT_SENTINEL);
|
||||||
|
if head != NEXT_SENTINEL {
|
||||||
|
self.write_node(head, self.read_next(head), idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.free_heads[order] = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flist_remove(&mut self, order: usize, idx: usize) {
|
||||||
|
unsafe {
|
||||||
|
let next = self.read_next(idx);
|
||||||
|
let prev = self.read_prev(idx);
|
||||||
|
|
||||||
|
self.write_node(idx, NEXT_SENTINEL, NEXT_SENTINEL);
|
||||||
|
|
||||||
|
if prev != NEXT_SENTINEL {
|
||||||
|
self.write_node(prev, next, self.read_prev(prev));
|
||||||
|
} else {
|
||||||
|
self.free_heads[order] = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if next != NEXT_SENTINEL {
|
||||||
|
self.write_node(next, self.read_next(next), prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flist_pop(&mut self, order: usize) -> Option<usize> {
|
||||||
|
let head = self.free_heads[order];
|
||||||
|
if head == NEXT_SENTINEL {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.flist_remove(order, head);
|
||||||
|
Some(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flist_contains(&self, order: usize, idx: usize) -> bool {
|
||||||
|
if self.free_heads[order] == idx {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
unsafe { self.read_prev(idx) != NEXT_SENTINEL }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(total_pages: usize, base_phys: u64) -> Self {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
free_lists,
|
free_heads: [NEXT_SENTINEL; MAX_ORDER + 1],
|
||||||
total_pages,
|
total_pages,
|
||||||
free_pages: 0,
|
free_pages: 0,
|
||||||
|
base_phys,
|
||||||
};
|
};
|
||||||
|
|
||||||
if total_pages == 0 {
|
if total_pages == 0 {
|
||||||
@@ -110,22 +104,19 @@ impl BuddyAllocator {
|
|||||||
while idx < total_pages {
|
while idx < total_pages {
|
||||||
let remaining = total_pages - idx;
|
let remaining = total_pages - idx;
|
||||||
|
|
||||||
// Alignment constraint: block at `idx` must be aligned to 2^order.
|
|
||||||
// trailing_zeros(0) is u32::MAX, so we clamp to MAX_ORDER.
|
|
||||||
let align_order = if idx == 0 {
|
let align_order = if idx == 0 {
|
||||||
MAX_ORDER
|
MAX_ORDER
|
||||||
} else {
|
} else {
|
||||||
(idx.trailing_zeros() as usize).min(MAX_ORDER)
|
(idx.trailing_zeros() as usize).min(MAX_ORDER)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Size constraint: 2^order ≤ remaining → order ≤ ⌊log₂(remaining)⌋.
|
|
||||||
let size_order = (usize::BITS as usize - 1)
|
let size_order = (usize::BITS as usize - 1)
|
||||||
- remaining.leading_zeros() as usize; // ⌊log2(remaining)⌋
|
- remaining.leading_zeros() as usize;
|
||||||
|
|
||||||
let order = MAX_ORDER.min(align_order).min(size_order);
|
let order = MAX_ORDER.min(align_order).min(size_order);
|
||||||
let block_size = 1usize << order;
|
let block_size = 1usize << order;
|
||||||
|
|
||||||
this.free_lists[order].push(idx);
|
this.flist_push(order, idx);
|
||||||
this.free_pages += block_size;
|
this.free_pages += block_size;
|
||||||
idx += block_size;
|
idx += block_size;
|
||||||
}
|
}
|
||||||
@@ -133,60 +124,30 @@ impl BuddyAllocator {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
//Allocation
|
|
||||||
/// Allocate a 2^`order`-page block.
|
|
||||||
///
|
|
||||||
/// Returns the **relative** page index of the block's first page, or `None`
|
|
||||||
/// if insufficient contiguous memory remains.
|
|
||||||
///
|
|
||||||
/// The returned index can be converted to a physical address:
|
|
||||||
/// ```text
|
|
||||||
/// phys = actor.managed_range.0.0 + (idx as u64) * PAGE_SIZE
|
|
||||||
/// ```
|
|
||||||
pub fn alloc(&mut self, order: usize) -> Option<usize> {
|
pub fn alloc(&mut self, order: usize) -> Option<usize> {
|
||||||
if order > MAX_ORDER {
|
if order > MAX_ORDER {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the smallest available order ≥ requested.
|
|
||||||
let found_order = (order..=MAX_ORDER)
|
let found_order = (order..=MAX_ORDER)
|
||||||
.find(|&o| !self.free_lists[o].is_empty())?;
|
.find(|&o| self.free_heads[o] != NEXT_SENTINEL)?;
|
||||||
|
|
||||||
// Consume one block from the found order.
|
let block_idx = self.flist_pop(found_order)
|
||||||
let block_idx = self.free_lists[found_order]
|
.expect("buddy: free_list non-empty but flist_pop returned None");
|
||||||
.pop()
|
|
||||||
.expect("buddy: free_list non-empty but pop() returned None");
|
|
||||||
|
|
||||||
// Debit: we removed a 2^found_order block from free.
|
|
||||||
self.free_pages -= 1 << found_order;
|
self.free_pages -= 1 << found_order;
|
||||||
|
|
||||||
// Split down to the requested order, putting buddies back into free lists.
|
|
||||||
//
|
|
||||||
// Invariant at each iteration:
|
|
||||||
// `block_idx` is the lower half of a 2^cur_order block.
|
|
||||||
// The upper half (= `block_idx + 2^(cur_order-1)`) is returned to the
|
|
||||||
// free list as an order-(cur_order-1) block.
|
|
||||||
let mut cur_order = found_order;
|
let mut cur_order = found_order;
|
||||||
while cur_order > order {
|
while cur_order > order {
|
||||||
cur_order -= 1;
|
cur_order -= 1;
|
||||||
let buddy_idx = block_idx + (1 << cur_order);
|
let buddy_idx = block_idx + (1 << cur_order);
|
||||||
self.free_lists[cur_order].push(buddy_idx);
|
self.flist_push(cur_order, buddy_idx);
|
||||||
self.free_pages += 1 << cur_order;
|
self.free_pages += 1 << cur_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Net accounting: removed 2^found_order, added (2^found_order − 2^order).
|
|
||||||
// Result: free_pages decreased by exactly 2^order. ✓
|
|
||||||
Some(block_idx)
|
Some(block_idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate `page_count` pages, rounding up to the nearest power of two.
|
|
||||||
///
|
|
||||||
/// Returns `(relative_page_idx, actual_order)`. The caller **must** pass the
|
|
||||||
/// same `order` to [`BuddyAllocator::free`] — mismatched orders corrupt the
|
|
||||||
/// allocator.
|
|
||||||
///
|
|
||||||
/// The wasted "rounding up" pages remain unusable until the block is freed.
|
|
||||||
/// For tightly-packed allocations, callers should use `alloc(order)` directly.
|
|
||||||
pub fn alloc_pages(&mut self, page_count: usize) -> Option<(usize, usize)> {
|
pub fn alloc_pages(&mut self, page_count: usize) -> Option<(usize, usize)> {
|
||||||
if page_count == 0 {
|
if page_count == 0 {
|
||||||
return None;
|
return None;
|
||||||
@@ -198,17 +159,6 @@ impl BuddyAllocator {
|
|||||||
self.alloc(order).map(|idx| (idx, order))
|
self.alloc(order).map(|idx| (idx, order))
|
||||||
}
|
}
|
||||||
|
|
||||||
//Deallocation
|
|
||||||
|
|
||||||
/// Return a 2^`order`-page block at **relative** index `block_idx` to the
|
|
||||||
/// free pool, coalescing with free buddies up the order chain.
|
|
||||||
///
|
|
||||||
/// # Panics (debug builds only)
|
|
||||||
/// - `order > MAX_ORDER`
|
|
||||||
/// - `block_idx + 2^order > total_pages`
|
|
||||||
///
|
|
||||||
/// In release builds the checks are elided for performance; passing incorrect
|
|
||||||
/// arguments causes undefined bookkeeping, not UB (no unsafe indexing).
|
|
||||||
pub fn free(&mut self, mut block_idx: usize, mut order: usize) {
|
pub fn free(&mut self, mut block_idx: usize, mut order: usize) {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
order <= MAX_ORDER,
|
order <= MAX_ORDER,
|
||||||
@@ -224,67 +174,60 @@ impl BuddyAllocator {
|
|||||||
self.total_pages
|
self.total_pages
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to coalesce with our buddy at each order.
|
|
||||||
//
|
|
||||||
// The buddy of a block at relative index `i` of order `k` is at:
|
|
||||||
// buddy_idx = i XOR 2^k
|
|
||||||
// This works because aligned buddy pairs always differ in exactly bit k.
|
|
||||||
while order < MAX_ORDER {
|
while order < MAX_ORDER {
|
||||||
let buddy_idx = block_idx ^ (1 << order);
|
let buddy_idx = block_idx ^ (1 << order);
|
||||||
|
|
||||||
// Buddy must lie entirely within the managed range.
|
|
||||||
let buddy_end = match buddy_idx.checked_add(1 << order) {
|
let buddy_end = match buddy_idx.checked_add(1 << order) {
|
||||||
Some(e) => e,
|
Some(e) => e,
|
||||||
None => break,
|
None => break,
|
||||||
};
|
};
|
||||||
if buddy_end > self.total_pages {
|
if buddy_end > self.total_pages {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for the buddy in the free list for this order.
|
if self.flist_contains(order, buddy_idx) {
|
||||||
// `swap_remove` is O(1) and order-preserving is not required.
|
self.flist_remove(order, buddy_idx);
|
||||||
if let Some(pos) = self.free_lists[order].iter().position(|&b| b == buddy_idx) {
|
self.free_pages -= 1 << order;
|
||||||
self.free_lists[order].swap_remove(pos);
|
|
||||||
self.free_pages -= 1 << order; // buddy was in free count; remove it
|
|
||||||
|
|
||||||
// Merged block starts at the lower address of the two.
|
|
||||||
block_idx = block_idx.min(buddy_idx);
|
block_idx = block_idx.min(buddy_idx);
|
||||||
order += 1;
|
order += 1;
|
||||||
// Continue trying to merge at the next level.
|
|
||||||
} else {
|
} else {
|
||||||
break; // buddy is allocated or out-of-range; stop coalescing
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the (possibly merged) block onto the appropriate free list.
|
|
||||||
self.free_pages += 1 << order;
|
self.free_pages += 1 << order;
|
||||||
self.free_lists[order].push(block_idx);
|
self.flist_push(order, block_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Introspection
|
|
||||||
|
|
||||||
/// Number of pages currently available for allocation.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn free_pages(&self) -> usize {
|
pub fn free_pages(&self) -> usize {
|
||||||
self.free_pages
|
self.free_pages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total pages in the managed range.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn total_pages(&self) -> usize {
|
pub fn total_pages(&self) -> usize {
|
||||||
self.total_pages
|
self.total_pages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `true` if the allocator has no free pages.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_exhausted(&self) -> bool {
|
pub fn is_exhausted(&self) -> bool {
|
||||||
self.free_pages == 0
|
self.free_pages == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dump free-list statistics for each order (useful in panic handlers).
|
|
||||||
///
|
|
||||||
/// Returns an array `[(order, free_block_count); MAX_ORDER + 1]`.
|
|
||||||
pub fn stats(&self) -> [(usize, usize); MAX_ORDER + 1] {
|
pub fn stats(&self) -> [(usize, usize); MAX_ORDER + 1] {
|
||||||
core::array::from_fn(|o| (o, self.free_lists[o].len()))
|
core::array::from_fn(|o| {
|
||||||
|
let count = if self.free_heads[o] == NEXT_SENTINEL {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
let mut cnt = 0;
|
||||||
|
let mut cur = self.free_heads[o];
|
||||||
|
while cur != NEXT_SENTINEL {
|
||||||
|
cnt += 1;
|
||||||
|
cur = unsafe { self.read_next(cur) };
|
||||||
|
}
|
||||||
|
cnt
|
||||||
|
};
|
||||||
|
(o, count)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ pub mod pm_router;
|
|||||||
/// Initialise the physical memory manager.
|
/// Initialise the physical memory manager.
|
||||||
///
|
///
|
||||||
/// Must be called before any heap allocation or VMM operation.
|
/// Must be called before any heap allocation or VMM operation.
|
||||||
pub fn init_pmm(memmap: &limine::response::MemoryMapResponse, hhdm_offset: u64) {
|
pub fn init_pmm(memmap: &limine::response::MemoryMapResponse) {
|
||||||
unsafe { pmm::BitmapPMM::init(memmap, hhdm_offset); }
|
unsafe { pmm::BitmapPMM::init(memmap); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialise CPU features required by the VMM (INVPCID detection).
|
/// Initialise CPU features required by the VMM (INVPCID detection).
|
||||||
@@ -25,8 +25,8 @@ pub fn init_cpu_features() {
|
|||||||
|
|
||||||
/// Initialise the kernel address space record (after PMM, heap, and the
|
/// Initialise the kernel address space record (after PMM, heap, and the
|
||||||
/// initial P4 page table have been set up in `kmain`).
|
/// initial P4 page table have been set up in `kmain`).
|
||||||
pub fn init_vmm(pml4_phys: address::PhysAddr, hhdm_offset: u64) {
|
pub fn init_vmm(pml4_phys: address::PhysAddr) {
|
||||||
vmm::init_kernel_space(pml4_phys, hhdm_offset);
|
vmm::init_kernel_space(pml4_phys);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Physical memory statistics: `(used_pages, total_pages)`.
|
/// Physical memory statistics: `(used_pages, total_pages)`.
|
||||||
|
|||||||
@@ -40,16 +40,15 @@ impl PageTable {
|
|||||||
phys: PhysAddr,
|
phys: PhysAddr,
|
||||||
size: u64,
|
size: u64,
|
||||||
flags: PageTableFlags,
|
flags: PageTableFlags,
|
||||||
hhdm: u64,
|
|
||||||
) {
|
) {
|
||||||
let pages = size.div_ceil(4096);
|
let pages = size.div_ceil(4096);
|
||||||
for i in 0..pages {
|
for i in 0..pages {
|
||||||
let offset = i * 4096;
|
let offset = i * 4096;
|
||||||
self.map_page(VirtAddr(virt.0 + offset), PhysAddr(phys.0 + offset), flags, hhdm);
|
self.map_page(VirtAddr(virt.0 + offset), PhysAddr(phys.0 + offset), flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_flags(&self, virt: VirtAddr, hhdm: u64) -> Option<PageTableFlags> {
|
pub fn get_flags(&self, virt: VirtAddr) -> Option<PageTableFlags> {
|
||||||
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
@@ -59,7 +58,7 @@ impl PageTable {
|
|||||||
($entry:expr) => {{
|
($entry:expr) => {{
|
||||||
let e = $entry;
|
let e = $entry;
|
||||||
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
||||||
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt(hhdm).as_mut_ptr::<PageTable>() }
|
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt().as_mut_ptr::<PageTable>() }
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +81,8 @@ impl PageTable {
|
|||||||
Some(PageTableFlags::from_bits_truncate(p1e))
|
Some(PageTableFlags::from_bits_truncate(p1e))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_flags(&mut self, virt: VirtAddr, flags: PageTableFlags, hhdm: u64) -> Result<(), ()> {
|
pub fn update_flags(&mut self, virt: VirtAddr, flags: PageTableFlags) -> Result<(), ()> {
|
||||||
let Some(p1) = self.walk_to_p1_mut(virt, hhdm, false) else { return Err(()); };
|
let Some(p1) = self.walk_to_p1_mut(virt, false) else { return Err(()); };
|
||||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
let entry = p1.entries[p1_idx];
|
let entry = p1.entries[p1_idx];
|
||||||
|
|
||||||
@@ -107,10 +106,9 @@ impl PageTable {
|
|||||||
virt: VirtAddr,
|
virt: VirtAddr,
|
||||||
phys: PhysAddr,
|
phys: PhysAddr,
|
||||||
flags: PageTableFlags,
|
flags: PageTableFlags,
|
||||||
hhdm: u64,
|
|
||||||
) {
|
) {
|
||||||
let p1 = self
|
let p1 = self
|
||||||
.walk_to_p1_mut(virt, hhdm, true /* create */)
|
.walk_to_p1_mut(virt, true /* create */)
|
||||||
.expect("map_page: OOM allocating intermediate page-table pages");
|
.expect("map_page: OOM allocating intermediate page-table pages");
|
||||||
|
|
||||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
@@ -126,8 +124,8 @@ impl PageTable {
|
|||||||
/// Does **not** free the underlying physical frame — that is the caller's
|
/// Does **not** free the underlying physical frame — that is the caller's
|
||||||
/// responsibility (e.g. `VmaBacking::do_unmap`).
|
/// responsibility (e.g. `VmaBacking::do_unmap`).
|
||||||
/// Silently returns if any level is not present (idempotent).
|
/// Silently returns if any level is not present (idempotent).
|
||||||
pub fn unmap_page(&mut self, virt: VirtAddr, hhdm: u64) {
|
pub fn unmap_page(&mut self, virt: VirtAddr) {
|
||||||
let Some(p1) = self.walk_to_p1_mut(virt, hhdm, false /* no create */) else {
|
let Some(p1) = self.walk_to_p1_mut(virt, false /* no create */) else {
|
||||||
return; // already absent — nothing to do
|
return; // already absent — nothing to do
|
||||||
};
|
};
|
||||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
@@ -144,7 +142,7 @@ impl PageTable {
|
|||||||
///
|
///
|
||||||
/// Handles 1 GiB and 2 MiB huge pages transparently.
|
/// Handles 1 GiB and 2 MiB huge pages transparently.
|
||||||
/// Returns `None` if any level is absent or the page is not present.
|
/// Returns `None` if any level is absent or the page is not present.
|
||||||
pub fn translate(&self, virt: VirtAddr, hhdm: u64) -> Option<PhysAddr> {
|
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
||||||
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
@@ -155,7 +153,7 @@ impl PageTable {
|
|||||||
($entry:expr) => {{
|
($entry:expr) => {{
|
||||||
let e = $entry;
|
let e = $entry;
|
||||||
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
||||||
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt(hhdm).as_mut_ptr::<PageTable>() }
|
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt().as_mut_ptr::<PageTable>() }
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,16 +207,16 @@ impl PageTable {
|
|||||||
///
|
///
|
||||||
/// If `create` is `false` and any intermediate entry is absent, returns `None`.
|
/// If `create` is `false` and any intermediate entry is absent, returns `None`.
|
||||||
/// If `create` is `true`, allocates missing intermediate pages from the PMM.
|
/// If `create` is `true`, allocates missing intermediate pages from the PMM.
|
||||||
fn walk_to_p1_mut(&mut self, virt: VirtAddr, hhdm: u64, create: bool)
|
fn walk_to_p1_mut(&mut self, virt: VirtAddr, create: bool)
|
||||||
-> Option<&mut Self>
|
-> Option<&mut Self>
|
||||||
{
|
{
|
||||||
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
|
|
||||||
let p3 = self.get_or_create_next_table(p4_idx, hhdm, create)?;
|
let p3 = self.get_or_create_next_table(p4_idx, create)?;
|
||||||
let p2 = p3.get_or_create_next_table(p3_idx, hhdm, create)?;
|
let p2 = p3.get_or_create_next_table(p3_idx, create)?;
|
||||||
p2.get_or_create_next_table(p2_idx, hhdm, create)
|
p2.get_or_create_next_table(p2_idx, create)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to the next-level table at `index`.
|
/// Return a mutable reference to the next-level table at `index`.
|
||||||
@@ -229,7 +227,6 @@ impl PageTable {
|
|||||||
fn get_or_create_next_table(
|
fn get_or_create_next_table(
|
||||||
&mut self,
|
&mut self,
|
||||||
index: usize,
|
index: usize,
|
||||||
hhdm: u64,
|
|
||||||
create: bool,
|
create: bool,
|
||||||
) -> Option<&mut Self> {
|
) -> Option<&mut Self> {
|
||||||
let entry = self.entries[index];
|
let entry = self.entries[index];
|
||||||
@@ -237,7 +234,7 @@ impl PageTable {
|
|||||||
if !create { return None; }
|
if !create { return None; }
|
||||||
|
|
||||||
let pt_phys = pmm_alloc().expect("VMM: OOM allocating page-table page");
|
let pt_phys = pmm_alloc().expect("VMM: OOM allocating page-table page");
|
||||||
let pt_virt = pt_phys.to_virt(hhdm);
|
let pt_virt = pt_phys.to_virt();
|
||||||
unsafe { core::ptr::write_bytes(pt_virt.as_mut_ptr::<u8>(), 0, 4096); }
|
unsafe { core::ptr::write_bytes(pt_virt.as_mut_ptr::<u8>(), 0, 4096); }
|
||||||
|
|
||||||
// Install with USER so both kernel and user pages can live under it;
|
// Install with USER so both kernel and user pages can live under it;
|
||||||
@@ -247,7 +244,7 @@ impl PageTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let next_phys = PhysAddr(self.entries[index] & PTE_ADDR_MASK);
|
let next_phys = PhysAddr(self.entries[index] & PTE_ADDR_MASK);
|
||||||
Some(unsafe { &mut *next_phys.to_virt(hhdm).as_mut_ptr::<Self>() })
|
Some(unsafe { &mut *next_phys.to_virt().as_mut_ptr::<Self>() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ pub enum PMRequest {
|
|||||||
size_pages: usize,
|
size_pages: usize,
|
||||||
channel_id: u16,
|
channel_id: u16,
|
||||||
},
|
},
|
||||||
/// Sentinel — never pushed onto the queue; result of unpack(0).
|
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,30 +159,18 @@ impl PMRequest {
|
|||||||
|
|
||||||
// Response type
|
// Response type
|
||||||
|
|
||||||
/// Result returned by `PMActor::process_messages()` for each completed request.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct PMResponse {
|
pub struct PMResponse {
|
||||||
/// Channel to route this response to. `0` = discard.
|
|
||||||
pub channel_id: u16,
|
pub channel_id: u16,
|
||||||
/// The actual outcome.
|
|
||||||
pub result: PMResult,
|
pub result: PMResult,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Outcome of a single PM operation.
|
/// Outcome of a single PM operation.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum PMResult {
|
pub enum PMResult {
|
||||||
/// Memory was allocated. `cap` is the strong capability to the region.
|
|
||||||
/// `order` is the buddy order — **must** be passed back to `Free`.
|
|
||||||
Allocated { cap: Capability, order: usize },
|
Allocated { cap: Capability, order: usize },
|
||||||
|
|
||||||
/// The actor had insufficient free pages.
|
|
||||||
/// Future: ballooning subsystem intercepts this and retries.
|
|
||||||
OutOfMemory { size_pages: usize },
|
OutOfMemory { size_pages: usize },
|
||||||
|
|
||||||
/// A fixed sub-region was carved successfully.
|
|
||||||
Carved { cap: Capability },
|
Carved { cap: Capability },
|
||||||
|
|
||||||
/// Free completed (no capability issued — memory returned to buddy pool).
|
|
||||||
Freed { pages_returned: usize },
|
Freed { pages_returned: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +181,6 @@ pub enum PMResult {
|
|||||||
/// `pop` — exactly one consumer, no locking on the read side.
|
/// `pop` — exactly one consumer, no locking on the read side.
|
||||||
pub struct PMActorQueue {
|
pub struct PMActorQueue {
|
||||||
buffer: [AtomicU64; QUEUE_SIZE],
|
buffer: [AtomicU64; QUEUE_SIZE],
|
||||||
/// Pad to separate producer-written `tail` from consumer-read `head`.
|
|
||||||
_pad0: [u8; 64],
|
_pad0: [u8; 64],
|
||||||
head: AtomicUsize,
|
head: AtomicUsize,
|
||||||
_pad1: [u8; 64],
|
_pad1: [u8; 64],
|
||||||
@@ -269,15 +255,10 @@ impl PMActorQueue {
|
|||||||
/// Only `process_messages()` ever mutates `buddy` and `queue.head`.
|
/// Only `process_messages()` ever mutates `buddy` and `queue.head`.
|
||||||
/// This is enforced by taking `&mut self` on `process_messages`.
|
/// This is enforced by taking `&mut self` on `process_messages`.
|
||||||
pub struct PMActor {
|
pub struct PMActor {
|
||||||
/// Unique identity within the actor federation.
|
|
||||||
pub actor_id: u64,
|
pub actor_id: u64,
|
||||||
/// Root strong capability over the entire managed physical range.
|
|
||||||
pub root_untyped: Capability,
|
pub root_untyped: Capability,
|
||||||
/// `(inclusive_start, exclusive_end)` physical addresses.
|
|
||||||
pub managed_range: (PhysAddr, PhysAddr),
|
pub managed_range: (PhysAddr, PhysAddr),
|
||||||
/// Inbox — producers write here, actor reads here.
|
|
||||||
queue: PMActorQueue,
|
queue: PMActorQueue,
|
||||||
/// Local buddy allocator. Only ever touched in `process_messages`.
|
|
||||||
buddy: BuddyAllocator,
|
buddy: BuddyAllocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +279,7 @@ impl PMActor {
|
|||||||
root_untyped: root_cap,
|
root_untyped: root_cap,
|
||||||
managed_range: (start, PhysAddr(start.0 + total_pages as u64 * PAGE_SIZE)),
|
managed_range: (start, PhysAddr(start.0 + total_pages as u64 * PAGE_SIZE)),
|
||||||
queue: PMActorQueue::new(),
|
queue: PMActorQueue::new(),
|
||||||
buddy: BuddyAllocator::new(total_pages),
|
buddy: BuddyAllocator::new(total_pages, start.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,10 +306,16 @@ impl PMActor {
|
|||||||
/// `buddy` and `queue.head` are mutated — no other thread touches them.
|
/// `buddy` and `queue.head` are mutated — no other thread touches them.
|
||||||
/// The only shared state is the queue's `tail`, which is written by producers
|
/// The only shared state is the queue's `tail`, which is written by producers
|
||||||
/// via `AtomicUsize::compare_exchange_weak`, never by this path.
|
/// via `AtomicUsize::compare_exchange_weak`, never by this path.
|
||||||
|
/// Maximum requests to process in a single `process_messages` call.
|
||||||
|
/// Prevents kernel starvation when the inbox is deep.
|
||||||
|
const MAX_MESSAGES_PER_CALL: usize = 64;
|
||||||
|
|
||||||
pub fn process_messages(&mut self) -> Vec<PMResponse> {
|
pub fn process_messages(&mut self) -> Vec<PMResponse> {
|
||||||
let mut responses = Vec::new();
|
let mut responses = Vec::new();
|
||||||
|
let mut remaining = Self::MAX_MESSAGES_PER_CALL;
|
||||||
|
|
||||||
while let Some(req) = self.queue.pop() {
|
while let Some(req) = self.queue.pop() {
|
||||||
|
remaining -= 1;
|
||||||
let resp = match req {
|
let resp = match req {
|
||||||
PMRequest::Allocate { size_pages, token_sig, channel_id } => {
|
PMRequest::Allocate { size_pages, token_sig, channel_id } => {
|
||||||
self.handle_allocate(size_pages, token_sig, channel_id)
|
self.handle_allocate(size_pages, token_sig, channel_id)
|
||||||
@@ -342,12 +329,13 @@ impl PMActor {
|
|||||||
PMRequest::None => continue,
|
PMRequest::None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only push responses that need routing.
|
|
||||||
// Free responses (channel_id == 0) are still pushed so callers can
|
|
||||||
// audit completion if needed; they may simply drop them.
|
|
||||||
if let Some(r) = resp {
|
if let Some(r) = resp {
|
||||||
responses.push(r);
|
responses.push(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if remaining == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responses
|
responses
|
||||||
@@ -434,9 +422,6 @@ impl PMActor {
|
|||||||
|
|
||||||
self.buddy.free(local_frame_idx, order);
|
self.buddy.free(local_frame_idx, order);
|
||||||
|
|
||||||
// Free never needs routing — return None to skip Vec push, or push with
|
|
||||||
// channel_id=0 for audit purposes. We skip to avoid unnecessary allocation.
|
|
||||||
// Callers that need a Free-complete signal should use a separate mechanism.
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,7 +431,6 @@ impl PMActor {
|
|||||||
size_pages: usize,
|
size_pages: usize,
|
||||||
channel_id: u16,
|
channel_id: u16,
|
||||||
) -> Option<PMResponse> {
|
) -> Option<PMResponse> {
|
||||||
// Validate offset + size within range.
|
|
||||||
let range_pages = self.buddy.total_pages();
|
let range_pages = self.buddy.total_pages();
|
||||||
if offset_pages >= range_pages
|
if offset_pages >= range_pages
|
||||||
|| size_pages == 0
|
|| size_pages == 0
|
||||||
|
|||||||
@@ -69,12 +69,21 @@ pub fn init() {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_router() -> &'static PMRouter {
|
pub fn get_router() -> &'static PMRouter {
|
||||||
|
// INVARIANT: `is_ready` is set once in `init()` and never cleared.
|
||||||
|
// The Acquire load on `is_ready` synchronises-with the Release store in
|
||||||
|
// `init()`, making the `Option::Some` write visible. Because nothing
|
||||||
|
// ever writes `None` or clears `is_ready`, the TOCTOU window between
|
||||||
|
// the load and the `unwrap_unchecked` is safe.
|
||||||
if ROUTER.is_ready.load(Ordering::Acquire) {
|
if ROUTER.is_ready.load(Ordering::Acquire) {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*ROUTER.inner.get()).as_ref().unwrap_unchecked()
|
(*ROUTER.inner.get()).as_ref().unwrap_unchecked()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("FATAL: PMRouter is accessed before initialization!")
|
#[cold]
|
||||||
|
fn not_initialized() -> ! {
|
||||||
|
panic!("FATAL: PMRouter is accessed before initialization!")
|
||||||
|
}
|
||||||
|
not_initialized()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,9 +140,10 @@ impl PMRouter {
|
|||||||
let channel = &self.channels[id as usize];
|
let channel = &self.channels[id as usize];
|
||||||
|
|
||||||
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
||||||
core::hint::spin_loop();
|
// HLT-ожидание вместо busy-wait: CPU останавливается до ближайшего
|
||||||
// TODO: Для "Focus Mode" и полноценного планировщика:
|
// прерывания (timer tick, IPI от route_responses и т.д.).
|
||||||
// scheduler::yield_to_actor();
|
// Когда появится планировщик — заменить на yield_to_actor().
|
||||||
|
unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
|
|||||||
@@ -1,20 +1,36 @@
|
|||||||
use crate::mem::address::PhysAddr;
|
use crate::mem::address::{PhysAddr, get_hhdm};
|
||||||
use crate::mem::allocator::Locked;
|
use crate::mem::allocator::Locked;
|
||||||
|
|
||||||
pub const PAGE_SIZE: u64 = 4096;
|
pub const PAGE_SIZE: u64 = 4096;
|
||||||
|
|
||||||
pub struct BitmapPMM {
|
pub struct BitmapPMM {
|
||||||
bitmap: &'static mut [u8],
|
bitmap: &'static mut [u8],
|
||||||
|
l1_bitmap: &'static mut [u64],
|
||||||
ref_counts: &'static mut [u16],
|
ref_counts: &'static mut [u16],
|
||||||
total_pages: usize,
|
total_pages: usize,
|
||||||
used_pages: usize,
|
used_pages: usize,
|
||||||
/// Byte index hint: next search starts here to amortise O(N) scans.
|
last_word: usize,
|
||||||
last_byte: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
|
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
|
||||||
|
|
||||||
impl BitmapPMM {
|
impl BitmapPMM {
|
||||||
|
unsafe fn read_word(&self, word_idx: usize) -> u64 {
|
||||||
|
let byte_off = word_idx * 8;
|
||||||
|
unsafe { (self.bitmap.as_ptr().add(byte_off) as *const u64).read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn l1_update_word(&mut self, word_idx: usize) {
|
||||||
|
let word = unsafe { self.read_word(word_idx) };
|
||||||
|
let l1_idx = word_idx / 64;
|
||||||
|
let l1_bit = word_idx % 64;
|
||||||
|
if word != !0u64 {
|
||||||
|
self.l1_bitmap[l1_idx] |= 1 << l1_bit;
|
||||||
|
} else {
|
||||||
|
self.l1_bitmap[l1_idx] &= !(1 << l1_bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn used_pages(&self) -> usize { self.used_pages }
|
pub fn used_pages(&self) -> usize { self.used_pages }
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -22,7 +38,7 @@ impl BitmapPMM {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn free_pages(&self) -> usize { self.total_pages.saturating_sub(self.used_pages) }
|
pub fn free_pages(&self) -> usize { self.total_pages.saturating_sub(self.used_pages) }
|
||||||
|
|
||||||
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse, hhdm_offset: u64) {
|
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse) {
|
||||||
let max_addr = mmap.entries().iter()
|
let max_addr = mmap.entries().iter()
|
||||||
.map(|e| e.base + e.length)
|
.map(|e| e.base + e.length)
|
||||||
.max()
|
.max()
|
||||||
@@ -32,7 +48,10 @@ impl BitmapPMM {
|
|||||||
|
|
||||||
let bitmap_size = total_pages.div_ceil(8);
|
let bitmap_size = total_pages.div_ceil(8);
|
||||||
let ref_counts_size = total_pages * core::mem::size_of::<u16>();
|
let ref_counts_size = total_pages * core::mem::size_of::<u16>();
|
||||||
let total_meta_size = bitmap_size + ref_counts_size;
|
let num_words = total_pages.div_ceil(64);
|
||||||
|
let l1_u64_count = num_words.div_ceil(64);
|
||||||
|
let l1_byte_size = l1_u64_count * 8;
|
||||||
|
let total_meta_size = bitmap_size + ref_counts_size + l1_byte_size;
|
||||||
|
|
||||||
let meta_phys = mmap.entries().iter()
|
let meta_phys = mmap.entries().iter()
|
||||||
.find(|e| {
|
.find(|e| {
|
||||||
@@ -42,8 +61,10 @@ impl BitmapPMM {
|
|||||||
.map(|e| e.base)
|
.map(|e| e.base)
|
||||||
.expect("PMM: no usable region large enough for metadata");
|
.expect("PMM: no usable region large enough for metadata");
|
||||||
|
|
||||||
let bitmap_ptr = (meta_phys + hhdm_offset) as *mut u8;
|
let hhdm = get_hhdm();
|
||||||
let ref_counts_ptr = (meta_phys + hhdm_offset + bitmap_size as u64) as *mut u16;
|
let bitmap_ptr = (meta_phys + hhdm) as *mut u8;
|
||||||
|
let ref_counts_ptr = (meta_phys + hhdm + bitmap_size as u64) as *mut u16;
|
||||||
|
let l1_ptr = (meta_phys + hhdm + bitmap_size as u64 + ref_counts_size as u64) as *mut u64;
|
||||||
|
|
||||||
let bitmap = unsafe { core::slice::from_raw_parts_mut(bitmap_ptr, bitmap_size) };
|
let bitmap = unsafe { core::slice::from_raw_parts_mut(bitmap_ptr, bitmap_size) };
|
||||||
bitmap.fill(0xFF);
|
bitmap.fill(0xFF);
|
||||||
@@ -51,12 +72,16 @@ impl BitmapPMM {
|
|||||||
let ref_counts = unsafe { core::slice::from_raw_parts_mut(ref_counts_ptr, total_pages) };
|
let ref_counts = unsafe { core::slice::from_raw_parts_mut(ref_counts_ptr, total_pages) };
|
||||||
ref_counts.fill(1);
|
ref_counts.fill(1);
|
||||||
|
|
||||||
|
let l1_bitmap = unsafe { core::slice::from_raw_parts_mut(l1_ptr, l1_u64_count) };
|
||||||
|
l1_bitmap.fill(0);
|
||||||
|
|
||||||
let mut pmm = Self {
|
let mut pmm = Self {
|
||||||
bitmap,
|
bitmap,
|
||||||
|
l1_bitmap,
|
||||||
ref_counts,
|
ref_counts,
|
||||||
total_pages,
|
total_pages,
|
||||||
used_pages: total_pages,
|
used_pages: total_pages,
|
||||||
last_byte: 0,
|
last_word: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
for entry in mmap.entries() {
|
for entry in mmap.entries() {
|
||||||
@@ -77,8 +102,6 @@ impl BitmapPMM {
|
|||||||
*PMM.lock() = Some(pmm);
|
*PMM.lock() = Some(pmm);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Core operations
|
|
||||||
|
|
||||||
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
||||||
if idx >= self.total_pages { return; }
|
if idx >= self.total_pages { return; }
|
||||||
@@ -91,8 +114,11 @@ impl BitmapPMM {
|
|||||||
|
|
||||||
if self.ref_counts[idx] == 0 {
|
if self.ref_counts[idx] == 0 {
|
||||||
self.bitmap[byte] &= !(1 << bit);
|
self.bitmap[byte] &= !(1 << bit);
|
||||||
self.used_pages -= 1;
|
self.used_pages -= 1;
|
||||||
if byte < self.last_byte { self.last_byte = byte; }
|
|
||||||
|
let word_idx = idx / 64;
|
||||||
|
self.l1_update_word(word_idx);
|
||||||
|
if word_idx < self.last_word { self.last_word = word_idx; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,10 +133,13 @@ impl BitmapPMM {
|
|||||||
if self.bitmap[byte] & (1 << bit) == 0 {
|
if self.bitmap[byte] & (1 << bit) == 0 {
|
||||||
self.bitmap[byte] |= 1 << bit;
|
self.bitmap[byte] |= 1 << bit;
|
||||||
self.ref_counts[idx] = 1;
|
self.ref_counts[idx] = 1;
|
||||||
self.used_pages += 1;
|
self.used_pages += 1;
|
||||||
} else if self.ref_counts[idx] == 0 {
|
} else if self.ref_counts[idx] == 0 {
|
||||||
self.ref_counts[idx] = 1;
|
self.ref_counts[idx] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let word_idx = idx / 64;
|
||||||
|
self.l1_update_word(word_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inc_ref_frame(&mut self, phys_addr: PhysAddr) {
|
pub fn inc_ref_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
@@ -126,31 +155,45 @@ impl BitmapPMM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
||||||
let len = self.bitmap.len();
|
let l1_len = self.l1_bitmap.len();
|
||||||
|
|
||||||
for pass in 0..2usize {
|
for pass in 0..2usize {
|
||||||
let (from, to) = if pass == 0 {
|
let (from, to) = if pass == 0 {
|
||||||
(self.last_byte, len)
|
(self.last_word / 64, l1_len)
|
||||||
} else {
|
} else {
|
||||||
(0, self.last_byte)
|
(0, self.last_word / 64)
|
||||||
};
|
};
|
||||||
|
|
||||||
for byte_idx in from..to {
|
for l1_idx in from..to {
|
||||||
if self.bitmap[byte_idx] == 0xFF { continue; }
|
let l1_word = self.l1_bitmap[l1_idx];
|
||||||
|
if l1_word == 0 { continue; }
|
||||||
|
|
||||||
for bit in 0..8u8 {
|
let word_offset = l1_word.trailing_zeros() as usize;
|
||||||
if self.bitmap[byte_idx] & (1 << bit) == 0 {
|
let word_idx = l1_idx * 64 + word_offset;
|
||||||
let page_idx = byte_idx * 8 + bit as usize;
|
|
||||||
if page_idx >= self.total_pages { return None; }
|
|
||||||
|
|
||||||
self.bitmap[byte_idx] |= 1 << bit;
|
let word = unsafe { self.read_word(word_idx) };
|
||||||
self.ref_counts[page_idx] = 1;
|
if word == !0u64 {
|
||||||
self.used_pages += 1;
|
self.l1_bitmap[l1_idx] &= !(1 << word_offset);
|
||||||
self.last_byte = byte_idx;
|
continue;
|
||||||
|
|
||||||
return Some(PhysAddr(page_idx as u64 * PAGE_SIZE));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let free_bit = (!word).trailing_zeros() as usize;
|
||||||
|
let page_idx = word_idx * 64 + free_bit;
|
||||||
|
if page_idx >= self.total_pages { continue; }
|
||||||
|
|
||||||
|
let byte = page_idx / 8;
|
||||||
|
let bit = page_idx % 8;
|
||||||
|
|
||||||
|
self.bitmap[byte] |= 1 << bit;
|
||||||
|
self.ref_counts[page_idx] = 1;
|
||||||
|
self.used_pages += 1;
|
||||||
|
self.last_word = word_idx;
|
||||||
|
|
||||||
|
if unsafe { self.read_word(word_idx) } == !0u64 {
|
||||||
|
self.l1_bitmap[l1_idx] &= !(1 << word_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(PhysAddr(page_idx as u64 * PAGE_SIZE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,9 +218,11 @@ impl BitmapPMM {
|
|||||||
for i in run_start..run_start + count {
|
for i in run_start..run_start + count {
|
||||||
self.bitmap[i / 8] |= 1 << (i % 8);
|
self.bitmap[i / 8] |= 1 << (i % 8);
|
||||||
self.ref_counts[i] = 1;
|
self.ref_counts[i] = 1;
|
||||||
|
let word_idx = i / 64;
|
||||||
|
self.l1_update_word(word_idx);
|
||||||
}
|
}
|
||||||
self.used_pages += count;
|
self.used_pages += count;
|
||||||
self.last_byte = run_start / 8;
|
self.last_word = run_start / 64;
|
||||||
return Some(PhysAddr(run_start as u64 * PAGE_SIZE));
|
return Some(PhysAddr(run_start as u64 * PAGE_SIZE));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -189,8 +234,6 @@ impl BitmapPMM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Module-level convenience functions
|
|
||||||
|
|
||||||
pub fn alloc_frame() -> Option<PhysAddr> {
|
pub fn alloc_frame() -> Option<PhysAddr> {
|
||||||
PMM.lock().as_mut()?.alloc_frame()
|
PMM.lock().as_mut()?.alloc_frame()
|
||||||
}
|
}
|
||||||
@@ -218,7 +261,3 @@ pub fn get_stats() -> (usize, usize) {
|
|||||||
(0, 0)
|
(0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ fn local_tlb_flush_asid(asid: u16) {
|
|||||||
unsafe {
|
unsafe {
|
||||||
core::arch::asm!(
|
core::arch::asm!(
|
||||||
"invpcid {ty}, [{desc}]",
|
"invpcid {ty}, [{desc}]",
|
||||||
ty = in(reg) 1u64, // type 1 = single-context flush
|
ty = in(reg) 1u64,
|
||||||
desc = in(reg) &desc,
|
desc = in(reg) &desc,
|
||||||
options(nostack, preserves_flags),
|
options(nostack, preserves_flags),
|
||||||
);
|
);
|
||||||
@@ -69,24 +69,14 @@ fn local_tlb_flush_asid(asid: u16) {
|
|||||||
// Error type
|
// Error type
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum VmError {
|
pub enum VmError {
|
||||||
/// PMM returned `None` — no physical frames available.
|
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
/// The requested virtual range overlaps an existing VMA.
|
|
||||||
RegionOverlap,
|
RegionOverlap,
|
||||||
/// No VMA covers the given address.
|
|
||||||
RegionNotFound,
|
RegionNotFound,
|
||||||
/// Address or size is not a multiple of 4096.
|
|
||||||
InvalidAlignment,
|
InvalidAlignment,
|
||||||
/// Size is zero, or `virt + size` would overflow.
|
|
||||||
InvalidRange,
|
InvalidRange,
|
||||||
/// Write fault on a read-only VMA, or exec fault on a NX VMA.
|
|
||||||
PermissionDenied,
|
PermissionDenied,
|
||||||
/// Page-fault in a non-lazy (already-eager or fixed) region — hardware bug
|
|
||||||
/// or an exploit attempt; the faulting task must be killed.
|
|
||||||
UnexpectedFault,
|
UnexpectedFault,
|
||||||
/// Address outside the x86-64 canonical range.
|
|
||||||
NonCanonical,
|
NonCanonical,
|
||||||
/// ASID pool exhausted (> 4094 simultaneous address spaces).
|
|
||||||
AsidExhausted,
|
AsidExhausted,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,23 +133,10 @@ impl VmaFlags {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum VmaBacking {
|
pub enum VmaBacking {
|
||||||
/// Anonymous pages (stack, heap, BSS).
|
|
||||||
/// Index `i` → frame for `virt_start + i * 4096`. `None` = not yet faulted in.
|
|
||||||
Anonymous(Vec<Option<PhysAddr>>),
|
Anonymous(Vec<Option<PhysAddr>>),
|
||||||
|
|
||||||
/// Fixed physical range. Frames are **not** freed on unmap.
|
|
||||||
/// Used for MMIO, identity-mapped RAM, framebuffer, DMA buffers.
|
|
||||||
Physical(PhysAddr),
|
Physical(PhysAddr),
|
||||||
|
|
||||||
/// Zero-copy borrow of another actor's frames.
|
|
||||||
///
|
|
||||||
/// `phys_base` is the physical address of the first page; the mapping covers
|
|
||||||
/// exactly `(virt_end - virt_start) / 4096` pages.
|
|
||||||
/// Frames are owned by `owner_cap` and **never** freed by this VMA.
|
|
||||||
Shared {
|
Shared {
|
||||||
/// Token of the capability that owns the frames.
|
|
||||||
owner_cap: u64,
|
owner_cap: u64,
|
||||||
/// Physical base (first page of the shared region).
|
|
||||||
phys_base: PhysAddr,
|
phys_base: PhysAddr,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -251,13 +228,13 @@ impl AsidAllocator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None // pool truly exhausted
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an ASID to the pool.
|
/// Return an ASID to the pool.
|
||||||
fn free(&mut self, asid: u16) {
|
fn free(&mut self, asid: u16) {
|
||||||
if asid == 0 || asid >= 4095 {
|
if asid == 0 || asid >= 4095 {
|
||||||
return; // sentinel values — never freed
|
return;
|
||||||
}
|
}
|
||||||
let word = asid as usize / 32;
|
let word = asid as usize / 32;
|
||||||
let bit = asid as usize % 32;
|
let bit = asid as usize % 32;
|
||||||
@@ -329,14 +306,12 @@ pub struct AddressSpace {
|
|||||||
pub pml4_phys: PhysAddr,
|
pub pml4_phys: PhysAddr,
|
||||||
/// Sorted (by virt_start), non-overlapping VMA list.
|
/// Sorted (by virt_start), non-overlapping VMA list.
|
||||||
regions: Vec<VmaRegion>,
|
regions: Vec<VmaRegion>,
|
||||||
/// HHDM offset for dereferencing page-table pages.
|
|
||||||
hhdm: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddressSpace {
|
impl AddressSpace {
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn pml4_raw(&self) -> *mut PageTable {
|
unsafe fn pml4_raw(&self) -> *mut PageTable {
|
||||||
self.pml4_phys.to_virt(self.hhdm).as_mut_ptr::<PageTable>()
|
self.pml4_phys.to_virt().as_mut_ptr::<PageTable>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_overlap(&self, start: VirtAddr, end: VirtAddr) -> Result<(), VmError> {
|
fn check_overlap(&self, start: VirtAddr, end: VirtAddr) -> Result<(), VmError> {
|
||||||
@@ -349,10 +324,9 @@ impl AddressSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone_for_fork(&mut self, child_cap_token: u64) -> Result<Self, VmError> {
|
pub fn clone_for_fork(&mut self, child_cap_token: u64) -> Result<Self, VmError> {
|
||||||
let mut child = AddressSpace::new(self.hhdm)?;
|
let mut child = AddressSpace::new()?;
|
||||||
let child_pml4 = unsafe { &mut *child.pml4_raw() };
|
let child_pml4 = unsafe { &mut *child.pml4_raw() };
|
||||||
let parent_pml4 = unsafe { &mut *self.pml4_raw() };
|
let parent_pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
let hhdm = self.hhdm;
|
|
||||||
|
|
||||||
for region in &mut self.regions {
|
for region in &mut self.regions {
|
||||||
let mut child_region = VmaRegion {
|
let mut child_region = VmaRegion {
|
||||||
@@ -373,10 +347,10 @@ impl AddressSpace {
|
|||||||
|
|
||||||
match &mut region.backing {
|
match &mut region.backing {
|
||||||
VmaBacking::Physical(base) => {
|
VmaBacking::Physical(base) => {
|
||||||
child_pml4.map_region(region.virt_start, *base, region.size(), region.flags.to_page_flags(), hhdm);
|
child_pml4.map_region(region.virt_start, *base, region.size(), region.flags.to_page_flags());
|
||||||
}
|
}
|
||||||
VmaBacking::Shared { phys_base, .. } => {
|
VmaBacking::Shared { phys_base, .. } => {
|
||||||
child_pml4.map_region(region.virt_start, *phys_base, region.size(), region.flags.to_page_flags(), hhdm);
|
child_pml4.map_region(region.virt_start, *phys_base, region.size(), region.flags.to_page_flags());
|
||||||
}
|
}
|
||||||
VmaBacking::Anonymous(frames) => {
|
VmaBacking::Anonymous(frames) => {
|
||||||
let cow_needed = region.flags.contains(VmaFlags::WRITE);
|
let cow_needed = region.flags.contains(VmaFlags::WRITE);
|
||||||
@@ -400,10 +374,10 @@ impl AddressSpace {
|
|||||||
page_flags.remove(PageTableFlags::WRITABLE);
|
page_flags.remove(PageTableFlags::WRITABLE);
|
||||||
page_flags.insert(PageTableFlags::COW);
|
page_flags.insert(PageTableFlags::COW);
|
||||||
|
|
||||||
let _ = parent_pml4.update_flags(virt, page_flags, hhdm);
|
let _ = parent_pml4.update_flags(virt, page_flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
child_pml4.map_page(virt, *frame, page_flags, hhdm);
|
child_pml4.map_page(virt, *frame, page_flags);
|
||||||
child_frames[i] = Some(*frame);
|
child_frames[i] = Some(*frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,12 +405,12 @@ impl AddressSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unmap pages + free frames (if owned) for one region.
|
/// Unmap pages + free frames (if owned) for one region.
|
||||||
fn do_unmap(pml4: *mut PageTable, region: &VmaRegion, hhdm: u64) {
|
fn do_unmap(pml4: *mut PageTable, region: &VmaRegion) {
|
||||||
let pml4 = unsafe { &mut *pml4 };
|
let pml4 = unsafe { &mut *pml4 };
|
||||||
let own = region.backing.owns_frames();
|
let own = region.backing.owns_frames();
|
||||||
for i in 0..region.pages() {
|
for i in 0..region.pages() {
|
||||||
let virt = VirtAddr(region.virt_start.0 + i as u64 * 4096);
|
let virt = VirtAddr(region.virt_start.0 + i as u64 * 4096);
|
||||||
pml4.unmap_page(virt, hhdm);
|
pml4.unmap_page(virt);
|
||||||
if own {
|
if own {
|
||||||
if let Some(frame) = region.backing.phys_for_page(i) {
|
if let Some(frame) = region.backing.phys_for_page(i) {
|
||||||
pmm::free_frame(frame);
|
pmm::free_frame(frame);
|
||||||
@@ -446,7 +420,6 @@ impl AddressSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn do_revoke_by_token(&mut self, cap_token: u64) {
|
fn do_revoke_by_token(&mut self, cap_token: u64) {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
|
|
||||||
let indices: Vec<usize> = self.regions
|
let indices: Vec<usize> = self.regions
|
||||||
@@ -458,7 +431,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
for idx in indices.into_iter().rev() {
|
for idx in indices.into_iter().rev() {
|
||||||
let region = self.regions.remove(idx);
|
let region = self.regions.remove(idx);
|
||||||
Self::do_unmap(pml4, ®ion, hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,34 +452,24 @@ impl AddressSpace {
|
|||||||
|
|
||||||
impl AddressSpace {
|
impl AddressSpace {
|
||||||
/// Allocate a fresh, empty address space with a zeroed PML4.
|
/// Allocate a fresh, empty address space with a zeroed PML4.
|
||||||
pub fn new(hhdm: u64) -> Result<Self, VmError> {
|
pub fn new() -> Result<Self, VmError> {
|
||||||
let pml4_phys = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let pml4_phys = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::write_bytes(pml4_phys.to_virt(hhdm).as_mut_ptr::<u8>(), 0, 4096);
|
core::ptr::write_bytes(pml4_phys.to_virt().as_mut_ptr::<u8>(), 0, 4096);
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
asid: alloc_asid()?,
|
asid: alloc_asid()?,
|
||||||
pml4_phys,
|
pml4_phys,
|
||||||
regions: Vec::new(),
|
regions: Vec::new(),
|
||||||
hhdm,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrap an already-active PML4 (bootloader-provided kernel table).
|
/// Wrap an already-active PML4 (bootloader-provided kernel table).
|
||||||
/// ASID 0 = kernel "no PCID tagging"; it is never returned to the pool.
|
/// ASID 0 = kernel "no PCID tagging"; it is never returned to the pool.
|
||||||
pub fn from_active(pml4_phys: PhysAddr, asid: u16, hhdm: u64) -> Self {
|
pub fn from_active(pml4_phys: PhysAddr, asid: u16) -> Self {
|
||||||
Self { asid, pml4_phys, regions: Vec::new(), hhdm }
|
Self { asid, pml4_phys, regions: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Anonymous / fixed mapping ─────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Map `size` bytes of virtual space starting at `virt`.
|
|
||||||
///
|
|
||||||
/// | `phys` | `VmaFlags::LAZY` | Behaviour |
|
|
||||||
/// |-----------|------------------|-----------------------------------------------|
|
|
||||||
/// | `Some(p)` | any | Fixed physical (MMIO / identity / DMA) |
|
|
||||||
/// | `None` | not set | Eager anonymous — allocate + zero + map now |
|
|
||||||
/// | `None` | set | Lazy anonymous — map frames on first fault |
|
|
||||||
pub fn map_region(
|
pub fn map_region(
|
||||||
&mut self,
|
&mut self,
|
||||||
virt: VirtAddr,
|
virt: VirtAddr,
|
||||||
@@ -526,11 +489,10 @@ impl AddressSpace {
|
|||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
let page_flags = flags.to_page_flags();
|
let page_flags = flags.to_page_flags();
|
||||||
let page_count = (size / 4096) as usize;
|
let page_count = (size / 4096) as usize;
|
||||||
let hhdm = self.hhdm;
|
|
||||||
|
|
||||||
let backing = match phys {
|
let backing = match phys {
|
||||||
Some(base) => {
|
Some(base) => {
|
||||||
pml4.map_region(virt, base, size, page_flags, hhdm);
|
pml4.map_region(virt, base, size, page_flags);
|
||||||
VmaBacking::Physical(base)
|
VmaBacking::Physical(base)
|
||||||
}
|
}
|
||||||
None if flags.contains(VmaFlags::LAZY) => {
|
None if flags.contains(VmaFlags::LAZY) => {
|
||||||
@@ -543,13 +505,12 @@ impl AddressSpace {
|
|||||||
for i in 0..page_count {
|
for i in 0..page_count {
|
||||||
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::write_bytes(frame.to_virt(hhdm).as_mut_ptr::<u8>(), 0, 4096);
|
core::ptr::write_bytes(frame.to_virt().as_mut_ptr::<u8>(), 0, 4096);
|
||||||
}
|
}
|
||||||
pml4.map_page(
|
pml4.map_page(
|
||||||
VirtAddr(virt.0 + i as u64 * 4096),
|
VirtAddr(virt.0 + i as u64 * 4096),
|
||||||
frame,
|
frame,
|
||||||
page_flags,
|
page_flags,
|
||||||
hhdm,
|
|
||||||
);
|
);
|
||||||
frames.push(Some(frame));
|
frames.push(Some(frame));
|
||||||
}
|
}
|
||||||
@@ -589,7 +550,6 @@ impl AddressSpace {
|
|||||||
|
|
||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
let page_flags = flags.to_page_flags();
|
let page_flags = flags.to_page_flags();
|
||||||
let hhdm = self.hhdm;
|
|
||||||
|
|
||||||
// Eagerly map all pages — the physical addresses are already known.
|
// Eagerly map all pages — the physical addresses are already known.
|
||||||
for i in 0..page_count {
|
for i in 0..page_count {
|
||||||
@@ -597,7 +557,6 @@ impl AddressSpace {
|
|||||||
VirtAddr(virt.0 + i as u64 * 4096),
|
VirtAddr(virt.0 + i as u64 * 4096),
|
||||||
PhysAddr(phys_base.0 + i as u64 * 4096),
|
PhysAddr(phys_base.0 + i as u64 * 4096),
|
||||||
page_flags,
|
page_flags,
|
||||||
hhdm,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,7 +576,6 @@ impl AddressSpace {
|
|||||||
/// Returns `Ok(())` if the fault was a valid lazy demand-page (caller should
|
/// Returns `Ok(())` if the fault was a valid lazy demand-page (caller should
|
||||||
/// `iretq` to retry). Returns `Err` for illegal accesses.
|
/// `iretq` to retry). Returns `Err` for illegal accesses.
|
||||||
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError> {
|
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError> {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let idx = self.find_idx(fault_addr).ok_or(VmError::RegionNotFound)?;
|
let idx = self.find_idx(fault_addr).ok_or(VmError::RegionNotFound)?;
|
||||||
|
|
||||||
let (virt_start, region_flags) = {
|
let (virt_start, region_flags) = {
|
||||||
@@ -633,7 +591,7 @@ impl AddressSpace {
|
|||||||
let page_virt = VirtAddr(virt_start.0 + page_idx as u64 * 4096);
|
let page_virt = VirtAddr(virt_start.0 + page_idx as u64 * 4096);
|
||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
|
|
||||||
let current_pte_flags = pml4.get_flags(page_virt, hhdm);
|
let current_pte_flags = pml4.get_flags(page_virt);
|
||||||
let is_cow = current_pte_flags.map_or(false, |f| f.contains(PageTableFlags::COW));
|
let is_cow = current_pte_flags.map_or(false, |f| f.contains(PageTableFlags::COW));
|
||||||
|
|
||||||
if write && is_cow {
|
if write && is_cow {
|
||||||
@@ -645,10 +603,12 @@ impl AddressSpace {
|
|||||||
let old_frame = frames[page_idx].expect("COW fault on unmapped page");
|
let old_frame = frames[page_idx].expect("COW fault on unmapped page");
|
||||||
let new_frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let new_frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
|
|
||||||
|
debug_assert!(old_frame != new_frame, "COW: old and new frame are the same!");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::copy_nonoverlapping(
|
core::ptr::copy_nonoverlapping(
|
||||||
old_frame.to_virt(hhdm).as_ptr::<u8>(),
|
old_frame.to_virt().as_ptr::<u8>(),
|
||||||
new_frame.to_virt(hhdm).as_mut_ptr::<u8>(),
|
new_frame.to_virt().as_mut_ptr::<u8>(),
|
||||||
4096,
|
4096,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -659,7 +619,7 @@ impl AddressSpace {
|
|||||||
target_flags.remove(PageTableFlags::COW);
|
target_flags.remove(PageTableFlags::COW);
|
||||||
target_flags.insert(PageTableFlags::WRITABLE);
|
target_flags.insert(PageTableFlags::WRITABLE);
|
||||||
|
|
||||||
pml4.map_page(page_virt, new_frame, target_flags, hhdm);
|
pml4.map_page(page_virt, new_frame, target_flags);
|
||||||
|
|
||||||
pmm::free_frame(old_frame);
|
pmm::free_frame(old_frame);
|
||||||
|
|
||||||
@@ -681,7 +641,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::write_bytes(frame.to_virt(hhdm).as_mut_ptr::<u8>(), 0, 4096);
|
core::ptr::write_bytes(frame.to_virt().as_mut_ptr::<u8>(), 0, 4096);
|
||||||
}
|
}
|
||||||
frames[page_idx] = Some(frame);
|
frames[page_idx] = Some(frame);
|
||||||
|
|
||||||
@@ -690,7 +650,7 @@ impl AddressSpace {
|
|||||||
target_flags.remove(PageTableFlags::WRITABLE);
|
target_flags.remove(PageTableFlags::WRITABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
pml4.map_page(page_virt, frame, target_flags, hhdm);
|
pml4.map_page(page_virt, frame, target_flags);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -699,7 +659,7 @@ impl AddressSpace {
|
|||||||
let idx = self.find_idx(virt).ok_or(VmError::RegionNotFound)?;
|
let idx = self.find_idx(virt).ok_or(VmError::RegionNotFound)?;
|
||||||
let region = self.regions.remove(idx);
|
let region = self.regions.remove(idx);
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
Self::do_unmap(pml4, ®ion, self.hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
tlb_flush_asid(self.asid);
|
tlb_flush_asid(self.asid);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -709,7 +669,6 @@ impl AddressSpace {
|
|||||||
/// Hardware access is terminated before this function returns.
|
/// Hardware access is terminated before this function returns.
|
||||||
/// Shared-backed VMAs are unmapped from the PT without freeing frames.
|
/// Shared-backed VMAs are unmapped from the PT without freeing frames.
|
||||||
pub fn revoke_by_token(&mut self, cap_token: u64) {
|
pub fn revoke_by_token(&mut self, cap_token: u64) {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
|
|
||||||
let indices: Vec<usize> = self.regions
|
let indices: Vec<usize> = self.regions
|
||||||
@@ -723,7 +682,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
for idx in indices.into_iter().rev() {
|
for idx in indices.into_iter().rev() {
|
||||||
let region = self.regions.remove(idx);
|
let region = self.regions.remove(idx);
|
||||||
Self::do_unmap(pml4, ®ion, hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
}
|
}
|
||||||
|
|
||||||
tlb_flush_asid(self.asid);
|
tlb_flush_asid(self.asid);
|
||||||
@@ -733,7 +692,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
/// Walk the live page table to translate `virt` → physical address.
|
/// Walk the live page table to translate `virt` → physical address.
|
||||||
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
||||||
unsafe { (*self.pml4_raw()).translate(virt, self.hhdm) }
|
unsafe { (*self.pml4_raw()).translate(virt) }
|
||||||
}
|
}
|
||||||
|
|
||||||
//Activation
|
//Activation
|
||||||
@@ -767,10 +726,9 @@ impl AddressSpace {
|
|||||||
|
|
||||||
impl Drop for AddressSpace {
|
impl Drop for AddressSpace {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
while let Some(region) = self.regions.pop() {
|
while let Some(region) = self.regions.pop() {
|
||||||
Self::do_unmap(pml4, ®ion, hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
}
|
}
|
||||||
pmm::free_frame(self.pml4_phys);
|
pmm::free_frame(self.pml4_phys);
|
||||||
// Return the ASID to the pool so it can be reused by future processes.
|
// Return the ASID to the pool so it can be reused by future processes.
|
||||||
@@ -793,7 +751,15 @@ pub fn tlb_flush_asid(asid: u16) {
|
|||||||
|
|
||||||
let active_cpus = ACTIVE_CPUS_MASK.load(Ordering::Acquire);
|
let active_cpus = ACTIVE_CPUS_MASK.load(Ordering::Acquire);
|
||||||
let current_core = crate::cpu::lapic::current_core_id();
|
let current_core = crate::cpu::lapic::current_core_id();
|
||||||
let target_mask = active_cpus & !(1u64 << current_core);
|
// u64 can only represent cores 0–63. APIC IDs may be >= 64 (CPUID
|
||||||
|
// returns up to 255), so guard the shift to avoid UB (panic in debug,
|
||||||
|
// wrap in release). When SMP with >64 cores is implemented, switch to
|
||||||
|
// a wider mask or a dynamic list of active APIC IDs.
|
||||||
|
let target_mask = if current_core < 64 {
|
||||||
|
active_cpus & !(1u64 << current_core)
|
||||||
|
} else {
|
||||||
|
active_cpus
|
||||||
|
};
|
||||||
|
|
||||||
if target_mask == 0 {
|
if target_mask == 0 {
|
||||||
return;
|
return;
|
||||||
@@ -830,7 +796,12 @@ pub fn handle_tlb_shootdown_ipi() {
|
|||||||
local_tlb_flush_asid(asid);
|
local_tlb_flush_asid(asid);
|
||||||
|
|
||||||
let current_core = crate::cpu::lapic::current_core_id();
|
let current_core = crate::cpu::lapic::current_core_id();
|
||||||
SHOOTDOWN_ACK.fetch_or(1u64 << current_core, Ordering::AcqRel);
|
// Only cores < 64 can ACK in a u64 mask. Cores >= 64 are not
|
||||||
|
// representable; their ACK would wrap and corrupt the mask.
|
||||||
|
// TODO: widen to u128 or use per-core ACK slots for >64 core SMP.
|
||||||
|
if current_core < 64 {
|
||||||
|
SHOOTDOWN_ACK.fetch_or(1u64 << current_core, Ordering::AcqRel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global kernel address space
|
// Global kernel address space
|
||||||
@@ -840,6 +811,6 @@ pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
|
|||||||
///
|
///
|
||||||
/// ASID 0 = PCID 0 = kernel (no per-process PCID tagging).
|
/// ASID 0 = PCID 0 = kernel (no per-process PCID tagging).
|
||||||
/// Must be called after `BitmapPMM::init` and `init_cpu_features`.
|
/// Must be called after `BitmapPMM::init` and `init_cpu_features`.
|
||||||
pub fn init_kernel_space(pml4_phys: PhysAddr, hhdm: u64) {
|
pub fn init_kernel_space(pml4_phys: PhysAddr) {
|
||||||
*KERNEL_SPACE.lock() = Some(AddressSpace::from_active(pml4_phys, 0, hhdm));
|
*KERNEL_SPACE.lock() = Some(AddressSpace::from_active(pml4_phys, 0));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user