feat: docs & ARCH 2.2, 2.3, 2.4

This commit is contained in:
Faynot
2026-07-07 16:40:41 +03:00
parent c092a81331
commit 1bfde637d0
50 changed files with 5113 additions and 1823 deletions

View File

@@ -1,19 +0,0 @@
# Документация ядра 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 |

View File

@@ -1,105 +0,0 @@
# Аллокатор кучи
**Файл**: `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

@@ -1,56 +0,0 @@
# Архитектура ядра 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

View File

@@ -0,0 +1,152 @@
# Процесс загрузки: концептуальная модель
## Цепочка загрузки
```
Питание включено
CPU reset vector (0xFFFFFFF0)
UEFI firmware / BIOS
Limine bootloader ─────────────────────────────┐
│ │
│ 1. Переводит CPU в 64-bit long mode │
│ 2. Настраивает page tables (identity map) │
│ 3. Загружает ядро по физическому адресу │
│ 4. Настраивает HHDM │
│ 5. Заполняет Limine requests │
│ 6. Передаёт управление на kmain │
│ │
▼ │
kmain() (точка входа) │
│ │
... инициализация ... │
│ │
▼ │
HCF (останов CPU) ─────────────────────────────┘
```
## Limine Boot Protocol
Elyz использует [Limine](https://github.com/limine-bootloader/limine) —
современный bootloader с открытым исходным кодом.
Статические `Limine requests` сообщают bootloader'у, что нужно ядру:
| Request | Назначение |
|---------|------------|
| `FramebufferRequest` | Получить framebuffer для графического вывода |
| `MemoryMapRequest` | Получить карту физической памяти |
| `HhdmRequest` | Получить HHDM offset |
| `ExecutableAddressRequest` | Получить физический/виртуальный адрес ядра |
| `BaseRevision` | Проверить совместимость с Limine |
### Размещение в секциях
Requests размещаются между специальными маркерами в секции `.data`:
```rust
#[used]
#[unsafe(link_section = ".requests_start_marker")]
static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
// ... все requests ...
#[used]
#[unsafe(link_section = ".requests_end_marker")]
static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new();
```
Линкер скрипт сохраняет эти секции:
```ld
.data : {
*(.data .data.*)
KEEP(*(.requests_start_marker))
KEEP(*(.requests))
KEEP(*(.requests_end_marker))
} :data
```
## Точка входа — kmain
Линкер скрипт: `ENTRY(kmain)`.
```rust
#[unsafe(no_mangle)]
unsafe extern "C" fn kmain() -> ! { ... }
```
Атрибуты:
- `no_mangle` — сохраняет имя `kmain` (линкер ищет именно его).
- `extern "C"` — C ABI (Linux x86-64 calling convention: RDI, RSI, ...).
- `unsafe` — на этапе инициализации все операции потенциально опасны.
- `-> !` — kmain никогда не возвращается (HALT).
## Build system
### GNUmakefile
```makefile
KARCH ?= x86_64
RUST_TARGET ?= $(KARCH)-unknown-none
RUST_PROFILE ?= dev
all:
RUSTFLAGS="-C relocation-model=static" \
cargo build --target $(RUST_TARGET) --profile $(RUST_PROFILE)
cp target/$(RUST_TARGET)/$(RUST_PROFILE_SUBDIR)/kernel .
```
- `relocation-model=static`: ядро не использует динамическую перелокацию.
- `--target x86_64-unknown-none`: bare-metal target (без ОС).
- Результат копируется в `kernel/` (корень).
### build.rs — linker script
```rust
fn main() {
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
println!("cargo:rustc-link-arg=-Tlinker-{arch}.ld");
println!("cargo:rerun-if-changed=linker-{arch}.ld");
}
```
Подставляет правильный linker script для архитектуры.
### rust-toolchain.toml
```toml
[toolchain]
channel = "nightly"
targets = ["x86_64-unknown-none"]
```
Требуется nightly Rust из-за:
- `#![no_std]`, `#![no_main]`.
- `core::arch::global_asm!`, `core::arch::asm!`.
- `const { ... }` в инициализации констант.
## Память: расположение после загрузки
```
Физическая память:
┌───────────────────────┐ 0x0
│ Reserved │
├───────────────────────┤
│ Usable (free) │ ← входит в mmap entries
├───────────────────────┤
│ Kernel image │ ← загружен Limine
├───────────────────────┤
│ PMM metadata │ ← bitmap + ref_counts + L1
├───────────────────────┤
│ ... (usable) │
└───────────────────────┘ max_addr
Виртуальная память (Higher Half):
0xFFFF_8000_0000_0000 ─── HHDM (вся физическая память 1:1)
0xFFFF_9000_0000_0000 ─── Kernel heap (8 MiB)
```

151
kernel/docs/boot/linker.md Normal file
View File

@@ -0,0 +1,151 @@
# Линкер-скрипты: архитектурные детали
## Назначение
Линкер-скрипты управляют расположением секций ELF-образа ядра.
Для каждой архитектуры — свой скрипт, но все они следуют одной схеме.
## Общая структура
```
OUTPUT_FORMAT(...) ← формат ELF (зависит от архитектуры)
ENTRY(kmain) ← точка входа
PHDRS ← сегменты (program headers)
{
text PT_LOAD;
rodata PT_LOAD;
data PT_LOAD;
}
SECTIONS ← расположение секций
{
. = 0xffffffff80000000; ← база higher half
.text : { *(.text .text.*) } :text
. = ALIGN(MAXPAGESIZE);
.rodata : { *(.rodata .rodata.*) } :rodata
. = ALIGN(MAXPAGESIZE);
.data : {
*(.data .data.*)
KEEP(*(.requests_start_marker))
KEEP(*(.requests))
KEEP(*(.requests_end_marker))
} :data
.bss : { *(.bss .bss.*) *(COMMON) } :data
/DISCARD/ : { *(.eh_frame*) *(.note .note.*) }
}
```
## Детали
### OUTPUT_FORMAT
| Архитектура | Формат |
|-------------|--------|
| x86_64 | `elf64-x86-64` |
| aarch64 | `elf64-littleaarch64` |
| riscv64 | `elf64-littleriscv` |
| loongarch64 | `elf64-loongarch` |
### Базовый адрес: 0xFFFFFFFF80000000
Ядро размещается в **higher half** — верхней 2 GiB виртуального
адресного пространства. Это стандартная практика для x86-64:
```
0x0000_0000_0000_00000 ─── user space (не используется ядром)
0xFFFF_8000_0000_00000 ─── kernel space (higher half)
0xFFFF_FFFF_FFFF_FFFF ─── конец
```
Любой адрес в области 0xFFFF800000000000 — 0xFFFFFFFFFFFFFFFF корректен;
0xFFFFFFFF80000000 выбран как начало typical higher half региона.
### PHDRS: PT_LOAD сегменты
Bootloader загружает только PT_LOAD сегменты. Их три:
1. **text**: код + inline-константы.
2. **rodata**: неизменяемые данные (строки, таблицы).
3. **data**: изменяемые данные + BSS.
### Section alignment
```ld
. = ALIGN(CONSTANT(MAXPAGESIZE));
```
MAXPAGESIZE = 0x1000 (4 KiB). Каждая секция начинается с новой страницы,
что даёт bootloader'у правильные MMU permissionы:
- text = read + execute (no write)
- rodata = read (no write, no execute)
- data = read + write (no execute)
### Limine Requests в .data
```ld
KEEP(*(.requests_start_marker))
KEEP(*(.requests))
KEEP(*(.requests_end_marker))
```
- `KEEP` — запрещает линкеру выбрасывать эти секции (dead code elimination).
- `.requests_start_marker` и `.requests_end_marker` — маркеры границ.
- Bootloader сканирует память между ними, чтобы найти requests.
### BSS
```ld
.bss : {
*(.bss .bss.*)
*(COMMON)
} :data
```
- BSS — неинициализированные глобальные переменные.
- Занимает место в виртуальной памяти, но не в ELF-файле.
- Bootloader обнуляет BSS перед передачей управления.
### DISCARD
```ld
/DISCARD/ : {
*(.eh_frame*)
*(.note .note.*)
}
```
- `.eh_frame*` — исключительные фреймы C++/Rust unwinding.
- `.note.*` — ELF notes.
- Не нужны bare-metal ядру, могут вызвать проблемы.
## Архитектурные различия
### RISC-V
```ld
.data : {
*(.data .data.*)
KEEP(*(.requests_start_marker))
KEEP(*(.requests))
KEEP(*(.requests_end_marker))
*(.sdata .sdata.*) ← RISC-V: small data
} :data
.bss : {
*(.sbss .sbss.*) ← RISC-V: small BSS
*(.bss .bss.*)
*(COMMON)
} :data
```
RISC-V имеет `.sdata`/`.sbss` секции для small data (GP-relative addressing).
### AArch64 и LoongArch64
Идентичны x86_64, за исключением OUTPUT_FORMAT.

View File

