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.