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

19
kernel/docs/README.md Normal file
View File

@@ -0,0 +1,19 @@
# Документация ядра LISA
LISA v0.1.0 — capability-based микроядро для x86-64, написанное на Rust (nightly).
## Разделы
| Раздел | Описание |
|---|---|
| [Архитектура](architecture.md) | Общая архитектура и взаимосвязи подсистем |
| [Точка входа](main.md) | `kmain()`, инициализация, FramebufferDisplay, тесты |
| [Физическая память](memory-management.md) | BitmapPMM, BuddyAllocator, Page Tables, адресация |
| [Виртуальная память](vmm.md) | AddressSpace, VMA, COW, Lazy, TLB shootdown, ASID/PCID |
| [Акторы памяти](pmactor.md) | PMActor, PMRouter, BuddyAllocator, асинхронные запросы |
| [Capability system](capabilities.md) | CNode, Capability, mint/revoke, KernelObject |
| [Прерывания](interrupts.md) | IDT, обработчики page fault / TLB shootdown, LAPIC |
| [Консоль и отладка](console.md) | TTY, Serial, макросы логирования |
| [Аллокатор кучи](allocator.md) | SlabAllocator, Locked, GlobalAlloc |
| [Очередь отзыва](events.md) | RevocationQueue, MMU уведомления |
| [Сборочная система](build-system.md) | Cargo, linker scripts, GNUmakefile, toolchain |

105
kernel/docs/allocator.md Normal file
View File

@@ -0,0 +1,105 @@
# Аллокатор кучи
**Файл**: `src/mem/allocator.rs`
Slab-аллокатор со spinlock-синхронизацией и fallback bump-аллокацией для больших блоков.
---
## Примитив синхронизации: `Locked<A>`
Самодельный spinlock (альтернатива `spin::Mutex`).
```rust
pub struct Locked<A> {
inner: UnsafeCell<A>,
lock: AtomicBool,
}
```
- `new(inner)` — создаёт с unlocked состоянием
- `lock() -> LockedGuard<'_, A>` — CAS-цикл на `AtomicBool` (спин-ожидание)
### LockedGuard
- `Deref`/`DerefMut` — доступ к внутренним данным
- `Drop``store(false, Release)` — освобождение блокировки
`unsafe impl<A> Sync for Locked<A>` — разработчик гарантирует корректность.
---
## SlabAllocator
```rust
pub struct SlabAllocator {
list_heads: [Option<&'static mut ListNode>; 9], // slab free list
large_block_free: Option<&'static mut LargeBlockNode>, // free list для >2048
heap_start: usize,
heap_end: usize,
next_bump: usize,
}
```
### Slab классы
```rust
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
```
9 классов — степени двойки от 8 до 2048.
### ListNode / LargeBlockNode
```rust
struct ListNode {
next: Option<&'static mut ListNode>,
}
struct LargeBlockNode {
size: usize,
next: Option<&'static mut LargeBlockNode>,
}
```
Односвязные списки свободных блоков.
### `init(&mut self, start, size)`
Устанавливает границы кучи: `heap_start = start`, `heap_end = start + size`, `next_bump = start`.
### `list_index(layout) -> Option<usize>`
Находит индекс наименьшего slab-класса, покрывающего `max(size, align)`.
Пример: layout size=20, align=8 → блок 20 > 16, следующий 32 → index 3 (32 байта).
### `fallback_alloc(&mut self, layout) -> *mut u8`
1. Проверяет `large_block_free` — если есть подходящий блок, отдаёт его
2. Иначе bump-аллокация: выравнивание, проверка `next_bump + size <= heap_end`, возврат `next_bump`
3. При OOM возвращает null
---
## Глобальный аллокатор
```rust
#[global_allocator]
pub static ALLOCATOR: Locked<SlabAllocator>;
```
### `GlobalAlloc::alloc(&self, layout) -> *mut u8`
1. Lock allocator
2. Если `layout.size()` влезает в slab класс:
- Находит `list_index`
- Если free list не пуст: pop голову, вернуть указатель
- Если пуст: `fallback_alloc(layout)` но с размером slab-блока (не `layout.size()`)
3. Для блоков > 2048: сразу `fallback_alloc(layout)`
### `GlobalAlloc::dealloc(&self, ptr, layout)`
1. Lock allocator
2. Для slab-sized: push обратно в slab free list как ListNode
3. Для больших: push в large_block_free как LargeBlockNode

View File

@@ -0,0 +1,56 @@
# Архитектура ядра LISA
LISA — **capability-based микроядро**. Вся физическая память управляется через систему capability (прав доступа к объектам). Ядро использует загрузчик **Limine** (протокол Limine boot protocol).
## Общая схема
```
kmain()
├── BitmapPMM.init() ── глобальный менеджер фреймов
├── LAPIC.init() ── доступ к Local APIC
├── PageTable::activate() ── включение страничной памяти
├── SlabAllocator.init() ── куча ядра
├── VMM::init_kernel_space() ── адресное пространство ядра
├── IDT::load() ── обработчики прерываний
├── PMRouter::init() ── 65536 каналов
├── CNode (cap тест) ── insert → mint → revoke
│ └── RevocationQueue.push() ── уведомление VMM
└── PMActor тест
├── PMRouter.alloc_channel()
├── PMActor.submit_request()
├── PMActor.process_messages()
└── BuddyAllocator (внутри PMActor)
```
## Поток ревокации
```
CNode::revoke()
→ RevocationQueue.push(token_sig)
→ (page fault handler)
→ process_deferred_mmu_events()
→ AddressSpace::process_pending_revocations()
→ RevocationQueue.pop()
→ AddressSpace::revoke_by_token(token) // анмаппинг VMA
→ TLB flush
```
## Поток PMActor запроса
```
1. PMRouter::alloc_channel() → получаем channel_id
2. PMActor::submit_request(Allocate { channel_id, ... })
3. (актор обрабатывает) PMActor::process_messages()
4. PMActor::handle_allocate() → BuddyAllocator::alloc_pages()
5. PMRouter::route_responses([PMResponse { channel_id, result }])
6. PMRouter::wait_for_response(channel_id) → spin до READY
7. Возврат результата клиенту
```
## Ключевые принципы
- **Lock-free очереди** — MPSC кольцевые буферы везде, где возможен контеншен (PMActorQueue, RevocationQueue, PMRouter channels)
- **Double-write logging** — каждое сообщение дублируется в фреймбуфер (консоль) и последовательный порт
- **HHDM** — Higher Half Direct Map: весь физическая память отображена 1:1 в верхней половине адресного пространства через HHDM offset
- **ASID/PCID** — аппаратная изоляция TLB между адресными пространствами
- **Распределённое управление памятью** — PMM выделяет только сырые фреймы, PMActor управляет диапазонами через BuddyAllocator

134
kernel/docs/build-system.md Normal file
View File