@@ -1,134 +0,0 @@
# Сборочная система
---
## 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
```

View File

@@ -1,147 +0,0 @@
# Система 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.

View File

@@ -0,0 +1,146 @@
# CNode: таблица capability: `mod.rs`
## Концептуальная модель
**CNode** (Capability Node) — это массив слотов, каждый из которых
может хранить один capability. Это аналог файловой таблицы в Unix,
но для capabilities.
```
CNode {
slots: Vec<Locked<CNodeSlot>>
}
CNodeSlot {
cap: Capability,
parent_idx: Option<usize>, // индекс родительского слота
}
```
## Инициализация
```rust
pub fn new(size: usize) -> Self {
let mut slots = Vec::with_capacity(size);
for _ in 0..size {
slots.push(Locked::new(CNodeSlot {
cap: Capability::empty(),
parent_idx: None,
}));
}
Self { slots }
}
```
Все слоты изначально пустые (`Capability::empty()`).
## Операции
### insert(slot, cap) — вставка
```rust
pub fn insert(&self, slot: usize, cap: Capability) -> Result<(), &'static str> {
if slot >= self.slots.len() { return Err("Index out of bounds"); }
let mut s = self.slots[slot].lock();
s.cap = cap;
Ok(())
}
```
Простая вставка без проверки (перезаписывает существующий).
### mint(src, dest, relation, rights) — создание потомка
```rust
pub fn mint(&self, src: usize, dest: usize, relation: Relation, rights: CapRights) -> Result<(), &'static str>
```
**Валидация:**
- src и dest в пределах массива.
- src != dest (дедлок не имеет смысла, но блокировка была бы корректна).
- src содержит валидный capability.
- src имеет GRANT.
**Процесс:**
1. Захват блокировок в порядке возрастания индекса (lock ranking).
2. Вычисление `final_rights = src.rights & rights`.
3. Копирование Capability в dest с новыми правами и relation.
4. Установка `parent_idx = Some(src)`.
### revoke(slot_idx) — отзыв
```rust
pub fn revoke(&self, slot_idx: usize) -> Result<(), &'static str>
```
Каскадное удаление:
```
revoke_internal(slot_idx):
├── 1. Поиск потомков:
│ for i in 0..slots.len():
│ if slots[i].parent_idx == Some(slot_idx):
│ revoke_internal(i) ← рекурсивно!
├── 2. Уничтожение себя:
│ cap = Capability::empty()
│ parent_idx = None
│ token = old_token_sig
└── 3. Отправка token в очередь:
if token != 0 && token != 0xDEAD_BEEF:
MMU_REVOCATION_QUEUE.push(token)
```
**Почему нет блокировок при рекурсии?**
- На каждом шаге проверка `is_child` захватывает и отпускает блокировку.
- Рекурсивный вызов происходит **после** освобождения блокировки.
- Это предотвращает взаимоблокировки.
**Фильтр токенов:**
- `token == 0`: пустой/невалидный capability.
- `token == 0xDEAD_BEEF`: сырой Untyped (для тестов/отладки).
- Эти токены не отправляются в очередь (бессмысленно).
### get_cap(slot) — чтение
```rust
pub fn get_cap(&self, slot: usize) -> Option<Capability> {
self.slots.get(slot).map(|s| s.lock().cap)
}
```
## Lock Ranking — детали
```rust
let (src_slot, dest_slot) = if src < dest {
_guard_low = self.slots[src].lock(); // меньший → первый
_guard_high = self.slots[dest].lock(); // больший → второй
} else {
_guard_low = self.slots[dest].lock();
_guard_high = self.slots[src].lock();
};
```
**Причина:** если поток A делает mint(5, 10), а поток B делает mint(10, 5),
без lock ranking они могут взаимно заблокироваться:
- A: lock(5) → ждёт lock(10)
- B: lock(10) → ждёт lock(5)
С lock ranking:
- A: lock(5) → lock(10)
- B: lock(5) → ждёт... → дождался → lock(10)
## Интеграция с событиями
После `revoke()`, токен попадает в `MMU_REVOCATION_QUEUE` (см.
`events.rs`). VMM обрабатывает очередь в `process_pending_revocations()`.
## Особенности реализации
1. **Vec<Locked<CNodeSlot>>**: каждый слот — отдельная spinlock-ячейка.
Это позволяет параллельно читать разные слоты.
2. **`pub slots`**: прямой доступ к слоту возможен (для тестов).
3. **panic на overflow**: если очередь отзыва переполнена — паника.
Это критический сбой подсистемы ресурсов.

View File

@@ -0,0 +1,115 @@
# Дескрипторы capability: `descriptor.rs`
## Назначение
Файл определяет базовые типы capability-системы:
что такое capability, какие бывают объекты, права и отношения.
## CapObject — что представляет capability
```rust
pub enum CapObject {
Empty, // Пустой слот
Memory { phys: PhysAddr, size_pages: usize }, // Фрейм памяти
CNode { phys: PhysAddr, slots: usize }, // Другой CNode
PMActor { id: u64 }, // PM Actor
}
```
### Memory
Capability на физическую память:
- `phys` — физический адрес начала.
- `size_pages` — размер в страницах.
### CNode
Capability на другой CNode:
- `phys` — физический адрес CNode.
- `slots` — количество слотов.
### PMActor
Capability на PMActor:
- `id` — уникальный идентификатор актора.
### Empty
Слот пуст. `is_valid()` возвращает `false`.
## CapRights — права доступа
```rust
bitflags! {
pub struct CapRights: u8 {
const READ = 1 << 0; // 0x01 — чтение
const WRITE = 1 << 1; // 0x02 — запись
const EXECUTE = 1 << 2; // 0x04 — исполнение
const GRANT = 1 << 3; // 0x08 — разрешение на mint
}
}
```
**Правила:**
- Права дочернего capability = `родительские_права & запрошенные_права`.
- Нельзя расширить права: если родитель не имеет GRANT, mint невозможен.
- `CapRights::all()` = R | W | X | G = 0x0F.
## Relation — тип связи
```rust
pub enum Relation {
Strong, // Владелец — сильная ссылка (объект жив, пока есть Strong)
Borrow, // Заёмщик — временный доступ
Transfer, // Передача — владение переходит без возможности отзыва
}
```
- **Strong**: capability владеет объектом. При revoke, объект может
быть уничтожен или возвращён пулу.
- **Borrow**: capability предоставляет временный доступ.
При revoke родителя, borrow-потомки тоже отзываются.
- **Transfer**: полная передача владения. Используется при IPC.
## Capability — полный дескриптор
```rust
#[derive(Debug, Clone, Copy)]
pub struct Capability {
pub object: CapObject, // Целевой объект
pub rights: CapRights, // Права доступа
pub relation: Relation, // Тип связи
pub token_sig: u64, // Уникальный подписывающий токен
}
```
### token_sig — назначение
- Уникальный 64-битный идентификатор capability.
- Используется для:
1. **Отзыва**: при revoke, token_sig отправляется в MMU_REVOCATION_QUEUE.
2. **Идентификации в VMM**: VMA хранят `cap_token == token_sig`.
3. **Отладки**: каждый capability можно однозначно отследить.
### Методы
```rust
impl Capability {
pub const fn empty() -> Self {
Self {
object: CapObject::Empty,
rights: CapRights::empty(),
relation: Relation::Borrow,
token_sig: 0,
}
}
pub fn is_valid(&self) -> bool {
!matches!(self.object, CapObject::Empty)
}
}
```
`token_sig = 0` зарезервирован для пустых/невалидных capability.
`token_sig = 0xDEAD_BEEF` используется для сырых Untyped
(не отправляется в MMU_REVOCATION_QUEUE, см. `cap/mod.rs`).

View File

@@ -0,0 +1,126 @@
# Capability-система: концептуальная модель
## Философия
**Capability** (дескриптор возможности) — это **неподделываемый токен**,
дающий право выполнить определённую операцию над определённым объектом.
В традиционных ОС (Linux, Windows) доступ контролируется через:
- PID + UID/GID + проверка при каждом системном вызове.
- MMU: page tables определяют, что отображено, но не кто отобразил.
В модели capabilities:
- **Если у вас нет capability — у вас нет доступа.**
- Capability хранятся в CNode — защищённой таблице, доступной только ядру.
- Capability можно создавать только от родительского capability
(иерархия наследования).
- Права можно только **урезать** (mint), но не расширить.
- Capability можно **отозвать** (revoke), что уничтожает его
и всех его потомков.
## Основные понятия
```
Capability {
object: CapObject, // на что указывает (Memory, CNode, PMActor...)
rights: CapRights, // права (R, W, X, G)
relation: Relation, // Strong (владеет), Borrow (заём), Transfer
token_sig: u64, // уникальный идентификатор (для revoke)
}
Relation {
Strong: владеет объектом (capability владеет памятью)
Borrow: заём — временный доступ без права распоряжаться
Transfer: передача — владение переходит получателю
}
CapRights {
READ = 0x1,
WRITE = 0x2,
EXECUTE = 0x4,
GRANT = 0x8, // разрешение создавать дочерние capability
}
```
## Иерархия и отзыв
```
CNode (массив слотов)
┌────────┬────────┬────────┬────────┐
│ slot 0 │ slot 1 │ slot 2 │ slot 3 │ ...
├────────┼────────┼────────┼────────┤
│ cap │ cap │ cap │ cap │
│ parent:│ parent:│ parent:│ parent:│
│ None │ Some(0)│ Some(0)│ Some(1)│
└────────┴────────┴────────┴────────┘
┌────────┴────────┐
▼ ▼
slot 1 slot 2
(mint from 0) (mint from 0)
revoke(0) → slot 0 уничтожается
→ рекурсивно: slot 1, slot 2 тоже уничтожаются
→ token_sig slot 0 отправляется в MMU_REVOCATION_QUEUE
→ VMM обработает отзыв при следующем page fault
```
### Mint — создание дочернего capability
```rust
cnode.mint(src, dest, relation, rights)
```
- `src` — исходный слот (должен иметь GRANT).
- `dest` — целевой слот (должен быть пустым).
- `relation` — как наследник связан с родителем.
- `rights` — права наследника (∩ с правами родителя).
### Revoke — отзыв capability
```rust
cnode.revoke(slot_idx)
```
1. Рекурсивно находит всех потомков и уничтожает их.
2. Уничтожает сам capability.
3. Отправляет `token_sig` в глобальную `MMU_REVOCATION_QUEUE`.
4. VMM при следующем page fault обрабатывает все накопленные отзывы.
## Lock Ranking — предотвращение дедлоков
В `mint()` захватываются две блокировки (src и dest). Чтобы избежать
инверсии блокировок, используется строгий порядок:
```rust
let (src_slot, dest_slot) = if src < dest {
// Захватываем меньший индекс первым
_guard_low = self.slots[src].lock();
_guard_high = self.slots[dest].lock();
} else {
_guard_low = self.slots[dest].lock();
_guard_high = self.slots[src].lock();
};
```
## Интеграция с VMM
При отзыве capability, VMM должна аннулировать все VMA, связанные
с отозванным токеном. Для этого:
1. `revoke()` пушит `token_sig` в lock-free очередь.
2. При page fault: `process_pending_revocations()` дренирует очередь.
3. VMA с `cap_token == token_sig` удаляются из AddressSpace.
4. TLB flush для синхронизации MMU.
## Использование в kmain()
```rust
let root_cnode = CNode::new(256);
// Вставка capability на фрейм
root_cnode.insert(0, mem_cap).unwrap();
// Mint с урезанными правами
root_cnode.mint(0, 10, Relation::Borrow, R | W).unwrap();
// Revoke — отзыв всех потомков
root_cnode.revoke(0);
```

View File

@@ -0,0 +1,73 @@
# Ядерные объекты: `object.rs`
## Концептуальная модель
`KernelObject` — это ref-counted представление объекта ядра.
В отличие от `Capability` (которая указывает на объект), `KernelObject`
— это сам объект с подсчётом ссылок.
## Структура
```rust
pub struct KernelObject {
pub phys_addr: PhysAddr, // физический адрес объекта
pub size_bits: u8, // размер в битах (2^size_bits)
pub obj_type: ObjectType, // тип объекта
pub ref_count: AtomicUsize, // счётчик ссылок
pub owner_id: u64, // ID владельца
}
```
## ObjectType — классификация объектов
```rust
pub enum ObjectType {
Untyped, // Сырая память без типа
Frame, // Фрейм (4KiB страница)
CNode, // Capability Node
ThreadBlock, // Блок управления потоком (TCB)
PageTable, // Таблица страниц
}
```
## Ref-counting
```rust
impl KernelObject {
pub fn add_ref(&self) {
self.ref_count.fetch_add(1, Ordering::Relaxed);
// ^ Relaxed: нас не волнует порядок других операций
// при увеличении счётчика. Только атомарность.
}
pub fn release(&self) -> bool {
self.ref_count.fetch_sub(1, Ordering::Release) == 1
// ^ Release: все операции до release видны тому,
// кто Acquire-читает ref_count.
// Возвращает true, если это была последняя ссылка
// (объект должен быть уничтожен).
}
}
```
## Текущее состояние
`KernelObject` определён, но пока не используется активно.
Capability-система в текущей версии работает напрямую с `CapObject`
и `PhysAddr`, без обёртки в `KernelObject`.
Планируемое использование:
- При выделении памяти через PMActor: создаётся KernelObject.
- Capability ссылается на KernelObject через ID/индекс.
- Когда последняя Strong capability удалена → KernelObject
уничтожается → память возвращается.
## Отличие CapObject vs KernelObject
| Характеристика | CapObject | KernelObject |
|---------------|-----------|--------------|
| Роль | Что capability представляет | Сам объект в памяти ядра |
| Ref-count | Нет | AtomicUsize |
| Хранение | В CNode slot | В отдельной таблице |
| Типы | Memory, CNode, PMActor | Untyped, Frame, CNode, ThreadBlock, PageTable |
| Связь | CapObject.Memory.phys = KernelObject.phys_addr | — |

View File

@@ -1,132 +0,0 @@
# Консоль и отладка
---
## 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]`) и доступны из любого модуля.

115
kernel/docs/cpu/idt.md Normal file
View File

@@ -0,0 +1,115 @@
# Interrupt Descriptor Table: `idt.rs`
## Аппаратная модель
IDT (Interrupt Descriptor Table) — это таблица из 256 entry (по 16 байт каждая),
которая сообщает CPU, куда передавать управление при прерываниях и исключениях.
```
IDT:
┌──────┬──────────────────────────────────────────────────────┐
│ 0 │ #DE — Divide Error │
│ 1 │ #DB — Debug │
│ 2 │ #NMI — Non-Maskable Interrupt │
│ 3 │ #BP — Breakpoint │
│ 4 │ #OF — Overflow │
│ 5 │ #BR — Bound Range Exceeded │
│ 6 │ #UD — Undefined Opcode │
│ 7 │ #NM — Device Not Available │
│ 8 │ #DF — Double Fault │
│ 9 │ #MF — Coprocessor Segment Overrun │
│ 10 │ #TS — Invalid TSS │
│ 11 │ #NP — Segment Not Present │
│ 12 │ #SS — Stack-Segment Fault │
│ 13 │ #GP — General Protection Fault │
│ 14 │ #PF — Page Fault │
│ 15-31│ Reserved / CPU exceptions │
│ 32-255│ User-defined (hardware interrupts) │
└──────┴──────────────────────────────────────────────────────┘
```
Загрузка IDT: инструкция `lidt [idtr_ptr]`, где `idtr_ptr` — это
6-байтовая структура `IdtPtr`:
```
IdtPtr:
┌──────────┬──────────┐
│ limit:16 │ base:48 │
└──────────┴──────────┘
```
## IdtEntry — 16-байтовая запись
```rust
#[repr(C, packed)]
pub struct IdtEntry {
offset_low: u16, // Бит 0:15 адреса обработчика
selector: u16, // Селектор сегмента кода (0x28 для ядра)
ist: u8, // Interrupt Stack Table
type_attr: u8, // Тип вентиля + флаги
offset_mid: u16, // Бит 16:31 адреса обработчика
offset_high: u32, // Бит 32:63 адреса обработчика
ignore: u32, // Зарезервировано
}
```
**type_attr:**
- Бит 7: Present (должен быть 1)
- Бит 6-5: DPL (Descriptor Privilege Level)
- Бит 4: Reserved (0)
- Бит 3-0: Gate Type (0xE = Interrupt Gate, 0xF = Trap Gate)
`set_handler(handler, selector, flags)`:
```rust
pub fn set_handler(&mut self, handler: u64, selector: u16, flags: u8) {
self.offset_low = handler as u16;
self.selector = selector;
self.ist = 0;
self.type_attr = flags | 0x80; // Present bit forced on
self.offset_mid = (handler >> 16) as u16;
self.offset_high = (handler >> 32) as u32;
}
```
## InterruptDescriptorTable — 256 entry
```rust
pub struct InterruptDescriptorTable {
entries: [IdtEntry; 256], // 256 × 16 = 4096 байт
}
```
**Методы:**
- `set_handler(vector, handler)` — устанавливает обработчик с
селектором 0x28 (GDT code segment) и type_attr 0x8E
(Interrupt Gate, Ring 0, Present).
- `load()``lidt` инструкция.
```rust
pub unsafe fn load(&'static self) {
let ptr = IdtPtr {
limit: (size_of::<Self>() - 1) as u16, // 4095
base: self as *const _ as u64,
};
asm!("lidt [{}]", in(reg) &ptr);
}
```
## Глобальная IDT
```rust
pub static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
```
`static mut` — потому что IDT модифицируется в ранней инициализации,
до включения прерываний. Потенциально может быть заменён на `static`
с `UnsafeCell`.
## Детали конфигурации
- **Selector**: `0x28` — это GDT entry для ring 0 code segment
(дескриптор 5, 5 × 8 = 0x28). Селектор сегмента кода в long mode.
- **Type 0x8E**: `1000_1110` = бит 7 (Present) + бит 3:0 = 1110
(Interrupt Gate, 32-bit). В 64-bit режиме все вентили — 64-bit,
флаг 0xE остаётся корректным.
- **IST**: 0 — не используем Interrupt Stack Table (один стек для
всех обработчиков).

View File

@@ -0,0 +1,200 @@
# Обработчики прерываний и исключений: `interrupts.rs`
## Концептуальная модель
Файл объединяет **обработчики исключений** (таблицу IDT) и
**межпроцессорные прерывания** (TLB shootdown). Это связующий слой
между аппаратурой (CPU exceptions, APIC) и программными подсистемами
(VMM, Memory).
## Структура обработчика (stub + Rust handler)
Каждый обработчик состоит из двух частей:
1. **Сборочный stub** (global_asm): сохраняет контекст, вызывает
Rust-функцию, восстанавливает контекст, iretq.
2. **Rust handler**: собственно обработка.
```
Пример: Page Fault
[stack]
page_fault_stub: error_code ← CPU пушет
push rax ...регистры...
push rcx
...
mov rdi, [rsp + 15*8] ← error_code как аргумент
call rust_page_fault_handler ← вызов Rust
pop r15 восстановление
...
pop rax
add rsp, 8 ← убираем error_code
iretq ← возврат
```
## Exception stubs (макрос)
```rust
macro_rules! exception_stub {
($name:ident, $handler:ident) => {
concat!(
".global ", stringify!($name), "\n",
stringify!($name), ":\n",
"push rax\npush rcx\n...push r15\n", // сохранение
"mov rdi, [rsp + 15*8]\n", // error_code
"call ", stringify!($handler), "\n",
"pop r15\n...pop rax\n", // восстановление
"add rsp, 8\n", // очистка error_code
"iretq\n",
)
};
}
```
Используется для:
- `page_fault_stub``rust_page_fault_handler` (вектор 14)
- `gpf_stub``rust_gpf_handler` (вектор 13)
- `double_fault_stub``rust_double_fault_handler` (вектор 8)
## TLB shootdown stub
```rust
global_asm!(
"tlb_shootdown_stub:",
"push rax\npush rcx\n...", // сохранение
"call rust_tlb_shootdown_handler",
"pop r15\n...pop rax\n", // восстановление
"iretq"
);
```
## Early handlers — для начальной загрузки
До того как VMM и slab allocator готовы, ядро не может обрабатывать
сложные исключения. Для векторов 0-31 генерируются ранние заглушки.
### Генерация (макрос `.altmacro`)
```asm
.macro early_stub vec
.globl early_handler_\vec
.balign 16
early_handler_\vec:
push 0 /* dummy error code (если нет аппаратного) */
push \vec /* номер вектора */
jmp early_common
.endm
```
`early_stub 0`..`early_stub 31` генерирует 32 обработчика.
### `early_common`
```asm
early_common:
push rax\npush rcx\n... // сохранение всех GP-регистров
mov rdi, [rsp + 15*8] // vector number
mov rsi, [rsp + 16*8] // error code (или dummy 0)
call rust_early_exception_handler
// never returns
```
### `rust_early_exception_handler(vector, error_code)`
```rust
pub extern "C" fn rust_early_exception_handler(vector: u64, _error_code: u64) -> ! {
serial::write_global(format_args!(
"\n!!! EARLY EXCEPTION !!! vector={} error_code={:#x}\nCPU halted.\n",
vector, _error_code
));
loop { asm!("cli; hlt"); }
}
```
Всегда HALT — раннее исключение фатально.
## init_early_exceptions() — настройка IDT для ранней загрузки
```rust
pub fn init_early_exceptions() {
let idt = addr_of_mut!(IDT);
// Устанавливаем early_handler_N для векторов 0-31
for (v, &handler) in early_handlers.iter().enumerate() {
(*idt).set_handler(v as u8, handler);
}
// Переопределяем критические:
(*idt).set_handler(8, double_fault_stub); // #DF
(*idt).set_handler(13, gpf_stub); // #GPF
(*idt).set_handler(14, page_fault_stub); // #PF
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub);
let ptr: &'static IDT = &*addr_of!(IDT);
ptr.load(); // lidt
}
```
## init_idt() — перезагрузка после полной инициализации
```rust
pub fn init_idt() {
// Переустанавливаем только Page Fault и TLB Shootdown
// (их обработчики уже переключились на VMM-aware версии)
let idt = addr_of_mut!(IDT);
(*idt).set_handler(14, page_fault_stub);
(*idt).set_handler(TLB_SHOOTDOWN_VECTOR, tlb_shootdown_stub);
let ptr: &'static IDT = &*addr_of!(IDT);
ptr.load();
}
```
## Обработчики исключений
### `rust_page_fault_handler(error_code)`
```rust
pub extern "C" fn rust_page_fault_handler(error_code: u64) {
let fault_addr: u64;
asm!("mov {}, cr2", out(reg) fault_addr); // читаем CR2
let write = (error_code & 0x2) != 0; // fault на запись?
let present = (error_code & 0x1) != 0; // PTE был PRESENT?
let mut vmm_guard = KERNEL_SPACE.lock();
if let Some(space) = vmm_guard.as_mut() {
space.process_pending_revocations(); // обрабатываем отзывы
match space.handle_fault(fault_addr, write) {
Ok(_) => {} // обработано: iretq retry
Err(e) => panic!(...), // необработанный fault
}
} else {
panic!("Page fault before KERNEL_SPACE!");
}
}
```
### `rust_gpf_handler(error_code)` и `rust_double_fault_handler(error_code)`
Оба — HALT с сообщением.
## TLB Shootdown обработчик
```rust
pub extern "C" fn rust_tlb_shootdown_handler() {
crate::mem::vmm::handle_tlb_shootdown_ipi();
crate::cpu::lapic::send_eoi(); // подтверждаем LAPIC прерывание
}
```
### Тайминги и безопасность
- TLB shootdown — IPI, требует минимальной задержки.
- Все операции в `handle_tlb_shootdown_ipi` — простые и быстрые.
- LAPIC EOI отправляется сразу после локального TLB flush.
## Константы
```rust
pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;
```
Вектор 0xFD (253) — в диапазоне пользовательских прерываний (32-255),
намеренно далеко от системных векторов 0-31.

