fix: bugs

This commit is contained in:
Faynot
2026-07-06 00:25:49 +03:00
parent 1b38c7f445
commit c092a81331
23 changed files with 2502 additions and 123 deletions

626
PLAN.md Normal file
View File

@@ -0,0 +1,626 @@
# Elyz (LISA) Microkernel — Полный план оптимизации и полировки
> Анализ кода по состоянию на июнь 2026.
> Составлен на основе исходников: `kernel/src/`, linker-скрипты, Cargo.toml, Makefile.
---
## Содержание
1. [Условные обозначения](#условные-обозначения)
2. [Критические баги (не работает)](#ф-1-критические-баги-фаза-1)
3. [Средние баги (UB / утечки)](#ф-1-критические-баги-фаза-1)
4. [Проблемы архитектуры](#ф-2-архитектурный-рефакторинг-фаза-2)
5. [Code Quality & Naming](#ф-3-code-quality--naming-фаза-3)
6. [Производительность](#ф-4-производительность-фаза-4)
7. [Инфраструктура](#ф-5-инфраструктура-фаза-5)
8. [Полный пошаговый план (сортированный)](#полный-пошаговый-план-сортированный-по-приоритету)
9. [Roadmap](#roadmap)
---
## Условные обозначения
| Метка | Значение |
|-------|----------|
| 🔴 CRIT | Системный баг — крах, silent corruption, UB |
| 🟡 HIGH | Утечка памяти, логическая ошибка |
| 🟢 MED | Неоптимальность, технический долг |
| 🔵 LOW | Косметика, нейминг, кодстайл |
---
## Ф 1: Критические баги (Фаза 1 — CRIT 1.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` аналогичная проверка предотвращает запись в маску по некорректному смещению.
```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.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]` функцию:
```rust
#[cold]
fn not_initialized() -> ! {
panic!("FATAL: PMRouter is accessed before initialization!")
}
```
---
### 🟡 MED 1.8 — `process_messages` не имеет квоты ✅ исправлено
**Файл**: `kernel/src/mem/pm_manages.rs:328-354`
**Проблема**: Дренирует всю очередь за один вызов. Если 10 000 сообщений, заблокирует ядро на неопределённое время.
**Решение**: Добавлен лимит `MAX_MESSAGES_PER_CALL = 64`. После обработки 64 сообщений вызов возвращает управление, позволяя другим компонентам ядра получить процессорное время.
---
### 🟡 MED 1.9 — Bitmap PMM инициализирует `ref_counts` в 1, потом маркирует used-фреймы ✅ исправлено
**Файл**: `kernel/src/mem/pmm.rs:49-76`
**Проблема**: `fill(0xFF)` + `fill(1)` для всех страниц, затем итерация free по USABLE. Лишняя работа.
**Решение**: Инвертирована логика: `fill(0)` для bitmap и ref_counts, затем итерация по **не-USABLE** записям для mark_used. Убрана лишняя запись в резервированные страницы.
---
### 🟡 MED 1.10 — Page fault handler берёт `KERNEL_SPACE.lock()` каждый раз ✅ исправлено
**Файл**: `kernel/src/cpu/interrupts.rs:109-148`
**Проблема**: Двойной захват `KERNEL_SPACE.lock()` — в `process_deferred_mmu_events()` и в `rust_page_fault_handler()`. На SMP каждый PF будет сериализован.
**Решение**: `process_deferred_mmu_events()` встроен в `rust_page_fault_handler` — блокировка захватывается один раз и удерживается для обеих операций. Добавлен TODO о переходе на `RwLock` для SMP.
---
## Ф 2: Архитектурный рефакторинг (Фаза 2)
### 🏗️ ARCH 2.1 — HHDM offset передаётся параметром в каждую функцию
**Проблема**: `hhdm: u64` тащится через все вызовы:
```rust
p4.map_region(virt, phys, size, flags, hhdm);
p4.map_page(virt, frame, flags, hhdm);
```
Загрязняет сигнатуры и не нужно (HHDM offset — константа после boot).
**Решение**: Глобальный `static HHDM_OFFSET: AtomicU64`:
```rust
pub fn init(offset: u64) { HHDM_OFFSET.store(offset, Ordering::Release); }
pub fn get() -> u64 { HHDM_OFFSET.load(Ordering::Relaxed) }
```
Убрать `hhdm` из параметров всех функций paging/vmm.
---
### 🏗️ ARCH 2.2 — Buddy allocator: O(n) поиск при free()
**Файл**: `kernel/src/mem/buddy.rs:246`
```rust
if let Some(pos) = self.free_lists[order].iter().position(|&b| b == buddy_idx) {
```
Линейный поиск по `Vec<usize>` — O(количество блоков этого порядка). При 10 000 блоков порядка 0, free() занимает тысячи итераций.
**Решение**: Перейти на **intrusive linked list** через сами страницы: первые 8 байт неиспользуемой страницы хранят `next` указатель. Даёт O(1) удаление.
Или хеш-таблицу: `HashMap<usize, ()>` (buddy_idx → exists).
---
### 🏗️ ARCH 2.3 — PMM Bitmap: O(n) allocation scan
**Файл**: `kernel/src/mem/pmm.rs:128-158`
Побайтовый (побитовый) scan всего bitmap в поиске нулевого бита. На системе с 64 GB RAM → 64M бит → до 8M итераций на аллокацию.
**Решение**: Использовать **tree bitmap** (buddy-style bitmap). Хранить дополнительный уровень с `u64` словами, где каждый бит = `all_ones(word)`. Позволяет за O(1) найти свободный блок через `ctz` / `clz`.
---
### 🏗️ ARCH 2.4 — Channel `wait_for_response` — busy-wait
**Файл**: `kernel/src/mem/pm_router.rs:130-137`
```rust
while channel.state.load(Ordering::Acquire) != STATE_READY {
core::hint::spin_loop();
}
```
100% CPU consumption в ожидании. Для реального асинхронного рантайма нужен yield-to-scheduler или waker.
**Решение**: Добавить `Waker` registration: поток регистрирует свой `Waker` в канале, а `route_responses` вызывает `wake()` при записи результата. Пока планировщика нет — хотя бы HLT-based ожидание.
---
### 🏗️ ARCH 2.5 — Capability revocation: O(n) рекурсия
**Файл**: `kernel/src/cap/mod.rs:97-137`
`revoke_internal()` сканирует **весь CNode** на каждом уровне рекурсии. С 256+ слотами и глубиной 10 → 2560 итераций.
**Решение**: Держать per-slot список children:
```rust
struct CNodeSlot {
cap: Capability,
parent: Option<usize>,
children: Vec<usize>, // явный список наследников
}
```
Отзыв — проход по дереву без сканирования всей таблицы.
---
### 🏗️ ARCH 2.6 — Нет SMP startup кода
`ACTIVE_CPUS_MASK = 1` (только BSP). TLB shootdown кидает IPI в пустоту. Нет:
- SIPI trampoline page
- Startup sequence для AP
- Per-CPU data areas
- Stack allocation для AP
**Решение**: Реализовать SIPI-последовательность:
1. Выделить страницу по адресу <1MB (real mode trampoline)
2. Записать startup код (выход из real mode → long mode → setup stack → jump to rust)
3. Отправить SIPI с вектором на эту страницу
4. AP просыпается, инициализирует LAPIC, регистрируется в `ACTIVE_CPUS_MASK`
---
### 🏗️ ARCH 2.7 — Вся память в HHDM identity-map при старте
**Файл**: `kernel/src/main.rs:194-201`
```rust
for entry in mmap_res.entries() {
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
if entry.entry_type != RESERVED {
p4.map_region(VirtAddr(entry.base), phys, entry.length, flags, hhdm_offset);
}
}
```
Все *не-reserved* регионы identity-map'ятся по физическому адресу (second mapping). На системе с 64GB RAM и 4KB страницами = 16M calls к `map_page`. Каждый call:
- Аллоцирует до 3 страниц таблиц (P3, P2, P1)
- INVLPG на каждую 4KB страницу
- Время: ~часы при эмуляции, минуты при KVM
**Решение**: Использовать **2MB huge pages** (`PageTableFlags::HUGE_PAGE`) для identity map. 64GB → 32K entries вместо 16M. INVLPG заменяется на CR3 reload.
---
## Ф 3: Code Quality & Naming (Фаза 3)
### 🧹 CQ 3.1 — Dead code: FramebufferDisplay + Console (main.rs)
**Файл**: `kernel/src/main.rs:42-144`
Две структуры:
- `FramebufferDisplay` (реализует `DrawTarget` для `embedded-graphics`)
- `Console` (обёртка над `FramebufferDisplay` + `embedded-graphics`)
Никто не используется. Настоящий консольный вывод — `tty::Console`. При этом `embedded-graphics` висит в Cargo.toml как зависимость.
**Решение**: Удалить об struct + реализацию, убрать `embedded-graphics` из зависимостей.
---
### 🧹 CQ 3.2 — Unused imports в main.rs
**Файл**: `kernel/src/main.rs:6-16`
```rust
use core::fmt::{self, Write};
use alloc::vec::Vec;
use crate::mem::paging::{PageTable, PageTableFlags};
use crate::mem::address::{PhysAddr, VirtAddr};
use crate::cap::{Relation, CapRights, Capability, CapObject};
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
```
Часть из них используется, часть — нет (например `fmt::self`, `PageTable`). После удаления dead code (3.1) ещё часть станет не нужна.
---
### 🧹 CQ 3.3 — `#[allow(dead_code)]` на весь mod vmm
**Файл**: `kernel/src/mem/vmm.rs:27`
```rust
#![allow(dead_code)]
```
Отключает warning для ВСЕГО модуля. 800+ строк кода с отключённой проверкой мёртвого кода. Означает что значительная часть VMM API не используется сейчас — надо либо использовать, либо убрать, либо маркировать `#[allow(dead_code)]` на конкретные единицы.
---
### 🧹 CQ 3.4 — Имя проекта: LISA / Elyz / kernel
| Где | Что написано |
|-----|-------------|
| `Cargo.toml` | `name = "LISA"` |
| `README.md` | "Elyz" |
| `limine.conf` | `Elyz (LISA KERNEL)` |
| `GNUmakefile` | `elyz-x86_64.iso` |
| `main.rs` | `LIS4` (LISA 4?) |
Три разных публичных имени.
---
### 🧹 CQ 3.5 — Странное имя файла: `pm_manages.rs`
Грамматически некорректно (глагол в имени файла). Основная структура — `PMActor`, файл должен называться `pm_actor.rs`.
---
### 🧹 CQ 3.6 — Мёртвые linker-скрипты
- `linker-aarch64.ld`
- `linker-riscv64.ld`
- `linker-loongarch64.ld`
Не используются (build.rs хардкодит `linker-x86_64.ld`). Ни один из этих таргетов не подключен в `rust-toolchain.toml`. Dead weight.
---
### 🧹 CQ 3.7 — Locked SpinLock без backoff
**Файл**: `kernel/src/mem/allocator.rs:27`
```rust
while self.lock.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
core::hint::spin_loop();
}
```
`spin_loop()` на x86 — это `REP NOP` (PAUSE). Это правильно, но при длительном ожидании нужно экспоненциально увеличивать задержку.
---
### 🧹 CQ 3.8 — `log!` максрос не lock-free
При вызове `info!(...)` внутри макроса:
1. Вызывается `SerialPort::init()` (см. CRIT 1.3)
2. Пишет на экран через framebuffer
3. Пишет в serial
Если в PF handler вызвать `info!`, и PF произошёл из-за framebuffer DMA или serial IRQ — будет reentrancy. Надо маркировать как `#[inline(never)]` или добавить reentrancy guard.
---
## Ф 4: Производительность (Фаза 4)
### ⚡ PERF 4.1 — Bitmap PMM: word-level skip
**Файл**: `kernel/src/mem/pmm.rs:139`
```rust
if self.bitmap[byte_idx] == 0xFF { continue; }
```
Проверка на уровне байта — можно на уровне `u64` слова: `if word == u64::MAX { skip 8 bytes }`. В 8x меньше итераций.
---
### ⚡ PERF 4.2 — Buddy free: O(n) → O(1)
**Файл**: `kernel/src/mem/buddy.rs:246`
См. ARCH 2.2. Замена `Vec` на intrusive list — ключевой перф-фикс.
---
### ⚡ PERF 4.4 — INVLPG в bulk_map: batch flush
**Файл**: `kernel/src/mem/paging.rs:119-121`
```rust
unsafe {
asm!("invlpg [{}]", in(reg) virt.0, ...);
}
```
`map_region` вызывает `map_page` в цикле — каждая итерация делает INVLPG. 16M страниц = 16M INVLPG. CPU заливает TLBs и потом их же сбрасывает.
**Решение**: Убрать INVLPG из `map_page` (оставить вызывающему), сделать отдельную функцию `flush_range(virt, pages)` или вызов `invpcid` type-2 (flush-by-range).
**Но осторожно**: INVLPG нужен при модификации активной таблицы. Если identity-map ещё не загружена — можно без INVLPG.
---
### ⚡ PERF 4.5 — TLB shootdown: busy-wait → IPI + HLT
**Файл**: `kernel/src/mem/vmm.rs:809-811`
```rust
while SHOOTDOWN_ACK.load(Ordering::Acquire) & target_mask != target_mask {
spin_loop();
}
```
Пока нет AP — код мёртвый. Когда AP появятся, надо чтобы AP висели в `HLT` и просыпались от IPI с TLB_SHOOTDOWN_VECTOR.
---
### ⚡ PERF 4.6 — Page fault handler: lock-free VMA tree
**Файл**: `kernel/src/cpu/interrupts.rs:110`
`KERNEL_SPACE.lock()` — каждый PF сериализуется. На одноядерной системе — OK, на SMP — bottleneck.
**Решение**: `RWLock` на VMA region или lock-free RB-tree для VMA поиска.
---
## Ф 5: Инфраструктура (Фаза 5)
### 🛠️ INFRA 5.1 — SMP AP Startup Code
Отсутствует. Нужен:
- Startup trampoline в real mode (<1MB)
- Переход в protected → long mode
- Настройка GDT/IDT для AP
- Per-CPU stack
- Регистрация в `ACTIVE_CPUS_MASK`
---
### 🛠️ INFRA 5.2 — Мультиархитектурность
Есть linker-скрипты под aarch64, riscv64, loongarch64, но:
- `build.rs` хардкодит `linker-x86_64.ld`
- ASM в interrupts.rs только x86
- LAPIC — x86-specific
- Port IO — x86-specific
- IDT — x86-specific
Для портирования нужна архитектурная абстракция — traits:
```rust
trait Arch {
type PteFlags;
fn init_interrupts();
fn send_ipi(core_id: u32, vector: u8);
fn read_cycle_counter() -> u64;
}
```
---
### 🛠️ INFRA 5.3 — Unit-тесты для Buddy Allocator
Buddy allocator — идеальный кандидат для `#[cfg(test)]`:
- Проверка инвариантов после alloc/free sequence
- Fuzzing случайных порядков
- Проверка coalescing (граничные случаи)
- Проверка alignment
---
### 🛠️ INFRA 5.4 — Утилита для бинарного патчинга
Сейчас результирующий ELF надо руками objcopy'ить. Можно добавить Cargo alias или build.rs скрипт для автоматизации.
---
### 🛠️ INFRA 5.5 — GDB/QEMU debugging support
Добавить `.gdbinit` / `gdb.py` с:
- `target remote localhost:1234`
- Разгрузка символов из ELF
- Макросы для просмотра page table, capability, VMA regions
---
## Ф 6: Безопасность (сквозная)
### 🛡️ SEC 6.1 — Capability minting не проверяет `Relation` parent
**Файл**: `kernel/src/cap/mod.rs:64-77`
```rust
if !src_slot.cap.rights.contains(CapRights::GRANT) {
return Err("Insufficient rights to mint (Missing GRANT flag)");
}
```
Проверяет только GRANT флаг. Но не проверяет `relation`: если source — `Borrow`, можно ли mint? В текущей реализации — можно. Должен быть check:
```rust
if src_slot.cap.relation == Relation::Borrow {
return Err("Cannot mint from borrowed capability");
}
```
---
### 🛡️ SEC 6.2 — CapGuardToken не реализован
В дескрипторе есть упоминание `guards: Vec<CapGuardToken>`, `Borrow` relation и guard-токены, логика самих guard'ов не реализована. Если есть borrow, mint не должен позволять дальнейшее распространение — но это не проверяется.
---
### 🛡️ SEC 6.3 — Нет проверки прав на операции с PMM через capabilities
`BitmapPMM` полностью глобальный: любой код может вызвать `alloc_frame()`, `free_frame()` без проверки capability. Капы пока хранят токены и права, но PMM их не проверяет.
---
---
## Полный пошаговый план (сортированный по приоритету)
### Этап 1: Исправить критические баги (сейчас — не работает)
```
[X] 1. ✅ HIGH — Исправить target_mask для >64 core
Добавлена проверка current_core < 64 перед shift.
Estimate: ~10 мин
[ ] 2. 🔥 HIGH — Исправить ref_counts инициализацию
Вместо fill(1) + free, сделать fill(0) + mark_used.
Estimate: ~15 мин
```
### Этап 2: Архитектурный рефакторинг
```
[ ] 3. 🏗️ ARCH — Глобальный HHDM_OFFSET
AtomicU64 static, убрать hhdm из сигнатур.
Estimate: ~1 ч
[ ] 4. 🏗️ ARCH — Intrusive free-list в buddy allocator
O(1) free вместо O(n).
Estimate: ~2 ч
[ ] 5. 🏗️ ARCH — Tree bitmap для PMM
word-level scan с ctz.
Estimate: ~1.5 ч
[ ] 6. 🏗️ ARCH — HLT-based wait в router channel
Пока нет планировщика — HLT вместо spin.
Estimate: ~30 мин
[ ] 7. 🏗️ ARCH — Capability revoke без O(n) scan
Children list в слоте.
Estimate: ~1 ч
[ ] 8. 🏗️ ARCH — Identity-map через 2MB huge pages
map_region с HUGE_PAGE флагом для identity.
Estimate: ~1 ч
```
### Этап 3: Code Quality
```
[ ] 9. 🧹 CQ — Удалить dead code (FramebufferDisplay, Console, embedded-graphics)
+ неиспользуемые импорты.
Estimate: ~20 мин
[ ] 10. 🧹 CQ — Убрать #![allow(dead_code)] из vmm.rs
Маркировать конкретные единицы.
Estimate: ~15 мин
[ ] 11. 🧹 CQ — Удалить мёртвые linker-скрипты
aarch64/riscv64/loongarch64.ld.
Estimate: ~5 мин
[ ] 12. 🧹 CQ — Переименовать pm_manages.rs → pm_actor.rs
+ все референсы.
Estimate: ~10 мин
[ ] 13. 🧹 CQ — Вынести panic.rs из main.rs
Отдельный файл для panic_handler.
Estimate: ~10 мин
[ ] 14. 🧹 CQ — Синхронизировать имена: LISA → Elyz
Cargo.toml, main.rs ("LIS4"), README.
Estimate: ~15 мин
[ ] 15. 🧹 CQ — Locked spinlock с exponential backoff
PAUSE + цикл задержки.
Estimate: ~15 мин
```
### Этап 4: Производительность
```
[ ] 16. ⚡ PERF — Bitmap scan: word-level (u64) вместо byte-level
+ ctzl для поиска бита.
Estimate: ~30 мин
[ ] 17. ⚡ PERF — INVLPG-free bulk map
Убрать INVLPG из map_page, вызов flush_range после.
Estimate: ~30 мин
[ ] 18. ⚡ PERF — process_messages quota
Max N сообщений за вызов.
Estimate: ~15 мин
```
### Этап 5: Инфраструктура
```
[ ] 19. 🛠️ INFRA — SMP AP startup code
SIPI → trampoline → long mode → Rust.
Estimate: ~1 неделя
[ ] 20. 🛠️ INFRA — Unit tests для buddy allocator
#[cfg(test)] модуль.
Estimate: ~1 ч
[ ] 21. 🛠️ INFRA — GDB script + QEMU -s
.gdbinit для отладки.
Estimate: ~30 мин
[ ] 22. 🛠️ INFRA — Arch trait для x86_64
Подготовка к портированию.
Estimate: ~3-4 ч
```
### Этап 6: Безопасность
```
[ ] 23. 🛡️ SEC — Проверка Relation при mint
Запретить mint из Borrow.
Estimate: ~10 мин
[ ] 24. 🛡️ SEC — CapGuard реализация
Привязка guard-токенов к source slot при mint.
Estimate: ~45 мин
```
---
## Roadmap (визуально)
```
Неделя 1 Неделя 2 Неделя 3
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
│ ✅ CRIT 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.*