@@ -0,0 +1,134 @@
# Сборочная система
---
## Cargo.toml
**Файл**: `Cargo.toml`
```toml
[package]
name = "LISA"
version = "0.1.0"
edition = "2024"
[lib]
# н/д — только бинарный крейт
[[bin]]
name = "kernel"
path = "src/main.rs"
[dependencies]
limine = "0.5" # Limine boot protocol
embedded-graphics = "0.8" # 2D фреймбуфер рисование
bitflags = "2.11.0" # bitflags макрос
[profile.dev]
panic = "abort" # без раскрутки стека
[profile.release]
panic = "abort"
```
---
## build.rs
**Файл**: `build.rs`
```rust
fn main() {
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
println!("cargo:rustc-link-arg=-Tlinker-{}.ld", arch);
println!("cargo:rerun-if-changed=linker-{}.ld", arch);
}
```
Передаёт линкер-скрипт в зависимости от архитектуры: `-Tlinker-x86_64.ld`, `-Tlinker-aarch64.ld`, etc.
---
## Linker скрипты
### Общая структура (все 4 архитектуры)
- **Entry**: `kmain`
- **Base address**: `0xFFFFFFFF80000000` — высшие 2 GiB, по спецификации Limine
- **Program headers**: `PT_LOAD` для `.text`, `.rodata`, `.data`
### Секции
```
SECTIONS {
.text : { *(.text .text.*) } → код
.rodata : { *(.rodata .rodata.*) } → только чтение (page-aligned)
.data : {
*(.requests_start_marker) → Limine requests
*(.requests)
*(.requests_end_marker)
*(.data .data.*)
}
.bss : { *(.bss .bss.*) } → нули
/DISCARD/ : { *(.eh_frame*) *(.note.*) }
}
```
### Архитектурные различия
| Архитектура | OUTPUT_FORMAT | Особенности |
|---|---|---|
| x86-64 | `elf64-x86-64` | Стандартный |
| aarch64 | `elf64-littleaarch64` | Стандартный |
| riscv64 | `elf64-littleriscv` | `.data` + `.sdata`, `.bss` + `.sbss` |
| loongarch64 | `elf64-loongarch` | Стандартный |
---
## GNUmakefile
**Файл**: `GNUmakefile`
### Переменные
```makefile
OUTPUT := kernel
KARCH ?= x86_64
RUST_TARGET := $(KARCH)-unknown-none
# riscv64 -> riscv64gc-unknown-none-elf
RUST_PROFILE ?= dev
```
### Цели
| Цель | Действие |
|---|---|
| `all` | `RUSTFLAGS="-C relocation-model=static" cargo build --target ...` + копирование `target/.../kernel``./kernel` |
| `clean` | `cargo clean` + rm `./kernel` |
| `distclean` | То же, что clean |
`relocation-model=static` — запрещает позиционно-независимый код (ядро загружается по фиксированному адресу).
---
## rust-toolchain.toml
**Файл**: `rust-toolchain.toml`
```toml
[toolchain]
channel = "nightly"
targets = ["x86_64-unknown-none"]
# aarch64-unknown-none, riscv64gc-unknown-none-elf, loongarch64-unknown-none (для будущего)
```
---
## .gitignore
**Файл**: `.gitignore`
```
/kernel # бинарный файл ядра
/target # артефакты сборки Cargo
```

147
kernel/docs/capabilities.md Normal file
View File

@@ -0,0 +1,147 @@
# Система Capability
---
## Дескрипторы Capability
**Файл**: `src/cap/descriptor.rs`
### Relation
```rust
pub enum Relation {
Strong, // владение (исключительный доступ)
Borrow, // заимствование (временный доступ)
Transfer, // передача (владение перешло)
}
```
### CapObject
```rust
pub enum CapObject {
Empty, // null (слот свободен)
Memory { phys: PhysAddr, size_pages: usize }, // регион физической памяти
CNode { phys: PhysAddr, slots: usize }, // узел capability (таблица)
PMActor { id: u64 }, // ссылка на PMActor
}
```
### CapRights (bitflags, u8)
```rust
READ = 1 << 0
WRITE = 1 << 1
EXECUTE = 1 << 2
GRANT = 1 << 3 // разрешение на mint (порождение потомков)
```
### Capability
```rust
pub struct Capability {
pub object: CapObject, // ссылка на объект
pub rights: CapRights, // права доступа
pub relation: Relation, // тип отношений
pub token_sig: u64, // уникальный токен для отзыва (MMU)
}
```
- `empty()` — возвращает Capability с `CapObject::Empty`
- `is_valid() -> bool``object != Empty`
---
## Объекты ядра
**Файл**: `src/cap/object.rs`
### ObjectType
```rust
pub enum ObjectType {
Untyped, // сырая нетипизированная память
Frame, // выделенный физический фрейм
CNode, // узел capability
ThreadBlock, // блок управления потоком
PageTable, // страница таблицы
}
```
### KernelObject
```rust
pub struct KernelObject {
pub phys_addr: PhysAddr,
pub size_bits: u8, // размер как степень двойки
pub obj_type: ObjectType,
pub ref_count: AtomicUsize, // атомарный счётчик ссылок
pub owner_id: u64, // ID владельца
}
```
- `add_ref()` — atomic increment (Relaxed ordering)
- `release() -> bool` — atomic decrement (Release). Возвращает true, если счётчик достиг 0
---
## CNode
**Файл**: `src/cap/mod.rs`
Узел capability, хранит массив слотов с индивидуальной блокировкой.
### CNodeSlot
```rust
struct CNodeSlot {
cap: Capability,
parent_idx: Option<usize>, // индекс родителя (для дерева) — None у корневых
}
```
### CNode
```rust
pub struct CNode {
slots: Vec<Locked<CNodeSlot>>,
}
```
### `new(size) -> Self`
Создаёт `size` пустых слотов с `parent_idx = None`.
### `insert(slot, cap) -> Result<(), &str>`
Проверка границ, сохранение Capability в слоте.
### `mint(src, dest, relation, rights) -> Result<(), &str>`
**Порождение дочерней capability с урезанными правами:**
1. **Lock ordering**: блокировка слотов по возрастанию индекса (deadlock prevention)
2. Source-слот не пуст, имеет GRANT право
3. `child_rights = parent_rights & requested_rights` — дочерние права не могут превышать родительские
4. Копирует Capability, устанавливает новые права, relation, `parent_idx`
5. Ошибка при `src == dest`
### `revoke(slot_idx) -> Result<(), &str>`
**Каскадный отзыв с уведомлением VMM:**
1. `revoke_internal(slot_idx)` — рекурсивно находит и уничтожает всех потомков
2. Уничтожает саму Capability в слоте (Empty)
3. Пушит `token_sig` в `MMU_REVOCATION_QUEUE` (кроме 0 и `0xDEAD_BEEF`)
4. **Паника** при переполнении очереди
### `revoke_internal(slot_idx)`
Приватная рекурсия:
1. Сканирует все слоты в поисках `parent_idx == slot_idx`
2. Для каждого потомка: рекурсивный вызов
3. Затем уничтожает свой слот (Empty)
### `get_cap(slot) -> Option<Capability>`
Возвращает копию Capability.