View File

@@ -0,0 +1,106 @@
# CPU подсистема: концептуальная модель
## Состав и ответственность
CPU подсистема отвечает за:
1. **Обработку прерываний и исключений** — IDT, обработчики.
2. **Исключения ранней загрузки** — пока ядро ещё не полностью инициализировано.
3. **TLB Shootdown** — межпроцессорное прерывание для синхронизации TLB.
4. **Local APIC** — программируемый контроллер прерываний.
```
CPU Subsystem
┌─────────────────────────────────────────────────────────┐
│ CPU Core #0 │
│ │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ LAPIC │ │ IDT │ │
│ │ │ │ │ │
│ │ ICR ────────┼────────► 0: #DE (Divide Error) │ │
│ │ EOI │ │ 1: #DB (Debug) │ │
│ │ (timer) │ │ ... │ │
│ │ │ │ 8: #DF (Double Fault) │ │
│ │ │ │ 13: #GP (GPF) │ │
│ │ │ │ 14: #PF (Page Fault) │ │
│ │ │ │ ... │ │
│ │ │ │ 0xFD: TLB Shootdown │ │
│ └──────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Модули
| Файл | Компонент | Функция |
|------|-----------|---------|
| `idt.rs` | IDT структуры | Определение IdtEntry, IdtPtr, InterruptDescriptorTable |
| `interrupts.rs` | Обработчики | Early handlers, Page Fault, Double Fault, GPF, TLB shootdown |
| `lapic.rs` | Local APIC | Инициализация LAPIC, EOI, broadcast IPI |
## Порядок инициализации
```
kmain()
├── cpu::interrupts::init_early_exceptions()
│ └── IDT для векторов 0-31 + TLB shootdown (0xFD)
├── ... (PMM, LAPIC, Page tables) ...
├── cpu::interrupts::init_idt()
│ └── Перезагрузка IDT с полными обработчиками
├── STI (разрешение прерываний)
└── ... (работа с прерываниями)
```
## Обработка исключений: два этапа
### Этап 1: Early (до VMM)
На раннем этапе загрузки (до настройки page tables и heap) ядро
не может обрабатывать сложные исключения. Для векторов 0-31
устанавливаются `early_handler_N`, которые:
1. Пушат вектор и (возможно) dummy error code.
2. Переходят в `early_common`.
3. Сохраняют все регистры.
4. Вызывают `rust_early_exception_handler(vector, error_code)`.
5. Паникуют (HALT).
### Этап 2: Полный (после VMM)
После инициализации VMM три критических исключения получают
полноценные обработчики:
- **Page Fault (#PF, вектор 14)**: попытка обработать (COW, lazy).
- **Double Fault (#DF, вектор 8)**: HALT с сообщением.
- **General Protection Fault (#GPF, вектор 13)**: HALT с сообщением.
- **TLB Shootdown (вектор 0xFD)**: межпроцессорный TLB сброс.
## TLB Shootdown: модель
При изменении page tables на одном ядре, TLB других ядер устаревает.
Протокол:
```
CPU 0 (инициатор) CPU 1 (мишень)
│ │
├── local_tlb_flush_asid() │
├── SHOOTDOWN_LOCK.lock() │
├── SHOOTDOWN_ASID = asid │
├── SHOOTDOWN_ACK = 0 │
├── LAPIC: broadcast IPI │
│ (вектор TLB_SHOOTDOWN_VECTOR) ───► прерывание!
│ ├── handle_tlb_shootdown_ipi()
│ ├── local_tlb_flush_asid()
│ ├── SHOOTDOWN_ACK |= 1 << core
│ ├── LAPIC::send_eoi()
│ └── iretq
│ │
├── spin_loop() ◄────────────────────┤
│ (ждёт ACK от всех ядер) │
├── SHOOTDOWN_LOCK.unlock() │
└── continue │
```

115
kernel/docs/cpu/lapic.md Normal file
View File

@@ -0,0 +1,115 @@
# Local APIC: `lapic.rs`
## Аппаратная модель
**Local APIC** (Advanced Programmable Interrupt Controller) — это
встроенный в каждое ядро x86-64 контроллер прерываний.
```
Local APIC (MMIO, начиная с 0xFEE00_000)
┌──────────────────────────────┐
│ 0x020: IRR (In-Service Reg) │
│ 0x030: TMR (Trigger Mode) │
│ 0x080: EOI │ ← запись 0 = подтверждение прерывания
│ 0x0B0: LINT0/LINT1 │
│ 0x0E0: Timer │
│ 0x200: LVT Error │
│ 0x300: ICR (Interrupt Cmd) │ ← отправка межпроцессорного прерывания
│ 0x310: ICR_HIGH (APIC ID) │
└──────────────────────────────┘
```
## Регистры
| Смещение | Регистр | Назначение |
|----------|---------|------------|
| 0x0B0 | EOI | End Of Interrupt — подтверждение обработки |
| 0x300 | ICR (low) | Interrupt Command Register — отправка IPI |
| 0x310 | ICR (high) | Destination APIC ID |
## Инициализация
```rust
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
pub fn init() {
// LAPIC отображён bootloader'ом в HHDM по адресу 0xFEE00_000
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + get_hhdm(), Ordering::SeqCst);
}
```
LAPIC расположен по физическому адресу `0xFEE0_0000`. Ядро получает
виртуальный базовый адрес, добавляя HHDM offset.
## Доступ к регистрам
```rust
#[inline(always)]
fn write_lapic_reg(offset: u64, value: u32) {
let base = LAPIC_VIRT_BASE.load(Ordering::Relaxed);
if base == 0 { return; } // LAPIC ещё не инициализирован
unsafe { core::ptr::write_volatile((base + offset) as *mut u32, value) }
}
```
- `write_volatile` — запрещает компилятору оптимизировать обращение
(регистры MMIO).
- `Relaxed` ordering — для отладки/инициализации достаточно.
## Операции
### send_eoi() — конец прерывания
```rust
pub fn send_eoi() {
write_lapic_reg(LAPIC_EOI, 0); // 0x0B0
}
```
### broadcast_ipi_exclude_self(vector) — IPI всем, кроме себя
```rust
pub fn broadcast_ipi_exclude_self(vector: u8) {
// ICR[19:18] = 10b (All Excluding Self)
// ICR[14] = 1 (Assert)
// ICR[7:0] = vector
let icr_low = (2 << 18) | (1 << 14) | (vector as u32);
write_lapic_reg(LAPIC_ICR_LOW, icr_low);
}
```
Используется для TLB shootdown — необходимо разослать всем ядрам
(кроме текущего) IPI с вектором `TLB_SHOOTDOWN_VECTOR`.
### current_core_id() — определение текущего ядра
```rust
pub fn current_core_id() -> u32 {
// CPUID leaf 1: EBX[31:24] = Local APIC ID
let mut ebx: u32;
asm!(
"mov {tmp:r}, rbx", // спрятать rbx (резерв LLVM)
"mov eax, 1", "cpuid",
"mov {out:e}, ebx", // сохранить EBX
"mov rbx, {tmp:r}", // восстановить rbx
tmp = out(reg) _,
out = out(reg) ebx,
...
);
ebx >> 24
}
```
**RBX проблема:** LLVM резервирует RBX, поэтому его нужно сохранять
и восстанавливать вручную вокруг CPUID инструкции.
## Тонкости
1. **MMIO vs MSR**: LAPIC можно программировать через MSR (IA32_APIC_BASE)
и через MMIO. Bootloader (Limine) настраивает MMIO mapping в HHDM.
2. **Инициализация**: LAPIC уже включён bootloader'ом. `init()` просто
сохраняет виртуальный адрес.
3. **x2APIC**: не используется (в текущей версии — MMIO xAPIC).
4. **EOI**: обязателен после каждого прерывания от LAPIC (включая IPI).
5. **ICR запись**: после записи в ICR Low, шина APIC доставляет
прерывание. Запись блокирующая (ждёт готовности шины).

View File

@@ -0,0 +1,88 @@
# Подсистема отладки: концептуальная модель
## Два канала вывода
Ядро имеет два параллельных канала для отладки:
1. **Экранный (framebuffer console)** — через `tty::Console`.
- Использует PSF2-шрифты.
- Цветной вывод (зелёный/жёлтый/красный для Info/Warn/Error).
- Медленнее, но визуально нагляднее.
2. **Serial port (COM1)** — через `debug::serial`.
- Текстовый вывод с ANSI escape codes.
- Работает через QEMU/KVM serial console.
- Быстрее, может быть перенаправлен в файл.
## LogLevel — уровни логирования
```rust
pub enum LogLevel {
Info, // Зелёный на экране, зелёный в serial
Warn, // Жёлтый
Error, // Красный
}
```
Каждый уровень имеет:
- `serial_color_code()` — ANSI escape code для serial.
- `console_color()` — RGB значение для framebuffer.
## Макросы
```rust
// Основной макрос
log!(console, level, module, format_args...)
// Специализированные
info!(console, module, format_args...)
warn!(console, module, format_args...)
error!(console, module, format_args...)
```
**Формат вывода на экран:**
```
[ LOG ] <module> | <message>
```
**Формат в serial:**
```
GREEN[ LOG] RESET <module> | <message>
```
## Цветовое кодирование
| Уровень | Экран (RGB) | Serial (ANSI) |
|---------|-------------|---------------|
| Info | 0x00FF00 | `\x1b[32m` (green) |
| Warn | 0xFFFF00 | `\x1b[33m` (yellow) |
| Error | 0xFF0000 | `\x1b[31m` (red) |
| Текст | 0xFFFFFF (white) | `\x1b[0m` (reset) |
## Использование в kmain()
```rust
// После инициализации serial
debug::serial::init_global();
// После инициализации console
info!(console, "BOOT", "LIS4 Kernel Starting...");
info!(console, "MEM", "BitmapPMM initialized.");
info!(console, "LAPIC", "Local APIC initialized.");
```
## Архитектура
```
┌──────────────┐ ┌───────────────────┐
│ kmain() │────►│ log!() macro │
└──────────────┘ └────────┬──────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Экран │ │ Serial │ │ Паника │
│ Console │ │ COM1 │ │ Handler │
│ (tty.rs) │ │(serial.rs)│ │(main.rs) │
└──────────┘ └──────────┘ └──────────┘
```

110
kernel/docs/debug/serial.md Normal file
View File

@@ -0,0 +1,110 @@
# Serial Port драйвер: `serial.rs`
## Назначение
Драйвер последовательного порта (UART 16550, COM1) для отладочного
вывода. Позволяет видеть сообщения ядра через QEMU serial console,
minicom, screen и т.д.
## Аппаратная модель
COM1 расположен по портам ввода-вывода `0x3F8`-`0x3FF`:
| Порт | Регистр | Назначение |
|------|---------|------------|
| 0x3F8 | DATA | Чтение/запись данных |
| 0x3F9 | IER | Interrupt Enable |
| 0x3FA | IIR/FCR | Interrupt ID / FIFO Control |
| 0x3FB | LCR | Line Control |
| 0x3FC | MCR | Modem Control |
| 0x3FD | LSR | Line Status |
| 0x3FE | MSR | Modem Status |
## Инициализация
```rust
pub unsafe fn init() -> Self {
let port = Self::COM1; // 0x3F8
outb(port + 1, 0x00); // IER = 0 (disable interrupts)
outb(port + 3, 0x80); // LCR DLAB=1 (enable baud rate programming)
outb(port + 0, 0x03); // Divisor LSB = 3 (38400 baud)
outb(port + 1, 0x00); // Divisor MSB = 0
outb(port + 3, 0x03); // LCR = 8N1 (8 bits, No parity, 1 stop)
outb(port + 2, 0xC7); // FCR = enable FIFO, clear, 14-byte threshold
outb(port + 4, 0x0B); // MCR = DTR+RTS+OUT2 (enable IRQ + handshake)
SerialPort(port)
}
```
### Детали конфигурации
1. **IER = 0**: отключаем прерывания UART (TODO: включить для RX).
2. **DLAB = 1**: разрешаем программирование делителя бода.
3. **Divisor = 3**: при тактовой 1.8432 MHz → 115200 / 3 = 38400 бод.
4. **LCR = 0x03**: 8N1 — 8 бит данных, нет чётности, 1 стоп-бит.
5. **FCR = 0xC7**: enable FIFO, clear both FIFOs, trigger at 14 bytes.
6. **MCR = 0x0B**: DTR=1, RTS=1, OUT2=1 (необходимо для IRQ на ISA шине).
## Отправка байта
```rust
fn is_transmit_empty(&self) -> bool {
unsafe { (inb(self.0 + 5) & 0x20) != 0 } // LSR bit 5 = Transmitter Holding Register Empty
}
pub fn send(&self, data: u8) {
while !self.is_transmit_empty() {} // Ждём, пока UART готов
unsafe { outb(self.0, data); }
}
```
## fmt::Write реализация
```rust
impl core::fmt::Write for SerialPort {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for byte in s.bytes() { self.send(byte); }
Ok(())
}
}
```
## Глобальный экземпляр
```rust
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);
}
}
```
`init_global()` вызывается из `kmain()` после инициализации TTY.
`write_global()` используется:
- В `log!()` макросе (через debug.rs).
- В `rust_panic()` обработчике.
- В обработчиках исключений (GPF, Double Fault, Early Exception).
## Низкоуровневый I/O
```rust
unsafe fn outb(port: u16, val: u8) {
asm!("out dx, al", in("dx") port, in("al") val,
options(nomem, nostack, preserves_flags));
}
unsafe fn inb(port: u16) -> u8 {
let res: u8;
asm!("in al, dx", out("al") res, in("dx") port,
options(nomem, nostack, preserves_flags));
res
}
```

View File

@@ -1,85 +0,0 @@
# Очередь отзыва (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`.

View File

