fix: bugs
This commit is contained in:
BIN
.PLAN.md.swp
Normal file
BIN
.PLAN.md.swp
Normal file
Binary file not shown.
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.*
|
||||||
19
kernel/docs/README.md
Normal file
19
kernel/docs/README.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Документация ядра LISA
|
||||||
|
|
||||||
|
LISA v0.1.0 — capability-based микроядро для x86-64, написанное на Rust (nightly).
|
||||||
|
|
||||||
|
## Разделы
|
||||||
|
|
||||||
|
| Раздел | Описание |
|
||||||
|
|---|---|
|
||||||
|
| [Архитектура](architecture.md) | Общая архитектура и взаимосвязи подсистем |
|
||||||
|
| [Точка входа](main.md) | `kmain()`, инициализация, FramebufferDisplay, тесты |
|
||||||
|
| [Физическая память](memory-management.md) | BitmapPMM, BuddyAllocator, Page Tables, адресация |
|
||||||
|
| [Виртуальная память](vmm.md) | AddressSpace, VMA, COW, Lazy, TLB shootdown, ASID/PCID |
|
||||||
|
| [Акторы памяти](pmactor.md) | PMActor, PMRouter, BuddyAllocator, асинхронные запросы |
|
||||||
|
| [Capability system](capabilities.md) | CNode, Capability, mint/revoke, KernelObject |
|
||||||
|
| [Прерывания](interrupts.md) | IDT, обработчики page fault / TLB shootdown, LAPIC |
|
||||||
|
| [Консоль и отладка](console.md) | TTY, Serial, макросы логирования |
|
||||||
|
| [Аллокатор кучи](allocator.md) | SlabAllocator, Locked, GlobalAlloc |
|
||||||
|
| [Очередь отзыва](events.md) | RevocationQueue, MMU уведомления |
|
||||||
|
| [Сборочная система](build-system.md) | Cargo, linker scripts, GNUmakefile, toolchain |
|
||||||
105
kernel/docs/allocator.md
Normal file
105
kernel/docs/allocator.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Аллокатор кучи
|
||||||
|
|
||||||
|
**Файл**: `src/mem/allocator.rs`
|
||||||
|
|
||||||
|
Slab-аллокатор со spinlock-синхронизацией и fallback bump-аллокацией для больших блоков.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Примитив синхронизации: `Locked<A>`
|
||||||
|
|
||||||
|
Самодельный spinlock (альтернатива `spin::Mutex`).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct Locked<A> {
|
||||||
|
inner: UnsafeCell<A>,
|
||||||
|
lock: AtomicBool,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `new(inner)` — создаёт с unlocked состоянием
|
||||||
|
- `lock() -> LockedGuard<'_, A>` — CAS-цикл на `AtomicBool` (спин-ожидание)
|
||||||
|
|
||||||
|
### LockedGuard
|
||||||
|
|
||||||
|
- `Deref`/`DerefMut` — доступ к внутренним данным
|
||||||
|
- `Drop` — `store(false, Release)` — освобождение блокировки
|
||||||
|
|
||||||
|
`unsafe impl<A> Sync for Locked<A>` — разработчик гарантирует корректность.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SlabAllocator
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct SlabAllocator {
|
||||||
|
list_heads: [Option<&'static mut ListNode>; 9], // slab free list
|
||||||
|
large_block_free: Option<&'static mut LargeBlockNode>, // free list для >2048
|
||||||
|
heap_start: usize,
|
||||||
|
heap_end: usize,
|
||||||
|
next_bump: usize,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Slab классы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
||||||
|
```
|
||||||
|
|
||||||
|
9 классов — степени двойки от 8 до 2048.
|
||||||
|
|
||||||
|
### ListNode / LargeBlockNode
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct ListNode {
|
||||||
|
next: Option<&'static mut ListNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LargeBlockNode {
|
||||||
|
size: usize,
|
||||||
|
next: Option<&'static mut LargeBlockNode>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Односвязные списки свободных блоков.
|
||||||
|
|
||||||
|
### `init(&mut self, start, size)`
|
||||||
|
|
||||||
|
Устанавливает границы кучи: `heap_start = start`, `heap_end = start + size`, `next_bump = start`.
|
||||||
|
|
||||||
|
### `list_index(layout) -> Option<usize>`
|
||||||
|
|
||||||
|
Находит индекс наименьшего slab-класса, покрывающего `max(size, align)`.
|
||||||
|
|
||||||
|
Пример: layout size=20, align=8 → блок 20 > 16, следующий 32 → index 3 (32 байта).
|
||||||
|
|
||||||
|
### `fallback_alloc(&mut self, layout) -> *mut u8`
|
||||||
|
|
||||||
|
1. Проверяет `large_block_free` — если есть подходящий блок, отдаёт его
|
||||||
|
2. Иначе bump-аллокация: выравнивание, проверка `next_bump + size <= heap_end`, возврат `next_bump`
|
||||||
|
3. При OOM возвращает null
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Глобальный аллокатор
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[global_allocator]
|
||||||
|
pub static ALLOCATOR: Locked<SlabAllocator>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### `GlobalAlloc::alloc(&self, layout) -> *mut u8`
|
||||||
|
|
||||||
|
1. Lock allocator
|
||||||
|
2. Если `layout.size()` влезает в slab класс:
|
||||||
|
- Находит `list_index`
|
||||||
|
- Если free list не пуст: pop голову, вернуть указатель
|
||||||
|
- Если пуст: `fallback_alloc(layout)` но с размером slab-блока (не `layout.size()`)
|
||||||
|
3. Для блоков > 2048: сразу `fallback_alloc(layout)`
|
||||||
|
|
||||||
|
### `GlobalAlloc::dealloc(&self, ptr, layout)`
|
||||||
|
|
||||||
|
1. Lock allocator
|
||||||
|
2. Для slab-sized: push обратно в slab free list как ListNode
|
||||||
|
3. Для больших: push в large_block_free как LargeBlockNode
|
||||||
56
kernel/docs/architecture.md
Normal file
56
kernel/docs/architecture.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Архитектура ядра LISA
|
||||||
|
|
||||||
|
LISA — **capability-based микроядро**. Вся физическая память управляется через систему capability (прав доступа к объектам). Ядро использует загрузчик **Limine** (протокол Limine boot protocol).
|
||||||
|
|
||||||
|
## Общая схема
|
||||||
|
|
||||||
|
```
|
||||||
|
kmain()
|
||||||
|
├── BitmapPMM.init() ── глобальный менеджер фреймов
|
||||||
|
├── LAPIC.init() ── доступ к Local APIC
|
||||||
|
├── PageTable::activate() ── включение страничной памяти
|
||||||
|
├── SlabAllocator.init() ── куча ядра
|
||||||
|
├── VMM::init_kernel_space() ── адресное пространство ядра
|
||||||
|
├── IDT::load() ── обработчики прерываний
|
||||||
|
├── PMRouter::init() ── 65536 каналов
|
||||||
|
├── CNode (cap тест) ── insert → mint → revoke
|
||||||
|
│ └── RevocationQueue.push() ── уведомление VMM
|
||||||
|
└── PMActor тест
|
||||||
|
├── PMRouter.alloc_channel()
|
||||||
|
├── PMActor.submit_request()
|
||||||
|
├── PMActor.process_messages()
|
||||||
|
└── BuddyAllocator (внутри PMActor)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Поток ревокации
|
||||||
|
|
||||||
|
```
|
||||||
|
CNode::revoke()
|
||||||
|
→ RevocationQueue.push(token_sig)
|
||||||
|
→ (page fault handler)
|
||||||
|
→ process_deferred_mmu_events()
|
||||||
|
→ AddressSpace::process_pending_revocations()
|
||||||
|
→ RevocationQueue.pop()
|
||||||
|
→ AddressSpace::revoke_by_token(token) // анмаппинг VMA
|
||||||
|
→ TLB flush
|
||||||
|
```
|
||||||
|
|
||||||
|
## Поток PMActor запроса
|
||||||
|
|
||||||
|
```
|
||||||
|
1. PMRouter::alloc_channel() → получаем channel_id
|
||||||
|
2. PMActor::submit_request(Allocate { channel_id, ... })
|
||||||
|
3. (актор обрабатывает) PMActor::process_messages()
|
||||||
|
4. PMActor::handle_allocate() → BuddyAllocator::alloc_pages()
|
||||||
|
5. PMRouter::route_responses([PMResponse { channel_id, result }])
|
||||||
|
6. PMRouter::wait_for_response(channel_id) → spin до READY
|
||||||
|
7. Возврат результата клиенту
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ключевые принципы
|
||||||
|
|
||||||
|
- **Lock-free очереди** — MPSC кольцевые буферы везде, где возможен контеншен (PMActorQueue, RevocationQueue, PMRouter channels)
|
||||||
|
- **Double-write logging** — каждое сообщение дублируется в фреймбуфер (консоль) и последовательный порт
|
||||||
|
- **HHDM** — Higher Half Direct Map: весь физическая память отображена 1:1 в верхней половине адресного пространства через HHDM offset
|
||||||
|
- **ASID/PCID** — аппаратная изоляция TLB между адресными пространствами
|
||||||
|
- **Распределённое управление памятью** — PMM выделяет только сырые фреймы, PMActor управляет диапазонами через BuddyAllocator
|
||||||
134
kernel/docs/build-system.md
Normal file
134
kernel/docs/build-system.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# Сборочная система
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cargo.toml
|
||||||
|
|
||||||
|
**Файл**: `Cargo.toml`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "LISA"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# н/д — только бинарный крейт
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "kernel"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
limine = "0.5" # Limine boot protocol
|
||||||
|
embedded-graphics = "0.8" # 2D фреймбуфер рисование
|
||||||
|
bitflags = "2.11.0" # bitflags макрос
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "abort" # без раскрутки стека
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## build.rs
|
||||||
|
|
||||||
|
**Файл**: `build.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||||
|
println!("cargo:rustc-link-arg=-Tlinker-{}.ld", arch);
|
||||||
|
println!("cargo:rerun-if-changed=linker-{}.ld", arch);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Передаёт линкер-скрипт в зависимости от архитектуры: `-Tlinker-x86_64.ld`, `-Tlinker-aarch64.ld`, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Linker скрипты
|
||||||
|
|
||||||
|
### Общая структура (все 4 архитектуры)
|
||||||
|
|
||||||
|
- **Entry**: `kmain`
|
||||||
|
- **Base address**: `0xFFFFFFFF80000000` — высшие 2 GiB, по спецификации Limine
|
||||||
|
- **Program headers**: `PT_LOAD` для `.text`, `.rodata`, `.data`
|
||||||
|
|
||||||
|
### Секции
|
||||||
|
|
||||||
|
```
|
||||||
|
SECTIONS {
|
||||||
|
.text : { *(.text .text.*) } → код
|
||||||
|
.rodata : { *(.rodata .rodata.*) } → только чтение (page-aligned)
|
||||||
|
.data : {
|
||||||
|
*(.requests_start_marker) → Limine requests
|
||||||
|
*(.requests)
|
||||||
|
*(.requests_end_marker)
|
||||||
|
*(.data .data.*)
|
||||||
|
}
|
||||||
|
.bss : { *(.bss .bss.*) } → нули
|
||||||
|
/DISCARD/ : { *(.eh_frame*) *(.note.*) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Архитектурные различия
|
||||||
|
|
||||||
|
| Архитектура | OUTPUT_FORMAT | Особенности |
|
||||||
|
|---|---|---|
|
||||||
|
| x86-64 | `elf64-x86-64` | Стандартный |
|
||||||
|
| aarch64 | `elf64-littleaarch64` | Стандартный |
|
||||||
|
| riscv64 | `elf64-littleriscv` | `.data` + `.sdata`, `.bss` + `.sbss` |
|
||||||
|
| loongarch64 | `elf64-loongarch` | Стандартный |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GNUmakefile
|
||||||
|
|
||||||
|
**Файл**: `GNUmakefile`
|
||||||
|
|
||||||
|
### Переменные
|
||||||
|
|
||||||
|
```makefile
|
||||||
|
OUTPUT := kernel
|
||||||
|
KARCH ?= x86_64
|
||||||
|
RUST_TARGET := $(KARCH)-unknown-none
|
||||||
|
# riscv64 -> riscv64gc-unknown-none-elf
|
||||||
|
RUST_PROFILE ?= dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Цели
|
||||||
|
|
||||||
|
| Цель | Действие |
|
||||||
|
|---|---|
|
||||||
|
| `all` | `RUSTFLAGS="-C relocation-model=static" cargo build --target ...` + копирование `target/.../kernel` → `./kernel` |
|
||||||
|
| `clean` | `cargo clean` + rm `./kernel` |
|
||||||
|
| `distclean` | То же, что clean |
|
||||||
|
|
||||||
|
`relocation-model=static` — запрещает позиционно-независимый код (ядро загружается по фиксированному адресу).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## rust-toolchain.toml
|
||||||
|
|
||||||
|
**Файл**: `rust-toolchain.toml`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
targets = ["x86_64-unknown-none"]
|
||||||
|
# aarch64-unknown-none, riscv64gc-unknown-none-elf, loongarch64-unknown-none (для будущего)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## .gitignore
|
||||||
|
|
||||||
|
**Файл**: `.gitignore`
|
||||||
|
|
||||||
|
```
|
||||||
|
/kernel # бинарный файл ядра
|
||||||
|
/target # артефакты сборки Cargo
|
||||||
|
```
|
||||||
147
kernel/docs/capabilities.md
Normal file
147
kernel/docs/capabilities.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Система Capability
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Дескрипторы Capability
|
||||||
|
|
||||||
|
**Файл**: `src/cap/descriptor.rs`
|
||||||
|
|
||||||
|
### Relation
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum Relation {
|
||||||
|
Strong, // владение (исключительный доступ)
|
||||||
|
Borrow, // заимствование (временный доступ)
|
||||||
|
Transfer, // передача (владение перешло)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CapObject
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum CapObject {
|
||||||
|
Empty, // null (слот свободен)
|
||||||
|
Memory { phys: PhysAddr, size_pages: usize }, // регион физической памяти
|
||||||
|
CNode { phys: PhysAddr, slots: usize }, // узел capability (таблица)
|
||||||
|
PMActor { id: u64 }, // ссылка на PMActor
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CapRights (bitflags, u8)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
READ = 1 << 0
|
||||||
|
WRITE = 1 << 1
|
||||||
|
EXECUTE = 1 << 2
|
||||||
|
GRANT = 1 << 3 // разрешение на mint (порождение потомков)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Capability
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct Capability {
|
||||||
|
pub object: CapObject, // ссылка на объект
|
||||||
|
pub rights: CapRights, // права доступа
|
||||||
|
pub relation: Relation, // тип отношений
|
||||||
|
pub token_sig: u64, // уникальный токен для отзыва (MMU)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `empty()` — возвращает Capability с `CapObject::Empty`
|
||||||
|
- `is_valid() -> bool` — `object != Empty`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Объекты ядра
|
||||||
|
|
||||||
|
**Файл**: `src/cap/object.rs`
|
||||||
|
|
||||||
|
### ObjectType
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum ObjectType {
|
||||||
|
Untyped, // сырая нетипизированная память
|
||||||
|
Frame, // выделенный физический фрейм
|
||||||
|
CNode, // узел capability
|
||||||
|
ThreadBlock, // блок управления потоком
|
||||||
|
PageTable, // страница таблицы
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### KernelObject
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct KernelObject {
|
||||||
|
pub phys_addr: PhysAddr,
|
||||||
|
pub size_bits: u8, // размер как степень двойки
|
||||||
|
pub obj_type: ObjectType,
|
||||||
|
pub ref_count: AtomicUsize, // атомарный счётчик ссылок
|
||||||
|
pub owner_id: u64, // ID владельца
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `add_ref()` — atomic increment (Relaxed ordering)
|
||||||
|
- `release() -> bool` — atomic decrement (Release). Возвращает true, если счётчик достиг 0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CNode
|
||||||
|
|
||||||
|
**Файл**: `src/cap/mod.rs`
|
||||||
|
|
||||||
|
Узел capability, хранит массив слотов с индивидуальной блокировкой.
|
||||||
|
|
||||||
|
### CNodeSlot
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct CNodeSlot {
|
||||||
|
cap: Capability,
|
||||||
|
parent_idx: Option<usize>, // индекс родителя (для дерева) — None у корневых
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CNode
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct CNode {
|
||||||
|
slots: Vec<Locked<CNodeSlot>>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `new(size) -> Self`
|
||||||
|
|
||||||
|
Создаёт `size` пустых слотов с `parent_idx = None`.
|
||||||
|
|
||||||
|
### `insert(slot, cap) -> Result<(), &str>`
|
||||||
|
|
||||||
|
Проверка границ, сохранение Capability в слоте.
|
||||||
|
|
||||||
|
### `mint(src, dest, relation, rights) -> Result<(), &str>`
|
||||||
|
|
||||||
|
**Порождение дочерней capability с урезанными правами:**
|
||||||
|
|
||||||
|
1. **Lock ordering**: блокировка слотов по возрастанию индекса (deadlock prevention)
|
||||||
|
2. Source-слот не пуст, имеет GRANT право
|
||||||
|
3. `child_rights = parent_rights & requested_rights` — дочерние права не могут превышать родительские
|
||||||
|
4. Копирует Capability, устанавливает новые права, relation, `parent_idx`
|
||||||
|
5. Ошибка при `src == dest`
|
||||||
|
|
||||||
|
### `revoke(slot_idx) -> Result<(), &str>`
|
||||||
|
|
||||||
|
**Каскадный отзыв с уведомлением VMM:**
|
||||||
|
|
||||||
|
1. `revoke_internal(slot_idx)` — рекурсивно находит и уничтожает всех потомков
|
||||||
|
2. Уничтожает саму Capability в слоте (Empty)
|
||||||
|
3. Пушит `token_sig` в `MMU_REVOCATION_QUEUE` (кроме 0 и `0xDEAD_BEEF`)
|
||||||
|
4. **Паника** при переполнении очереди
|
||||||
|
|
||||||
|
### `revoke_internal(slot_idx)`
|
||||||
|
|
||||||
|
Приватная рекурсия:
|
||||||
|
1. Сканирует все слоты в поисках `parent_idx == slot_idx`
|
||||||
|
2. Для каждого потомка: рекурсивный вызов
|
||||||
|
3. Затем уничтожает свой слот (Empty)
|
||||||
|
|
||||||
|
### `get_cap(slot) -> Option<Capability>`
|
||||||
|
|
||||||
|
Возвращает копию Capability.
|
||||||
132
kernel/docs/console.md
Normal file
132
kernel/docs/console.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# Консоль и отладка
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TTY — PSF2 терминал
|
||||||
|
|
||||||
|
**Файл**: `src/tty.rs`
|
||||||
|
|
||||||
|
Шрифтовой терминал на основе PSF2 (PC Screen Font v2). Это **реально используемая консоль** в `kmain`.
|
||||||
|
|
||||||
|
### Psf2Header — `#[repr(C, packed)]`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct Psf2Header {
|
||||||
|
magic: u32, // магическое число PSF2
|
||||||
|
version: u32,
|
||||||
|
header_size: u32,
|
||||||
|
flags: u32,
|
||||||
|
num_glyphs: u32, // количество глифов
|
||||||
|
bytes_per_glyph: u32,
|
||||||
|
height: u32, // высота шрифта в пикселях
|
||||||
|
width: u32, // ширина шрифта в пикселях
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Console
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct Console<'a> {
|
||||||
|
framebuffer: &'a Framebuffer<'a>,
|
||||||
|
font: &'static [u8], // сырые данные PSF2
|
||||||
|
x: usize, // курсор X (в пикселях)
|
||||||
|
y: usize, // курсор Y (в пикселях)
|
||||||
|
fg_color: u32, // цвет текста (0xFFFFFF = белый)
|
||||||
|
bg_color: u32, // цвет фона (0x000000 = чёрный)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Методы
|
||||||
|
|
||||||
|
| Метод | Описание |
|
||||||
|
|---|---|
|
||||||
|
| `new(framebuffer, font)` | Создаёт консоль, курсор в (0,0) |
|
||||||
|
| `set_color(fg)` | Устанавливает цвет текста |
|
||||||
|
| `clear()` | Зануляет фреймбуфер, сбрасывает курсор |
|
||||||
|
| `header()` | Возвращает ссылку на Psf2Header |
|
||||||
|
| `scroll()` | Сдвигает фреймбуфер на высоту шрифта, зануляет низ |
|
||||||
|
| `draw_glyph(glyph_index, x, y)` | Рисует глиф: сканирует bitmap шрифта, пишет `fg_color` для установленных битов |
|
||||||
|
| `write_char(c)` | Обрабатывает `\n`, word wrap, поиск глифа (0 для отсутствующих), скролл |
|
||||||
|
| `write_str(s)` | Реализация `fmt::Write` — итерация по символам |
|
||||||
|
|
||||||
|
`draw_glyph` — ключевой метод: строки шрифта — это побитовое представление, каждый бит — один пиксель. Если бит установлен → пишется `fg_color`, иначе пропускается.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Serial — последовательный порт
|
||||||
|
|
||||||
|
**Файл**: `src/debug/serial.rs`
|
||||||
|
|
||||||
|
Драйвер UART 16550 на COM1.
|
||||||
|
|
||||||
|
### SerialPort
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct SerialPort(pub u16); // номер порта
|
||||||
|
```
|
||||||
|
|
||||||
|
### Инициализация UART (`init()`)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
port + 1 = 0x00 // отключение прерываний
|
||||||
|
port + 3 = 0x80 // DLAB = 1 (доступ к делителю)
|
||||||
|
port + 0 = 0x03 // делитель младший байт (~38400 бод)
|
||||||
|
port + 1 = 0x00 // делитель старший байт
|
||||||
|
port + 3 = 0x03 // 8N1: 8 бит, no parity, 1 stop bit
|
||||||
|
port + 2 = 0xC7 // FIFO enable, clear, 14-byte threshold
|
||||||
|
port + 4 = 0x0B // DTR + RTS (data terminal ready)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Методы
|
||||||
|
|
||||||
|
| Метод | Описание |
|
||||||
|
|---|---|
|
||||||
|
| `is_transmit_empty() -> bool` | Проверка бита 5 Line Status Register |
|
||||||
|
| `send(data: u8)` | Ждёт `is_transmit_empty()`, затем пишет в порт |
|
||||||
|
| `write_str(s)` (fmt::Write) | Поcимвольная отправка |
|
||||||
|
|
||||||
|
### Функции
|
||||||
|
|
||||||
|
| Функция | Описание |
|
||||||
|
|---|---|
|
||||||
|
| `init_global()` | Инициализирует COM1, сохраняет в `SERIAL_PORT` |
|
||||||
|
| `write_global(args: Arguments)` | Пишет форматированную строку в serial |
|
||||||
|
| `outb(port, val)` | `unsafe`: инструкция `out` (write I/O port) |
|
||||||
|
| `inb(port) -> u8` | `unsafe`: инструкция `in` (read I/O port) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Логирование
|
||||||
|
|
||||||
|
**Файл**: `src/debug.rs`
|
||||||
|
|
||||||
|
### LogLevel
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum LogLevel {
|
||||||
|
Info, // зелёный: ANSI \x1b[32m, RGB 0x00FF00
|
||||||
|
Warn, // жёлтый: ANSI \x1b[33m, RGB 0xFFFF00
|
||||||
|
Error, // красный: ANSI \x1b[31m, RGB 0xFF0000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Макросы
|
||||||
|
|
||||||
|
#### `log!(console, level, module, $($arg)*)`
|
||||||
|
|
||||||
|
Формат: `[ LOG ] <module> | <message>\n`
|
||||||
|
|
||||||
|
Двойной вывод:
|
||||||
|
- **Console**: цветной `[ LOG ]` (по LogLevel) + белый `<module> | <message>`
|
||||||
|
- **Serial**: ANSI-цветной `[ LOG ]` + `<module> | <message>` + ANSI reset
|
||||||
|
|
||||||
|
#### `info!(console, module, ...)`
|
||||||
|
Обёртка над `log!` с `LogLevel::Info`. Зелёный `[ LOG ]`.
|
||||||
|
|
||||||
|
#### `warn!(console, module, ...)`
|
||||||
|
Обёртка над `log!` с `LogLevel::Warn`. Жёлтый `[ LOG ]`.
|
||||||
|
|
||||||
|
#### `error!(console, module, ...)`
|
||||||
|
Обёртка над `log!` с `LogLevel::Error`. Красный `[ LOG ]`.
|
||||||
|
|
||||||
|
Все макросы экспортируются (`#[macro_export]`) и доступны из любого модуля.
|
||||||
85
kernel/docs/events.md
Normal file
85
kernel/docs/events.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Очередь отзыва (RevocationQueue)
|
||||||
|
|
||||||
|
**Файл**: `src/events.rs`
|
||||||
|
|
||||||
|
Lock-free MPSC (Multi-Producer Single-Consumer) очередь для уведомления VMM об отзыве capability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RevocationQueue
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct RevocationQueue {
|
||||||
|
buffer: [AtomicU64; QUEUE_SIZE], // 1024 слота
|
||||||
|
_pad0: [u8; 64], // cache-line padding
|
||||||
|
head: AtomicUsize, // индекс потребителя (VMM)
|
||||||
|
_pad1: [u8; 64], // ещё padding
|
||||||
|
tail: AtomicUsize, // индекс производителя (CNode)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Cache-line padding (64 байта) предотвращает **false sharing** между head и tail на разных кэш-линиях.
|
||||||
|
|
||||||
|
### Константы
|
||||||
|
|
||||||
|
- `QUEUE_SIZE = 1024`
|
||||||
|
- `QUEUE_MASK = 1023`
|
||||||
|
|
||||||
|
### `new() -> Self`
|
||||||
|
|
||||||
|
Все слоты = 0, head = 0, tail = 0.
|
||||||
|
|
||||||
|
### `push(token_sig: u64) -> Result<(), &'static str>`
|
||||||
|
|
||||||
|
1. Читает `tail` (Relaxed)
|
||||||
|
2. CAS-цикл: пытается захватить следующий слот
|
||||||
|
3. Если буфер полон (`tail - head == QUEUE_SIZE`): ошибка
|
||||||
|
4. Записывает `token_sig` в `buffer[tail & QUEUE_MASK]` (Release ordering)
|
||||||
|
5. Обновляет `tail` (Release)
|
||||||
|
|
||||||
|
**Производители**: CNode::revoke() — при каждом отзыве пушит token_sig.
|
||||||
|
|
||||||
|
### `pop() -> Option<u64>`
|
||||||
|
|
||||||
|
1. Читает `tail` (Acquire) и `head` (Relaxed)
|
||||||
|
2. Если `head == tail`: буфер пуст, None
|
||||||
|
3. Читает `buffer[head & QUEUE_MASK]` (Acquire)
|
||||||
|
4. Обновляет `head` (Release)
|
||||||
|
5. Возвращает token
|
||||||
|
|
||||||
|
**Потребитель**: VMM — `AddressSpace::process_pending_revocations()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Глобальный экземпляр
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub static MMU_REVOCATION_QUEUE: RevocationQueue = RevocationQueue::new();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Полный цикл ревокации
|
||||||
|
|
||||||
|
```
|
||||||
|
CNode::revoke(slot_idx)
|
||||||
|
→ revoke_internal(slot_idx) // рекурсивное уничтожение потомков
|
||||||
|
→ slot = Empty
|
||||||
|
→ MMU_REVOCATION_QUEUE.push(token_sig)
|
||||||
|
|
||||||
|
... позже, при page fault ...
|
||||||
|
|
||||||
|
rust_page_fault_handler()
|
||||||
|
→ process_deferred_mmu_events()
|
||||||
|
→ KERNEL_SPACE.lock()
|
||||||
|
→ process_pending_revocations()
|
||||||
|
→ while let Some(token) = MMU_REVOCATION_QUEUE.pop()
|
||||||
|
→ revoke_by_token(token)
|
||||||
|
→ unmap matching VMAs (кроме PINNED)
|
||||||
|
→ TLB flush
|
||||||
|
|
||||||
|
→ KERNEL_SPACE.lock()
|
||||||
|
→ handle_fault(fault_addr, write) // обработка исходного fault
|
||||||
|
```
|
||||||
|
|
||||||
|
**Почему такая очерёдность?** Ревокации обрабатываются **до** захвата VMM для page fault, чтобы избежать deadlock'а: и revoke, и handle_fault хотят lock на `KERNEL_SPACE`.
|
||||||
142
kernel/docs/interrupts.md
Normal file
142
kernel/docs/interrupts.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# Прерывания
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IDT (Interrupt Descriptor Table)
|
||||||
|
|
||||||
|
**Файл**: `src/cpu/idt.rs`
|
||||||
|
|
||||||
|
### IdtEntry — `#[repr(C, packed)]`
|
||||||
|
|
||||||
|
Структура записи IDT (x86-64):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct IdtEntry {
|
||||||
|
offset_low: u16, // адрес обработчика [15:0]
|
||||||
|
selector: u16, // селектор кода (0x28 — kernel CS)
|
||||||
|
ist: u8, // Interrupt Stack Table index
|
||||||
|
type_attr: u8, // тип шлюза, DPL, Present
|
||||||
|
offset_mid: u16, // адрес обработчика [31:16]
|
||||||
|
offset_high: u32, // адрес обработчика [63:32]
|
||||||
|
ignore: u32, // резерв (0)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `new()` — все поля нули
|
||||||
|
- `set_handler(handler: u64, selector: u16, flags: u8)` — устанавливает адрес и флаги. Принудительно ставит Present (0x80) в flags.
|
||||||
|
|
||||||
|
### IdtPtr — `#[repr(C, packed)]`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct IdtPtr {
|
||||||
|
limit: u16, // размер IDT - 1
|
||||||
|
base: u64, // адрес IDT
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Загружается инструкцией `lidt`.
|
||||||
|
|
||||||
|
### InterruptDescriptorTable
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct InterruptDescriptorTable {
|
||||||
|
entries: [IdtEntry; 256], // 256 векторов
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `set_handler(vector, handler: u64)` — устанавливает обработчик на вектор. Селектор: `0x28` (kernel code), flags: `0x8E` (32-bit interrupt gate, ring 0, present).
|
||||||
|
- `load()` — `unsafe fn`: вызывает `lidt` с `&self`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Обработчики прерываний
|
||||||
|
|
||||||
|
**Файл**: `src/cpu/interrupts.rs`
|
||||||
|
|
||||||
|
### Глобальная IDT
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub static mut IDT: InterruptDescriptorTable;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Константы
|
||||||
|
|
||||||
|
`TLB_SHOOTDOWN_VECTOR: u8 = 0xFD`
|
||||||
|
|
||||||
|
### Ассемблерные заглушки (global_asm!)
|
||||||
|
|
||||||
|
#### `page_fault_stub`
|
||||||
|
|
||||||
|
```asm
|
||||||
|
save all registers (push rax, rcx, rdx, rbx, rbp, rsi, rdi, r8-r15)
|
||||||
|
mov rdi, [rsp + 15*8] ; error code из стека → 1-й аргумент
|
||||||
|
call rust_page_fault_handler
|
||||||
|
restore all registers
|
||||||
|
add rsp, 8 ; убрать error code
|
||||||
|
iretq
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `tlb_shootdown_stub`
|
||||||
|
|
||||||
|
```asm
|
||||||
|
save all registers
|
||||||
|
call rust_tlb_shootdown_handler
|
||||||
|
restore all registers
|
||||||
|
iretq
|
||||||
|
```
|
||||||
|
|
||||||
|
### `init_idt()`
|
||||||
|
|
||||||
|
- Устанавливает вектор 14 → `page_fault_stub`
|
||||||
|
- Устанавливает вектор `TLB_SHOOTDOWN_VECTOR` → `tlb_shootdown_stub`
|
||||||
|
- `IDT.load()`
|
||||||
|
|
||||||
|
### `process_deferred_mmu_events()`
|
||||||
|
|
||||||
|
Лочит `KERNEL_SPACE`, вызывает `process_pending_revocations()` — дренаж очереди отзыва.
|
||||||
|
|
||||||
|
### `rust_page_fault_handler(error_code: u64)`
|
||||||
|
|
||||||
|
`#[unsafe(no_mangle)] pub extern "C" fn`
|
||||||
|
|
||||||
|
1. Читает CR2 (адрес fault)
|
||||||
|
2. Из error_code: бит 1 = write, бит 0 = present
|
||||||
|
3. **Сначала** `process_deferred_mmu_events()` — обработка ревокаций до захвата VMM (deadlock prevention)
|
||||||
|
4. Лочит `KERNEL_SPACE`, вызывает `handle_fault(virt_addr, write)`
|
||||||
|
5. При неудаче: паника с деталями (address, read/write, present, error_code)
|
||||||
|
6. При успехе: возврат — инструкция будет перезапущена
|
||||||
|
|
||||||
|
### `rust_tlb_shootdown_handler()`
|
||||||
|
|
||||||
|
1. `handle_tlb_shootdown_ipi()` — локальный сброс TLB
|
||||||
|
2. `send_eoi()` — подтверждение LAPIC
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Local APIC
|
||||||
|
|
||||||
|
**Файл**: `src/cpu/lapic.rs`
|
||||||
|
|
||||||
|
Драйвер Local APIC (Advanced Programmable Interrupt Controller).
|
||||||
|
|
||||||
|
### Константы
|
||||||
|
|
||||||
|
```rust
|
||||||
|
LAPIC_DEFAULT_BASE = 0xFEE0_0000 // стандартный MMIO адрес LAPIC
|
||||||
|
LAPIC_EOI = 0x0B0 // End-Of-Interrupt регистр
|
||||||
|
LAPIC_ICR_LOW = 0x300 // Interrupt Command Register (low)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `LAPIC_VIRT_BASE: AtomicU64`
|
||||||
|
|
||||||
|
Кэшированный виртуальный адрес LAPIC MMIO (HHDM + default base).
|
||||||
|
|
||||||
|
### Функции
|
||||||
|
|
||||||
|
| Функция | Описание |
|
||||||
|
|---|---|
|
||||||
|
| `init(hhdm_offset)` | Вычисляет и сохраняет `LAPIC_VIRT_BASE = 0xFEE0_0000 + hhdm_offset` |
|
||||||
|
| `current_core_id() -> u32` | CPUID leaf 1, EBX[31:24] — Initial APIC ID |
|
||||||
|
| `write_lapic_reg(offset, value)` | Volatile write u32 в LAPIC MMIO |
|
||||||
|
| `send_eoi()` | `write_lapic_reg(LAPIC_EOI, 0)` — подтверждение прерывания |
|
||||||
|
| `broadcast_ipi_exclude_self(vector)` | IPI всем остальным ядрам через ICR (destination shorthand = 10b = all except self) |
|
||||||
98
kernel/docs/main.md
Normal file
98
kernel/docs/main.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Точка входа: `src/main.rs`
|
||||||
|
|
||||||
|
Точка входа ядра (`kmain`). Инициализирует оборудование, подсистемы памяти, запускает тесты capability и PMActor.
|
||||||
|
|
||||||
|
## Атрибуты крейта
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![no_std] // без стандартной библиотеки
|
||||||
|
#![no_main] // без точки входа из libc
|
||||||
|
extern crate alloc; // аллокатор кучи
|
||||||
|
```
|
||||||
|
|
||||||
|
## Константы
|
||||||
|
|
||||||
|
- `KERNEL_FONT: &[u8]` — встроенный PSF2 шрифт 8×16 на 512 глифов (включён через `include_bytes!("font.psf")`)
|
||||||
|
|
||||||
|
## Limine Boot Protocol Request
|
||||||
|
|
||||||
|
Статические переменные в секции `.requests` (через `#[unsafe(link_section = ".requests")]`):
|
||||||
|
|
||||||
|
| Переменная | Тип | Назначение |
|
||||||
|
|---|---|---|
|
||||||
|
| `MEMORY_MAP_REQUEST` | `MemoryMapRequest` | Карта физической памяти |
|
||||||
|
| `HHDM_REQUEST` | `HhdmRequest` | Higher Half Direct Map offset |
|
||||||
|
| `KERNEL_ADDR_REQUEST` | `ExecutableAddressRequest` | Базовые адреса ядра (physical + virtual) |
|
||||||
|
| `BASE_REVISION` | `BaseRevision` | Проверка версии Limine |
|
||||||
|
| `FRAMEBUFFER_REQUEST` | `FramebufferRequest` | Линейный фреймбуфер |
|
||||||
|
| `_START_MARKER` | `RequestsStartMarker` | Маркер начала секции requests |
|
||||||
|
| `_END_MARKER` | `RequestsEndMarker` | Маркер конца секции requests |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `FramebufferDisplay<'a>`
|
||||||
|
|
||||||
|
Низкоуровневый рендерер пикселей, обёртка над `limine::framebuffer::Framebuffer`.
|
||||||
|
|
||||||
|
**impl `DrawTarget` (embedded_graphics):**
|
||||||
|
- `Color = Rgb888`, `Error = Infallible`
|
||||||
|
- `draw_iter(pixels)` — запись пикселей в память фреймбуфера с проверкой границ
|
||||||
|
|
||||||
|
**impl `OriginDimensions`:**
|
||||||
|
- `size()` — размеры фреймбуфера
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `Console<'a>` (встроенный, НЕ используется)
|
||||||
|
|
||||||
|
Обёртка над FramebufferDisplay. **В `kmain` используется `tty::Console`**, этот — мёртвый код.
|
||||||
|
|
||||||
|
Поля: `display`, `x`, `y`, `current_color`.
|
||||||
|
Методы: `new()`, `set_color()`, `clear()`, `scroll()`, `write_str()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `kmain()` — точка входа
|
||||||
|
|
||||||
|
`unsafe extern "C" fn() -> !`
|
||||||
|
|
||||||
|
1. **Проверка `BASE_REVISION.is_supported()`** — убеждается, что загрузчик поддерживает нужную версию Limine
|
||||||
|
2. **Получение ответов Limine**: фреймбуфер, карта памяти, HHDM offset, адреса ядра
|
||||||
|
3. **Инициализация `tty::Console`** — создаёт консоль на первом фреймбуфере, чистит экран
|
||||||
|
4. **Инициализация Serial** — `debug::serial::init_global()` для отладки через COM1
|
||||||
|
5. **Вывод "BOOT"** через `info!` макрос
|
||||||
|
6. **Инициализация BitmapPMM** — из карты памяти и HHDM смещения
|
||||||
|
7. **Инициализация LAPIC** — `cpu::lapic::init(hhdm_offset)` для MMIO доступа к Local APIC
|
||||||
|
8. **Создание P4 (PML4)** — выделяет фрейм, зануляет его
|
||||||
|
9. **Картирование HHDM** — все non-reserved entry памяти маппятся identity + HHDM
|
||||||
|
10. **Картирование .text секции ядра** — по виртуальному базовому адресу
|
||||||
|
11. **Активация страничных таблиц** — `p4.activate(p4_phys)` — запись CR3
|
||||||
|
12. **Выделение кучи 8 MiB** — постранично маппится в `0xFFFF_9000_0000_0000`
|
||||||
|
13. **Инициализация SlabAllocator** — `ALLOCATOR.init()` с диапазоном кучи
|
||||||
|
14. **`init_cpu_features()`** — обнаружение INVPCID инструкции
|
||||||
|
15. **Регистрация адресного пространства ядра** — `vmm::init_kernel_space()`
|
||||||
|
16. **Инициализация IDT** — регистрация обработчиков page fault и TLB shootdown
|
||||||
|
17. **Инициализация PMRouter** — 65536 каналов маршрутизации
|
||||||
|
18. **Включение прерываний** — `sti` инструкция
|
||||||
|
19. **Тест capability системы**: создание CNode, insert/mint/revoke
|
||||||
|
20. **PMActor + Buddy тест** (строки 257-359): создание актора, аллокация, OOM, carve, free+coalescing
|
||||||
|
21. **Вывод ASCII-арт логотипа**
|
||||||
|
22. **Бесконечный HLT** — `hcf()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `hcf()`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn hcf() -> ! {
|
||||||
|
loop { x86_64::instructions::hlt(); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Бесконечный цикл HLT — снижает энергопотребление, процессор ждёт следующего прерывания.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `rust_panic(info: &PanicInfo) -> !`
|
||||||
|
|
||||||
|
Обработчик паники: пишет информацию в serial, затем вызывает `hcf()`.
|
||||||
243
kernel/docs/memory-management.md
Normal file
243
kernel/docs/memory-management.md
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# Управление физической памятью
|
||||||
|
|
||||||
|
## Содержание
|
||||||
|
|
||||||
|
1. [Адресация (PhysAddr/VirtAddr)](#physaddr--virtaddr)
|
||||||
|
2. [BitmapPMM — менеджер физических фреймов](#bitmappmm)
|
||||||
|
3. [BuddyAllocator — блочный аллокатор](#buddyallocator)
|
||||||
|
4. [Page Tables — страничные таблицы x86-64](#page-tables)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PhysAddr / VirtAddr
|
||||||
|
|
||||||
|
**Файл**: `src/mem/address.rs`
|
||||||
|
|
||||||
|
Типы-обёртки над `u64` с HHDM-трансляцией.
|
||||||
|
|
||||||
|
### `PhysAddr(pub u64)` — `#[repr(transparent)]`
|
||||||
|
|
||||||
|
| Метод | Описание |
|
||||||
|
|---|---|
|
||||||
|
| `to_virt(hhdm_offset) -> VirtAddr` | `PhysAddr + hhdm_offset` — вход в HHDM |
|
||||||
|
| `is_aligned() -> bool` | Проверка 4 KiB выравнивания |
|
||||||
|
| `align_down() -> Self` | Выравнивание вниз до границы страницы |
|
||||||
|
| `align_up() -> Self` | Выравнивание вверх до границы страницы |
|
||||||
|
|
||||||
|
### `VirtAddr(pub u64)` — `#[repr(transparent)]`
|
||||||
|
|
||||||
|
| Метод | Описание |
|
||||||
|
|---|---|
|
||||||
|
| `to_phys(hhdm_offset) -> Option<PhysAddr>` | `VirtAddr - hhdm_offset`, возвращает None если адрес ниже HHDM |
|
||||||
|
| `as_ptr::<T>() -> *const T` | Приведение к константному указателю |
|
||||||
|
| `as_mut_ptr::<T>() -> *mut T` | Приведение к mutable указателю |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BitmapPMM
|
||||||
|
|
||||||
|
**Файл**: `src/mem/pmm.rs`
|
||||||
|
|
||||||
|
Первичный менеджер физической памяти через битовую карту + счётчики ссылок.
|
||||||
|
|
||||||
|
### Структура
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct BitmapPMM {
|
||||||
|
bitmap: &'static mut [u8], // один бит на страницу (1 = занята)
|
||||||
|
ref_counts: &'static mut [u16], // счётчики ссылок для COW/shared
|
||||||
|
total_pages: usize, // всего физических страниц в системе
|
||||||
|
used_pages: usize, // количество выделенных страниц
|
||||||
|
last_byte: usize, // подсказка для следующего сканирования
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Глобальная: `pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None)`.
|
||||||
|
|
||||||
|
### `init(mmap, hhdm_offset)`
|
||||||
|
|
||||||
|
Полный поток инициализации:
|
||||||
|
|
||||||
|
1. Вычисляет макс. физический адрес из карты памяти Limine
|
||||||
|
2. Рассчитывает размер bitmap и ref_counts
|
||||||
|
3. Находит USABLE регион для хранения метаданных
|
||||||
|
4. Зануляет bitmap, все биты = 1 (все заняты), ref_counts = 1
|
||||||
|
5. Проходит по карте памяти: все USABLE → `free_frame()`
|
||||||
|
6. **Лочит** страницы метаданных (чтобы не выдать повторно)
|
||||||
|
7. **Лочит** страницу 0 (зарезервирована)
|
||||||
|
8. Сохраняет в глобальную `PMM`
|
||||||
|
|
||||||
|
### `free_frame(addr)`
|
||||||
|
|
||||||
|
- Декрементит `ref_count`
|
||||||
|
- Если `ref_count == 0`: очищает бит, декрементит `used_pages`, обновляет `last_byte`
|
||||||
|
|
||||||
|
### `lock_frame(addr)`
|
||||||
|
|
||||||
|
- Если страница свободна: бит = 1, `ref_count = 1`, `used_pages++`
|
||||||
|
- Если страница занята и `ref_count` был 0: `ref_count = 1` (восстановление)
|
||||||
|
|
||||||
|
### `inc_ref_frame(addr)`
|
||||||
|
|
||||||
|
- Инкрементит `ref_count` (с насыщением) — для COW
|
||||||
|
|
||||||
|
### `alloc_frame() -> Option<PhysAddr>`
|
||||||
|
|
||||||
|
**Двухпроходный сканер:**
|
||||||
|
|
||||||
|
1. Проход 1: от `last_byte` до конца bitmap
|
||||||
|
2. Проход 2: от 0 до `last_byte`
|
||||||
|
|
||||||
|
Сканирует побайтово в поисках байта с нулевым битом (`!0xFF`), внутри байта через `trailing_ones`. Устанавливает бит, `ref_count = 1`, `used_pages++`, обновляет `last_byte`.
|
||||||
|
|
||||||
|
### `alloc_contiguous(count) -> Option<PhysAddr>`
|
||||||
|
|
||||||
|
Линейный поиск `count` последовательных свободных страниц.
|
||||||
|
|
||||||
|
### Функции-обёртки
|
||||||
|
|
||||||
|
- `alloc_frame()`, `alloc_contiguous(count)`, `free_frame(addr)`, `inc_ref_frame(addr)` — захватывают `PMM.lock()`
|
||||||
|
- `get_stats() -> (usize, usize)` — возвращает `(used, total)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BuddyAllocator
|
||||||
|
|
||||||
|
**Файл**: `src/mem/buddy.rs`
|
||||||
|
|
||||||
|
Power-of-two блочный аллокатор для per-actor управления физической памятью.
|
||||||
|
|
||||||
|
### Константы
|
||||||
|
|
||||||
|
- `MAX_ORDER = 11` — макс. порядок (2^11 = 2048 страниц = 8 MiB)
|
||||||
|
|
||||||
|
### `order_for(page_count) -> usize`
|
||||||
|
|
||||||
|
Вычисляет минимальное k такое, что `2^k >= page_count`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
order_for(1) = 0 // 2^0 = 1
|
||||||
|
order_for(3) = 2 // 2^2 = 4 >= 3
|
||||||
|
order_for(2048) = 11 // 2^11 = 2048
|
||||||
|
```
|
||||||
|
|
||||||
|
### Структура
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct BuddyAllocator {
|
||||||
|
free_lists: [Vec<usize>; MAX_ORDER + 1], // списки блоков по порядкам
|
||||||
|
total_pages: usize, // всего страниц
|
||||||
|
free_pages: usize, // свободно страниц
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Инвариант: `free_pages == Σ 2^k * len(free_lists[k])`.
|
||||||
|
|
||||||
|
### `new(total_pages) -> Self`
|
||||||
|
|
||||||
|
**Жадное разложение** диапазона на блоки максимального размера:
|
||||||
|
|
||||||
|
- Итерация по диапазону
|
||||||
|
- На каждой позиции вычисляет макс. порядок (ограничен alignment через `trailing_zeros` и остатком)
|
||||||
|
- Добавляет блок в соответствующий `free_list`
|
||||||
|
|
||||||
|
Пример: 6 страниц → блок 4 (order 2) + блок 2 (order 1).
|
||||||
|
|
||||||
|
### `alloc(order) -> Option<usize>`
|
||||||
|
|
||||||
|
1. Находит наименьший `k >= order` с непустым `free_list[k]`
|
||||||
|
2. Pop с конца (O(1))
|
||||||
|
3. **Разбивает** до нужного порядка: при каждом разбиении buddy кладётся в список порядком ниже
|
||||||
|
4. Возвращает индекс
|
||||||
|
|
||||||
|
### `alloc_pages(page_count) -> Option<(usize, usize)>`
|
||||||
|
|
||||||
|
Округляет page_count до степени двойки, вызывает `alloc()`. Возвращает `(index, order)`.
|
||||||
|
|
||||||
|
### `free(block_idx, order)`
|
||||||
|
|
||||||
|
1. Добавляет блок в `free_list[order]`
|
||||||
|
2. **Coalescing** на каждом уровне:
|
||||||
|
- `buddy_idx = block_idx ^ (1 << order)` (XOR)
|
||||||
|
- Ищет buddy в `free_list[order]` (линейно, `swap_remove`)
|
||||||
|
- Если найден: удаляет его, объединяет, переходит на уровень выше
|
||||||
|
- Если не найден или вне диапазона: стоп
|
||||||
|
|
||||||
|
### Интроспекция
|
||||||
|
|
||||||
|
- `free_pages()`, `total_pages()`, `is_exhausted()`, `stats()` (per-order counts)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Page Tables
|
||||||
|
|
||||||
|
**Файл**: `src/mem/paging.rs`
|
||||||
|
|
||||||
|
4-уровневая страничная таблица x86-64: P4 → P3 → P2 → P1 (512 entry × 8 байт = 4 KiB каждая).
|
||||||
|
|
||||||
|
### `PageTableFlags` (bitflags)
|
||||||
|
|
||||||
|
| Флаг | Бит | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| `PRESENT` | 0 | Страница в памяти |
|
||||||
|
| `WRITABLE` | 1 | Разрешена запись |
|
||||||
|
| `USER` | 2 | Доступ из ring 3 |
|
||||||
|
| `WRITE_THROUGH` | 3 | Write-through кэширование |
|
||||||
|
| `NO_CACHE` | 4 | Отключение кэширования |
|
||||||
|
| `ACCESSED` | 5 | Было обращение |
|
||||||
|
| `DIRTY` | 6 | Была запись |
|
||||||
|
| `HUGE_PAGE` | 7 | 2 MiB (P2) или 1 GiB (P3) |
|
||||||
|
| `GLOBAL` | 8 | Не сбрасывается при перезагрузке CR3 |
|
||||||
|
| `COW` | 9 | **Кастомный** — Copy-on-Write |
|
||||||
|
| `NO_EXECUTE` | 63 | Запрет исполнения (NX bit) |
|
||||||
|
|
||||||
|
### `PageTable` — `#[repr(C, align(4096))]`
|
||||||
|
|
||||||
|
Поле: `entries: [u64; 512]`
|
||||||
|
|
||||||
|
### Методы
|
||||||
|
|
||||||
|
#### `map_page(virt, phys, flags, hhdm)`
|
||||||
|
|
||||||
|
1. Walk/create P4 → P3 → P2 → P1 (создаёт отсутствующие)
|
||||||
|
2. Записывает PTE = `phys | flags`
|
||||||
|
3. `invlpg`
|
||||||
|
|
||||||
|
#### `get_or_create_next_table(index, hhdm, create) -> Option<&mut PageTable>`
|
||||||
|
|
||||||
|
- Читает PTE, проверяет PRESENT
|
||||||
|
- Если есть: `&mut *(hhdm + phys_addr)` (HHDM dereference)
|
||||||
|
- Если нет и `create == true`: `pmm_alloc()`, зануляет, ставит entry с `PRESENT|WRITABLE|USER`, возвращает
|
||||||
|
- Если нет и `create == false`: None
|
||||||
|
|
||||||
|
#### `walk_to_p1_mut(virt, hhdm, create) -> Option<&mut PageTable>`
|
||||||
|
|
||||||
|
Из virtual address вычисляет индексы P4[47:39], P3[38:30], P2[29:21], P1[20:12], проходит цепочку.
|
||||||
|
|
||||||
|
#### `unmap_page(virt, hhdm)`
|
||||||
|
|
||||||
|
Walk (без create), зануляет PTE. `invlpg`. Идемпотентен.
|
||||||
|
|
||||||
|
#### `translate(virt, hhdm) -> Option<PhysAddr>`
|
||||||
|
|
||||||
|
Walk 4 уровней. Поддерживает huge pages (1 GiB на P3, 2 MiB на P2). Возвращает PhysAddr + page offset.
|
||||||
|
|
||||||
|
#### `map_region(virt, phys, size, flags, hhdm)`
|
||||||
|
|
||||||
|
Цикл по 4 KiB страницам, каждая → `map_page()`.
|
||||||
|
|
||||||
|
#### `get_flags(virt, hhdm) -> Option<PageTableFlags>`
|
||||||
|
|
||||||
|
Walk, возвращает флаги PTE (с поддержкой huge pages).
|
||||||
|
|
||||||
|
#### `update_flags(virt, flags, hhdm) -> Result<(), ()>`
|
||||||
|
|
||||||
|
Walk, обновляет флаги, сохраняя физический адрес. `invlpg`.
|
||||||
|
|
||||||
|
#### `activate(phys_addr)`
|
||||||
|
|
||||||
|
`unsafe`: запись CR3 = phys_addr → полный сброс TLB.
|
||||||
|
|
||||||
|
### `pmm_alloc() -> Option<PhysAddr>`
|
||||||
|
|
||||||
|
Обёртка над `PMM.lock().unwrap().alloc_frame()` — разрывает циклическую зависимость между paging и pmm.
|
||||||
173
kernel/docs/pmactor.md
Normal file
173
kernel/docs/pmactor.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Акторы физической памяти (PMActor) и маршрутизатор (PM Router)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Формат сообщений
|
||||||
|
|
||||||
|
Вся коммуникация использует упаковку в `u64`:
|
||||||
|
|
||||||
|
```
|
||||||
|
[8b opcode | 16b channel_id | 20b arg1 | 20b arg2]
|
||||||
|
```
|
||||||
|
|
||||||
|
Константы: `OPCODE_SHIFT = 56`, `CHAN_SHIFT = 40`, `ARG1_SHIFT = 20`
|
||||||
|
Маски: `CHAN_MASK = 0x0000_FFFF`, `ARG_MASK = 0x000F_FFFF`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PMActor
|
||||||
|
|
||||||
|
**Файл**: `src/mem/pm_manages.rs`
|
||||||
|
|
||||||
|
Распределённый актор физической памяти. Каждый актор владеет фиксрованным диапазоном и управляет им через BuddyAllocator.
|
||||||
|
|
||||||
|
### PMRequest
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum PMRequest {
|
||||||
|
Allocate { size_pages: usize, token_sig: u32, channel_id: u16 },
|
||||||
|
Free { local_frame_idx: usize, order: usize },
|
||||||
|
Carve { offset_pages: usize, size_pages: usize, channel_id: u16 },
|
||||||
|
None, // sentinel (не может быть отправлен)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Методы: `pack() -> u64`, `unpack(val: u64) -> Self`.
|
||||||
|
|
||||||
|
### PMResult
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum PMResult {
|
||||||
|
Allocated { cap: Capability, order: usize },
|
||||||
|
OutOfMemory { size_pages: usize },
|
||||||
|
Carved { cap: Capability },
|
||||||
|
Freed { pages_returned: usize },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PMResponse
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMResponse {
|
||||||
|
pub channel_id: u16,
|
||||||
|
pub result: PMResult,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PMActorQueue — Lock-free MPSC
|
||||||
|
|
||||||
|
Кольцевой буфер на 1024 слота (`AtomicU64`) с cache-line padding.
|
||||||
|
|
||||||
|
- `send(req)` — CAS loop на `tail` (multi-producer). Отклоняет None.
|
||||||
|
- `pop()` — single-consumer. `Acquire`/`Release` ordering.
|
||||||
|
|
||||||
|
### PMActor
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMActor {
|
||||||
|
actor_id: u64,
|
||||||
|
root_untyped: Capability,
|
||||||
|
managed_range: (PhysAddr, PhysAddr), // [start, end)
|
||||||
|
queue: PMActorQueue,
|
||||||
|
buddy: BuddyAllocator,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `new(actor_id, root_cap, start, size_bytes) -> Self`
|
||||||
|
|
||||||
|
Конструктор.
|
||||||
|
|
||||||
|
#### `submit_request(req) -> Result<(), &str>`
|
||||||
|
|
||||||
|
Thread-safe: пушит в `queue`.
|
||||||
|
|
||||||
|
#### `process_messages(&mut self) -> Vec<PMResponse>`
|
||||||
|
|
||||||
|
Дренирует очередь, обрабатывает каждый запрос через приватные методы.
|
||||||
|
|
||||||
|
**Только из контекста актора** (`&mut self` гарантирует single consumer).
|
||||||
|
|
||||||
|
#### Приватные обработчики
|
||||||
|
|
||||||
|
**`handle_allocate(size_pages, token_sig, channel_id) -> Option<PMResponse>`**
|
||||||
|
|
||||||
|
1. `buddy.alloc_pages(size_pages)` → `(local_idx, order)`
|
||||||
|
2. Строит сильную дочернюю Capability с уникальным `token_sig`
|
||||||
|
3. Возвращает `Allocated { cap, order }`
|
||||||
|
4. При неудаче: `OutOfMemory` (планируется ballooning)
|
||||||
|
|
||||||
|
**`handle_free(local_frame_idx, order) -> Option<PMResponse>`**
|
||||||
|
|
||||||
|
1. Валидация границ
|
||||||
|
2. `buddy.free(local_frame_idx, order)`
|
||||||
|
3. Возвращает None (маршрутизация не нужна)
|
||||||
|
|
||||||
|
**`handle_carve(offset_pages, size_pages, channel_id) -> Option<PMResponse>`**
|
||||||
|
|
||||||
|
1. Валидация диапазона
|
||||||
|
2. Возвращает `Carved { cap }`
|
||||||
|
3. Не взаимодействует с buddy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PMRouter
|
||||||
|
|
||||||
|
**Файл**: `src/mem/pm_router.rs`
|
||||||
|
|
||||||
|
Глобальная ткань маршрутизации: 65536 lock-free каналов.
|
||||||
|
|
||||||
|
### Константы
|
||||||
|
|
||||||
|
- `CHANNEL_COUNT = 65536`
|
||||||
|
- Состояния: `STATE_FREE = 0`, `STATE_PENDING = 1`, `STATE_READY = 2`
|
||||||
|
|
||||||
|
### Channel — `#[repr(align(64))]`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct Channel {
|
||||||
|
state: AtomicU8,
|
||||||
|
next_free: AtomicU16,
|
||||||
|
result: UnsafeCell<Option<PMResult>>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PMRouter
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct PMRouter {
|
||||||
|
channels: Box<[Channel]>, // 65536 каналов
|
||||||
|
free_head: AtomicU16, // голова free list (0 = sentinel)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GlobalRouter
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static ROUTER: GlobalRouter; // синглтон
|
||||||
|
```
|
||||||
|
|
||||||
|
### `init()`
|
||||||
|
|
||||||
|
1. Создаёт Vec из 65536 каналов, соединённых в linked list (каждый `next_free` указывает на следующий)
|
||||||
|
2. Преобразует в `Box<[Channel]>`
|
||||||
|
3. Сохраняет в `ROUTER.inner`, устанавливает `is_ready`
|
||||||
|
|
||||||
|
### `alloc_channel() -> Option<u16>`
|
||||||
|
|
||||||
|
CAS-цикл на `free_head`: извлекает голову списка, устанавливает `state = PENDING`.
|
||||||
|
|
||||||
|
### `route_responses(responses: Vec<PMResponse>)`
|
||||||
|
|
||||||
|
Для каждого PMResponse с ненулевым `channel_id`: сохраняет `result` в канале, устанавливает `state = READY`.
|
||||||
|
|
||||||
|
### `wait_for_response(id: u16) -> PMResult`
|
||||||
|
|
||||||
|
Spin-wait до `state == READY`. Забирает результат, пушит канал обратно в freelist, возвращает результат.
|
||||||
|
|
||||||
|
### `request_and_wait(actor, req_builder) -> PMResult`
|
||||||
|
|
||||||
|
Convenience: аллоцирует канал, строит запрос с channel_id, отправляет актору, ждёт ответ. (process_messages должен быть вызван внешне.)
|
||||||
|
|
||||||
|
### `dispatch(responses: Vec<PMResponse>)`
|
||||||
|
|
||||||
|
Точка входа для маршрутизации ответов через `PMActor::route_responses()` (вызов на глобальном роутере).
|
||||||
212
kernel/docs/vmm.md
Normal file
212
kernel/docs/vmm.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
# Виртуальная память (VMM)
|
||||||
|
|
||||||
|
**Файл**: `src/mem/vmm.rs`
|
||||||
|
|
||||||
|
Управление адресными пространствами: PML4 + VMA списки, lazy demand paging, copy-on-write, shared mappings, ASID/PCID, TLB shootdown IPI.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Глобальные статики
|
||||||
|
|
||||||
|
| Переменная | Тип | Назначение |
|
||||||
|
|---|---|---|
|
||||||
|
| `ACTIVE_CPUS_MASK` | `AtomicU64` | Битовая маска активных CPU (core 0 по умолчанию) |
|
||||||
|
| `SHOOTDOWN_LOCK` | `Locked<()>` | Блокировка TLB shootdown IPI |
|
||||||
|
| `SHOOTDOWN_ASID` | `AtomicU16` | ASID, подлежащий сбросу |
|
||||||
|
| `SHOOTDOWN_ACK` | `AtomicU64` | Битовая маска подтверждений удалённых ядер |
|
||||||
|
| `INVPCID_SUPPORTED` | `AtomicBool` | Поддержка инструкции INVPCID |
|
||||||
|
| `ASID_ALLOC` | `Locked<AsidAllocator>` | Аллокатор ASID/PCID |
|
||||||
|
| `KERNEL_SPACE` | `Locked<Option<AddressSpace>>` | Адресное пространство ядра |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Типы ошибок: `VmError`
|
||||||
|
|
||||||
|
- `OutOfMemory` — нет свободной физической памяти
|
||||||
|
- `RegionOverlap` — VMA перекрывается с существующей
|
||||||
|
- `RegionNotFound` — VMA не найдена
|
||||||
|
- `InvalidAlignment` — неверное выравнивание
|
||||||
|
- `InvalidRange` — некорректный диапазон
|
||||||
|
- `PermissionDenied` — нет прав доступа
|
||||||
|
- `UnexpectedFault` — page fault в не-lazy, не-COW регионе
|
||||||
|
- `NonCanonical` — неканонический адрес
|
||||||
|
- `AsidExhausted` — кончились ASID
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `VmaBacking` — типы подложки
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum VmaBacking {
|
||||||
|
Anonymous(Vec<Option<PhysAddr>>), // per-page tracking: None = ещё не выделена
|
||||||
|
Physical(PhysAddr), // фиксированный физический диапазон (MMIO, DMA)
|
||||||
|
Shared { owner_cap: u64, phys_base: PhysAddr }, // zero-copy borrow
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `VmaFlags` (bitflags)
|
||||||
|
|
||||||
|
| Флаг | Бит | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| `READ` | 0 | Чтение |
|
||||||
|
| `WRITE` | 1 | Запись |
|
||||||
|
| `EXEC` | 2 | Исполнение |
|
||||||
|
| `USER` | 3 | Доступ из userspace |
|
||||||
|
| `LAZY` | 4 | Demand paging (фрейм при page fault) |
|
||||||
|
| `SHARED` | 5 | Разделяемая память |
|
||||||
|
| `PINNED` | 6 | Защита от revoke |
|
||||||
|
| `NOCACHE` | 7 | Отключение кэширования |
|
||||||
|
| `MMIO` | 8 | Memory-mapped I/O |
|
||||||
|
| `COW` | 9 | Copy-on-Write |
|
||||||
|
|
||||||
|
**`to_page_flags() -> PageTableFlags`**: преобразует VmaFlags в аппаратные флаги. PRESENT всегда включён, NO_EXECUTE если не EXEC.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `AsidAllocator`
|
||||||
|
|
||||||
|
Битовая карта на 4096 ASID/PCID (128 × u32).
|
||||||
|
|
||||||
|
- ASID 0 (ядро) и ASID 4095 (зарезервирован) — заняты постоянно
|
||||||
|
- `alloc() -> Option<u16>` — двухпроходный с пропуском целых слов (амортизированное O(1))
|
||||||
|
- `free(asid)` — сброс бита
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `VmaRegion`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct VmaRegion {
|
||||||
|
virt_start: VirtAddr,
|
||||||
|
virt_end: VirtAddr,
|
||||||
|
flags: VmaFlags,
|
||||||
|
cap_token: u64,
|
||||||
|
backing: VmaBacking,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Методы: `contains(addr)`, `size()`, `pages()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `AddressSpace`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct AddressSpace {
|
||||||
|
asid: u16,
|
||||||
|
pml4_phys: PhysAddr,
|
||||||
|
regions: Vec<VmaRegion>, // sorted by virt_start, non-overlapping
|
||||||
|
hhdm: u64,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Создание
|
||||||
|
|
||||||
|
| Метод | Описание |
|
||||||
|
|---|---|
|
||||||
|
| `new(hhdm)` | Выделяет PML4 (занулённый), аллоцирует ASID |
|
||||||
|
| `from_active(pml4_phys, asid, hhdm)` | Обёртка для существующей PML4 (ядро, ASID=0) |
|
||||||
|
|
||||||
|
### `map_region(virt, phys, size, flags, cap_token) -> Result<VirtAddr>`
|
||||||
|
|
||||||
|
1. Валидация выравнивания (4 KiB) и проверка перекрытий (`check_overlap`)
|
||||||
|
2. Создание `VmaBacking`:
|
||||||
|
- **LAZY** → `Anonymous(vec![None; page_count])`
|
||||||
|
- **Physical** (если `phys` не None и нет LAZY) → `Physical(phys)`
|
||||||
|
- **Eager anonymous**: цикл по страницам, `pmm_alloc() + zero + map`
|
||||||
|
3. Вставка в `regions` с сохранением сортировки (`insert_sorted`)
|
||||||
|
|
||||||
|
### `map_shared(virt, phys_base, page_count, flags, owner_cap)`
|
||||||
|
|
||||||
|
Eager маппинг физического диапазона без владения фреймами (не освобождаются при unmap).
|
||||||
|
|
||||||
|
### `handle_fault(fault_addr, write) -> Result<(), VmError>`
|
||||||
|
|
||||||
|
Обработка page fault:
|
||||||
|
|
||||||
|
1. `find_idx(fault_addr)` — бинарный поиск региона
|
||||||
|
2. Проверка прав: write fault в WRITE-регионе
|
||||||
|
3. **COW**: если флаг COW и пишем:
|
||||||
|
- Выделяет новый фрейм
|
||||||
|
- Копирует содержимое старого
|
||||||
|
- Обновляет PTE: убирает COW, добавляет WRITABLE
|
||||||
|
4. **LAZY**: если флаг LAZY:
|
||||||
|
- Выделяет фрейм, зануляет, маппит
|
||||||
|
- Обновляет `Vec<Option<PhysAddr>>`
|
||||||
|
5. Иначе: `Err(UnexpectedFault)`
|
||||||
|
|
||||||
|
### `unmap_region(virt) -> Result<(), VmError>`
|
||||||
|
|
||||||
|
1. `find_idx(virt)` → регион
|
||||||
|
2. `do_unmap(region)`: анмаппит PTE, освобождает фреймы если Anonymous
|
||||||
|
3. Удаляет регион из списка
|
||||||
|
4. `tlb_flush_asid(self.asid)`
|
||||||
|
|
||||||
|
### `revoke_by_token(cap_token)`
|
||||||
|
|
||||||
|
Анмаппит все VMA с совпадающим `cap_token` (кроме PINNED). Сливает TLB.
|
||||||
|
|
||||||
|
### `process_pending_revocations(&mut self)`
|
||||||
|
|
||||||
|
1. Дренирует `MMU_REVOCATION_QUEUE` (pop в цикле)
|
||||||
|
2. Для каждого токена → `revoke_by_token(token)`
|
||||||
|
3. TLB flush если была хотя бы одна ревокация
|
||||||
|
|
||||||
|
### `clone_for_fork(child_cap_token) -> Result<Self, VmError>`
|
||||||
|
|
||||||
|
Создание дочернего адресного пространства (fork):
|
||||||
|
|
||||||
|
- **Physical/Shared**: маппинг напрямую
|
||||||
|
- **Anonymous**: **COW setup**:
|
||||||
|
- Убирает WRITABLE из PTE родителя
|
||||||
|
- Устанавливает COW флаг в обоих PTEs
|
||||||
|
- `inc_ref_frame()` для всех фреймов
|
||||||
|
|
||||||
|
### `translate(virt) -> Option<PhysAddr>`
|
||||||
|
|
||||||
|
Аппаратный walk страничной таблицы (через `PageTable::translate`).
|
||||||
|
|
||||||
|
### `activate()`
|
||||||
|
|
||||||
|
`unsafe`: запись CR3 = `pml4_phys | asid | (1<<63)`. Бит 63 = NOFLUSH — сохраняет PCID-тегированные записи TLB.
|
||||||
|
|
||||||
|
### `Drop`
|
||||||
|
|
||||||
|
1. Анмаппит все регионы
|
||||||
|
2. Освобождает PML4 фрейм через `free_frame`
|
||||||
|
3. Возвращает ASID в пул
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TLB Management
|
||||||
|
|
||||||
|
### `local_tlb_flush_asid(asid)`
|
||||||
|
|
||||||
|
- INVPCID type 1 если поддерживается (single-context flush)
|
||||||
|
- Иначе `tlb_flush_all()`
|
||||||
|
|
||||||
|
### `tlb_flush_asid(asid)`
|
||||||
|
|
||||||
|
1. `local_tlb_flush_asid(asid)` — локальный сброс
|
||||||
|
2. Устанавливает глобальные статики: `SHOOTDOWN_ASID = asid`, `SHOOTDOWN_ACK = 0`
|
||||||
|
3. `broadcast_ipi_exclude_self(TLB_SHOOTDOWN_VECTOR)` — IPI всем остальным ядрам
|
||||||
|
4. Spin-loop: `while SHOOTDOWN_ACK != ACTIVE_CPUS_MASK { hlt(); }`
|
||||||
|
|
||||||
|
### `tlb_flush_all()`
|
||||||
|
|
||||||
|
Читает CR3 → перезаписывает без бита 63 → полный сброс всех non-global entries.
|
||||||
|
|
||||||
|
### `handle_tlb_shootdown_ipi()`
|
||||||
|
|
||||||
|
1. `local_tlb_flush_asid(SHOOTDOWN_ASID.load())`
|
||||||
|
2. Устанавливает свой бит в `SHOOTDOWN_ACK` (через `fetch_or`)
|
||||||
|
|
||||||
|
### `init_cpu_features()`
|
||||||
|
|
||||||
|
CPUID leaf 7, проверка EBX[10] (INVPCID). Сохраняет в `INVPCID_SUPPORTED`.
|
||||||
|
|
||||||
|
### `init_kernel_space(pml4_phys, hhdm)`
|
||||||
|
|
||||||
|
Регистрирует PML4 ядра как `KERNEL_SPACE` с ASID 0.
|
||||||
@@ -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\
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -180,18 +180,31 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
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, hhdm_offset); }
|
||||||
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
|
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
|
||||||
|
|
||||||
|
cpu::lapic::init(hhdm_offset);
|
||||||
|
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(hhdm_offset);
|
||||||
|
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(hhdm_offset);
|
||||||
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
|
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
|
||||||
@@ -379,6 +392,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 +400,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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ pub struct BitmapPMM {
|
|||||||
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_byte: usize,
|
last_byte: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +58,7 @@ impl BitmapPMM {
|
|||||||
last_byte: 0,
|
last_byte: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Free all USABLE pages (gap pages not covered by any entry stay locked).
|
||||||
for entry in mmap.entries() {
|
for entry in mmap.entries() {
|
||||||
if entry.entry_type == limine::memory_map::EntryType::USABLE {
|
if entry.entry_type == limine::memory_map::EntryType::USABLE {
|
||||||
for addr in (entry.base..entry.base + entry.length).step_by(PAGE_SIZE as usize) {
|
for addr in (entry.base..entry.base + entry.length).step_by(PAGE_SIZE as usize) {
|
||||||
@@ -67,6 +67,7 @@ impl BitmapPMM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metadata pages sit inside a USABLE region — lock them back.
|
||||||
let meta_end = (meta_phys + total_meta_size as u64 + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
|
let meta_end = (meta_phys + total_meta_size as u64 + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
|
||||||
for addr in (meta_phys..meta_end).step_by(PAGE_SIZE as usize) {
|
for addr in (meta_phys..meta_end).step_by(PAGE_SIZE as usize) {
|
||||||
pmm.lock_frame(PhysAddr(addr));
|
pmm.lock_frame(PhysAddr(addr));
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -498,15 +475,6 @@ impl AddressSpace {
|
|||||||
Self { asid, pml4_phys, regions: Vec::new(), hhdm }
|
Self { asid, pml4_phys, regions: Vec::new(), hhdm }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 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,
|
||||||
@@ -645,6 +613,8 @@ 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(hhdm).as_ptr::<u8>(),
|
||||||
@@ -793,7 +763,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 +808,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
|
||||||
|
|||||||
Reference in New Issue
Block a user