132
kernel/docs/console.md Normal file
View File

@@ -0,0 +1,132 @@
# Консоль и отладка
---
## TTY — PSF2 терминал
**Файл**: `src/tty.rs`
Шрифтовой терминал на основе PSF2 (PC Screen Font v2). Это **реально используемая консоль** в `kmain`.
### Psf2Header — `#[repr(C, packed)]`
```rust
struct Psf2Header {
magic: u32, // магическое число PSF2
version: u32,
header_size: u32,
flags: u32,
num_glyphs: u32, // количество глифов
bytes_per_glyph: u32,
height: u32, // высота шрифта в пикселях
width: u32, // ширина шрифта в пикселях
}
```
### Console
```rust
pub struct Console<'a> {
framebuffer: &'a Framebuffer<'a>,
font: &'static [u8], // сырые данные PSF2
x: usize, // курсор X (в пикселях)
y: usize, // курсор Y (в пикселях)
fg_color: u32, // цвет текста (0xFFFFFF = белый)
bg_color: u32, // цвет фона (0x000000 = чёрный)
}
```
### Методы
| Метод | Описание |
|---|---|
| `new(framebuffer, font)` | Создаёт консоль, курсор в (0,0) |
| `set_color(fg)` | Устанавливает цвет текста |
| `clear()` | Зануляет фреймбуфер, сбрасывает курсор |
| `header()` | Возвращает ссылку на Psf2Header |
| `scroll()` | Сдвигает фреймбуфер на высоту шрифта, зануляет низ |
| `draw_glyph(glyph_index, x, y)` | Рисует глиф: сканирует bitmap шрифта, пишет `fg_color` для установленных битов |
| `write_char(c)` | Обрабатывает `\n`, word wrap, поиск глифа (0 для отсутствующих), скролл |
| `write_str(s)` | Реализация `fmt::Write` — итерация по символам |
`draw_glyph` — ключевой метод: строки шрифта — это побитовое представление, каждый бит — один пиксель. Если бит установлен → пишется `fg_color`, иначе пропускается.
---
## Serial — последовательный порт
**Файл**: `src/debug/serial.rs`
Драйвер UART 16550 на COM1.
### SerialPort
```rust
pub struct SerialPort(pub u16); // номер порта
```
### Инициализация UART (`init()`)
```rust
port + 1 = 0x00 // отключение прерываний
port + 3 = 0x80 // DLAB = 1 (доступ к делителю)
port + 0 = 0x03 // делитель младший байт (~38400 бод)
port + 1 = 0x00 // делитель старший байт
port + 3 = 0x03 // 8N1: 8 бит, no parity, 1 stop bit
port + 2 = 0xC7 // FIFO enable, clear, 14-byte threshold
port + 4 = 0x0B // DTR + RTS (data terminal ready)
```
### Методы
| Метод | Описание |
|---|---|
| `is_transmit_empty() -> bool` | Проверка бита 5 Line Status Register |
| `send(data: u8)` | Ждёт `is_transmit_empty()`, затем пишет в порт |
| `write_str(s)` (fmt::Write) | Поcимвольная отправка |
### Функции
| Функция | Описание |
|---|---|
| `init_global()` | Инициализирует COM1, сохраняет в `SERIAL_PORT` |
| `write_global(args: Arguments)` | Пишет форматированную строку в serial |
| `outb(port, val)` | `unsafe`: инструкция `out` (write I/O port) |
| `inb(port) -> u8` | `unsafe`: инструкция `in` (read I/O port) |
---
## Логирование
**Файл**: `src/debug.rs`
### LogLevel
```rust
pub enum LogLevel {
Info, // зелёный: ANSI \x1b[32m, RGB 0x00FF00
Warn, // жёлтый: ANSI \x1b[33m, RGB 0xFFFF00
Error, // красный: ANSI \x1b[31m, RGB 0xFF0000
}
```
### Макросы
#### `log!(console, level, module, $($arg)*)`
Формат: `[ LOG ] <module> | <message>\n`
Двойной вывод:
- **Console**: цветной `[ LOG ]` (по LogLevel) + белый `<module> | <message>`
- **Serial**: ANSI-цветной `[ LOG ]` + `<module> | <message>` + ANSI reset
#### `info!(console, module, ...)`
Обёртка над `log!` с `LogLevel::Info`. Зелёный `[ LOG ]`.
#### `warn!(console, module, ...)`
Обёртка над `log!` с `LogLevel::Warn`. Жёлтый `[ LOG ]`.
#### `error!(console, module, ...)`
Обёртка над `log!` с `LogLevel::Error`. Красный `[ LOG ]`.
Все макросы экспортируются (`#[macro_export]`) и доступны из любого модуля.

85
kernel/docs/events.md Normal file
View File

@@ -0,0 +1,85 @@
# Очередь отзыва (RevocationQueue)
**Файл**: `src/events.rs`
Lock-free MPSC (Multi-Producer Single-Consumer) очередь для уведомления VMM об отзыве capability.
---
## RevocationQueue
```rust
pub struct RevocationQueue {
buffer: [AtomicU64; QUEUE_SIZE], // 1024 слота
_pad0: [u8; 64], // cache-line padding
head: AtomicUsize, // индекс потребителя (VMM)
_pad1: [u8; 64], // ещё padding
tail: AtomicUsize, // индекс производителя (CNode)
}
```
Cache-line padding (64 байта) предотвращает **false sharing** между head и tail на разных кэш-линиях.
### Константы
- `QUEUE_SIZE = 1024`
- `QUEUE_MASK = 1023`
### `new() -> Self`
Все слоты = 0, head = 0, tail = 0.
### `push(token_sig: u64) -> Result<(), &'static str>`
1. Читает `tail` (Relaxed)
2. CAS-цикл: пытается захватить следующий слот
3. Если буфер полон (`tail - head == QUEUE_SIZE`): ошибка
4. Записывает `token_sig` в `buffer[tail & QUEUE_MASK]` (Release ordering)
5. Обновляет `tail` (Release)
**Производители**: CNode::revoke() — при каждом отзыве пушит token_sig.
### `pop() -> Option<u64>`
1. Читает `tail` (Acquire) и `head` (Relaxed)
2. Если `head == tail`: буфер пуст, None
3. Читает `buffer[head & QUEUE_MASK]` (Acquire)
4. Обновляет `head` (Release)
5. Возвращает token
**Потребитель**: VMM — `AddressSpace::process_pending_revocations()`.
---
## Глобальный экземпляр
```rust
pub static MMU_REVOCATION_QUEUE: RevocationQueue = RevocationQueue::new();
```
---
## Полный цикл ревокации
```
CNode::revoke(slot_idx)
→ revoke_internal(slot_idx) // рекурсивное уничтожение потомков
→ slot = Empty
→ MMU_REVOCATION_QUEUE.push(token_sig)
... позже, при page fault ...
rust_page_fault_handler()
→ process_deferred_mmu_events()
→ KERNEL_SPACE.lock()
→ process_pending_revocations()
→ while let Some(token) = MMU_REVOCATION_QUEUE.pop()
→ revoke_by_token(token)
→ unmap matching VMAs (кроме PINNED)
→ TLB flush
→ KERNEL_SPACE.lock()
→ handle_fault(fault_addr, write) // обработка исходного fault
```
**Почему такая очерёдность?** Ревокации обрабатываются **до** захвата VMM для page fault, чтобы избежать deadlock'а: и revoke, и handle_fault хотят lock на `KERNEL_SPACE`.