@@ -0,0 +1,128 @@
# Очередь отзыва RevocationQueue: `events.rs`
## Концептуальная модель
**RevocationQueue** — это глобальная lock-free кольцевая очередь,
которая соединяет capability-систему (CNode) с виртуальной памятью (VMM).
### Назначение
Когда capability отзывается, VMM должна аннулировать все VMA,
связанные с этим capability. Но:
- CNode.revoke() не может напрямую обратиться к VMM (разные подсистемы,
разное время жизни блокировок).
- VMM может быть в обработке page fault'а.
- Отзыв может быть каскадным и рекурсивным.
Решение: **асинхронная очередь**.
```
CNode.revoke(token_1)
└──→ push(token_1) ───→ MMU_REVOCATION_QUEUE ──→ pop() ──→ VMM
CNode.revoke(token_2) │
│ │
└──→ push(token_2) ─────────────────────────────────────────┤
Page Fault handler │
│ │
└──→ process_pending_revocations() ←────────────────────────┘
```
## RevocationQueue — детали
```rust
pub struct RevocationQueue {
buffer: [AtomicU64; QUEUE_SIZE], // 1024 entry
_pad0: [u8; 64], // padding: кэш-линия
head: AtomicUsize, // читатель (VMM)
_pad1: [u8; 64], // padding: кэш-линия
tail: AtomicUsize, // писатели (CNode revoke)
}
```
Тот же паттерн, что и в `PMActorQueue`: разделение head и tail
по разным кэш-линиям предотвращает false sharing.
### push(token_sig)
```rust
pub fn push(&self, token_sig: u64) -> Result<(), &'static str> {
let mut tail = self.tail.load(Ordering::Relaxed);
loop {
let head = self.head.load(Ordering::Acquire);
if tail.wrapping_sub(head) >= QUEUE_SIZE {
return Err("Revocation queue overflow");
}
match self.tail.compare_exchange_weak(tail, tail+1, AcqRel, Relaxed) {
Ok(_) => {
self.buffer[tail & QUEUE_MASK].store(token_sig, Release);
return Ok(());
}
Err(actual) => tail = actual,
}
}
}
```
### pop()
```rust
pub fn pop(&self) -> Option<u64> {
let head = self.head.load(Ordering::Relaxed);
let tail = self.tail.load(Ordering::Acquire);
if head == tail { return None; }
let token = self.buffer[head & QUEUE_MASK].load(Ordering::Acquire);
self.head.store(head.wrapping_add(1), Ordering::Release);
Some(token)
}
```
## Глобальный экземпляр
```rust
pub static MMU_REVOCATION_QUEUE: RevocationQueue = RevocationQueue::new();
```
## VMM: обработка отзывов
```rust
// В interrupts.rs: rust_page_fault_handler()
let mut vmm_guard = KERNEL_SPACE.lock();
if let Some(space) = vmm_guard.as_mut() {
space.process_pending_revocations(); // ← дренирует очередь
match space.handle_fault(fault_addr, write) { ... }
}
```
```rust
// В vmm.rs: AddressSpace
pub fn process_pending_revocations(&mut self) {
let mut needs_flush = false;
while let Some(token) = MMU_REVOCATION_QUEUE.pop() {
self.do_revoke_by_token(token);
needs_flush = true;
}
if needs_flush {
tlb_flush_asid(self.asid);
}
}
```
## Почему именно так?
1. **Lock-free**: отзыв capability не блокируется VMM.
2. **Batch processing**: VMM обрабатывает сразу все накопленные отзывы
при следующем page fault, экономя TLB flush'ы.
3. **Deadlock prevention**: process_pending_revocations вызывается
**до** handle_fault, избегая циклических зависимостей между
блокировками capability и VMM.
4. **Простота**: кольцевой буфер с CAS — проверенная временем структура.
## Ограничения
- Размер: 1024 entry. Если очередь переполняется — **паника**.
Это проектный выбор: переполнение означает, что VMM не успевает
обрабатывать отзывы быстрее, чем CNode генерирует новые.
- Однопоточный pop (только page fault handler дренирует очередь).

File diff suppressed because it is too large Load Diff

70
kernel/docs/index.md Normal file
View File

@@ -0,0 +1,70 @@
# Документация ядра Elyz (LISA)
## Структура документации
```
kernel/docs/
├── index.md ← этот файл: оглавление
├── overview/
│ ├── introduction.md ← Концептуальная модель (7 принципов)
│ └── architecture.md ← Архитектура, схема, порядок инициализации
├── memory/
│ ├── introduction.md ← Трёхуровневая модель памяти
│ ├── address-translation.md ← PhysAddr, VirtAddr, HHDM
│ ├── physical-memory.md ← BitmapPMM (глобальный)
│ ├── paging.md ← PageTable, 4-level walk
│ ├── buddy.md ← BuddyAllocator (intrusive list)
│ ├── pm-actor.md ← PMActor + MPSC очередь
│ ├── pm-router.md ← PMRouter (65536 каналов)
│ ├── allocator.md ← Slab-аллокатор (куча)
│ └── vmm.md ← AddressSpace, VMA, COW, TLB
├── cpu/
│ ├── introduction.md ← CPU подсистема, 2 этапа обработки
│ ├── idt.md ← IDT entry, InterruptDescriptorTable
│ ├── interrupts.md ← Обработчики, stubs, early exceptions
│ └── lapic.md ← Local APIC, IPI, EOI
├── capability/
│ ├── introduction.md ← Capability model, mint, revoke
│ ├── descriptors.md ← Capability, CapObject, CapRights
│ ├── cnode.md ← CNode, lock ranking
│ └── objects.md ← KernelObject (ref-counted)
├── debug/
│ ├── introduction.md ← Отладка: экран + serial
│ └── serial.md ← COM1 UART 16550 драйвер
├── boot/
│ ├── introduction.md ← Процесс загрузки, Limine
│ └── linker.md ← Линкер-скрипты
└── events/
└── introduction.md ← RevocationQueue (lock-free)
```
## Быстрый старт по файлам
| Файл ядра | Что делает | Документация |
|-----------|-----------|--------------|
| `src/main.rs` | Точка входа, инициализация, тесты | `overview/architecture.md` (раздел порядка инициализации) |
| `src/mem/pmm.rs` | Глобальный PMM (Bitmap + L1) | `memory/physical-memory.md` |
| `src/mem/buddy.rs` | Buddy-аллокатор | `memory/buddy.md` |
| `src/mem/paging.rs` | 4-уровневые page tables | `memory/paging.md` |
| `src/mem/pm_manages.rs` | PMActor + очередь | `memory/pm-actor.md` |
| `src/mem/pm_router.rs` | PM Router каналы | `memory/pm-router.md` |
| `src/mem/vmm.rs` | AddressSpace, COW, TLB | `memory/vmm.md` |
| `src/mem/allocator.rs` | Slab-аллокатор | `memory/allocator.md` |
| `src/mem/address.rs` | Физические/виртуальные адреса | `memory/address-translation.md` |
| `src/cpu/idt.rs` | IDT структуры | `cpu/idt.md` |
| `src/cpu/interrupts.rs` | Обработчики исключений | `cpu/interrupts.md` |
| `src/cpu/lapic.rs` | Local APIC | `cpu/lapic.md` |
| `src/cap/mod.rs` | CNode | `capability/cnode.md` |
| `src/cap/descriptor.rs` | Capability типы | `capability/descriptors.md` |
| `src/cap/object.rs` | KernelObject | `capability/objects.md` |
| `src/debug/mod.rs` | Log macros | `debug/introduction.md` |
| `src/debug/serial.rs` | Serial driver | `debug/serial.md` |
| `src/events.rs` | Очередь отзыва | `events/introduction.md` |
| `src/tty.rs` | Framebuffer console | `tty.md` |

View File

@@ -1,142 +0,0 @@
# Прерывания
---
## 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) |

View File

@@ -1,98 +0,0 @@
# Точка входа: `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

@@ -1,243 +0,0 @@
# Управление физической памятью
## Содержание
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.

View File

@@ -0,0 +1,103 @@
# Адресация и трансляция: `address.rs`
## Концептуальная модель
В x86-64 MMU транслирует виртуальные адреса (48 бит, канонические)
в физические (до 52 бит) через 4-уровневую иерархию page tables.
Ядро использует **Higher-Half Direct Map (HHDM)** — область виртуальной
памяти, где вся физическая память отображена 1:1 с фиксированным смещением.
```
Физический адрес 0x0
│ + HHDM_OFFSET (например, 0xFFFF8000_0000_0000)
Виртуальный адрес 0xFFFF8000_0000_0000
```
## Физический адрес: `PhysAddr(pub u64)`
Представляет собой физический адрес. Это **newtype** над `u64` — строгая
типизация предотвращает случайное смешивание физических и виртуальных адресов.
**Методы:**
- `to_virt()` — преобразует в виртуальный адрес через HHDM:
`VirtAddr(self.0 + HHDM_OFFSET)`
- `is_aligned()` — проверка выравнивания на 4KiB
- `align_down()` / `align_up()` — выравнивание вниз/вверх до границы 4KiB
## Виртуальный адрес: `VirtAddr(pub u64)`
**Методы:**
- `to_phys()` — обратное преобразование: вычитает HHDM, возвращает `None`
если адрес ниже HHDM (не канонический для физической памяти)
- `as_ptr<T>()` — получить `*const T` (для чтения)
- `as_mut_ptr<T>()` — получить `*mut T` (для записи)
## HHDM Offset: глобальное состояние
```rust
static HHDM_OFFSET: AtomicU64 = AtomicU64::new(0);
```
- `init_hhdm(offset: u64)` — вызывается в `kmain()` сразу после получения
ответа от bootloader'а. Сохраняет offset через `Ordering::Release`.
- `get_hhdm() -> u64` — читает offset через `Ordering::Relaxed` (высокая
производительность, на этой архитектуре атомарность гарантирована).
### Почему Atomic?
HHDM инициализируется однократно до включения прерываний и создания
дополнительных потоков. Atomic гарантирует, что даже если в будущем
инструкции будут переупорядочены, значение будет видно всем ядрам.
## Код и абстракция
```rust
// address.rs (полный код)
static HHDM_OFFSET: AtomicU64 = AtomicU64::new(0);
pub fn init_hhdm(offset: u64) {
HHDM_OFFSET.store(offset, Ordering::Release);
// ^ Release: все предыдущие записи становятся видимы для
// всех acquire-операций на HHDM_OFFSET
}
pub fn get_hhdm() -> u64 {
HHDM_OFFSET.load(Ordering::Relaxed)
// ^ Relaxed: в однопоточном контексте и при инициализации
// до включения прерываний это безопасно и максимально быстро
}
#[repr(transparent)]
pub struct PhysAddr(pub u64);
// ^ repr(transparent): гарантирует, что PhysAddr имеет то же
// представление в памяти, что и u64 — важно для FFI и передачи
// в ассемблерные инструкции (mov cr3, rax)
impl PhysAddr {
pub fn to_virt(self) -> VirtAddr {
VirtAddr(self.0 + get_hhdm())
// ^ Концептуально: физический адрес + HHDM смещение даёт
// виртуальный адрес, по которому этот физический фрейм
// отображён в higher half
}
}
impl VirtAddr {
pub fn as_mut_ptr<T>(self) -> *mut T {
self.0 as *mut T
// ^ Прямая интерпретация числа как указателя.
// Безопасность на стороне вызывающего кода.
}
}
```
## Где используется
- `PhysAddr::to_virt()` — в `paging.rs` для доступа к Page Table entries,
в `pmm.rs` для обнуления фреймов, в `buddy.rs` для intrusive list.
- `VirtAddr::as_mut_ptr()` — в `paging.rs` для создания ссылок на PTE.
- `init_hhdm()` — в `kmain()` при старте.
- `get_hhdm()` — в `lapic.rs` для вычисления LAPIC_base.

View File

@@ -0,0 +1,211 @@
# Slab-аллокатор: куча ядра
## Концептуальная модель
**SlabAllocator** — это глобальный аллокатор кучи для ядра.
Rust-программы используют `alloc::vec::Vec`, `alloc::boxed::Box` и т.д. —
все они в конечном счёте вызывают `GlobalAlloc::alloc()`.
### Стратегия
Для маленьких блоков (≤ 2048 байт) — **slab lists**:
предварительно нарезанные блоки фиксированного размера.
Для больших блоков (> 2048 байт) — **freelist больших блоков**:
освобождённые блоки переиспользуются.
Если ни там, ни там нет — **bump allocation**:
последовательная раздача из заранее выделенного региона.
```
Запрос alloc(32 байта):
1. list_index(32) = 2 (BLOCK_SIZES[2] = 32)
2. list_heads[2] есть свободный блок?
- Да: отдаём его, заменяем голову списка
- Нет: bump-аллокация блока размером 32 (fallback_alloc)
Запрос alloc(4096 байт):
1. list_index(4096) = None (максимум 2048)
2. large_block_free есть блок ≥ 4096?
- Да: отдаём
- Нет: bump-аллокация
```
## Структуры данных
### Slab списки — `list_heads`
```rust
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
struct ListNode {
next: Option<&'static mut ListNode>,
}
pub struct SlabAllocator {
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()], // 9 списков
large_block_free: Option<&'static mut LargeBlockNode>,
heap_start: usize,
heap_end: usize,
next_bump: usize,
}
```
### LargeBlockNode — для блоков > 2048 байт
```rust
struct LargeBlockNode {
size: usize,
next: Option<&'static mut LargeBlockNode>,
}
```
### Инициализация: `init(start, size)`
```rust
pub fn init(&mut self, start: usize, size: usize) {
self.heap_start = start;
self.next_bump = start;
self.heap_end = start + size;
}
```
Вызывается в kmain() после настройки page table для области `0xFFFF_9000_0000_0000`.
## GlobalAlloc — реализация
### alloc(layout)
```rust
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let mut allocator = self.lock();
match SlabAllocator::list_index(&layout) {
Some(index) => {
// 1. Пробуем slab list
match allocator.list_heads[index].take() {
Some(node) => {
allocator.list_heads[index] = node.next.take();
node as *mut ListNode as *mut u8
}
None => {
// 2. Нет в slab — bump alloc целого блока
let block_size = BLOCK_SIZES[index];
allocator.fallback_alloc(
Layout::from_size_align(block_size, block_size).unwrap()
)
}
}
}
None => allocator.fallback_alloc(layout) // > 2048
}
}
```
### fallback_alloc(layout)
```rust
fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
let size = layout.size().max(layout.align());
// 1. Пробуем large block free list (для > 2048)
if size > 2048 {
// поиск по large_block_free
while let Some(ref mut node) = *field {
if node.size >= size {
// отдаём, удаляем из списка
return node as *mut u8;
}
field = &mut node.next;
}
}
// 2. Bump alloc
let alloc_start = (self.next_bump + layout.align() - 1) & !(layout.align() - 1);
let alloc_end = alloc_start.checked_add(layout.size())?;
if alloc_end > self.heap_end {
null_mut() // OOM
} else {
self.next_bump = alloc_end;
alloc_start as *mut u8
}
}
```
### dealloc(ptr, layout)
```rust
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let mut allocator = self.lock();
match SlabAllocator::list_index(&layout) {
Some(index) => {
// Добавляем в slab list (переиспользование)
let new_node = ListNode { next: allocator.list_heads[index].take() };
let new_node_ptr = ptr as *mut ListNode;
unsafe { new_node_ptr.write(new_node); }
allocator.list_heads[index] = Some(&mut *new_node_ptr);
}
None => {
// Добавляем в large block free list
let new_node = LargeBlockNode {
size: layout.size().max(layout.align()),
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);
}
}
}
```
**Важно:** `layout.size()` должен быть ≥ `size_of::<ListNode>()`, чтобы
освобождённый блок мог хранить указатели списка. Гарантируется, потому
что наименьший BLOCK_SIZE (8) ≥ `size_of::<Option<&'static mut ListNode>>`
(8 байт на 64-bit).
## Locked<T> — примитивная spinlock-обёртка
```rust
pub struct Locked<A> {
inner: UnsafeCell<A>,
lock: AtomicBool,
}
```
- `lock()` — spin-wait с CAS + `hint::spin_loop()`.
- `LockedGuard` — RAII guard, при Drop отпускает блокировку.
- `unsafe impl Sync` — потому что `lock()` гарантирует взаимное исключение.
Используется не только для аллокатора, но и для PMM, serial port,
VMM KERNEL_SPACE и других глобальных структур.
## Глобальный аллокатор
```rust
#[global_allocator]
pub static ALLOCATOR: Locked<SlabAllocator> = Locked::new(SlabAllocator::new());
```
Этот `static` перехватывает все вызовы `alloc::alloc::alloc()`,
делая возможным использование `Vec`, `Box`, `String` и т.д. в ядре.
## Heap init в kmain()
```rust
let heap_start = 0xFFFF_9000_0000_0000;
let heap_size = 8 * 1024 * 1024; // 8 MiB
// Предварительно map'им все страницы кучи
for i in (0..heap_size).step_by(4096) {
let frame = mem::pmm::alloc_frame().expect("OOM");
p4.map_page(VirtAddr(heap_start + i), frame, flags);
}
// Инициализируем аллокатор
allocator::ALLOCATOR.lock().init(heap_start as usize, heap_size);
```
## Почему slab?
1. **Скорость**: alloc/dealloc — O(1) для малых блоков.
2. **Нет фрагментации**: блоки фиксированного размера.
3. **Локальность**: блоки одного размера рядом в памяти.
4. **Простота**: ~180 строк кода.

