Files
Elyz/PLAN.md
2026-07-06 00:25:49 +03:00

25 KiB
Raw Permalink Blame History

Elyz (LISA) Microkernel — Полный план оптимизации и полировки

Анализ кода по состоянию на июнь 2026.
Составлен на основе исходников: kernel/src/, linker-скрипты, Cargo.toml, Makefile.


Содержание

  1. Условные обозначения
  2. Критические баги (не работает)
  3. Средние баги (UB / утечки)
  4. Проблемы архитектуры
  5. Code Quality & Naming
  6. Производительность
  7. Инфраструктура
  8. Полный пошаговый план (сортированный)
  9. Roadmap

Условные обозначения

Метка Значение
🔴 CRIT Системный баг — крах, silent corruption, UB
🟡 HIGH Утечка памяти, логическая ошибка
🟢 MED Неоптимальность, технический долг
🔵 LOW Косметика, нейминг, кодстайл

Ф 1: Критические баги (Фаза 1 — CRIT 1.11.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.71.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-последовательность:

  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

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.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

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

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 16 готово     │▸▸    │  ARCH 3  HHDM global    │▸▸    │  PERF 16 word-level PMM │
│  ✅ MED 1.71.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.