142
kernel/docs/interrupts.md Normal file
View File

@@ -0,0 +1,142 @@
# Прерывания
---
## IDT (Interrupt Descriptor Table)
**Файл**: `src/cpu/idt.rs`
### IdtEntry — `#[repr(C, packed)]`
Структура записи IDT (x86-64):
```rust
struct IdtEntry {
offset_low: u16, // адрес обработчика [15:0]
selector: u16, // селектор кода (0x28 — kernel CS)
ist: u8, // Interrupt Stack Table index
type_attr: u8, // тип шлюза, DPL, Present
offset_mid: u16, // адрес обработчика [31:16]
offset_high: u32, // адрес обработчика [63:32]
ignore: u32, // резерв (0)
}
```
- `new()` — все поля нули
- `set_handler(handler: u64, selector: u16, flags: u8)` — устанавливает адрес и флаги. Принудительно ставит Present (0x80) в flags.
### IdtPtr — `#[repr(C, packed)]`
```rust
struct IdtPtr {
limit: u16, // размер IDT - 1
base: u64, // адрес IDT
}
```
Загружается инструкцией `lidt`.
### InterruptDescriptorTable
```rust
struct InterruptDescriptorTable {
entries: [IdtEntry; 256], // 256 векторов
}
```
- `set_handler(vector, handler: u64)` — устанавливает обработчик на вектор. Селектор: `0x28` (kernel code), flags: `0x8E` (32-bit interrupt gate, ring 0, present).
- `load()``unsafe fn`: вызывает `lidt` с `&self`.
---
## Обработчики прерываний
**Файл**: `src/cpu/interrupts.rs`
### Глобальная IDT
```rust
pub static mut IDT: InterruptDescriptorTable;
```
### Константы
`TLB_SHOOTDOWN_VECTOR: u8 = 0xFD`
### Ассемблерные заглушки (global_asm!)
#### `page_fault_stub`
```asm
save all registers (push rax, rcx, rdx, rbx, rbp, rsi, rdi, r8-r15)
mov rdi, [rsp + 15*8] ; error code из стека → 1-й аргумент
call rust_page_fault_handler
restore all registers
add rsp, 8 ; убрать error code
iretq
```
#### `tlb_shootdown_stub`
```asm
save all registers
call rust_tlb_shootdown_handler
restore all registers
iretq
```
### `init_idt()`
- Устанавливает вектор 14 → `page_fault_stub`
- Устанавливает вектор `TLB_SHOOTDOWN_VECTOR``tlb_shootdown_stub`
- `IDT.load()`
### `process_deferred_mmu_events()`
Лочит `KERNEL_SPACE`, вызывает `process_pending_revocations()` — дренаж очереди отзыва.
### `rust_page_fault_handler(error_code: u64)`
`#[unsafe(no_mangle)] pub extern "C" fn`
1. Читает CR2 (адрес fault)
2. Из error_code: бит 1 = write, бит 0 = present
3. **Сначала** `process_deferred_mmu_events()` — обработка ревокаций до захвата VMM (deadlock prevention)
4. Лочит `KERNEL_SPACE`, вызывает `handle_fault(virt_addr, write)`
5. При неудаче: паника с деталями (address, read/write, present, error_code)
6. При успехе: возврат — инструкция будет перезапущена
### `rust_tlb_shootdown_handler()`
1. `handle_tlb_shootdown_ipi()` — локальный сброс TLB
2. `send_eoi()` — подтверждение LAPIC
---
## Local APIC
**Файл**: `src/cpu/lapic.rs`
Драйвер Local APIC (Advanced Programmable Interrupt Controller).
### Константы
```rust
LAPIC_DEFAULT_BASE = 0xFEE0_0000 // стандартный MMIO адрес LAPIC
LAPIC_EOI = 0x0B0 // End-Of-Interrupt регистр
LAPIC_ICR_LOW = 0x300 // Interrupt Command Register (low)
```
### `LAPIC_VIRT_BASE: AtomicU64`
Кэшированный виртуальный адрес LAPIC MMIO (HHDM + default base).
### Функции
| Функция | Описание |
|---|---|
| `init(hhdm_offset)` | Вычисляет и сохраняет `LAPIC_VIRT_BASE = 0xFEE0_0000 + hhdm_offset` |
| `current_core_id() -> u32` | CPUID leaf 1, EBX[31:24] — Initial APIC ID |
| `write_lapic_reg(offset, value)` | Volatile write u32 в LAPIC MMIO |
| `send_eoi()` | `write_lapic_reg(LAPIC_EOI, 0)` — подтверждение прерывания |
| `broadcast_ipi_exclude_self(vector)` | IPI всем остальным ядрам через ICR (destination shorthand = 10b = all except self) |

98
kernel/docs/main.md Normal file
View File