267
kernel/docs/memory/buddy.md Normal file
View File

@@ -0,0 +1,267 @@
# Buddy Allocator: `buddy.rs`
## Концептуальная модель
Buddy-аллокатор — это алгоритм управления памятью, который:
- Делит память на блоки размером `2^order` страниц.
- Каждый блок может быть либо свободен, либо занят.
- При освобождении блок объединяется (coalesce) с соседом (buddy),
если тот тоже свободен, образуя блок вдвое большего размера.
```
Пример: порядок 0 (1 страница), порядок 1 (2 страницы), порядок 2 (4 страницы)
Order 2: [ 0-3 ] [ 4-7 ] [ 8-11 ]
Order 1: [ 0-1 ][ 2-3 ] [ 4-5 ][ 6-7 ] [ 8-9 ][ 10-11 ]
Order 0: [0][1][2][3] [4][5][6][7] [8][9][10][11] [12][13][14][15]
^
buddy-пара: (0,1), (2,3), (4,5)...
buddy(i) = i XOR (1 << order)
```
## Intrusive List (список в самих страницах)
Вместо отдельной структуры данных для списков свободных блоков,
Elyz использует **intrusive linked list** — указатели хранятся прямо
внутри свободных физических страниц:
```
Страница (4 KiB):
┌──────────────┐
│ next: usize │ ← указатель на следующую свободную страницу
├──────────────┤
│ prev: usize │ ← указатель на предыдущую свободную страницу
├──────────────┤
│ │
│ (не занято) │
│ │
└──────────────┘
```
Для доступа к странице по индексу используется HHDM:
```rust
fn page_virt(&self, idx: usize) -> VirtAddr {
PhysAddr(self.base_phys + idx as u64 * 4096).to_virt()
}
```
## Структура BuddyAllocator
```rust
pub struct BuddyAllocator {
free_heads: [usize; MAX_ORDER + 1], // головы списков для каждого порядка
total_pages: usize, // всего страниц в управлении
free_pages: usize, // свободно страниц
base_phys: u64, // физический адрес начала
}
```
- `MAX_ORDER = 11` — максимальный порядок (2^11 = 2048 страниц = 8 MiB).
- `NEXT_SENTINEL = usize::MAX` — маркер конца списка.
- `free_heads[order]` — индекс первой свободной страницы порядка `order`.
## Операции со списком
### flist_push(order, idx) — добавить в голову
```rust
fn flist_push(&mut self, order: usize, idx: usize) {
let head = self.free_heads[order];
// Устанавливаем: node.next = head, node.prev = SENTINEL
self.write_node(idx, head, NEXT_SENTINEL);
if head != NEXT_SENTINEL {
// head.prev = idx
self.write_node(head, self.read_next(head), idx);
}
self.free_heads[order] = idx;
}
```
### flist_remove(order, idx) — удалить из списка
```rust
fn flist_remove(&mut self, order: usize, idx: usize) {
let next = self.read_next(idx);
let prev = self.read_prev(idx);
// Очищаем указатели удаляемого узла
self.write_node(idx, NEXT_SENTINEL, NEXT_SENTINEL);
if prev != NEXT_SENTINEL {
self.write_node(prev, next, self.read_prev(prev));
} else {
self.free_heads[order] = next; // удалили голову
}
if next != NEXT_SENTINEL {
self.write_node(next, self.read_next(next), prev);
}
}
```
### flist_pop(order) — извлечь из головы
```rust
fn flist_pop(&mut self, order: usize) -> Option<usize> {
let head = self.free_heads[order];
if head == NEXT_SENTINEL { return None; }
self.flist_remove(order, head);
Some(head)
}
```
## Инициализация: `new(total_pages, base_phys)`
Стратегия: разбить весь диапазон на максимально возможные блоки.
```rust
pub fn new(total_pages: usize, base_phys: u64) -> Self {
let mut this = Self {
free_heads: [NEXT_SENTINEL; MAX_ORDER + 1],
total_pages, free_pages: 0, base_phys,
};
if total_pages == 0 { return this; }
let mut idx = 0;
while idx < total_pages {
let remaining = total_pages - idx;
// Максимальный порядок с учётом выравнивания и остатка
let align_order = /* макс порядок по выравниванию idx */;
let size_order = /* макс порядок по remaining */;
let order = MAX_ORDER.min(align_order).min(size_order);
let block_size = 1usize << order;
this.flist_push(order, idx);
this.free_pages += block_size;
idx += block_size;
}
this
}
```
### Как определяются align_order и size_order
- `align_order`: если `idx == 0``MAX_ORDER`; иначе `(idx.trailing_zeros()).min(MAX_ORDER)`.
Чем больше нулевых битов в idx, тем выше порядок выравнивания.
- `size_order`: `(64 - 1) - remaining.leading_zeros()` — максимальная степень двойки
≤ remaining.
## Аллокация: `alloc(order)`
```rust
pub fn alloc(&mut self, order: usize) -> Option<usize> {
// 1. Ищем первый непустой список начиная с order
let found_order = (order..=MAX_ORDER)
.find(|&o| self.free_heads[o] != NEXT_SENTINEL)?;
// 2. Извлекаем блок из found_order
let block_idx = self.flist_pop(found_order);
self.free_pages -= 1 << found_order;
// 3. Разбиваем до нужного порядка (split)
let mut cur_order = found_order;
while cur_order > order {
cur_order -= 1;
let buddy_idx = block_idx + (1 << cur_order);
self.flist_push(cur_order, buddy_idx);
self.free_pages += 1 << cur_order;
}
Some(block_idx)
// Возвращается индекс первого блока
}
```
### Пример split
```
Запрос: order 1 (2 страницы)
Найден: order 3 (8 страниц, блок [0-7])
Шаг 1: cur_order = 3 → 2
buddy = 0 + 4 = 4
push(order=2, idx=4) — блок [4-7] в order 2
Шаг 2: cur_order = 2 → 1
buddy = 0 + 2 = 2
push(order=1, idx=2) — блок [2-3] в order 1
Результат: order=1, idx=0 — блок [0-1]
```
## Освобождение: `free(block_idx, order)` + coalesce
```rust
pub fn free(&mut self, mut block_idx: usize, mut order: usize) {
// Пытаемся объединить с buddy
while order < MAX_ORDER {
let buddy_idx = block_idx ^ (1 << order);
// Проверка: buddy в пределах памяти?
let buddy_end = buddy_idx.checked_add(1 << order)?;
if buddy_end > self.total_pages { break; }
// Buddy свободен?
if self.flist_contains(order, buddy_idx) {
// Удаляем buddy из его списка
self.flist_remove(order, buddy_idx);
self.free_pages -= 1 << order;
// Объединяем: block_idx = min(block_idx, buddy_idx)
block_idx = block_idx.min(buddy_idx);
order += 1;
} else {
break; // buddy занят, не можем объединить
}
}
// Добавляем объединённый блок в список
self.free_pages += 1 << order;
self.flist_push(order, block_idx);
}
```
### Пример coalesce
```
Освобождаем: order 0, idx = 0
Блок [0] возвращается в список order 0.
Проверка: buddy(0, order 0) = 0 XOR 1 = 1
Если idx=1 тоже свободен в order 0:
- Удаляем idx=1 из order 0
- block_idx = min(0, 1) = 0
- order = 1
- Проверка: buddy(0, order 1) = 0 XOR 2 = 2
Если idx=2 свободен в order 1:
- Удаляем idx=2 из order 1
- order = 2
- ...
```
## alloc_pages(page_count) — универсальная аллокация
```rust
pub fn alloc_pages(&mut self, page_count: usize) -> Option<(usize, usize)> {
let order = order_for(page_count);
self.alloc(order).map(|idx| (idx, order))
}
```
`order_for()` вычисляет `ceil(log2(page_count))`.
## Вспомогательные функции
- `free_pages()` / `total_pages()` — геттеры.
- `is_exhausted()``free_pages == 0`.
- `stats()` — возвращает `[(order, count)]` для каждого порядка (количество
свободных блоков). Используется для отладки.
- `flist_contains(order, idx)` — проверка, есть ли блок в списке
(проверяет `prev != NEXT_SENTINEL` или `head == idx`).
## Почему Intrusive List?
1. **Zero overhead**: не нужно отдельной аллокации для списков.
2. **O(1)**: push/pop/remove — все O(1) операции.
3. **Константная память**: аллокатор не потребляет дополнительной памяти
сверх управляемых страниц.
4. **Кэш-дружественность**: доступ к next/prev обращается к той же странице,
которая будет выделена.
## Где используется
- `PMActor` содержит `BuddyAllocator` для управления своим диапазоном.
- Прямые тесты в `kmain()` демонстрируют all/alloc/free/coalesce/exhaustion.

View File

@@ -0,0 +1,82 @@
# Управление памятью: концептуальная модель
## Архитектурная философия
Управление памятью в Elyz построено как **трёхуровневая иерархия**,
где каждый уровень решает свою задачу и взаимодействует с соседними
через строго определённые интерфейсы.
```
Уровень 1: BitmapPMM (глобальный, физический)
│ предоставляет сырые фреймы
Уровень 2: PMActor + BuddyAllocator (распределённый, физический)
│ управляет диапазонами, выдаёт под-диапазоны
Уровень 3: AddressSpace (VMM) (виртуальный)
│ отображает физические фреймы в виртуальные адреса
CPU (MMU, page tables)
```
### Зачем три уровня?
- **BitmapPMM** — глобальный аллокатор физических фреймов. Простой,
надёжный, но неэффективный для частых alloc/free маленьких блоков.
Используется для начальной загрузки и для Page Table страниц.
- **BuddyAllocator + PMActor** — распределённая модель. Каждый актор
управляет своим диапазоном физической памяти через buddy-алгоритм.
Это даёт: (1) изоляцию — актор A не может истощить память актора B;
(2) масштабирование — акторы могут работать параллельно;
(3) предсказуемость — каждый актор знает свой лимит.
- **AddressSpace (VMM)** — виртуальные адресные пространства. PML4,
VMA-деревья, copy-on-write, lazy mapping. Использует PMM для
аллокации Page Table страниц (через `pmm_alloc` в `paging.rs`).
### Разделение ответственности по файлам
| Файл | Компонент | Роль в абстракции |
|------|-----------|-------------------|
| `address.rs` | PhysAddr / VirtAddr / HHDM | Базовые типы для адресов |
| `pmm.rs` | BitmapPMM | Глобальный менеджер физических фреймов |
| `buddy.rs` | BuddyAllocator | O(1) buddy allocator (intrusive list) |
| `pm_manages.rs` | PMActor + PMActorQueue | Актёр физической памяти + MPSC очередь |
| `pm_router.rs` | PMRouter | Lock-free маршрутизация запросов/ответов |
| `paging.rs` | PageTable | Аппаратные 4-уровневые page tables |
| `vmm.rs` | AddressSpace + VMA | Виртуальные адресные пространства |
| `allocator.rs` | SlabAllocator | Кучевой аллокатор (global_allocator) |
| `mod.rs` | Экспорт | Фасад подсистемы |
### Поток данных: типичная аллокация
```
Процесс A хочет 16 страниц:
1. Код пользователя отправляет PMRequest::Allocate в PMActor через
PMRouter::alloc_channel() + actor.submit_request()
2. Когда актор получает CPU, process_messages() вызывает
buddy.alloc_pages(16)
3. BuddyAllocator находит блок порядка 4 (2^4 = 16) или больше,
разбивает его при необходимости
4. PMActor создаёт Capability с CapObject::Memory { phys, size_pages }
5. Ответ (PMResponse::Allocated) отправляется через PMRouter
6. Получатель может отобразить фреймы в своё AddressSpace через
map_region() или map_shared()
```
### Поток данных: page fault
```
CPU ловит #PF (page fault):
1. interrupt.rs: rust_page_fault_handler() читает CR2
2. Получает блокировку KERNEL_SPACE
3. process_pending_revocations() — обрабатывает накопленные отзывы
4. handle_fault() проверяет: это COW? это lazy region?
5. Если COW — копируем страницу (copy-on-write)
6. Если lazy — alloc_frame() из PMM + map_page()
7. Если нераспознанный fault — KERNEL PANIC
```

View File

