25 KiB
Elyz (LISA) Microkernel — Полный план оптимизации и полировки
Анализ кода по состоянию на июнь 2026.
Составлен на основе исходников:kernel/src/, linker-скрипты, Cargo.toml, Makefile.
Содержание
- Условные обозначения
- Критические баги (не работает)
- Средние баги (UB / утечки)
- Проблемы архитектуры
- Code Quality & Naming
- Производительность
- Инфраструктура
- Полный пошаговый план (сортированный)
- 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 аналогичная проверка предотвращает запись в маску по некорректному смещению.
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] функцию:
#[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 тащится через все вызовы:
p4.map_region(virt, phys, size, flags, hhdm);
p4.map_page(virt, frame, flags, hhdm);
Загрязняет сигнатуры и не нужно (HHDM offset — константа после boot).
Решение: Глобальный static HHDM_OFFSET: AtomicU64:
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
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
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:
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-последовательность:
- Выделить страницу по адресу <1MB (real mode trampoline)
- Записать startup код (выход из real mode → long mode → setup stack → jump to rust)
- Отправить SIPI с вектором на эту страницу
- AP просыпается, инициализирует LAPIC, регистрируется в
ACTIVE_CPUS_MASK
🏗️ ARCH 2.7 — Вся память в HHDM identity-map при старте
Файл: kernel/src/main.rs:194-201
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
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
#![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.ldlinker-riscv64.ldlinker-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
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!(...) внутри макроса:
- Вызывается
SerialPort::init()(см. CRIT 1.3) - Пишет на экран через framebuffer
- Пишет в 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
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
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
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:
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
if !src_slot.cap.rights.contains(CapRights::GRANT) {
return Err("Insufficient rights to mint (Missing GRANT flag)");
}
Проверяет только GRANT флаг. Но не проверяет relation: если source — Borrow, можно ли mint? В текущей реализации — можно. Должен быть check:
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.