@@ -0,0 +1,98 @@
# Точка входа: `src/main.rs`
Точка входа ядра (`kmain`). Инициализирует оборудование, подсистемы памяти, запускает тесты capability и PMActor.
## Атрибуты крейта
```rust
#![no_std] // без стандартной библиотеки
#![no_main] // без точки входа из libc
extern crate alloc; // аллокатор кучи
```
## Константы
- `KERNEL_FONT: &[u8]` — встроенный PSF2 шрифт 8×16 на 512 глифов (включён через `include_bytes!("font.psf")`)
## Limine Boot Protocol Request
Статические переменные в секции `.requests` (через `#[unsafe(link_section = ".requests")]`):
| Переменная | Тип | Назначение |
|---|---|---|
| `MEMORY_MAP_REQUEST` | `MemoryMapRequest` | Карта физической памяти |
| `HHDM_REQUEST` | `HhdmRequest` | Higher Half Direct Map offset |
| `KERNEL_ADDR_REQUEST` | `ExecutableAddressRequest` | Базовые адреса ядра (physical + virtual) |
| `BASE_REVISION` | `BaseRevision` | Проверка версии Limine |
| `FRAMEBUFFER_REQUEST` | `FramebufferRequest` | Линейный фреймбуфер |
| `_START_MARKER` | `RequestsStartMarker` | Маркер начала секции requests |
| `_END_MARKER` | `RequestsEndMarker` | Маркер конца секции requests |
---
## `FramebufferDisplay<'a>`
Низкоуровневый рендерер пикселей, обёртка над `limine::framebuffer::Framebuffer`.
**impl `DrawTarget` (embedded_graphics):**
- `Color = Rgb888`, `Error = Infallible`
- `draw_iter(pixels)` — запись пикселей в память фреймбуфера с проверкой границ
**impl `OriginDimensions`:**
- `size()` — размеры фреймбуфера
---
## `Console<'a>` (встроенный, НЕ используется)
Обёртка над FramebufferDisplay. **В `kmain` используется `tty::Console`**, этот — мёртвый код.
Поля: `display`, `x`, `y`, `current_color`.
Методы: `new()`, `set_color()`, `clear()`, `scroll()`, `write_str()`.
---
## `kmain()` — точка входа
`unsafe extern "C" fn() -> !`
1. **Проверка `BASE_REVISION.is_supported()`** — убеждается, что загрузчик поддерживает нужную версию Limine
2. **Получение ответов Limine**: фреймбуфер, карта памяти, HHDM offset, адреса ядра
3. **Инициализация `tty::Console`** — создаёт консоль на первом фреймбуфере, чистит экран
4. **Инициализация Serial**`debug::serial::init_global()` для отладки через COM1
5. **Вывод "BOOT"** через `info!` макрос
6. **Инициализация BitmapPMM** — из карты памяти и HHDM смещения
7. **Инициализация LAPIC**`cpu::lapic::init(hhdm_offset)` для MMIO доступа к Local APIC
8. **Создание P4 (PML4)** — выделяет фрейм, зануляет его
9. **Картирование HHDM** — все non-reserved entry памяти маппятся identity + HHDM
10. **Картирование .text секции ядра** — по виртуальному базовому адресу
11. **Активация страничных таблиц**`p4.activate(p4_phys)` — запись CR3
12. **Выделение кучи 8 MiB** — постранично маппится в `0xFFFF_9000_0000_0000`
13. **Инициализация SlabAllocator**`ALLOCATOR.init()` с диапазоном кучи
14. **`init_cpu_features()`** — обнаружение INVPCID инструкции
15. **Регистрация адресного пространства ядра**`vmm::init_kernel_space()`
16. **Инициализация IDT** — регистрация обработчиков page fault и TLB shootdown
17. **Инициализация PMRouter** — 65536 каналов маршрутизации
18. **Включение прерываний**`sti` инструкция
19. **Тест capability системы**: создание CNode, insert/mint/revoke
20. **PMActor + Buddy тест** (строки 257-359): создание актора, аллокация, OOM, carve, free+coalescing
21. **Вывод ASCII-арт логотипа**
22. **Бесконечный HLT**`hcf()`
---
## `hcf()`
```rust
fn hcf() -> ! {
loop { x86_64::instructions::hlt(); }
}
```
Бесконечный цикл HLT — снижает энергопотребление, процессор ждёт следующего прерывания.
---
## `rust_panic(info: &PanicInfo) -> !`
Обработчик паники: пишет информацию в serial, затем вызывает `hcf()`.

View File