@@ -0,0 +1,196 @@
# Page Tables и отображение памяти: `paging.rs`
## Концептуальная модель
x86-64 использует **4-уровневую иерархию таблиц страниц**:
```
Виртуальный адрес (48 бит):
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ PML4 │ PDPT │ PD │ PT │ offset │
│ bits │ bits │ bits │ bits │ (12) │
│ 47:39 │ 38:30 │ 29:21 │ 20:12 │ 11:0 │
└────┬────┴────┬────┴────┬────┴────┬────┴─────────┘
│ │ │ │
▼ ▼ ▼ ▼
PML4 ──► PDPT ──► PD ──► PT ──► Физический фрейм
(512 ent) (512 ent) (512 ent) (512 ent) (4 KiB)
```
Каждая запись (PTE) = 8 байт:
```
Bit 63: NX (No Execute)
Bits 62:52: Ignored/AVL
Bits 51:12: Physical frame address (4KiB aligned)
Bits 11:9: Ignored/AVL
Bits 8: Global
Bits 7: PS (Page Size = 1 GiB для PDPT, 2 MiB для PD)
Bits 6: Dirty
Bits 5: Accessed
Bits 4: PCD (Cache Disable)
Bits 3: PWT (Write Through)
Bits 2: U/S (User/Supervisor)
Bits 1: R/W (Read/Write)
Bit 0: Present
```
## PageTable — представление таблицы
```rust
#[repr(C, align(4096))]
pub struct PageTable {
entries: [u64; 512],
}
```
- `512` entries × 8 байт = 4096 байт (ровно одна страница).
- `align(4096)` — гарантирует, что структура размещается на физической
странице, как требует аппаратура.
## PageTableFlags — битовые флаги
```rust
bitflags! {
pub struct PageTableFlags: u64 {
const PRESENT = 1 << 0;
const WRITABLE = 1 << 1;
const USER = 1 << 2;
const WRITE_THROUGH = 1 << 3;
const NO_CACHE = 1 << 4;
const ACCESSED = 1 << 5;
const DIRTY = 1 << 6;
const HUGE_PAGE = 1 << 7;
const GLOBAL = 1 << 8;
const COW = 1 << 9; // Elyz-specific: Copy-on-Write marker
const NO_EXECUTE = 1 << 63;
}
}
```
Флаг `COW` (бит 9) — Elyz-специфичный, используется в AddressSpace для
маркировки страниц, которые нужно копировать при записи.
## Основные операции PageTable
### map_page(virt, phys, flags) — отображение одной страницы
```rust
pub fn map_page(&mut self, virt: VirtAddr, phys: PhysAddr, flags: PageTableFlags) {
let p1 = self.walk_to_p1_mut(virt, true).expect("...");
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
p1.entries[p1_idx] = phys.0 | flags.bits();
asm!("invlpg [{}]", in(reg) virt.0);
}
```
**Процесс:**
1. `walk_to_p1_mut(virt, create=true)` проходит 4 уровня (P4→P3→P2→P1),
создавая недостающие таблицы через `pmm_alloc()`.
2. Записывает физический адрес с флагами в P1 entry.
3. `INVLPG` — сбрасывает TLB для этой страницы.
### map_region(virt, phys, size, flags) — отображение диапазона
Вызывает `map_page()` для каждой страницы в диапазоне.
`size.div_ceil(4096)` — количество страниц.
### unmap_page(virt) — удаление отображения
```rust
pub fn unmap_page(&mut self, virt: VirtAddr) {
// walk_to_p1_mut(virt, false) — не создаём, только ищем
let Some(p1) = self.walk_to_p1_mut(virt, false) else { return; };
p1.entries[p1_idx] = 0; // Clear PTE
asm!("invlpg [{}]", in(reg) virt.0);
}
```
**Важно:** не освобождает физический фрейм — это ответственность
Caller'а (VMM). Идемпотентна: если PTE нет, ничего не делает.
### translate(virt) — трансляция виртуального адреса
Проходит 4 уровня, проверяя PRESENT на каждом уровне.
Поддерживает huge pages (1 GiB и 2 MiB):
- Если P3 entry имеет HUGE_PAGE — 1 GiB страница.
- Если P2 entry имеет HUGE_PAGE — 2 MiB страница.
### get_flags(virt) / update_flags(virt) — чтение/запись флагов
`get_flags` возвращает флаги PTE для виртуального адреса.
`update_flags` меняет флаги, сохраняя физический адрес, и вызывает INVLPG.
### activate(phys_addr) — загрузка в CR3
```rust
pub unsafe fn activate(&self, phys_addr: PhysAddr) {
asm!("mov cr3, {0}", in(reg) phys_addr.0);
}
```
Полная замена таблиц страниц. Полный TLB flush (без PCID).
В `vmm.rs::AddressSpace::activate()` используется улучшенная версия
с PCID и битом NOFLUSH.
## walk_to_p1_mut — сердце навигации
```rust
fn walk_to_p1_mut(&mut self, virt: VirtAddr, create: bool) -> Option<&mut Self> {
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
let p3 = self.get_or_create_next_table(p4_idx, create)?;
let p2 = p3.get_or_create_next_table(p3_idx, create)?;
p2.get_or_create_next_table(p2_idx, create)
}
```
### get_or_create_next_table(index, create) — следующий уровень
```rust
fn get_or_create_next_table(&mut self, index: usize, create: bool) -> Option<&mut Self> {
if entry & PRESENT == 0 {
if !create { return None; }
// Аллоцируем новый фрейм для таблицы
let pt_phys = pmm_alloc().expect("OOM");
// Обнуляем (все entry = 0 = not present)
core::ptr::write_bytes(pt_virt.as_mut_ptr::<u8>(), 0, 4096);
// Устанавливаем entry: PRESENT | WRITABLE | USER
self.entries[index] = pt_phys.0 | (PRESENT | WRITABLE | USER);
}
// Преобразуем физический адрес entry в ссылку на PageTable
let next_phys = PhysAddr(self.entries[index] & PTE_ADDR_MASK);
Some(unsafe { &mut *next_phys.to_virt().as_mut_ptr::<Self>() })
}
```
### pte_addr_mask — маска адреса
```rust
const PTE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
```
Биты 51:12 — физический адрес с выравниванием 4KiB.
(0x000F_FFFF_FFFF_F000 = 52 бита адреса, очищенные нижние 12 бит).
## pmm_alloc — шлюз к PMM
```rust
pub fn pmm_alloc() -> Option<PhysAddr> {
PMM.lock().as_mut()?.alloc_frame()
}
```
Тонкая обёртка, чтобы избежать циклической зависимости между paging и pmm.
## Ключевые инварианты
1. Каждая PageTable занимает ровно одну физическую страницу (4 KiB).
2. Физический адрес таблицы всегда 4KiB-выровнен.
3. Все intermediate таблицы создаются с флагом USER (чтобы и ядро,
и пользователь могли их использовать). Разделение на user/kernel
контролируется на уровне P1 entry.
4. `INVLPG` вызывается после каждого изменения PTE для консистентности TLB.

View File

@@ -0,0 +1,132 @@
# Глобальный менеджер физической памяти: `pmm.rs`
## Концептуальная модель
`BitmapPMM` — это **глобальный, единственный в системе**, распределитель
физических фреймов (4 KiB). Он отвечает на вопрос:
> Какой физический фрейм сейчас свободен?
### Структуры данных
PMM использует **трёхуровневую битовую карту**:
```
Уровень 1 (L1 bitmap): u64 слова
бит в L1 = 1 если соответствующее слово L0 не полностью занято
Уровень 0 (L0 bitmap): байты
каждый байт = 8 фреймов, каждый бит = 1 фрейм
бит = 1 → фрейм занят
бит = 0 → фрейм свободен
Ref-counts (счётчики ссылок): u16
для каждого фрейма — сколько раз он был захвачен
```
### Зачем L1 (ускорение)?
Наивный линейный поиск по битовой карте размером в мегабайты — O(N).
L1-битмап позволяет пропускать целые группы по 64 слова (4096 фреймов),
где нет свободных страниц.
**Поиск:**
1. Ищем `l1_bitmap[l1_idx]` с ненулевым значением (есть свободные).
2. Вычисляем `word_idx` по позиции бита внутри L1-слова.
3. Читаем `bitmap[word_idx]` как u64.
4. Если слово == `!0` (все заняты) — очищаем бит в L1, продолжаем.
5. Иначе — `(!word).trailing_zeros()` даёт номер свободного бита.
6. Вычисляем `page_idx` и возвращаем `PhysAddr`.
### Инициализация
```rust
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse) {
```
1. Находим максимальный физический адрес из всех entry карты памяти.
2. Вычисляем `total_pages = max_addr / PAGE_SIZE`.
3. Вычисляем размер метаданных:
- `bitmap_size = total_pages / 8` (1 бит на фрейм)
- `ref_counts_size = total_pages * 2` (u16 на фрейм)
- `l1_size = (total_pages / 64 / 64) * 8`
4. Ищем **первый USABLE регион** размером >= метаданные.
5. Размещаем метаданные в этом регионе: сначала bitmap, потом ref_counts,
потом L1.
6. **Заполняем bitmap = 0xFF** (все занято).
7. **Проходим по карте памяти**: для каждого USABLE entry очищаем биты
(free_frame).
8. **Блокируем фреймы метаданных** (они теперь заняты PMM).
9. **Блокируем фрейм 0** (традиционно reserved).
10. Сохраняем `PMM.lock() = Some(pmm)`.
### alloc_frame() — пошагово
```rust
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
```
1. Двухпроходный поиск: сначала от `last_word` до конца, потом от начала.
(`last_word` — хинт для локализации, даёт амортизированное O(1)).
2. Для каждого L1-слова: если != 0 — есть свободные.
3. `trailing_zeros()` даёт первое слово с хотя бы одним свободным битом.
4. Читаем слово как u64.
5. `(!word).trailing_zeros()` даёт свободный бит.
6. Вычисляем `page_idx`.
7. Устанавливаем бит = 1, ref_count = 1, used_pages += 1.
8. Если слово стало полностью занятым — очищаем бит в L1.
9. Обновляем `last_word`.
### free_frame() — пошагово
```rust
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
```
1. Если бит уже 1 (фрейм занят):
- Уменьшаем ref_count (saturating_sub).
- Если ref_count == 0: очищаем бит, used_pages -= 1, обновляем L1.
2. Если бит == 0 (уже свободен) — ничего не делаем (идемпотентность).
### lock_frame() — захват без освобождения
Используется для резервирования фреймов, которые не должны быть
освобождены (нулевой фрейм, фреймы метаданных PMM).
### inc_ref_frame() — увеличение счётчика
Используется COW (Copy-on-Write) при fork для учёта разделяемых страниц.
## Thread Safety
```rust
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
```
Locked<T> — примитивная spinlock-обёртка (см. `allocator.rs`).
Все операции PMM требуют захвата блокировки.
## Глобальные функции
В `pmm.rs` определены функции-обёртки для удобства:
| Функция | Вызывает |
|---------|----------|
| `alloc_frame()` | `PMM.lock().as_mut()?.alloc_frame()` |
| `alloc_contiguous(n)` | `PMM.lock().as_mut()?.alloc_contiguous(n)` |
| `free_frame(addr)` | `PMM.lock().as_mut()?.free_frame(addr)` |
| `inc_ref_frame(addr)` | `PMM.lock().as_mut()?.inc_ref_frame(addr)` |
| `get_stats()` | `PMM.lock().as_ref()?.used_pages/total_pages` |
## alloc_contiguous() — выделение непрерывной области
Линейный проход по всем страницам, поиск `count` последовательных
свободных фреймов. O(total_pages) — используется редко, только для
устройств без IOMMU.
## Ключевая инварианта
`BitmapPMM` не знает о buddy-аллокаторах и PMActors. Он просто
отвечает на запросы «дай фрейм» и «забери фрейм». PMActors получают
свои диапазоны из PMM на этапе инициализации и больше к нему не
обращаются.

View File

@@ -0,0 +1,258 @@
# PM Actor: распределённый менеджер физической памяти
## Концептуальная модель
**PMActor** (Physical Memory Actor) — это изолированный агент, который
владеет фиксированным диапазоном физической памяти и управляет им через
BuddyAllocator. Он следует **модели акторов**:
- У каждого актора есть **почтовый ящик** (MPSC queue).
- Другие компоненты **отправляют ему сообщения** (Allocate, Free, Carve).
- Актёр **обрабатывает сообщения**, когда получает CPU time.
- Актёр **отправляет ответы** через PM Router.
```
┌──────────────────────────────────────────────────────┐
│ PMActor #1 │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ MPSC Inbox (PMActorQueue) │ │
│ │ [Alloc] [Free] [Carve] [Alloc] [Free] ... │ │
│ └──────────────────────┬───────────────────────┘ │
│ │ pop │
│ ┌──────────────────────▼───────────────────────┐ │
│ │ process_messages() │ │
│ │ ┌────────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │handle_alloc│ │handle_free│ │handle_carve│ │ │
│ │ └──────┬─────┘ └────┬─────┘ └─────┬────┘ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌────────────────────────────────────────┐ │ │
│ │ │ BuddyAllocator │ │ │
│ │ │ [диапазон: 0x4000_0000 — 0x4000_xxxx] │ │ │
│ │ └────────────────────────────────────────┘ │ │
│ └────────────────────────┬──────────────────────┘ │
│ │ Vec<PMResponse> │
│ ▼ │
│ Через PMRouter.route_responses() │
└──────────────────────────────────────────────────────┘
```
## Структура PMActor
```rust
pub struct PMActor {
pub actor_id: u64, // Уникальный ID
pub root_untyped: Capability, // Capability на весь диапазон
pub managed_range: (PhysAddr, PhysAddr), // [start, end)
queue: PMActorQueue, // MPSC inbox
buddy: BuddyAllocator, // Buddy-аллокатор
}
```
- `actor_id` — используется в `token_sig` для уникальной идентификации.
- `root_untyped` — Strong capability на весь диапазон (корень дерева).
- `managed_range` — физические границы (начало, конец).
- `queue` — lock-free MPSC кольцевой буфер.
- `buddy` — аллокатор для этого диапазона.
## PMActorQueue — lock-free MPSC кольцевой буфер
```rust
pub struct PMActorQueue {
buffer: [AtomicU64; QUEUE_SIZE], // 1024 entry
_pad0: [u8; 64],
head: AtomicUsize, // читатель (актор)
_pad1: [u8; 64],
tail: AtomicUsize, // писатели (производители)
}
```
### Зачем padding?
`_pad0` и `_pad1` размером 64 байта — это размер кэш-линии x86-64.
Разделение head и tail по разным кэш-линиям предотвращает **false sharing**
(когда два ядра пишут в разные переменные, но они на одной кэш-линии).
### send(req) — Multi-Producer
```rust
pub fn send(&self, req: PMRequest) -> Result<(), &'static str> {
let packed = req.pack();
let mut tail = self.tail.load(Ordering::Relaxed);
loop {
let head = self.head.load(Ordering::Acquire);
if tail.wrapping_sub(head) >= QUEUE_SIZE {
return Err("Queue overflow");
}
// CAS: пытаемся захватить слот
match self.tail.compare_exchange_weak(tail, tail+1, AcqRel, Relaxed) {
Ok(_) => {
self.buffer[tail & QUEUE_MASK].store(packed, Release);
return Ok(());
}
Err(actual) => tail = actual,
}
}
}
```
- `wrapping_sub`: кольцевой буфер, переполнение при разнице >= QUEUE_SIZE.
- `compare_exchange_weak`: позволяет нескольким продюсерам соревноваться.
- Store с `Release`: гарантирует, что данные видны потребителю.
### pop() — Single-Consumer
```rust
pub fn pop(&self) -> Option<PMRequest> {
let head = self.head.load(Ordering::Relaxed);
let tail = self.tail.load(Ordering::Acquire);
if head == tail { return None; }
let packed = self.buffer[head & QUEUE_MASK].load(Ordering::Acquire);
self.head.store(head + 1, Ordering::Release);
Some(PMRequest::unpack(packed))
}
```
- Только актор вызывает pop — никакой конкуренции.
- Acquire на tail: видим последнюю запись продюсера.
## Message Packing
Каждое сообщение упаковывается в одно `u64`:
```
Bit 63:56 OPCODE (8 bits) 1=Alloc, 2=Free, 3=Carve
Bit 55:40 CHANNEL_ID (16 bits) маршрутизация ответа; 0 = discard
Bit 39:20 ARG1 (20 bits) size_pages/local_frame_idx/offset_pages
Bit 19: 0 ARG2 (20 bits) token_sig (low 20 bits) / size_pages / order
```
Максимум: 2^20 = 1 048 576 страниц = 4 GiB на одну аллокацию.
## Типы сообщений (PMRequest)
```rust
pub enum PMRequest {
Allocate {
size_pages: usize, // сколько страниц
token_sig: u32, // тег capability
channel_id: u16, // куда отправить ответ
},
Free {
local_frame_idx: usize, // относительный индекс
order: usize, // buddy order
},
Carve {
offset_pages: usize, // смещение от начала
size_pages: usize, // размер
channel_id: u16, // куда отправить ответ
},
None,
}
```
## PMResponse — результат
```rust
pub struct PMResponse {
pub channel_id: u16,
pub result: PMResult,
}
pub enum PMResult {
Allocated { cap: Capability, order: usize },
OutOfMemory { size_pages: usize },
Carved { cap: Capability },
Freed { pages_returned: usize },
}
```
## Обработка сообщений
### handle_allocate(size_pages, token_sig, channel_id)
```rust
fn handle_allocate(&mut self, size_pages, token_sig, channel_id) -> Option<PMResponse> {
match self.buddy.alloc_pages(size_pages) {
Some((rel_idx, order)) => {
let phys = PhysAddr(self.managed_range.0.0 + rel_idx * PAGE_SIZE);
let cap = Capability {
object: CapObject::Memory { phys, size_pages: 1 << order },
rights: CapRights::READ | WRITE | GRANT,
relation: Relation::Strong,
token_sig: (token_sig as u64) ^ self.actor_id ^ (rel_idx as u64),
};
Some(PMResponse { channel_id, result: Allocated { cap, order } })
}
None => {
Some(PMResponse { channel_id, result: OutOfMemory { size_pages } })
}
}
}
```
**Детали:**
- `rel_idx` — относительный индекс в buddy (от 0 до total_pages).
- Физический адрес: `managed_range.start + rel_idx * 4096`.
- `token_sig` — XOR от token_sig запроса, actor_id и rel_idx для уникальности.
- Размер в capability: `1 << order` (округляется до степени двойки).
### handle_free(local_frame_idx, order)
```rust
fn handle_free(&mut self, local_frame_idx, order) -> Option<PMResponse> {
// Валидация границ
let block_end = local_frame_idx.saturating_add(1usize << order);
if block_end > self.buddy.total_pages() {
debug_assert!(false, "Out of range");
return None;
}
self.buddy.free(local_frame_idx, order);
None // Free не требует ответа
}
```
### handle_carve(offset_pages, size_pages, channel_id)
Carve — выделение под-диапазона без участия buddy-аллокатора.
Используется для статических размещений (framebuffer alias).
```rust
fn handle_carve(&mut self, offset_pages, size_pages, channel_id) -> Option<PMResponse> {
let range_pages = self.buddy.total_pages();
// Проверка границ
if offset_pages >= range_pages || size_pages == 0 || ... {
return Some(PMResponse { channel_id, result: OutOfMemory { size_pages } });
}
let phys = PhysAddr(self.managed_range.0.0 + offset_pages * PAGE_SIZE);
let cap = Capability {
object: CapObject::Memory { phys, size_pages },
rights: CapRights::READ | WRITE | GRANT,
relation: Relation::Strong,
token_sig: self.actor_id ^ offset_pages,
};
Some(PMResponse { channel_id, result: Carved { cap } })
}
```
## Ballooning hook (TODO)
Когда `BuddyAllocator::alloc_pages` возвращает `None`, актор генерирует
`OutOfMemory`. Будущая подсистема ballooning будет перехватывать этот
ответ до того, как он дойдёт до запросившего, и договариваться о передаче
памяти от соседнего актора.
## Владение и capabilities
- PMActor держит Strong capability на весь свой диапазон.
- Все выделенные или вырезанные под-диапазоны — тоже Strong (дочерние).
- При освобождении через Free buddy-дерево объединяет блоки.
- Актёр **никогда** не обращается к глобальному BitmapPMM.
## Максимальное количество сообщений за вызов
```rust
const MAX_MESSAGES_PER_CALL: usize = 64;
```
Ограничение предотвращает голодание ядра, когда inbox глубок.

