From c092a813317f3925f33b09b647ffc573ca56849e Mon Sep 17 00:00:00 2001 From: Faynot Date: Mon, 6 Jul 2026 00:25:49 +0300 Subject: [PATCH] fix: bugs --- .PLAN.md.swp | Bin 0 -> 16384 bytes PLAN.md | 626 +++++++++++++++++++++++++++++++ kernel/docs/README.md | 19 + kernel/docs/allocator.md | 105 ++++++ kernel/docs/architecture.md | 56 +++ kernel/docs/build-system.md | 134 +++++++ kernel/docs/capabilities.md | 147 ++++++++ kernel/docs/console.md | 132 +++++++ kernel/docs/events.md | 85 +++++ kernel/docs/interrupts.md | 142 +++++++ kernel/docs/main.md | 98 +++++ kernel/docs/memory-management.md | 243 ++++++++++++ kernel/docs/pmactor.md | 173 +++++++++ kernel/docs/vmm.md | 212 +++++++++++ kernel/src/cpu/interrupts.rs | 250 ++++++++++-- kernel/src/debug.rs | 8 +- kernel/src/debug/serial.rs | 30 +- kernel/src/main.rs | 21 +- kernel/src/mem/allocator.rs | 37 +- kernel/src/mem/pm_manages.rs | 36 +- kernel/src/mem/pm_router.rs | 11 +- kernel/src/mem/pmm.rs | 3 +- kernel/src/mem/vmm.rs | 57 +-- 23 files changed, 2502 insertions(+), 123 deletions(-) create mode 100644 .PLAN.md.swp create mode 100644 PLAN.md create mode 100644 kernel/docs/README.md create mode 100644 kernel/docs/allocator.md create mode 100644 kernel/docs/architecture.md create mode 100644 kernel/docs/build-system.md create mode 100644 kernel/docs/capabilities.md create mode 100644 kernel/docs/console.md create mode 100644 kernel/docs/events.md create mode 100644 kernel/docs/interrupts.md create mode 100644 kernel/docs/main.md create mode 100644 kernel/docs/memory-management.md create mode 100644 kernel/docs/pmactor.md create mode 100644 kernel/docs/vmm.md diff --git a/.PLAN.md.swp b/.PLAN.md.swp new file mode 100644 index 0000000000000000000000000000000000000000..68d93f4209b5fe3913149fe5a6a9c8d1aa2aed2e GIT binary patch literal 16384 zcmeHOYit}>6`sC%v;|S|0|~@Qf-0};vAyfHeuZ5QH+&jD8 zwKtEV{3)7MK701goqNtb_nhzEbH*8{zq6x@nrnj!KA%>Uf1JAGo^Ivb70>>_!jUj;9J1AfNufc0=@-&3w%T@V5hHBo`bGedir+b z_Z1$v=>zYx0oOex?eBZ-ccJZQN&A1i_88jUDQW+=*PcMzRtR7j{CUr7*Kz$~N&8je zu9Ue7$`$&1&HgXn0=@-&3-}iBE#ON?0yhJHyJ|Cplu9?*dS;1AbA zAK*3M$G|s$9Y74wfqvlYz%Q>+l$U{@0Y3!{APQUsTnRW=E6S@tH4p&4g2Kb?z-pil zxCwX-^#=!d1^5;43*hI#JwO}?0}a3sY{+(uRVY?eS348#h%@gz@0@m~oik3Zy1Ip& zakNc4b57nJaYvnLcON;E?q2sWIrHwr_|A8y+(YD?a&l;MrkuPp$9)c>W!{;rCFcou zFNVxhu%7Dc>j~fHS&aKlIj}QRBc2w5=bhOSQSKPVhE4p60&Z}0@xrvDr)YZP8g0;=GFfvY&bS9-6merrY4}K;h^dL<04CD!e%sP{>#yTW${Mz^!5 zOxD8R#=sU&L(NBoB6(+8t|~9UqJjjg&tpD3qd9k!zV)jA)>ide}CwkUb>*2+Qt0?Mza{n_z_8{cy-RXV!fP7C+n~W?I36JzX-I zZjhMZ3-GB?(uWLvm?8yru&Q~AWRp8HTDm`*6nELI5%}*9#Nv*Xbw)r4o1KKiOV4D# zex6qy(Q%NC#=e2;u^Q4-QG=|6VValXn;GE=?3%0V)#lK0$6PHPvx2fRPP)_VD4z8x zmNiX^8L;#Pyc3@9JPV(1M8u!KBoNauExd(C$vG!soGKAg9OdXHG>~DkdST@8U@c<1 zvhTYG1D^9&S8wZH-!0samnly|PL8R8$%B^>ja$2VU`blr6ef;-7;!`hgMUe~nSp=i zIZl)Dg!7Q}9^;XienT(ON`*nnAKrs!QY%Mcr*q|3{ zm~NwZtgH*&N_g)64;Py3T}&pRQel8uG(<`Ti%~KCf_j3qM}?7!4og+y890ET)dP?XRu|0AcF#L6c)o!l;U2idN@By1 zwnhXyk5ObuOGZtj-!=#Ot)W!Pj;3}d`{U_2R0Wd&r!g)Z3yXwrP;}Wei%bu074)71 z*9a};ej%2Vx1PzObObgNk)UXfPi$K3%(EDPw+ZGZC<&S?Y(DN`8Il!7o~w6iG(14HDR^6evtykr>L#B zV;dr{Npb*NH-_aL>esAz6w2|MLBAZy7RT59)9zx?h(aJlO#|2=5<2+N0QW{8= z89`{{sTLH300k@Vg^PT!2b=BqGNKK#Iovqw3@>X z0l|V=CA-b?!ng-&$cmew6pq?VR`7TY!J{x54iNk){N_tc-+2mi+9xHF^CTvhW7gwI z!V~t$)qDn)WrZh&V5}f-{_`N+lHTr^N7HL+>F(Z?7ENgB?NzHX@+zP-quH4dmh!ac z4gV1s|ufCl>{V z0!jl5Jl|0P9$QB+HZKW>Po7sDHV>*$qXMU>G8CNrqKIHX39*Qaa!@74`=7)GS=^44U>xWGz6e|cTn)U1y1*Ea1#Dmw@HyZE)Ck@K-UWUQyaK!o90%gS9Y67}J;Ccq?3`ee0lrI6Zs3r6OZ=+`LHtGb&fDrH!>^29S0rJ2pfNj|T_@Dnz z3smFmk2D9Ho|n;!fX3E^WEIC{rkzIBoktN>3=a>pS zqM*}PMa72(kauVUCKCUA;YmJdI#Y5$aX!YO4u`Rt!hQVq>R=b#>RXiJ0}Bs5Uc9b& zaVZ^?h4Jx)@sZ2-@0e8OqcSY!zptY4xUqOoT(oVWU{DP|Xbh`}3>P&qQP>Gej&pM->0l!gQ$h^an(!zQ1YAPvmv&TVlEvhCw8KC z%Z?{B+i0N&!;M^rL9(t0d4fjsNDarZ@o-8!nANbN!cAsHiVAnZ7C=@in?ZHbGE;V; zx`N!tu2t@)?G!?0b8g~0C;piqBaUjW$+v0gjXYL`3ZgrvNa-bdp*C5j*OQnICytyr zBR9fnmc?O@z%h6o^6u15Pc(9zrJ}vPVX+q1YnY-gU#fY*kU04N1Tr9vnov$Tf=Tl` z5m^l{7A9m3Qkbkn=>|27O{!o?-m^+#OcW$$#c3H^YI&B8jonip`4iNCnk^@}gKgJ} z{|XD9(Lh!hk_&{pnw$EY!pqIpP;R!`!RP?LaVR_{0-n$FQ=H&=B9TKa7rAl$I%l90Yd`t{~Dkd5LsgJzm3 zU9(e}N)mC#;YzoNCu!=f9X%bi@X%vyqC`4{j0$CQd^JkXS`P(96a6@A;Je6SAQCB$xtCHiY8g0%xK9$9K^WV z!-?~>XFn8c$~2OrsWGt=X3b&3kQdeG7Siqe%%W#<63H*&M3{I5YEK-J)-rMH zTC$~d$EiYzKY&TV)+q2XXVJiJ+mQQFg|!T0EoBY)g2%>7TGO7^zOC&D74b%77IjXr z28Wp}9~6Tvh^o=Ki671o4n(|`ohf}7Zw;+ Анализ кода по состоянию на июнь 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` — O(количество блоков этого порядка). При 10 000 блоков порядка 0, free() занимает тысячи итераций. + +**Решение**: Перейти на **intrusive linked list** через сами страницы: первые 8 байт неиспользуемой страницы хранят `next` указатель. Даёт O(1) удаление. + +Или хеш-таблицу: `HashMap` (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, + children: Vec, // явный список наследников +} +``` + +Отзыв — проход по дереву без сканирования всей таблицы. + +--- + +### 🏗️ 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`, `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.* diff --git a/kernel/docs/README.md b/kernel/docs/README.md new file mode 100644 index 0000000..46e8804 --- /dev/null +++ b/kernel/docs/README.md @@ -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 | diff --git a/kernel/docs/allocator.md b/kernel/docs/allocator.md new file mode 100644 index 0000000..9e722d1 --- /dev/null +++ b/kernel/docs/allocator.md @@ -0,0 +1,105 @@ +# Аллокатор кучи + +**Файл**: `src/mem/allocator.rs` + +Slab-аллокатор со spinlock-синхронизацией и fallback bump-аллокацией для больших блоков. + +--- + +## Примитив синхронизации: `Locked` + +Самодельный spinlock (альтернатива `spin::Mutex`). + +```rust +pub struct Locked { + inner: UnsafeCell, + lock: AtomicBool, +} +``` + +- `new(inner)` — создаёт с unlocked состоянием +- `lock() -> LockedGuard<'_, A>` — CAS-цикл на `AtomicBool` (спин-ожидание) + +### LockedGuard + +- `Deref`/`DerefMut` — доступ к внутренним данным +- `Drop` — `store(false, Release)` — освобождение блокировки + +`unsafe impl Sync for Locked` — разработчик гарантирует корректность. + +--- + +## 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` + +Находит индекс наименьшего 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; +``` + +### `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 diff --git a/kernel/docs/architecture.md b/kernel/docs/architecture.md new file mode 100644 index 0000000..37630ad --- /dev/null +++ b/kernel/docs/architecture.md @@ -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 diff --git a/kernel/docs/build-system.md b/kernel/docs/build-system.md new file mode 100644 index 0000000..069522e --- /dev/null +++ b/kernel/docs/build-system.md @@ -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 +``` diff --git a/kernel/docs/capabilities.md b/kernel/docs/capabilities.md new file mode 100644 index 0000000..987dffd --- /dev/null +++ b/kernel/docs/capabilities.md @@ -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, // индекс родителя (для дерева) — None у корневых +} +``` + +### CNode + +```rust +pub struct CNode { + slots: Vec>, +} +``` + +### `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. diff --git a/kernel/docs/console.md b/kernel/docs/console.md new file mode 100644 index 0000000..06c4ec6 --- /dev/null +++ b/kernel/docs/console.md @@ -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 ] | \n` + +Двойной вывод: +- **Console**: цветной `[ LOG ]` (по LogLevel) + белый ` | ` +- **Serial**: ANSI-цветной `[ LOG ]` + ` | ` + 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]`) и доступны из любого модуля. diff --git a/kernel/docs/events.md b/kernel/docs/events.md new file mode 100644 index 0000000..581cb79 --- /dev/null +++ b/kernel/docs/events.md @@ -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` + +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`. diff --git a/kernel/docs/interrupts.md b/kernel/docs/interrupts.md new file mode 100644 index 0000000..78a2156 --- /dev/null +++ b/kernel/docs/interrupts.md @@ -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) | diff --git a/kernel/docs/main.md b/kernel/docs/main.md new file mode 100644 index 0000000..a503668 --- /dev/null +++ b/kernel/docs/main.md @@ -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()`. diff --git a/kernel/docs/memory-management.md b/kernel/docs/memory-management.md new file mode 100644 index 0000000..7dde83c --- /dev/null +++ b/kernel/docs/memory-management.md @@ -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` | `VirtAddr - hhdm_offset`, возвращает None если адрес ниже HHDM | +| `as_ptr::() -> *const T` | Приведение к константному указателю | +| `as_mut_ptr::() -> *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> = 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` + +**Двухпроходный сканер:** + +1. Проход 1: от `last_byte` до конца bitmap +2. Проход 2: от 0 до `last_byte` + +Сканирует побайтово в поисках байта с нулевым битом (`!0xFF`), внутри байта через `trailing_ones`. Устанавливает бит, `ref_count = 1`, `used_pages++`, обновляет `last_byte`. + +### `alloc_contiguous(count) -> Option` + +Линейный поиск `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; 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` + +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` + +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` + +Walk, возвращает флаги PTE (с поддержкой huge pages). + +#### `update_flags(virt, flags, hhdm) -> Result<(), ()>` + +Walk, обновляет флаги, сохраняя физический адрес. `invlpg`. + +#### `activate(phys_addr)` + +`unsafe`: запись CR3 = phys_addr → полный сброс TLB. + +### `pmm_alloc() -> Option` + +Обёртка над `PMM.lock().unwrap().alloc_frame()` — разрывает циклическую зависимость между paging и pmm. diff --git a/kernel/docs/pmactor.md b/kernel/docs/pmactor.md new file mode 100644 index 0000000..4f756f0 --- /dev/null +++ b/kernel/docs/pmactor.md @@ -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` + +Дренирует очередь, обрабатывает каждый запрос через приватные методы. + +**Только из контекста актора** (`&mut self` гарантирует single consumer). + +#### Приватные обработчики + +**`handle_allocate(size_pages, token_sig, channel_id) -> Option`** + +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`** + +1. Валидация границ +2. `buddy.free(local_frame_idx, order)` +3. Возвращает None (маршрутизация не нужна) + +**`handle_carve(offset_pages, size_pages, channel_id) -> Option`** + +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>, +} +``` + +### 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` + +CAS-цикл на `free_head`: извлекает голову списка, устанавливает `state = PENDING`. + +### `route_responses(responses: Vec)` + +Для каждого 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)` + +Точка входа для маршрутизации ответов через `PMActor::route_responses()` (вызов на глобальном роутере). diff --git a/kernel/docs/vmm.md b/kernel/docs/vmm.md new file mode 100644 index 0000000..b73200e --- /dev/null +++ b/kernel/docs/vmm.md @@ -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` | Аллокатор ASID/PCID | +| `KERNEL_SPACE` | `Locked>` | Адресное пространство ядра | + +--- + +## Типы ошибок: `VmError` + +- `OutOfMemory` — нет свободной физической памяти +- `RegionOverlap` — VMA перекрывается с существующей +- `RegionNotFound` — VMA не найдена +- `InvalidAlignment` — неверное выравнивание +- `InvalidRange` — некорректный диапазон +- `PermissionDenied` — нет прав доступа +- `UnexpectedFault` — page fault в не-lazy, не-COW регионе +- `NonCanonical` — неканонический адрес +- `AsidExhausted` — кончились ASID + +--- + +## `VmaBacking` — типы подложки + +```rust +pub enum VmaBacking { + Anonymous(Vec>), // 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` — двухпроходный с пропуском целых слов (амортизированное 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, // 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` + +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>` +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` + +Создание дочернего адресного пространства (fork): + +- **Physical/Shared**: маппинг напрямую +- **Anonymous**: **COW setup**: + - Убирает WRITABLE из PTE родителя + - Устанавливает COW флаг в обоих PTEs + - `inc_ref_frame()` для всех фреймов + +### `translate(virt) -> Option` + +Аппаратный 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. diff --git a/kernel/src/cpu/interrupts.rs b/kernel/src/cpu/interrupts.rs index 48553c4..e93c706 100644 --- a/kernel/src/cpu/interrupts.rs +++ b/kernel/src/cpu/interrupts.rs @@ -7,25 +7,29 @@ pub static mut IDT: crate::cpu::idt::InterruptDescriptorTable = crate::cpu::idt: pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD; -global_asm!( - ".global page_fault_stub", - "page_fault_stub:", - "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", +macro_rules! exception_stub { + ($name:ident, $handler:ident) => { + concat!( + ".global ", stringify!($name), "\n", + stringify!($name), ":\n", + "push rax\npush rcx\npush rdx\npush rbx\npush rbp\n", + "push rsi\npush rdi\npush r8\npush r9\npush r10\n", + "push r11\npush r12\npush r13\npush r14\npush r15\n", + "mov rdi, [rsp + 15*8]\n", + "call ", stringify!($handler), "\n", + "pop r15\npop r14\npop r13\npop r12\npop r11\n", + "pop r10\npop r9\npop r8\npop rdi\npop rsi\n", + "pop rbp\npop rbx\npop rdx\npop rcx\npop rax\n", + "add rsp, 8\n", + "iretq\n", + ) + }; +} +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", "tlb_shootdown_stub:", "push rax", @@ -43,12 +47,7 @@ global_asm!( "push r13", "push r14", "push r15", - "call rust_tlb_shootdown_handler", - - "mov rdi, [rsp + 15*8]", - "call rust_page_fault_handler", - "pop r15", "pop r14", "pop r13", @@ -64,36 +63,202 @@ global_asm!( "pop rdx", "pop rcx", "pop rax", - - "add rsp, 8", "iretq" ); unsafe extern "C" { fn page_fault_stub(); + fn gpf_stub(); + fn double_fault_stub(); fn tlb_shootdown_stub(); } -pub fn init_idt() { +pub fn init_early_exceptions() { unsafe { - let idt_mut_ptr = core::ptr::addr_of_mut!(IDT); - (*idt_mut_ptr).set_handler(14, page_fault_stub as u64); + let idt = core::ptr::addr_of_mut!(IDT); + 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 idt_static_ref: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT); - idt_static_ref.load(); + let ptr: &'static crate::cpu::idt::InterruptDescriptorTable = &*core::ptr::addr_of!(IDT); + ptr.load(); } } - -pub fn process_deferred_mmu_events() { - let mut vmm_guard = KERNEL_SPACE.lock(); - if let Some(space) = vmm_guard.as_mut() { - space.process_pending_revocations(); +pub fn init_idt() { + // init_early_exceptions already loaded the IDT; this just overrides + // vectors that the full kernel needs. Calling lidt again is harmless. + unsafe { + 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)] pub extern "C" fn rust_page_fault_handler(error_code: 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 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(); if let Some(space) = vmm_guard.as_mut() { + space.process_pending_revocations(); + match space.handle_fault(virt_addr, write) { - Ok(_) => return, + Ok(_) => {}, Err(e) => { panic!( "KERNEL PANIC: Unprocessed failure of virtual memory (Page Fault)!\n\ diff --git a/kernel/src/debug.rs b/kernel/src/debug.rs index 6e7ab6f..3778eca 100644 --- a/kernel/src/debug.rs +++ b/kernel/src/debug.rs @@ -36,15 +36,13 @@ macro_rules! log { let _ = writeln!($console, $($arg)*); // Serial debug output - let mut sp = unsafe { $crate::debug::serial::SerialPort::init() }; - let _ = writeln!( - sp, - "{}[{:>5}]\x1b[0m {:<8} | {}", + $crate::debug::serial::write_global(format_args!( + "{}[{:>5}]\x1b[0m {:<8} | {}\n", $level.serial_color_code(), "LOG", $module, format_args!($($arg)*) - ); + )); }}; } diff --git a/kernel/src/debug/serial.rs b/kernel/src/debug/serial.rs index 7a79a23..19b4f87 100644 --- a/kernel/src/debug/serial.rs +++ b/kernel/src/debug/serial.rs @@ -1,4 +1,6 @@ use core::arch::asm; +use core::fmt::Write; +use crate::mem::allocator::Locked; pub struct SerialPort(u16); @@ -7,16 +9,14 @@ impl SerialPort { pub unsafe fn init() -> Self { let port = Self::COM1; - // В новых версиях Rust даже внутри unsafe fn - // вызовы других 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 + 3, 0x03); // 8 bits, no parity, 1 stop bit - outb(port + 2, 0xC7); // Enable FIFO - outb(port + 4, 0x0B); // IRQs enabled, RTS/DSR set + outb(port + 3, 0x80); + outb(port + 0, 0x03); + outb(port + 1, 0x00); + outb(port + 3, 0x03); + outb(port + 2, 0xC7); + outb(port + 4, 0x0B); } SerialPort(port) } @@ -38,6 +38,20 @@ impl core::fmt::Write for SerialPort { } } +static SERIAL_PORT: Locked> = 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 { asm!("out dx, al", in("dx") port, in("al") val, options(nomem, nostack, preserves_flags)); diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 7e4cf1f..2e9fcce 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -180,18 +180,31 @@ unsafe extern "C" fn kmain() -> ! { let mut console = tty::Console::new(&fb, KERNEL_FONT); console.clear(); + debug::serial::init_global(); + + cpu::interrupts::init_early_exceptions(); info!(console, "BOOT", "LIS4 Kernel Starting..."); unsafe { mem::pmm::BitmapPMM::init(&mmap_res, hhdm_offset); } 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 = unsafe { &mut *p4_phys.to_virt(hhdm_offset).as_mut_ptr::() }; + 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::() }; + info!(console, "BOOT", "zeroing page..."); unsafe { core::ptr::write_bytes(p4 as *mut _ as *mut u8, 0, 4096); } + info!(console, "BOOT", "zero done"); 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 virt_hhdm = phys.to_virt(hhdm_offset); p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset); @@ -379,6 +392,7 @@ unsafe extern "C" fn kmain() -> ! { } fn hcf() -> ! { + unsafe { asm!("cli", options(nomem, nostack, preserves_flags)); } loop { unsafe { asm!("hlt", options(nomem, nostack, preserves_flags)); } } @@ -386,7 +400,6 @@ fn hcf() -> ! { #[panic_handler] fn rust_panic(info: &core::panic::PanicInfo) -> ! { - let mut sp = unsafe { debug::serial::SerialPort::init() }; - let _ = writeln!(sp, "KERNEL PANIC: {:?}", info); + debug::serial::write_global(format_args!("KERNEL PANIC: {:?}\n", info)); hcf(); } diff --git a/kernel/src/mem/allocator.rs b/kernel/src/mem/allocator.rs index 2b203b2..de1447a 100644 --- a/kernel/src/mem/allocator.rs +++ b/kernel/src/mem/allocator.rs @@ -55,8 +55,14 @@ struct ListNode { 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 { list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()], + large_block_free: Option<&'static mut LargeBlockNode>, heap_start: usize, heap_end: usize, next_bump: usize, @@ -66,6 +72,7 @@ impl SlabAllocator { pub const fn new() -> Self { Self { list_heads: [None, None, None, None, None, None, None, None, None], + large_block_free: None, heap_start: 0, heap_end: 0, next_bump: 0, @@ -84,11 +91,28 @@ impl SlabAllocator { } 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_end = alloc_start.checked_add(layout.size()).unwrap_or(self.heap_end + 1); if alloc_end > self.heap_end { - null_mut() // Out of memory + null_mut() } else { self.next_bump = alloc_end; alloc_start as *mut u8 @@ -139,6 +163,17 @@ unsafe impl GlobalAlloc for Locked { } } 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); + } } } } diff --git a/kernel/src/mem/pm_manages.rs b/kernel/src/mem/pm_manages.rs index ed74704..90b93a0 100644 --- a/kernel/src/mem/pm_manages.rs +++ b/kernel/src/mem/pm_manages.rs @@ -94,7 +94,6 @@ pub enum PMRequest { size_pages: usize, channel_id: u16, }, - /// Sentinel — never pushed onto the queue; result of unpack(0). None, } @@ -160,30 +159,18 @@ impl PMRequest { // Response type -/// Result returned by `PMActor::process_messages()` for each completed request. #[derive(Debug, Clone, Copy)] pub struct PMResponse { - /// Channel to route this response to. `0` = discard. pub channel_id: u16, - /// The actual outcome. pub result: PMResult, } /// Outcome of a single PM operation. #[derive(Debug, Clone, Copy)] 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 }, - - /// The actor had insufficient free pages. - /// Future: ballooning subsystem intercepts this and retries. OutOfMemory { size_pages: usize }, - - /// A fixed sub-region was carved successfully. Carved { cap: Capability }, - - /// Free completed (no capability issued — memory returned to buddy pool). Freed { pages_returned: usize }, } @@ -194,7 +181,6 @@ pub enum PMResult { /// `pop` — exactly one consumer, no locking on the read side. pub struct PMActorQueue { buffer: [AtomicU64; QUEUE_SIZE], - /// Pad to separate producer-written `tail` from consumer-read `head`. _pad0: [u8; 64], head: AtomicUsize, _pad1: [u8; 64], @@ -269,15 +255,10 @@ impl PMActorQueue { /// Only `process_messages()` ever mutates `buddy` and `queue.head`. /// This is enforced by taking `&mut self` on `process_messages`. pub struct PMActor { - /// Unique identity within the actor federation. pub actor_id: u64, - /// Root strong capability over the entire managed physical range. pub root_untyped: Capability, - /// `(inclusive_start, exclusive_end)` physical addresses. pub managed_range: (PhysAddr, PhysAddr), - /// Inbox — producers write here, actor reads here. queue: PMActorQueue, - /// Local buddy allocator. Only ever touched in `process_messages`. buddy: BuddyAllocator, } @@ -325,10 +306,16 @@ impl PMActor { /// `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 /// 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 { let mut responses = Vec::new(); + let mut remaining = Self::MAX_MESSAGES_PER_CALL; while let Some(req) = self.queue.pop() { + remaining -= 1; let resp = match req { PMRequest::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, }; - // 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 { responses.push(r); } + + if remaining == 0 { + break; + } } responses @@ -434,9 +422,6 @@ impl PMActor { 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 } @@ -446,7 +431,6 @@ impl PMActor { size_pages: usize, channel_id: u16, ) -> Option { - // Validate offset + size within range. let range_pages = self.buddy.total_pages(); if offset_pages >= range_pages || size_pages == 0 diff --git a/kernel/src/mem/pm_router.rs b/kernel/src/mem/pm_router.rs index f1cca0a..babf350 100644 --- a/kernel/src/mem/pm_router.rs +++ b/kernel/src/mem/pm_router.rs @@ -69,12 +69,21 @@ pub fn init() { #[inline(always)] 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) { unsafe { (*ROUTER.inner.get()).as_ref().unwrap_unchecked() } } else { - panic!("FATAL: PMRouter is accessed before initialization!") + #[cold] + fn not_initialized() -> ! { + panic!("FATAL: PMRouter is accessed before initialization!") + } + not_initialized() } } diff --git a/kernel/src/mem/pmm.rs b/kernel/src/mem/pmm.rs index 9165214..0047712 100644 --- a/kernel/src/mem/pmm.rs +++ b/kernel/src/mem/pmm.rs @@ -8,7 +8,6 @@ pub struct BitmapPMM { ref_counts: &'static mut [u16], total_pages: usize, used_pages: usize, - /// Byte index hint: next search starts here to amortise O(N) scans. last_byte: usize, } @@ -59,6 +58,7 @@ impl BitmapPMM { last_byte: 0, }; + // Free all USABLE pages (gap pages not covered by any entry stay locked). for entry in mmap.entries() { if entry.entry_type == limine::memory_map::EntryType::USABLE { 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); for addr in (meta_phys..meta_end).step_by(PAGE_SIZE as usize) { pmm.lock_frame(PhysAddr(addr)); diff --git a/kernel/src/mem/vmm.rs b/kernel/src/mem/vmm.rs index 820e0c4..4dbcb67 100644 --- a/kernel/src/mem/vmm.rs +++ b/kernel/src/mem/vmm.rs @@ -56,7 +56,7 @@ fn local_tlb_flush_asid(asid: u16) { unsafe { core::arch::asm!( "invpcid {ty}, [{desc}]", - ty = in(reg) 1u64, // type 1 = single-context flush + ty = in(reg) 1u64, desc = in(reg) &desc, options(nostack, preserves_flags), ); @@ -69,24 +69,14 @@ fn local_tlb_flush_asid(asid: u16) { // Error type #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VmError { - /// PMM returned `None` — no physical frames available. OutOfMemory, - /// The requested virtual range overlaps an existing VMA. RegionOverlap, - /// No VMA covers the given address. RegionNotFound, - /// Address or size is not a multiple of 4096. InvalidAlignment, - /// Size is zero, or `virt + size` would overflow. InvalidRange, - /// Write fault on a read-only VMA, or exec fault on a NX VMA. 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, - /// Address outside the x86-64 canonical range. NonCanonical, - /// ASID pool exhausted (> 4094 simultaneous address spaces). AsidExhausted, } @@ -143,23 +133,10 @@ impl VmaFlags { #[derive(Debug)] pub enum VmaBacking { - /// Anonymous pages (stack, heap, BSS). - /// Index `i` → frame for `virt_start + i * 4096`. `None` = not yet faulted in. Anonymous(Vec>), - - /// Fixed physical range. Frames are **not** freed on unmap. - /// Used for MMIO, identity-mapped RAM, framebuffer, DMA buffers. 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 { - /// Token of the capability that owns the frames. owner_cap: u64, - /// Physical base (first page of the shared region). phys_base: PhysAddr, }, } @@ -251,13 +228,13 @@ impl AsidAllocator { } } } - None // pool truly exhausted + None } /// Return an ASID to the pool. fn free(&mut self, asid: u16) { if asid == 0 || asid >= 4095 { - return; // sentinel values — never freed + return; } let word = asid as usize / 32; let bit = asid as usize % 32; @@ -498,15 +475,6 @@ impl AddressSpace { 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( &mut self, virt: VirtAddr, @@ -645,6 +613,8 @@ impl AddressSpace { let old_frame = frames[page_idx].expect("COW fault on unmapped page"); 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 { core::ptr::copy_nonoverlapping( old_frame.to_virt(hhdm).as_ptr::(), @@ -793,7 +763,15 @@ pub fn tlb_flush_asid(asid: u16) { let active_cpus = ACTIVE_CPUS_MASK.load(Ordering::Acquire); 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 { return; @@ -830,7 +808,12 @@ pub fn handle_tlb_shootdown_ipi() { local_tlb_flush_asid(asid); 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