@@ -0,0 +1,243 @@
# Управление физической памятью
## Содержание
1. [Адресация (PhysAddr/VirtAddr)](#physaddr--virtaddr)
2. [BitmapPMM — менеджер физических фреймов](#bitmappmm)
3. [BuddyAllocator — блочный аллокатор](#buddyallocator)
4. [Page Tables — страничные таблицы x86-64](#page-tables)
---
## PhysAddr / VirtAddr
**Файл**: `src/mem/address.rs`
Типы-обёртки над `u64` с HHDM-трансляцией.
### `PhysAddr(pub u64)` — `#[repr(transparent)]`
| Метод | Описание |
|---|---|
| `to_virt(hhdm_offset) -> VirtAddr` | `PhysAddr + hhdm_offset` — вход в HHDM |
| `is_aligned() -> bool` | Проверка 4 KiB выравнивания |
| `align_down() -> Self` | Выравнивание вниз до границы страницы |
| `align_up() -> Self` | Выравнивание вверх до границы страницы |
### `VirtAddr(pub u64)` — `#[repr(transparent)]`
| Метод | Описание |
|---|---|
| `to_phys(hhdm_offset) -> Option<PhysAddr>` | `VirtAddr - hhdm_offset`, возвращает None если адрес ниже HHDM |
| `as_ptr::<T>() -> *const T` | Приведение к константному указателю |
| `as_mut_ptr::<T>() -> *mut T` | Приведение к mutable указателю |
---
## BitmapPMM
**Файл**: `src/mem/pmm.rs`
Первичный менеджер физической памяти через битовую карту + счётчики ссылок.
### Структура
```rust
pub struct BitmapPMM {
bitmap: &'static mut [u8], // один бит на страницу (1 = занята)
ref_counts: &'static mut [u16], // счётчики ссылок для COW/shared
total_pages: usize, // всего физических страниц в системе
used_pages: usize, // количество выделенных страниц
last_byte: usize, // подсказка для следующего сканирования
}
```
Глобальная: `pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None)`.
### `init(mmap, hhdm_offset)`
Полный поток инициализации:
1. Вычисляет макс. физический адрес из карты памяти Limine
2. Рассчитывает размер bitmap и ref_counts
3. Находит USABLE регион для хранения метаданных
4. Зануляет bitmap, все биты = 1 (все заняты), ref_counts = 1
5. Проходит по карте памяти: все USABLE → `free_frame()`
6. **Лочит** страницы метаданных (чтобы не выдать повторно)
7. **Лочит** страницу 0 (зарезервирована)
8. Сохраняет в глобальную `PMM`
### `free_frame(addr)`
- Декрементит `ref_count`
- Если `ref_count == 0`: очищает бит, декрементит `used_pages`, обновляет `last_byte`
### `lock_frame(addr)`
- Если страница свободна: бит = 1, `ref_count = 1`, `used_pages++`
- Если страница занята и `ref_count` был 0: `ref_count = 1` (восстановление)
### `inc_ref_frame(addr)`
- Инкрементит `ref_count` (с насыщением) — для COW
### `alloc_frame() -> Option<PhysAddr>`
**Двухпроходный сканер:**
1. Проход 1: от `last_byte` до конца bitmap
2. Проход 2: от 0 до `last_byte`
Сканирует побайтово в поисках байта с нулевым битом (`!0xFF`), внутри байта через `trailing_ones`. Устанавливает бит, `ref_count = 1`, `used_pages++`, обновляет `last_byte`.
### `alloc_contiguous(count) -> Option<PhysAddr>`
Линейный поиск `count` последовательных свободных страниц.
### Функции-обёртки
- `alloc_frame()`, `alloc_contiguous(count)`, `free_frame(addr)`, `inc_ref_frame(addr)` — захватывают `PMM.lock()`
- `get_stats() -> (usize, usize)` — возвращает `(used, total)`
---
## BuddyAllocator
**Файл**: `src/mem/buddy.rs`
Power-of-two блочный аллокатор для per-actor управления физической памятью.
### Константы
- `MAX_ORDER = 11` — макс. порядок (2^11 = 2048 страниц = 8 MiB)
### `order_for(page_count) -> usize`
Вычисляет минимальное k такое, что `2^k >= page_count`.
```rust
order_for(1) = 0 // 2^0 = 1
order_for(3) = 2 // 2^2 = 4 >= 3
order_for(2048) = 11 // 2^11 = 2048
```
### Структура
```rust
pub struct BuddyAllocator {
free_lists: [Vec<usize>; MAX_ORDER + 1], // списки блоков по порядкам
total_pages: usize, // всего страниц
free_pages: usize, // свободно страниц
}
```
Инвариант: `free_pages == Σ 2^k * len(free_lists[k])`.
### `new(total_pages) -> Self`
**Жадное разложение** диапазона на блоки максимального размера:
- Итерация по диапазону
- На каждой позиции вычисляет макс. порядок (ограничен alignment через `trailing_zeros` и остатком)
- Добавляет блок в соответствующий `free_list`
Пример: 6 страниц → блок 4 (order 2) + блок 2 (order 1).
### `alloc(order) -> Option<usize>`
1. Находит наименьший `k >= order` с непустым `free_list[k]`
2. Pop с конца (O(1))
3. **Разбивает** до нужного порядка: при каждом разбиении buddy кладётся в список порядком ниже
4. Возвращает индекс
### `alloc_pages(page_count) -> Option<(usize, usize)>`
Округляет page_count до степени двойки, вызывает `alloc()`. Возвращает `(index, order)`.
### `free(block_idx, order)`
1. Добавляет блок в `free_list[order]`
2. **Coalescing** на каждом уровне:
- `buddy_idx = block_idx ^ (1 << order)` (XOR)
- Ищет buddy в `free_list[order]` (линейно, `swap_remove`)
- Если найден: удаляет его, объединяет, переходит на уровень выше
- Если не найден или вне диапазона: стоп
### Интроспекция
- `free_pages()`, `total_pages()`, `is_exhausted()`, `stats()` (per-order counts)
---
## Page Tables
**Файл**: `src/mem/paging.rs`
4-уровневая страничная таблица x86-64: P4 → P3 → P2 → P1 (512 entry × 8 байт = 4 KiB каждая).
### `PageTableFlags` (bitflags)
| Флаг | Бит | Описание |
|---|---|---|
| `PRESENT` | 0 | Страница в памяти |
| `WRITABLE` | 1 | Разрешена запись |
| `USER` | 2 | Доступ из ring 3 |
| `WRITE_THROUGH` | 3 | Write-through кэширование |
| `NO_CACHE` | 4 | Отключение кэширования |
| `ACCESSED` | 5 | Было обращение |
| `DIRTY` | 6 | Была запись |
| `HUGE_PAGE` | 7 | 2 MiB (P2) или 1 GiB (P3) |
| `GLOBAL` | 8 | Не сбрасывается при перезагрузке CR3 |
| `COW` | 9 | **Кастомный** — Copy-on-Write |
| `NO_EXECUTE` | 63 | Запрет исполнения (NX bit) |
### `PageTable` — `#[repr(C, align(4096))]`
Поле: `entries: [u64; 512]`
### Методы
#### `map_page(virt, phys, flags, hhdm)`
1. Walk/create P4 → P3 → P2 → P1 (создаёт отсутствующие)
2. Записывает PTE = `phys | flags`
3. `invlpg`
#### `get_or_create_next_table(index, hhdm, create) -> Option<&mut PageTable>`
- Читает PTE, проверяет PRESENT
- Если есть: `&mut *(hhdm + phys_addr)` (HHDM dereference)
- Если нет и `create == true`: `pmm_alloc()`, зануляет, ставит entry с `PRESENT|WRITABLE|USER`, возвращает
- Если нет и `create == false`: None
#### `walk_to_p1_mut(virt, hhdm, create) -> Option<&mut PageTable>`
Из virtual address вычисляет индексы P4[47:39], P3[38:30], P2[29:21], P1[20:12], проходит цепочку.
#### `unmap_page(virt, hhdm)`
Walk (без create), зануляет PTE. `invlpg`. Идемпотентен.
#### `translate(virt, hhdm) -> Option<PhysAddr>`
Walk 4 уровней. Поддерживает huge pages (1 GiB на P3, 2 MiB на P2). Возвращает PhysAddr + page offset.
#### `map_region(virt, phys, size, flags, hhdm)`
Цикл по 4 KiB страницам, каждая → `map_page()`.
#### `get_flags(virt, hhdm) -> Option<PageTableFlags>`
Walk, возвращает флаги PTE (с поддержкой huge pages).
#### `update_flags(virt, flags, hhdm) -> Result<(), ()>`
Walk, обновляет флаги, сохраняя физический адрес. `invlpg`.
#### `activate(phys_addr)`
`unsafe`: запись CR3 = phys_addr → полный сброс TLB.
### `pmm_alloc() -> Option<PhysAddr>`
Обёртка над `PMM.lock().unwrap().alloc_frame()` — разрывает циклическую зависимость между paging и pmm.

173
kernel/docs/pmactor.md Normal file
View File

@@ -0,0 +1,173 @@
# Акторы физической памяти (PMActor) и маршрутизатор (PM Router)
---
## Формат сообщений
Вся коммуникация использует упаковку в `u64`:
```
[8b opcode | 16b channel_id | 20b arg1 | 20b arg2]
```
Константы: `OPCODE_SHIFT = 56`, `CHAN_SHIFT = 40`, `ARG1_SHIFT = 20`
Маски: `CHAN_MASK = 0x0000_FFFF`, `ARG_MASK = 0x000F_FFFF`
---
## PMActor
**Файл**: `src/mem/pm_manages.rs`
Распределённый актор физической памяти. Каждый актор владеет фиксрованным диапазоном и управляет им через BuddyAllocator.
### PMRequest
```rust
pub enum PMRequest {
Allocate { size_pages: usize, token_sig: u32, channel_id: u16 },
Free { local_frame_idx: usize, order: usize },
Carve { offset_pages: usize, size_pages: usize, channel_id: u16 },
None, // sentinel (не может быть отправлен)
}
```
Методы: `pack() -> u64`, `unpack(val: u64) -> Self`.
### PMResult
```rust
pub enum PMResult {
Allocated { cap: Capability, order: usize },
OutOfMemory { size_pages: usize },
Carved { cap: Capability },
Freed { pages_returned: usize },
}
```
### PMResponse
```rust
pub struct PMResponse {
pub channel_id: u16,
pub result: PMResult,
}
```
### PMActorQueue — Lock-free MPSC
Кольцевой буфер на 1024 слота (`AtomicU64`) с cache-line padding.
- `send(req)` — CAS loop на `tail` (multi-producer). Отклоняет None.
- `pop()` — single-consumer. `Acquire`/`Release` ordering.
### PMActor
```rust
pub struct PMActor {
actor_id: u64,
root_untyped: Capability,
managed_range: (PhysAddr, PhysAddr), // [start, end)
queue: PMActorQueue,
buddy: BuddyAllocator,
}
```
#### `new(actor_id, root_cap, start, size_bytes) -> Self`
Конструктор.
#### `submit_request(req) -> Result<(), &str>`
Thread-safe: пушит в `queue`.
#### `process_messages(&mut self) -> Vec<PMResponse>`
Дренирует очередь, обрабатывает каждый запрос через приватные методы.
**Только из контекста актора** (`&mut self` гарантирует single consumer).
#### Приватные обработчики
**`handle_allocate(size_pages, token_sig, channel_id) -> Option<PMResponse>`**
1. `buddy.alloc_pages(size_pages)``(local_idx, order)`
2. Строит сильную дочернюю Capability с уникальным `token_sig`
3. Возвращает `Allocated { cap, order }`
4. При неудаче: `OutOfMemory` (планируется ballooning)
**`handle_free(local_frame_idx, order) -> Option<PMResponse>`**
1. Валидация границ
2. `buddy.free(local_frame_idx, order)`
3. Возвращает None (маршрутизация не нужна)
**`handle_carve(offset_pages, size_pages, channel_id) -> Option<PMResponse>`**
1. Валидация диапазона
2. Возвращает `Carved { cap }`
3. Не взаимодействует с buddy
---
## PMRouter
**Файл**: `src/mem/pm_router.rs`
Глобальная ткань маршрутизации: 65536 lock-free каналов.
### Константы
- `CHANNEL_COUNT = 65536`
- Состояния: `STATE_FREE = 0`, `STATE_PENDING = 1`, `STATE_READY = 2`
### Channel — `#[repr(align(64))]`
```rust
struct Channel {
state: AtomicU8,
next_free: AtomicU16,
result: UnsafeCell<Option<PMResult>>,
}
```
### PMRouter
```rust
pub struct PMRouter {
channels: Box<[Channel]>, // 65536 каналов
free_head: AtomicU16, // голова free list (0 = sentinel)
}
```
### GlobalRouter
```rust
static ROUTER: GlobalRouter; // синглтон
```
### `init()`
1. Создаёт Vec из 65536 каналов, соединённых в linked list (каждый `next_free` указывает на следующий)
2. Преобразует в `Box<[Channel]>`
3. Сохраняет в `ROUTER.inner`, устанавливает `is_ready`
### `alloc_channel() -> Option<u16>`
CAS-цикл на `free_head`: извлекает голову списка, устанавливает `state = PENDING`.
### `route_responses(responses: Vec<PMResponse>)`
Для каждого PMResponse с ненулевым `channel_id`: сохраняет `result` в канале, устанавливает `state = READY`.
### `wait_for_response(id: u16) -> PMResult`
Spin-wait до `state == READY`. Забирает результат, пушит канал обратно в freelist, возвращает результат.
### `request_and_wait(actor, req_builder) -> PMResult`
Convenience: аллоцирует канал, строит запрос с channel_id, отправляет актору, ждёт ответ. (process_messages должен быть вызван внешне.)
### `dispatch(responses: Vec<PMResponse>)`
Точка входа для маршрутизации ответов через `PMActor::route_responses()` (вызов на глобальном роутере).

212
kernel/docs/vmm.md Normal file
View File

@@ -0,0 +1,212 @@
# Виртуальная память (VMM)
**Файл**: `src/mem/vmm.rs`
Управление адресными пространствами: PML4 + VMA списки, lazy demand paging, copy-on-write, shared mappings, ASID/PCID, TLB shootdown IPI.
---
## Глобальные статики
| Переменная | Тип | Назначение |
|---|---|---|
| `ACTIVE_CPUS_MASK` | `AtomicU64` | Битовая маска активных CPU (core 0 по умолчанию) |
| `SHOOTDOWN_LOCK` | `Locked<()>` | Блокировка TLB shootdown IPI |
| `SHOOTDOWN_ASID` | `AtomicU16` | ASID, подлежащий сбросу |
| `SHOOTDOWN_ACK` | `AtomicU64` | Битовая маска подтверждений удалённых ядер |
| `INVPCID_SUPPORTED` | `AtomicBool` | Поддержка инструкции INVPCID |
| `ASID_ALLOC` | `Locked<AsidAllocator>` | Аллокатор ASID/PCID |
| `KERNEL_SPACE` | `Locked<Option<AddressSpace>>` | Адресное пространство ядра |
---
## Типы ошибок: `VmError`
- `OutOfMemory` — нет свободной физической памяти
- `RegionOverlap` — VMA перекрывается с существующей
- `RegionNotFound` — VMA не найдена
- `InvalidAlignment` — неверное выравнивание
- `InvalidRange` — некорректный диапазон
- `PermissionDenied` — нет прав доступа
- `UnexpectedFault` — page fault в не-lazy, не-COW регионе
- `NonCanonical` — неканонический адрес
- `AsidExhausted` — кончились ASID
---
## `VmaBacking` — типы подложки
```rust
pub enum VmaBacking {
Anonymous(Vec<Option<PhysAddr>>), // per-page tracking: None = ещё не выделена
Physical(PhysAddr), // фиксированный физический диапазон (MMIO, DMA)
Shared { owner_cap: u64, phys_base: PhysAddr }, // zero-copy borrow
}
```
---
## `VmaFlags` (bitflags)
| Флаг | Бит | Описание |
|---|---|---|
| `READ` | 0 | Чтение |
| `WRITE` | 1 | Запись |
| `EXEC` | 2 | Исполнение |
| `USER` | 3 | Доступ из userspace |
| `LAZY` | 4 | Demand paging (фрейм при page fault) |
| `SHARED` | 5 | Разделяемая память |
| `PINNED` | 6 | Защита от revoke |
| `NOCACHE` | 7 | Отключение кэширования |
| `MMIO` | 8 | Memory-mapped I/O |
| `COW` | 9 | Copy-on-Write |
**`to_page_flags() -> PageTableFlags`**: преобразует VmaFlags в аппаратные флаги. PRESENT всегда включён, NO_EXECUTE если не EXEC.
---
## `AsidAllocator`
Битовая карта на 4096 ASID/PCID (128 × u32).
- ASID 0 (ядро) и ASID 4095 (зарезервирован) — заняты постоянно
- `alloc() -> Option<u16>` — двухпроходный с пропуском целых слов (амортизированное O(1))
- `free(asid)`сброс бита
---
## `VmaRegion`
```rust
pub struct VmaRegion {
virt_start: VirtAddr,
virt_end: VirtAddr,
flags: VmaFlags,
cap_token: u64,
backing: VmaBacking,
}
```
Методы: `contains(addr)`, `size()`, `pages()`.
---
## `AddressSpace`
```rust
pub struct AddressSpace {
asid: u16,
pml4_phys: PhysAddr,
regions: Vec<VmaRegion>, // sorted by virt_start, non-overlapping
hhdm: u64,
}
```
### Создание
| Метод | Описание |
|---|---|
| `new(hhdm)` | Выделяет PML4 (занулённый), аллоцирует ASID |
| `from_active(pml4_phys, asid, hhdm)` | Обёртка для существующей PML4 (ядро, ASID=0) |
### `map_region(virt, phys, size, flags, cap_token) -> Result<VirtAddr>`
1. Валидация выравнивания (4 KiB) и проверка перекрытий (`check_overlap`)
2. Создание `VmaBacking`:
- **LAZY** → `Anonymous(vec![None; page_count])`
- **Physical** (если `phys` не None и нет LAZY) → `Physical(phys)`
- **Eager anonymous**: цикл по страницам, `pmm_alloc() + zero + map`
3. Вставка в `regions` с сохранением сортировки (`insert_sorted`)
### `map_shared(virt, phys_base, page_count, flags, owner_cap)`
Eager маппинг физического диапазона без владения фреймами (не освобождаются при unmap).
### `handle_fault(fault_addr, write) -> Result<(), VmError>`
Обработка page fault:
1. `find_idx(fault_addr)` — бинарный поиск региона
2. Проверка прав: write fault в WRITE-регионе
3. **COW**: если флаг COW и пишем:
- Выделяет новый фрейм
- Копирует содержимое старого
- Обновляет PTE: убирает COW, добавляет WRITABLE
4. **LAZY**: если флаг LAZY:
- Выделяет фрейм, зануляет, маппит
- Обновляет `Vec<Option<PhysAddr>>`
5. Иначе: `Err(UnexpectedFault)`
### `unmap_region(virt) -> Result<(), VmError>`
1. `find_idx(virt)` → регион
2. `do_unmap(region)`: анмаппит PTE, освобождает фреймы если Anonymous
3. Удаляет регион из списка
4. `tlb_flush_asid(self.asid)`
### `revoke_by_token(cap_token)`
Анмаппит все VMA с совпадающим `cap_token` (кроме PINNED). Сливает TLB.
### `process_pending_revocations(&mut self)`
1. Дренирует `MMU_REVOCATION_QUEUE` (pop в цикле)
2. Для каждого токена → `revoke_by_token(token)`
3. TLB flush если была хотя бы одна ревокация
### `clone_for_fork(child_cap_token) -> Result<Self, VmError>`
Создание дочернего адресного пространства (fork):
- **Physical/Shared**: маппинг напрямую
- **Anonymous**: **COW setup**:
- Убирает WRITABLE из PTE родителя
- Устанавливает COW флаг в обоих PTEs
- `inc_ref_frame()` для всех фреймов
### `translate(virt) -> Option<PhysAddr>`
Аппаратный walk страничной таблицы (через `PageTable::translate`).
### `activate()`
`unsafe`: запись CR3 = `pml4_phys | asid | (1<<63)`. Бит 63 = NOFLUSH — сохраняет PCID-тегированные записи TLB.
### `Drop`
1. Анмаппит все регионы
2. Освобождает PML4 фрейм через `free_frame`
3. Возвращает ASID в пул
---
## TLB Management
### `local_tlb_flush_asid(asid)`
- INVPCID type 1 если поддерживается (single-context flush)
- Иначе `tlb_flush_all()`
### `tlb_flush_asid(asid)`
1. `local_tlb_flush_asid(asid)` — локальный сброс
2. Устанавливает глобальные статики: `SHOOTDOWN_ASID = asid`, `SHOOTDOWN_ACK = 0`
3. `broadcast_ipi_exclude_self(TLB_SHOOTDOWN_VECTOR)` — IPI всем остальным ядрам
4. Spin-loop: `while SHOOTDOWN_ACK != ACTIVE_CPUS_MASK { hlt(); }`
### `tlb_flush_all()`
Читает CR3 → перезаписывает без бита 63 → полный сброс всех non-global entries.
### `handle_tlb_shootdown_ipi()`
1. `local_tlb_flush_asid(SHOOTDOWN_ASID.load())`
2. Устанавливает свой бит в `SHOOTDOWN_ACK` (через `fetch_or`)
### `init_cpu_features()`
CPUID leaf 7, проверка EBX[10] (INVPCID). Сохраняет в `INVPCID_SUPPORTED`.
### `init_kernel_space(pml4_phys, hhdm)`
Регистрирует PML4 ядра как `KERNEL_SPACE` с ASID 0.

View File

@@ -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\

View File

@@ -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)*)
);
));
}};
}