View File

@@ -0,0 +1,189 @@
# PM Router: Lock-Free маршрутизация ответов
## Концептуальная модель
**PM Router** — это центральный коммутатор, соединяющий исполнителей
запросов (PMActors) с потребителями результатов (процессы/потоки).
- Он предоставляет **65536 каналов** для асинхронной коммуникации.
- Каналы **lock-free**: alloc/release канала — CAS на атомарных счётчиках.
- Исключает блокировки между продюсером и консьюмером.
```
Процесс A PMActor #1
│ │
│ alloc_channel() │
│ ─────────► router ──────► │
│ │
│ submit_request(Alloc) │
│ ────────────────────────► │
│ │ process_messages()
│ │ ──────► buddy.alloc()
│ │
│ ◄─────────────── │
│ route_responses([Resp]) │
│ router │
│ │
│ wait_for_response(ch) │
│ ◄═══ RESULT ════ │
│ │
│ free_channel (неявно) │
```
## Структура канала
```rust
#[repr(align(64))]
pub struct Channel {
state: AtomicU8, // FREE=0, PENDING=1, READY=2
next_free: AtomicU16, // указатель в стеке свободных каналов
result: UnsafeCell<Option<PMResult>>, // ячейка результата
}
```
- `align(64)` — каждая кэш-линия содержит ровно один канал.
- `state` — конечный автомат: FREE → PENDING → READY → FREE.
- `UnsafeCell` — потому что write происходит из route_responses,
read — из wait_for_response. Синхронизация через state.
## Структура PMRouter
```rust
pub struct PMRouter {
channels: Box<[Channel]>, // 65536 каналов, Box<[T]> в куче
free_head: AtomicU16, // стек свободных каналов
}
```
### Стек свободных каналов
Изначально все каналы свободны, `free_head = 1` (канал 0 зарезервирован
как «discard» — ответы на канал 0 игнорируются).
```
free_head ──► channel[1].next_free = 2
channel[2].next_free = 3
channel[3].next_free = 4
...
channel[65535].next_free = 0 (NULL)
```
## Операции
### alloc_channel() — выделить канал
```rust
pub fn alloc_channel(&self) -> Option<u16> {
let mut head = self.free_head.load(Acquire);
loop {
if head == 0 { return None; } // нет свободных
let next = self.channels[head].next_free.load(Relaxed);
// CAS: free_head = head → next
match self.free_head.compare_exchange_weak(head, next, AcqRel, Acquire) {
Ok(_) => {
self.channels[head].state.store(STATE_PENDING, Release);
return Some(head);
}
Err(new) => head = new,
}
}
}
```
Lock-free: CAS на `free_head` позволяет нескольким продюсерам
конкурировать без блокировок.
### route_responses(responses) — запись результатов
```rust
pub fn route_responses(&self, responses: Vec<PMResponse>) {
for resp in responses {
if resp.channel_id == 0 { continue; }
let channel = &self.channels[resp.channel_id as usize];
unsafe { *channel.result.get() = Some(resp.result); }
channel.state.store(STATE_READY, Release);
// TODO: Focus Mode — пробуждение ожидающего потока
}
}
```
### wait_for_response(id) — ожидание результата
```rust
pub fn wait_for_response(&self, id: u16) -> PMResult {
let channel = &self.channels[id as usize];
// busy-wait с HLT
while channel.state.load(Acquire) != STATE_READY {
unsafe { asm!("hlt") }; // CPU остановка до прерывания
}
let result = unsafe { (*channel.result.get()).take().unwrap() };
channel.state.store(STATE_FREE, Release);
// Возвращаем канал в стек свободных
let mut head = self.free_head.load(Relaxed);
loop {
channel.next_free.store(head, Relaxed);
match self.free_head.compare_exchange_weak(head, id, Release, Relaxed) {
Ok(_) => break,
Err(new) => head = new,
}
}
result
}
```
### request_and_wait — синхронная обёртка
```rust
pub fn request_and_wait<F>(actor: &PMActor, req_builder: F) -> PMResult
where
F: FnOnce(u16) -> PMRequest
{
let router = get_router();
let channel_id = router.alloc_channel().expect("OOM");
let req = req_builder(channel_id);
actor.submit_request(req).expect("Inbox full");
router.wait_for_response(channel_id)
}
```
## Глобальный экземпляр
```rust
static ROUTER: GlobalRouter = GlobalRouter {
is_ready: AtomicBool::new(false),
inner: UnsafeCell::new(None),
};
```
Double-checked initialization:
1. `init()` — сетап всех каналов, `is_ready = true`.
2. `get_router()` — проверяет `is_ready`, затем `unwrap_unchecked()`.
**Безопасность**: `is_ready` устанавливается один раз и никогда не
очищается, поэтому TOCTOU между проверкой и unwrap — безопасен.
## dispatch() — утилита
```rust
pub fn dispatch(responses: Vec<PMResponse>) {
get_router().route_responses(responses);
}
```
## Ключевые свойства
| Свойство | Значение |
|----------|----------|
| Количество каналов | 65536 |
| Размер канала | 64 байта (1 cache line) |
| alloc_channel | Lock-free, O(1) |
| route_responses | O(N), N = количество ответов |
| wait_for_response | Lock-free + HLT |
| Состояния | FREE → PENDING → READY → FREE |
## Где используется
- В `kmain()`: тестирование PMActor через Router.
- Будущие IPC и процесс-менеджер будут использовать Router для
асинхронного взаимодействия с PMActors.

267
kernel/docs/memory/vmm.md Normal file
View File

@@ -0,0 +1,267 @@
# Virtual Memory Manager: `vmm.rs`
## Концептуальная модель
**AddressSpace** — это представление виртуального адресного пространства
процесса. Оно состоит из:
1. **Аппаратной части**: PML4 (Page Map Level 4) — корневая таблица страниц.
2. **Программной части**: VMA (Virtual Memory Area) список — отсортированный
перечень отображённых регионов с метаданными.
```
AddressSpace
┌─────────────────────────────────────────────┐
│ asid: u16 (PCID = Process Context ID) │
│ pml4_phys: PhysAddr (адрес PML4) │
│ regions: Vec<VmaRegion> (сортирован по адресу)│
└─────────────────────────────────────────────┘
┌───────────────┐
│ PML4 (P4) │ ◄── CR3
├───────────────┤
│ PDE (P3) │
├───────────────┤
│ PTE (P2) │
├───────────────┤
│ PTE (P1) │ ◄── 4 KiB страницы
└───────────────┘
```
## VmaRegion — регион виртуальной памяти
```rust
pub struct VmaRegion {
pub virt_start: VirtAddr, // начало (выровнено на 4k)
pub virt_end: VirtAddr, // конец (эксклюзивный)
pub flags: VmaFlags, // READ, WRITE, EXEC, USER, LAZY, SHARED, COW...
pub cap_token: u64, // токен capability (для revoke)
pub backing: VmaBacking, // откуда берутся физические страницы
}
```
### VmaBacking — источник физических страниц
```rust
pub enum VmaBacking {
Anonymous(Vec<Option<PhysAddr>>),
// ^ Owned: каждая страница выделена из PMM под этот VMA.
// Option: None = lazy (страница ещё не выделена).
Physical(PhysAddr),
// ^ Не-owned: всё отображение — непрерывный физический диапазон.
// Frame не освобождаются при unmap.
Shared { owner_cap: u64, phys_base: PhysAddr },
// ^ Zero-copy shared mapping: чужие физические страницы.
// Frame не освобождаются при unmap.
// При revoke owner_cap — все Shared VMA с этим токеном удаляются.
}
```
### VmaFlags
```rust
pub struct VmaFlags: u32 {
const READ = 1 << 0;
const WRITE = 1 << 1;
const EXEC = 1 << 2;
const USER = 1 << 3;
const LAZY = 1 << 4; // Demand-paging: page fault = alloc frame
const SHARED = 1 << 5; // Zero-copy shared mapping
const PINNED = 1 << 6; // Защищён от revoke
const NOCACHE = 1 << 7; // Cache disabled (для MMIO)
const MMIO = 1 << 8; // Memory-mapped I/O
const COW = 1 << 9; // Copy-on-Write active
}
```
`to_page_flags()` транслирует VmaFlags в PageTableFlags:
- PRESENT всегда
- WRITE → WRITABLE
- EXEC → NO_EXECUTE (инвертировано!)
- LAZY → PRESENT без фрейма (fault отлавливается)
- COW → COW (кастомный бит 9)
## ASID Allocator — выделение PCID
x86-64 PCID (Process Context Identifier) — 12-битный идентификатор (0-4095).
```rust
struct AsidAllocator {
bitmap: [u32; 128], // 4096 бит
next_hint: u16, // хинт для амортизированного O(1)
}
```
- ASID 0: kernel (no PCID tagging) — всегда занят.
- ASID 4095: reserved by Intel spec — всегда занят.
- ASID 14094: пользовательские.
`alloc()` — Two-pass: от hint до 4094, затем от 1 до hint.
Пропускает полностью занятые 32-битные слова.
## Основные операции AddressSpace
### `new()` — создание пустого пространства
1. Аллоцирует фрейм из PMM для PML4.
2. Обнуляет его (все entry = not present).
3. Выделяет ASID из ASID_ALLOC.
4. Пустой список regions.
### `from_active(pml4_phys, asid)` — обёртка существующего PML4
Используется для kernel space (ASID 0).
### `map_region(virt, phys, size, flags, cap_token)` — отображение
```rust
pub fn map_region(&mut self, virt: VirtAddr, phys: Option<PhysAddr>,
size: u64, flags: VmaFlags, cap_token: u64) -> Result<VirtAddr, VmError>
```
- `phys = Some(base)`: Physical mapping — непрерывный диапазон.
- `phys = None + LAZY`: Demand-paging — фреймы выделяются при fault'е.
- `phys = None + !LAZY`: Eager alloc — все фреймы выделяются сейчас.
Создаёт VmaRegion, вставляет в отсортированный список.
### `map_shared(virt, phys_base, page_count, flags, owner_cap)` — zero-copy
Как map_region, но:
- Не копирует фреймы (не владеет ими).
- Всегда eagerly отображает (адреса известны).
- Записывает `cap_token = owner_cap` для revoke.
### `unmap_region(virt)` — удаление VMA
1. Находит VMA по адресу.
2. Удаляет из списка.
3. Вызывает `do_unmap(pml4, region)`:
- unmap_page для каждой страницы.
- Если backing owns_frames (Anonymous) — free_frame.
4. TLB flush.
### `handle_fault(fault_addr, write)` — Page Fault handler
```rust
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError>
```
1. Находит VMA, содержащий fault_addr.
2. Проверяет права (write + !WRITE = PermissionDenied).
3. Проверяет COW:
- Если write + COW: аллоцирует новый фрейм, копирует данные,
заменяет PTE, освобождает старый фрейм.
4. Проверяет LAZY:
- Если LAZY + page не выделена: alloc_frame, обнуление, map_page.
5. Иначе: UnexpectedFault → panic.
### `clone_for_fork(child_cap_token)` — fork адресного пространства
1. Создаёт пустое AddressSpace::new().
2. Для каждого VMA родителя:
- Physical/Shared: просто копирует PTE (та же физическая память).
- Anonymous (owned): COW.
* Устанавливает COW флаг на родительские PTE.
* Убирает WRITABLE с родительских PTE.
* Отображает те же фреймы в дочернее пространство (без WRITABLE).
* При записи → handle_fault → copy-on-write.
### `revoke_by_token(cap_token)` / `do_revoke_by_token(cap_token)` — отзыв
Удаляет все нем-PINNED VMA с заданным cap_token.
- Собирает индексы регионов, соответствующих токену.
- Удаляет в обратном порядке (чтобы индексы не сдвигались).
- TLB flush.
### `process_pending_revocations()` — обработка очереди отзыва
Дренирует глобальную `MMU_REVOCATION_QUEUE` и revoke-ит каждый токен.
Вызывается перед handle_fault при page fault'е, чтобы избежать deadlock
с capability subsystem.
## TLB Management
### `local_tlb_flush_asid(asid)` — сброс TLB для одного ASID
```rust
fn local_tlb_flush_asid(asid: u16) {
if INVPCID_SUPPORTED {
// INVPCID type 1 (single-context): сбрасывает только
// TLB entries, помеченные данным PCID.
asm!("invpcid {ty}, [{desc}]", ...);
} else {
// Fallback: полный сброс TLB (все контексты)
tlb_flush_all();
}
}
```
### `tlb_flush_asid(asid)` — TLB shootdown (SMP)
```rust
pub fn tlb_flush_asid(asid: u16) {
local_tlb_flush_asid(asid); // всегда сбрасываем локальный
let target_mask = active_cpus & !(1 << current_core);
if target_mask == 0 { return; }
// Lock SHOOTDOWN_LOCK → записываем ASID → broadcast IPI → wait ACK
SHOOTDOWN_ASID.store(asid);
SHOOTDOWN_ACK.store(0);
lapic::broadcast_ipi_exclude_self(TLB_SHOOTDOWN_VECTOR);
while SHOOTDOWN_ACK.load() & target_mask != target_mask {
spin_loop();
}
}
```
### `tlb_flush_all()` — полный сброс
Перезапись CR3 без PCID NOFLUSH бита.
## AddressSpace::activate() — контекстный переключатель
```rust
pub unsafe fn activate(&self) {
let cr3 = self.pml4_phys.0 | u64::from(self.asid) | (1u64 << 63);
// ^ Биты 11:0 = ASID, бит 63 = NOFLUSH (не сбрасывать TLB)
asm!("mov cr3, {0}", in(reg) cr3);
}
```
## Глобальный kernel space
```rust
pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
```
Инициализируется в `init_kernel_space(pml4_phys)` после создания
первичных page tables.
## Drop для AddressSpace
При Drop:
1. unmap всех регионов (с освобождением owned frames).
2. free_frame(pml4_phys).
3. free_asid(asid).
## VmError — типы ошибок
```rust
pub enum VmError {
OutOfMemory, // Нет фреймов в PMM
RegionOverlap, // VMA пересекается с существующим
RegionNotFound, // Нет VMA по адресу
InvalidAlignment, // Адрес/размер не выровнены на 4k
InvalidRange, // Нулевой размер или переполнение
PermissionDenied, // Нет прав на запись
UnexpectedFault, // Fault в не-lazy, не-COW регионе
NonCanonical, // Некорректный адрес
AsidExhausted, // Закончились ASID
}
```

View File

@@ -0,0 +1,105 @@
# Архитектура ядра Elyz
## Высокоуровневая схема
```
┌─────────────────────────────────────────────────────────────────────┐
│ kmain() — точка входа │
│ (инициализация: Limine, PMM, LAPIC, IDT, VMM, PM Router, Caps) │
└──────────────┬──────────────────────────────────────┬───────────────┘
│ │
┌──────────▼──────────┐ ┌─────────▼──────────────┐
│ memory subsystem │ │ capability system │
│ │ │ │
│ ┌───────────────┐ │ │ ┌──────────────────┐ │
│ │ BitmapPMM │ │ │ │ CapDescriptor │ │
│ │ (глобальный) │ │ │ │ (права + объект) │ │
│ └───────┬───────┘ │ │ └────────┬─────────┘ │
│ │ │ │ │ │
│ ┌───────▼───────┐ │ │ ┌────────▼─────────┐ │
│ │ PM Actor + │ │ │ │ CNode │ │
│ │ BuddyAlloc │ │ │ │ (таблица caps) │ │
│ └───────┬───────┘ │ │ └──────────────────┘ │
│ │ │ │ │
│ ┌───────▼───────┐ │ └────────────────────────┘
│ │ PM Router │ │
│ │ (каналы) │ │
│ └───────┬───────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ AddressSpace │ │
│ │ (VMM, PML4) │ │
│ └───────────────┘ │
└─────────────────────┘
┌──────────────────────┐ ┌──────────────────────┐
│ cpu subsystem │ │ debug subsystem │
│ ┌────────────────┐ │ │ ┌─────────────────┐ │
│ │ IDT + Interrupt│ │ │ │ Serial port │ │
│ │ Handlers │ │ │ │ (COM1) │ │
│ └────────────────┘ │ │ └─────────────────┘ │
│ ┌────────────────┐ │ │ ┌─────────────────┐ │
│ │ Local APIC │ │ │ │ Log macros │ │
│ └────────────────┘ │ │ │ (info!/warn!) │ │
└──────────────────────┘ │ └─────────────────┘ │
└──────────────────────┘
```
## Порядок инициализации в `kmain()`
1. **Limine requests** — статические структуры, сообщающие bootloader'у,
что ядру нужны: framebuffer, memory map, HHDM offset, адрес ядра.
2. **BASE_REVISION проверка** — убеждаемся, что Limine совместим.
3. **HHDM инициализация** — сохраняем Higher-Half Direct Map offset.
4. **Framebuffer/TTY** — создаём консоль для вывода на экран.
5. **Serial port** — инициализируем COM1 для отладки.
6. **Early IDT** — загружаем базовые обработчики исключений (031).
7. **BitmapPMM::init()** — сканируем карту памяти, строим битовую карту.
8. **LAPIC::init()** — настраиваем Local APIC.
9. **Создание P4** — аллоцируем фрейм для корневой таблицы страниц.
10. **Identity map** — отображаем всю физическую память в HHDM
и (для non-reserved) в идентичное отображение.
11. **Map kernel** — отображаем образ ядра.
12. **Activate P4** — загружаем новую таблицу страниц в CR3.
13. **Heap init** — резервируем 8 MiB для slab-аллокатора.
14. **CPU features** — проверяем INVPCID.
15. **VMM init** — регистрируем AddressSpace ядра.
16. **IDT final** — перезагружаем IDT с Page Fault и TLB shootdown.
17. **PM Router init** — создаём 65536 каналов.
18. **STI** — разрешаем прерывания.
19. **Capability тесты** — тестируем CNode, mint, revoke.
20. **PMActor тесты** — создаём актор, тестируем Alloc/Free/Carve/OOM.
21. **Buddy тесты** — прямое тестирование аллокатора.
22. **PMM stress test** — alloc/free 64 фреймов.
23. **HCF** — гасим CPU.
## Модульная структура
```
src/
├── main.rs — точка входа, инициализация, тесты
├── cap/ — capability система
│ ├── mod.rs — CNode (таблица дескрипторов)
│ ├── descriptor.rs — типы Capability, CapObject, CapRights, Relation
│ └── object.rs — KernelObject (ref-counted)
├── cpu/ — подсистема CPU
│ ├── mod.rs — реэкспорт модулей
│ ├── idt.rs — IDT entry/table/ptr
│ ├── interrupts.rs — обработчики исключений, TLB shootdown
│ └── lapic.rs — Local APIC драйвер
├── debug/ — подсистема отладки
│ ├── mod.rs — LogLevel, log!/info!/warn! макросы
│ └── serial.rs — COM1 serial port драйвер
├── events.rs — RevocationQueue (lock-free кольцевой буфер)
├── tty.rs — Framebuffer console (PSF2 шрифты)
├── mem/ — подсистема памяти
│ ├── mod.rs — реэкспорт модулей
│ ├── address.rs — PhysAddr, VirtAddr, HHDM offset
│ ├── allocator.rs — SlabAllocator + Locked<T> + глобальный аллокатор
│ ├── buddy.rs — BuddyAllocator (intrusive list, O(1))
│ ├── paging.rs — PageTable, 4-level page walk
│ ├── pmm.rs — BitmapPMM (tree bitmap с L1 ускорением)
│ ├── pm_manages.rs — PMActor + PMActorQueue (MPSC)
│ ├── pm_router.rs — PMRouter (65536 lock-free каналов)
│ └── vmm.rs — AddressSpace, VMA, COW, fork, TLB shootdown
```

View File

@@ -0,0 +1,72 @@
# Введение в ядро Elyz (LISA)
## Концептуальная модель
Elyz (LISA) — это модульное, capability-ориентированное ядро для x86-64,
спроектированное вокруг следующих архитектурных принципов:
### 1. Capability-ориентированная безопасность
Вместо традиционной модели «всё или ничего» (ring 0 vs ring 3), Elyz
использует систему **capabilities** (дескрипторов прав). Каждый дескриптор
представляет собой *неподделываемый токен*, дающий доступ к конкретному
объекту ядра (фрейму памяти, CNode, PMActor) с определёнными правами.
- Capability — это не просто число; это структура с полями `object`, `rights`,
`relation`, `token_sig`.
- Capability можно *создавать* (mint) с урезанными правами от родительского
дескриптора.
- Capability можно *отозвать* (revoke), что каскадно уничтожает всех потомков.
- Система гарантирует, что access — это всегда наличие capability.
### 2. Многоуровневое управление памятью
Управление физической памятью разделено на три уровня:
| Уровень | Компонент | Ответственность |
|---------|-----------|-----------------|
| 1 (глобальный) | `BitmapPMM` | Владение всей физической памятью, аллокация/освобождение по 4KiB фреймам. Инициализируется из карты памяти bootloader'а. |
| 2 (распределённый) | `PMActor` | Актёр физической памяти — владеет *диапазоном* физической памяти, использует `BuddyAllocator` для аллокации внутри этого диапазона. Каждый PMActor — изолированный аллокатор. |
| 3 (виртуальный) | `AddressSpace` (VMM) | Управляет виртуальными адресными пространствами (PML4 + VMA-деревья). Использует PMM для аллокации PT-страниц и lazy demand-paging. |
Эти уровни связывает **PM Router** — lock-free система каналов для асинхронной
пересылки запросов и ответов между исполнителями.
### 3. Асинхронная модель акторов
PMActor следует модели CSP (Communicating Sequential Processes):
- Каждый актор имеет lock-free MPSC-очередь входящих сообщений.
- Потоки/процессы отправляют запросы (Alloc/Free/Carve) в очередь актора.
- Когда актор получает CPU time, он вызывает `process_messages()` и
отправляет ответы через PM Router.
- PM Router использует 65536 предварительно выделенных каналов для
lock-free маршрутизации результатов.
### 4. Виртуальная память с Copy-on-Write
`AddressSpace` поддерживает:
- **Lazy demand-paging**: физическая страница выделяется только при
page fault'е.
- **Copy-on-Write (COW)**: при fork'е адресного пространства страницы
разделяются между родителем и потомком; копирование происходит
при первой записи.
- **Zero-copy shared mappings**: физические страницы, принадлежащие
capability, могут быть отображены в другое адресное пространство
без копирования. Владелец страниц — capability, а не VMA.
### 5. Отзыв (Revocation) через lock-free очередь
Система отзыва дескрипторов использует глобальную lock-free кольцевую
очередь `MMU_REVOCATION_QUEUE`. При отзыве capability:
1. Токен capability помещается в очередь.
2. При следующем page fault'е VMM обрабатывает накопленные отзывы.
3. Все VMA, связанные с отозванным токеном, аннулируются.
### 6. Минималистичный дизайн
- `#![no_std]` — никакой стандартной библиотеки.
- `#![no_main]` — точка входа `kmain`.
- Все структуры данных, аллокаторы и драйверы написаны с нуля.
- Единственные внешние зависимости: `limine` (bootloader протокол),
`embedded-graphics` (шрифты), `bitflags`.

View File

@@ -1,173 +0,0 @@
# Акторы физической памяти (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()` (вызов на глобальном роутере).

150
kernel/docs/tty.md Normal file
View File

@@ -0,0 +1,150 @@
# Framebuffer Console: `tty.rs`
## Назначение
Драйвер графического вывода текста на framebuffer. Использует
PSF2 (PC Screen Font 2) шрифты для отрисовки символов.
Является основным визуальным каналом вывода ядра (через info! макрос).
## Структура Console
```rust
pub struct Console<'a> {
framebuffer: &'a Framebuffer<'a>, // ссылка на Limine framebuffer
font: &'static [u8], // PSF2 шрифт
pub x: usize, // текущая позиция X (колонка)
pub y: usize, // текущая позиция Y (строка)
pub fg_color: u32, // цвет текста (RGB)
pub bg_color: u32, // цвет фона (RGB)
}
```
## PSF2 Header
```rust
#[repr(C, packed)]
struct Psf2Header {
magic: u32, // 0x864AB572 (PSF2 magic)
version: u32,
header_size: u32, // размер заголовка
flags: u32,
num_glyphs: u32, // количество глифов
bytes_per_glyph: u32, // байт на глиф
height: u32, // высота глифа в пикселях
width: u32, // ширина глифа в пикселях
}
```
Шрифт — `font.psf` (8×16, встроен через `include_bytes!`).
## Операции
### clear()
Заполняет весь framebuffer нулями. Сбрасывает позицию курсора в (0, 0).
```rust
pub fn clear(&mut self) {
unsafe {
core::ptr::write_bytes(fb.addr(), 0, (fb.pitch() * fb.height()) as usize);
}
self.x = 0;
self.y = 0;
}
```
### scroll()
Сдвигает содержимое framebuffer вверх на высоту шрифта.
Новые строки заполняются нулями.
```rust
fn scroll(&mut self) {
let shift = font_height * pitch; // смещение на 1 строку
let size = pitch * (height - font_height); // сколько копировать
unsafe {
ptr::copy(addr.add(shift), addr, size);
ptr::write_bytes(addr.add(size), 0, shift);
}
self.y -= font_height;
}
```
### draw_glyph(glyph_index, x, y)
Рисует глиф (символ) в заданной позиции.
```rust
fn draw_glyph(&mut self, glyph_index: u32, x: usize, y: usize) {
let bytes_per_line = (width + 7) / 8; // пиксели → байты
let glyph_offset = header_size + (glyph_index * bytes_per_glyph);
for cy in 0..height {
let glyph_row = self.font[glyph_offset + cy * bytes_per_line];
for cx in 0..width {
if (glyph_row & (0x80 >> cx)) != 0 {
// Пиксель включён — рисуем fg_color
let offset = ((y + cy) * fb_pitch) + ((x + cx) * 4);
unsafe {
fb_addr.add(offset).cast::<u32>().write_volatile(self.fg_color);
}
}
// Если пиксель выключен — фон остаётся (нет очистки)
}
}
}
```
- `width=8`, `bytes_per_line=1`: каждый ряд глифа = 1 байт.
- `(0x80 >> cx)`: проверка бита слева направо (MSB first).
- `write_volatile`: запрещает компилятору оптимизировать запись в MMIO.
### write_char(c)
Печатает символ:
- `\n` → переход на новую строку.
- Другие символы → draw_glyph. Если символ > num_glyphs → глиф 0.
- Перенос строки по ширине экрана.
- Скроллинг при достижении низа.
## fmt::Write реализация
```rust
impl fmt::Write for Console<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
self.write_char(c);
}
Ok(())
}
}
```
## Использование в kmain()
```rust
let fb = fb_res.framebuffers().next().expect("No framebuffer");
let mut console = tty::Console::new(&fb, KERNEL_FONT);
console.clear();
info!(console, "BOOT", "LIS4 Kernel Starting...");
// ... остальные логи
let _ = writeln!(console, "{}", logo); // ASCII art при загрузке
```
## Детали реализации
- **Цвета**: write_volatile для 32-битного пикселя. Формат — RGBA?
(зависит от режима framebuffer).
- **Фон**: не перерисовывается. Только пиксели текста.
Для полной очистки — clear().
- **PSF2 magic**: `0x864AB572` — проверяется в заголовке.
- **Производительность**: draw_glyph простая, без буферизации.
Для скроллинга — memcpy всего framebuffer.
## Зависимости
- `KERNEL_FONT: &[u8]` — встроен в main.rs через `include_bytes!("font.psf")`.
- `limine::framebuffer::Framebuffer` — через HHDM отображённый framebuffer.

View File

@@ -1,212 +0,0 @@
# Виртуальная память (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.