View File

@@ -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<Option<SerialPort>> = Locked::new(None);
pub fn init_global() {
let mut guard = SERIAL_PORT.lock();
*guard = Some(unsafe { SerialPort::init() });
}
pub fn write_global(args: core::fmt::Arguments) {
let mut guard = SERIAL_PORT.lock();
if let Some(ref mut sp) = *guard {
let _ = sp.write_fmt(args);
}
}
unsafe fn outb(port: u16, val: u8) {
unsafe {
asm!("out dx, al", in("dx") port, in("al") val, options(nomem, nostack, preserves_flags));

View File

@@ -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::<PageTable>() };
info!(console, "BOOT", "alloc_frame ok: phys=0x{:x}", p4_phys.0);
let virt = p4_phys.to_virt(hhdm_offset);
info!(console, "BOOT", "virt=0x{:x}", virt.0);
let p4 = unsafe { &mut *virt.as_mut_ptr::<PageTable>() };
info!(console, "BOOT", "zeroing page...");
unsafe { core::ptr::write_bytes(p4 as *mut _ as *mut u8, 0, 4096); }
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();
}

View File

@@ -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<SlabAllocator> {
}
}
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);
}
}
}
}

View File

@@ -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<PMResponse> {
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<PMResponse> {
// Validate offset + size within range.
let range_pages = self.buddy.total_pages();
if offset_pages >= range_pages
|| size_pages == 0

View File

@@ -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()
}
}

View File

@@ -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));

View File

@@ -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<Option<PhysAddr>>),
/// 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::<u8>(),
@@ -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 063. 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