fix: bugs
This commit is contained in:
19
kernel/docs/README.md
Normal file
19
kernel/docs/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Документация ядра LISA
|
||||
|
||||
LISA v0.1.0 — capability-based микроядро для x86-64, написанное на Rust (nightly).
|
||||
|
||||
## Разделы
|
||||
|
||||
| Раздел | Описание |
|
||||
|---|---|
|
||||
| [Архитектура](architecture.md) | Общая архитектура и взаимосвязи подсистем |
|
||||
| [Точка входа](main.md) | `kmain()`, инициализация, FramebufferDisplay, тесты |
|
||||
| [Физическая память](memory-management.md) | BitmapPMM, BuddyAllocator, Page Tables, адресация |
|
||||
| [Виртуальная память](vmm.md) | AddressSpace, VMA, COW, Lazy, TLB shootdown, ASID/PCID |
|
||||
| [Акторы памяти](pmactor.md) | PMActor, PMRouter, BuddyAllocator, асинхронные запросы |
|
||||
| [Capability system](capabilities.md) | CNode, Capability, mint/revoke, KernelObject |
|
||||
| [Прерывания](interrupts.md) | IDT, обработчики page fault / TLB shootdown, LAPIC |
|
||||
| [Консоль и отладка](console.md) | TTY, Serial, макросы логирования |
|
||||
| [Аллокатор кучи](allocator.md) | SlabAllocator, Locked, GlobalAlloc |
|
||||
| [Очередь отзыва](events.md) | RevocationQueue, MMU уведомления |
|
||||
| [Сборочная система](build-system.md) | Cargo, linker scripts, GNUmakefile, toolchain |
|
||||
105
kernel/docs/allocator.md
Normal file
105
kernel/docs/allocator.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Аллокатор кучи
|
||||
|
||||
**Файл**: `src/mem/allocator.rs`
|
||||
|
||||
Slab-аллокатор со spinlock-синхронизацией и fallback bump-аллокацией для больших блоков.
|
||||
|
||||
---
|
||||
|
||||
## Примитив синхронизации: `Locked<A>`
|
||||
|
||||
Самодельный spinlock (альтернатива `spin::Mutex`).
|
||||
|
||||
```rust
|
||||
pub struct Locked<A> {
|
||||
inner: UnsafeCell<A>,
|
||||
lock: AtomicBool,
|
||||
}
|
||||
```
|
||||
|
||||
- `new(inner)` — создаёт с unlocked состоянием
|
||||
- `lock() -> LockedGuard<'_, A>` — CAS-цикл на `AtomicBool` (спин-ожидание)
|
||||
|
||||
### LockedGuard
|
||||
|
||||
- `Deref`/`DerefMut` — доступ к внутренним данным
|
||||
- `Drop` — `store(false, Release)` — освобождение блокировки
|
||||
|
||||
`unsafe impl<A> Sync for Locked<A>` — разработчик гарантирует корректность.
|
||||
|
||||
---
|
||||
|
||||
## SlabAllocator
|
||||
|
||||
```rust
|
||||
pub struct SlabAllocator {
|
||||
list_heads: [Option<&'static mut ListNode>; 9], // slab free list
|
||||
large_block_free: Option<&'static mut LargeBlockNode>, // free list для >2048
|
||||
heap_start: usize,
|
||||
heap_end: usize,
|
||||
next_bump: usize,
|
||||
}
|
||||
```
|
||||
|
||||
### Slab классы
|
||||
|
||||
```rust
|
||||
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048];
|
||||
```
|
||||
|
||||
9 классов — степени двойки от 8 до 2048.
|
||||
|
||||
### ListNode / LargeBlockNode
|
||||
|
||||
```rust
|
||||
struct ListNode {
|
||||
next: Option<&'static mut ListNode>,
|
||||
}
|
||||
|
||||
struct LargeBlockNode {
|
||||
size: usize,
|
||||
next: Option<&'static mut LargeBlockNode>,
|
||||
}
|
||||
```
|
||||
|
||||
Односвязные списки свободных блоков.
|
||||
|
||||
### `init(&mut self, start, size)`
|
||||
|
||||
Устанавливает границы кучи: `heap_start = start`, `heap_end = start + size`, `next_bump = start`.
|
||||
|
||||
### `list_index(layout) -> Option<usize>`
|
||||
|
||||
Находит индекс наименьшего slab-класса, покрывающего `max(size, align)`.
|
||||
|
||||
Пример: layout size=20, align=8 → блок 20 > 16, следующий 32 → index 3 (32 байта).
|
||||
|
||||
### `fallback_alloc(&mut self, layout) -> *mut u8`
|
||||
|
||||
1. Проверяет `large_block_free` — если есть подходящий блок, отдаёт его
|
||||
2. Иначе bump-аллокация: выравнивание, проверка `next_bump + size <= heap_end`, возврат `next_bump`
|
||||
3. При OOM возвращает null
|
||||
|
||||
---
|
||||
|
||||
## Глобальный аллокатор
|
||||
|
||||
```rust
|
||||
#[global_allocator]
|
||||
pub static ALLOCATOR: Locked<SlabAllocator>;
|
||||
```
|
||||
|
||||
### `GlobalAlloc::alloc(&self, layout) -> *mut u8`
|
||||
|
||||
1. Lock allocator
|
||||
2. Если `layout.size()` влезает в slab класс:
|
||||
- Находит `list_index`
|
||||
- Если free list не пуст: pop голову, вернуть указатель
|
||||
- Если пуст: `fallback_alloc(layout)` но с размером slab-блока (не `layout.size()`)
|
||||
3. Для блоков > 2048: сразу `fallback_alloc(layout)`
|
||||
|
||||
### `GlobalAlloc::dealloc(&self, ptr, layout)`
|
||||
|
||||
1. Lock allocator
|
||||
2. Для slab-sized: push обратно в slab free list как ListNode
|
||||
3. Для больших: push в large_block_free как LargeBlockNode
|
||||
56
kernel/docs/architecture.md
Normal file
56
kernel/docs/architecture.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Архитектура ядра LISA
|
||||
|
||||
LISA — **capability-based микроядро**. Вся физическая память управляется через систему capability (прав доступа к объектам). Ядро использует загрузчик **Limine** (протокол Limine boot protocol).
|
||||
|
||||
## Общая схема
|
||||
|
||||
```
|
||||
kmain()
|
||||
├── BitmapPMM.init() ── глобальный менеджер фреймов
|
||||
├── LAPIC.init() ── доступ к Local APIC
|
||||
├── PageTable::activate() ── включение страничной памяти
|
||||
├── SlabAllocator.init() ── куча ядра
|
||||
├── VMM::init_kernel_space() ── адресное пространство ядра
|
||||
├── IDT::load() ── обработчики прерываний
|
||||
├── PMRouter::init() ── 65536 каналов
|
||||
├── CNode (cap тест) ── insert → mint → revoke
|
||||
│ └── RevocationQueue.push() ── уведомление VMM
|
||||
└── PMActor тест
|
||||
├── PMRouter.alloc_channel()
|
||||
├── PMActor.submit_request()
|
||||
├── PMActor.process_messages()
|
||||
└── BuddyAllocator (внутри PMActor)
|
||||
```
|
||||
|
||||
## Поток ревокации
|
||||
|
||||
```
|
||||
CNode::revoke()
|
||||
→ RevocationQueue.push(token_sig)
|
||||
→ (page fault handler)
|
||||
→ process_deferred_mmu_events()
|
||||
→ AddressSpace::process_pending_revocations()
|
||||
→ RevocationQueue.pop()
|
||||
→ AddressSpace::revoke_by_token(token) // анмаппинг VMA
|
||||
→ TLB flush
|
||||
```
|
||||
|
||||
## Поток PMActor запроса
|
||||
|
||||
```
|
||||
1. PMRouter::alloc_channel() → получаем channel_id
|
||||
2. PMActor::submit_request(Allocate { channel_id, ... })
|
||||
3. (актор обрабатывает) PMActor::process_messages()
|
||||
4. PMActor::handle_allocate() → BuddyAllocator::alloc_pages()
|
||||
5. PMRouter::route_responses([PMResponse { channel_id, result }])
|
||||
6. PMRouter::wait_for_response(channel_id) → spin до READY
|
||||
7. Возврат результата клиенту
|
||||
```
|
||||
|
||||
## Ключевые принципы
|
||||
|
||||
- **Lock-free очереди** — MPSC кольцевые буферы везде, где возможен контеншен (PMActorQueue, RevocationQueue, PMRouter channels)
|
||||
- **Double-write logging** — каждое сообщение дублируется в фреймбуфер (консоль) и последовательный порт
|
||||
- **HHDM** — Higher Half Direct Map: весь физическая память отображена 1:1 в верхней половине адресного пространства через HHDM offset
|
||||
- **ASID/PCID** — аппаратная изоляция TLB между адресными пространствами
|
||||
- **Распределённое управление памятью** — PMM выделяет только сырые фреймы, PMActor управляет диапазонами через BuddyAllocator
|
||||
134
kernel/docs/build-system.md
Normal file
134
kernel/docs/build-system.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Сборочная система
|
||||
|
||||
---
|
||||
|
||||
## Cargo.toml
|
||||
|
||||
**Файл**: `Cargo.toml`
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "LISA"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
# н/д — только бинарный крейт
|
||||
|
||||
[[bin]]
|
||||
name = "kernel"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
limine = "0.5" # Limine boot protocol
|
||||
embedded-graphics = "0.8" # 2D фреймбуфер рисование
|
||||
bitflags = "2.11.0" # bitflags макрос
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort" # без раскрутки стека
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## build.rs
|
||||
|
||||
**Файл**: `build.rs`
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
println!("cargo:rustc-link-arg=-Tlinker-{}.ld", arch);
|
||||
println!("cargo:rerun-if-changed=linker-{}.ld", arch);
|
||||
}
|
||||
```
|
||||
|
||||
Передаёт линкер-скрипт в зависимости от архитектуры: `-Tlinker-x86_64.ld`, `-Tlinker-aarch64.ld`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Linker скрипты
|
||||
|
||||
### Общая структура (все 4 архитектуры)
|
||||
|
||||
- **Entry**: `kmain`
|
||||
- **Base address**: `0xFFFFFFFF80000000` — высшие 2 GiB, по спецификации Limine
|
||||
- **Program headers**: `PT_LOAD` для `.text`, `.rodata`, `.data`
|
||||
|
||||
### Секции
|
||||
|
||||
```
|
||||
SECTIONS {
|
||||
.text : { *(.text .text.*) } → код
|
||||
.rodata : { *(.rodata .rodata.*) } → только чтение (page-aligned)
|
||||
.data : {
|
||||
*(.requests_start_marker) → Limine requests
|
||||
*(.requests)
|
||||
*(.requests_end_marker)
|
||||
*(.data .data.*)
|
||||
}
|
||||
.bss : { *(.bss .bss.*) } → нули
|
||||
/DISCARD/ : { *(.eh_frame*) *(.note.*) }
|
||||
}
|
||||
```
|
||||
|
||||
### Архитектурные различия
|
||||
|
||||
| Архитектура | OUTPUT_FORMAT | Особенности |
|
||||
|---|---|---|
|
||||
| x86-64 | `elf64-x86-64` | Стандартный |
|
||||
| aarch64 | `elf64-littleaarch64` | Стандартный |
|
||||
| riscv64 | `elf64-littleriscv` | `.data` + `.sdata`, `.bss` + `.sbss` |
|
||||
| loongarch64 | `elf64-loongarch` | Стандартный |
|
||||
|
||||
---
|
||||
|
||||
## GNUmakefile
|
||||
|
||||
**Файл**: `GNUmakefile`
|
||||
|
||||
### Переменные
|
||||
|
||||
```makefile
|
||||
OUTPUT := kernel
|
||||
KARCH ?= x86_64
|
||||
RUST_TARGET := $(KARCH)-unknown-none
|
||||
# riscv64 -> riscv64gc-unknown-none-elf
|
||||
RUST_PROFILE ?= dev
|
||||
```
|
||||
|
||||
### Цели
|
||||
|
||||
| Цель | Действие |
|
||||
|---|---|
|
||||
| `all` | `RUSTFLAGS="-C relocation-model=static" cargo build --target ...` + копирование `target/.../kernel` → `./kernel` |
|
||||
| `clean` | `cargo clean` + rm `./kernel` |
|
||||
| `distclean` | То же, что clean |
|
||||
|
||||
`relocation-model=static` — запрещает позиционно-независимый код (ядро загружается по фиксированному адресу).
|
||||
|
||||
---
|
||||
|
||||
## rust-toolchain.toml
|
||||
|
||||
**Файл**: `rust-toolchain.toml`
|
||||
|
||||
```toml
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
targets = ["x86_64-unknown-none"]
|
||||
# aarch64-unknown-none, riscv64gc-unknown-none-elf, loongarch64-unknown-none (для будущего)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## .gitignore
|
||||
|
||||
**Файл**: `.gitignore`
|
||||
|
||||
```
|
||||
/kernel # бинарный файл ядра
|
||||
/target # артефакты сборки Cargo
|
||||
```
|
||||
147
kernel/docs/capabilities.md
Normal file
147
kernel/docs/capabilities.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Система Capability
|
||||
|
||||
---
|
||||
|
||||
## Дескрипторы Capability
|
||||
|
||||
**Файл**: `src/cap/descriptor.rs`
|
||||
|
||||
### Relation
|
||||
|
||||
```rust
|
||||
pub enum Relation {
|
||||
Strong, // владение (исключительный доступ)
|
||||
Borrow, // заимствование (временный доступ)
|
||||
Transfer, // передача (владение перешло)
|
||||
}
|
||||
```
|
||||
|
||||
### CapObject
|
||||
|
||||
```rust
|
||||
pub enum CapObject {
|
||||
Empty, // null (слот свободен)
|
||||
Memory { phys: PhysAddr, size_pages: usize }, // регион физической памяти
|
||||
CNode { phys: PhysAddr, slots: usize }, // узел capability (таблица)
|
||||
PMActor { id: u64 }, // ссылка на PMActor
|
||||
}
|
||||
```
|
||||
|
||||
### CapRights (bitflags, u8)
|
||||
|
||||
```rust
|
||||
READ = 1 << 0
|
||||
WRITE = 1 << 1
|
||||
EXECUTE = 1 << 2
|
||||
GRANT = 1 << 3 // разрешение на mint (порождение потомков)
|
||||
```
|
||||
|
||||
### Capability
|
||||
|
||||
```rust
|
||||
pub struct Capability {
|
||||
pub object: CapObject, // ссылка на объект
|
||||
pub rights: CapRights, // права доступа
|
||||
pub relation: Relation, // тип отношений
|
||||
pub token_sig: u64, // уникальный токен для отзыва (MMU)
|
||||
}
|
||||
```
|
||||
|
||||
- `empty()` — возвращает Capability с `CapObject::Empty`
|
||||
- `is_valid() -> bool` — `object != Empty`
|
||||
|
||||
---
|
||||
|
||||
## Объекты ядра
|
||||
|
||||
**Файл**: `src/cap/object.rs`
|
||||
|
||||
### ObjectType
|
||||
|
||||
```rust
|
||||
pub enum ObjectType {
|
||||
Untyped, // сырая нетипизированная память
|
||||
Frame, // выделенный физический фрейм
|
||||
CNode, // узел capability
|
||||
ThreadBlock, // блок управления потоком
|
||||
PageTable, // страница таблицы
|
||||
}
|
||||
```
|
||||
|
||||
### KernelObject
|
||||
|
||||
```rust
|
||||
pub struct KernelObject {
|
||||
pub phys_addr: PhysAddr,
|
||||
pub size_bits: u8, // размер как степень двойки
|
||||
pub obj_type: ObjectType,
|
||||
pub ref_count: AtomicUsize, // атомарный счётчик ссылок
|
||||
pub owner_id: u64, // ID владельца
|
||||
}
|
||||
```
|
||||
|
||||
- `add_ref()` — atomic increment (Relaxed ordering)
|
||||
- `release() -> bool` — atomic decrement (Release). Возвращает true, если счётчик достиг 0
|
||||
|
||||
---
|
||||
|
||||
## CNode
|
||||
|
||||
**Файл**: `src/cap/mod.rs`
|
||||
|
||||
Узел capability, хранит массив слотов с индивидуальной блокировкой.
|
||||
|
||||
### CNodeSlot
|
||||
|
||||
```rust
|
||||
struct CNodeSlot {
|
||||
cap: Capability,
|
||||
parent_idx: Option<usize>, // индекс родителя (для дерева) — None у корневых
|
||||
}
|
||||
```
|
||||
|
||||
### CNode
|
||||
|
||||
```rust
|
||||
pub struct CNode {
|
||||
slots: Vec<Locked<CNodeSlot>>,
|
||||
}
|
||||
```
|
||||
|
||||
### `new(size) -> Self`
|
||||
|
||||
Создаёт `size` пустых слотов с `parent_idx = None`.
|
||||
|
||||
### `insert(slot, cap) -> Result<(), &str>`
|
||||
|
||||
Проверка границ, сохранение Capability в слоте.
|
||||
|
||||
### `mint(src, dest, relation, rights) -> Result<(), &str>`
|
||||
|
||||
**Порождение дочерней capability с урезанными правами:**
|
||||
|
||||
1. **Lock ordering**: блокировка слотов по возрастанию индекса (deadlock prevention)
|
||||
2. Source-слот не пуст, имеет GRANT право
|
||||
3. `child_rights = parent_rights & requested_rights` — дочерние права не могут превышать родительские
|
||||
4. Копирует Capability, устанавливает новые права, relation, `parent_idx`
|
||||
5. Ошибка при `src == dest`
|
||||
|
||||
### `revoke(slot_idx) -> Result<(), &str>`
|
||||
|
||||
**Каскадный отзыв с уведомлением VMM:**
|
||||
|
||||
1. `revoke_internal(slot_idx)` — рекурсивно находит и уничтожает всех потомков
|
||||
2. Уничтожает саму Capability в слоте (Empty)
|
||||
3. Пушит `token_sig` в `MMU_REVOCATION_QUEUE` (кроме 0 и `0xDEAD_BEEF`)
|
||||
4. **Паника** при переполнении очереди
|
||||
|
||||
### `revoke_internal(slot_idx)`
|
||||
|
||||
Приватная рекурсия:
|
||||
1. Сканирует все слоты в поисках `parent_idx == slot_idx`
|
||||
2. Для каждого потомка: рекурсивный вызов
|
||||
3. Затем уничтожает свой слот (Empty)
|
||||
|
||||
### `get_cap(slot) -> Option<Capability>`
|
||||
|
||||
Возвращает копию Capability.
|
||||
132
kernel/docs/console.md
Normal file
132
kernel/docs/console.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Консоль и отладка
|
||||
|
||||
---
|
||||
|
||||
## TTY — PSF2 терминал
|
||||
|
||||
**Файл**: `src/tty.rs`
|
||||
|
||||
Шрифтовой терминал на основе PSF2 (PC Screen Font v2). Это **реально используемая консоль** в `kmain`.
|
||||
|
||||
### Psf2Header — `#[repr(C, packed)]`
|
||||
|
||||
```rust
|
||||
struct Psf2Header {
|
||||
magic: u32, // магическое число PSF2
|
||||
version: u32,
|
||||
header_size: u32,
|
||||
flags: u32,
|
||||
num_glyphs: u32, // количество глифов
|
||||
bytes_per_glyph: u32,
|
||||
height: u32, // высота шрифта в пикселях
|
||||
width: u32, // ширина шрифта в пикселях
|
||||
}
|
||||
```
|
||||
|
||||
### Console
|
||||
|
||||
```rust
|
||||
pub struct Console<'a> {
|
||||
framebuffer: &'a Framebuffer<'a>,
|
||||
font: &'static [u8], // сырые данные PSF2
|
||||
x: usize, // курсор X (в пикселях)
|
||||
y: usize, // курсор Y (в пикселях)
|
||||
fg_color: u32, // цвет текста (0xFFFFFF = белый)
|
||||
bg_color: u32, // цвет фона (0x000000 = чёрный)
|
||||
}
|
||||
```
|
||||
|
||||
### Методы
|
||||
|
||||
| Метод | Описание |
|
||||
|---|---|
|
||||
| `new(framebuffer, font)` | Создаёт консоль, курсор в (0,0) |
|
||||
| `set_color(fg)` | Устанавливает цвет текста |
|
||||
| `clear()` | Зануляет фреймбуфер, сбрасывает курсор |
|
||||
| `header()` | Возвращает ссылку на Psf2Header |
|
||||
| `scroll()` | Сдвигает фреймбуфер на высоту шрифта, зануляет низ |
|
||||
| `draw_glyph(glyph_index, x, y)` | Рисует глиф: сканирует bitmap шрифта, пишет `fg_color` для установленных битов |
|
||||
| `write_char(c)` | Обрабатывает `\n`, word wrap, поиск глифа (0 для отсутствующих), скролл |
|
||||
| `write_str(s)` | Реализация `fmt::Write` — итерация по символам |
|
||||
|
||||
`draw_glyph` — ключевой метод: строки шрифта — это побитовое представление, каждый бит — один пиксель. Если бит установлен → пишется `fg_color`, иначе пропускается.
|
||||
|
||||
---
|
||||
|
||||
## Serial — последовательный порт
|
||||
|
||||
**Файл**: `src/debug/serial.rs`
|
||||
|
||||
Драйвер UART 16550 на COM1.
|
||||
|
||||
### SerialPort
|
||||
|
||||
```rust
|
||||
pub struct SerialPort(pub u16); // номер порта
|
||||
```
|
||||
|
||||
### Инициализация UART (`init()`)
|
||||
|
||||
```rust
|
||||
port + 1 = 0x00 // отключение прерываний
|
||||
port + 3 = 0x80 // DLAB = 1 (доступ к делителю)
|
||||
port + 0 = 0x03 // делитель младший байт (~38400 бод)
|
||||
port + 1 = 0x00 // делитель старший байт
|
||||
port + 3 = 0x03 // 8N1: 8 бит, no parity, 1 stop bit
|
||||
port + 2 = 0xC7 // FIFO enable, clear, 14-byte threshold
|
||||
port + 4 = 0x0B // DTR + RTS (data terminal ready)
|
||||
```
|
||||
|
||||
### Методы
|
||||
|
||||
| Метод | Описание |
|
||||
|---|---|
|
||||
| `is_transmit_empty() -> bool` | Проверка бита 5 Line Status Register |
|
||||
| `send(data: u8)` | Ждёт `is_transmit_empty()`, затем пишет в порт |
|
||||
| `write_str(s)` (fmt::Write) | Поcимвольная отправка |
|
||||
|
||||
### Функции
|
||||
|
||||
| Функция | Описание |
|
||||
|---|---|
|
||||
| `init_global()` | Инициализирует COM1, сохраняет в `SERIAL_PORT` |
|
||||
| `write_global(args: Arguments)` | Пишет форматированную строку в serial |
|
||||
| `outb(port, val)` | `unsafe`: инструкция `out` (write I/O port) |
|
||||
| `inb(port) -> u8` | `unsafe`: инструкция `in` (read I/O port) |
|
||||
|
||||
---
|
||||
|
||||
## Логирование
|
||||
|
||||
**Файл**: `src/debug.rs`
|
||||
|
||||
### LogLevel
|
||||
|
||||
```rust
|
||||
pub enum LogLevel {
|
||||
Info, // зелёный: ANSI \x1b[32m, RGB 0x00FF00
|
||||
Warn, // жёлтый: ANSI \x1b[33m, RGB 0xFFFF00
|
||||
Error, // красный: ANSI \x1b[31m, RGB 0xFF0000
|
||||
}
|
||||
```
|
||||
|
||||
### Макросы
|
||||
|
||||
#### `log!(console, level, module, $($arg)*)`
|
||||
|
||||
Формат: `[ LOG ] <module> | <message>\n`
|
||||
|
||||
Двойной вывод:
|
||||
- **Console**: цветной `[ LOG ]` (по LogLevel) + белый `<module> | <message>`
|
||||
- **Serial**: ANSI-цветной `[ LOG ]` + `<module> | <message>` + ANSI reset
|
||||
|
||||
#### `info!(console, module, ...)`
|
||||
Обёртка над `log!` с `LogLevel::Info`. Зелёный `[ LOG ]`.
|
||||
|
||||
#### `warn!(console, module, ...)`
|
||||
Обёртка над `log!` с `LogLevel::Warn`. Жёлтый `[ LOG ]`.
|
||||
|
||||
#### `error!(console, module, ...)`
|
||||
Обёртка над `log!` с `LogLevel::Error`. Красный `[ LOG ]`.
|
||||
|
||||
Все макросы экспортируются (`#[macro_export]`) и доступны из любого модуля.
|
||||
85
kernel/docs/events.md
Normal file
85
kernel/docs/events.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Очередь отзыва (RevocationQueue)
|
||||
|
||||
**Файл**: `src/events.rs`
|
||||
|
||||
Lock-free MPSC (Multi-Producer Single-Consumer) очередь для уведомления VMM об отзыве capability.
|
||||
|
||||
---
|
||||
|
||||
## RevocationQueue
|
||||
|
||||
```rust
|
||||
pub struct RevocationQueue {
|
||||
buffer: [AtomicU64; QUEUE_SIZE], // 1024 слота
|
||||
_pad0: [u8; 64], // cache-line padding
|
||||
head: AtomicUsize, // индекс потребителя (VMM)
|
||||
_pad1: [u8; 64], // ещё padding
|
||||
tail: AtomicUsize, // индекс производителя (CNode)
|
||||
}
|
||||
```
|
||||
|
||||
Cache-line padding (64 байта) предотвращает **false sharing** между head и tail на разных кэш-линиях.
|
||||
|
||||
### Константы
|
||||
|
||||
- `QUEUE_SIZE = 1024`
|
||||
- `QUEUE_MASK = 1023`
|
||||
|
||||
### `new() -> Self`
|
||||
|
||||
Все слоты = 0, head = 0, tail = 0.
|
||||
|
||||
### `push(token_sig: u64) -> Result<(), &'static str>`
|
||||
|
||||
1. Читает `tail` (Relaxed)
|
||||
2. CAS-цикл: пытается захватить следующий слот
|
||||
3. Если буфер полон (`tail - head == QUEUE_SIZE`): ошибка
|
||||
4. Записывает `token_sig` в `buffer[tail & QUEUE_MASK]` (Release ordering)
|
||||
5. Обновляет `tail` (Release)
|
||||
|
||||
**Производители**: CNode::revoke() — при каждом отзыве пушит token_sig.
|
||||
|
||||
### `pop() -> Option<u64>`
|
||||
|
||||
1. Читает `tail` (Acquire) и `head` (Relaxed)
|
||||
2. Если `head == tail`: буфер пуст, None
|
||||
3. Читает `buffer[head & QUEUE_MASK]` (Acquire)
|
||||
4. Обновляет `head` (Release)
|
||||
5. Возвращает token
|
||||
|
||||
**Потребитель**: VMM — `AddressSpace::process_pending_revocations()`.
|
||||
|
||||
---
|
||||
|
||||
## Глобальный экземпляр
|
||||
|
||||
```rust
|
||||
pub static MMU_REVOCATION_QUEUE: RevocationQueue = RevocationQueue::new();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Полный цикл ревокации
|
||||
|
||||
```
|
||||
CNode::revoke(slot_idx)
|
||||
→ revoke_internal(slot_idx) // рекурсивное уничтожение потомков
|
||||
→ slot = Empty
|
||||
→ MMU_REVOCATION_QUEUE.push(token_sig)
|
||||
|
||||
... позже, при page fault ...
|
||||
|
||||
rust_page_fault_handler()
|
||||
→ process_deferred_mmu_events()
|
||||
→ KERNEL_SPACE.lock()
|
||||
→ process_pending_revocations()
|
||||
→ while let Some(token) = MMU_REVOCATION_QUEUE.pop()
|
||||
→ revoke_by_token(token)
|
||||
→ unmap matching VMAs (кроме PINNED)
|
||||
→ TLB flush
|
||||
|
||||
→ KERNEL_SPACE.lock()
|
||||
→ handle_fault(fault_addr, write) // обработка исходного fault
|
||||
```
|
||||
|
||||
**Почему такая очерёдность?** Ревокации обрабатываются **до** захвата VMM для page fault, чтобы избежать deadlock'а: и revoke, и handle_fault хотят lock на `KERNEL_SPACE`.
|
||||
142
kernel/docs/interrupts.md
Normal file
142
kernel/docs/interrupts.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Прерывания
|
||||
|
||||
---
|
||||
|
||||
## IDT (Interrupt Descriptor Table)
|
||||
|
||||
**Файл**: `src/cpu/idt.rs`
|
||||
|
||||
### IdtEntry — `#[repr(C, packed)]`
|
||||
|
||||
Структура записи IDT (x86-64):
|
||||
|
||||
```rust
|
||||
struct IdtEntry {
|
||||
offset_low: u16, // адрес обработчика [15:0]
|
||||
selector: u16, // селектор кода (0x28 — kernel CS)
|
||||
ist: u8, // Interrupt Stack Table index
|
||||
type_attr: u8, // тип шлюза, DPL, Present
|
||||
offset_mid: u16, // адрес обработчика [31:16]
|
||||
offset_high: u32, // адрес обработчика [63:32]
|
||||
ignore: u32, // резерв (0)
|
||||
}
|
||||
```
|
||||
|
||||
- `new()` — все поля нули
|
||||
- `set_handler(handler: u64, selector: u16, flags: u8)` — устанавливает адрес и флаги. Принудительно ставит Present (0x80) в flags.
|
||||
|
||||
### IdtPtr — `#[repr(C, packed)]`
|
||||
|
||||
```rust
|
||||
struct IdtPtr {
|
||||
limit: u16, // размер IDT - 1
|
||||
base: u64, // адрес IDT
|
||||
}
|
||||
```
|
||||
|
||||
Загружается инструкцией `lidt`.
|
||||
|
||||
### InterruptDescriptorTable
|
||||
|
||||
```rust
|
||||
struct InterruptDescriptorTable {
|
||||
entries: [IdtEntry; 256], // 256 векторов
|
||||
}
|
||||
```
|
||||
|
||||
- `set_handler(vector, handler: u64)` — устанавливает обработчик на вектор. Селектор: `0x28` (kernel code), flags: `0x8E` (32-bit interrupt gate, ring 0, present).
|
||||
- `load()` — `unsafe fn`: вызывает `lidt` с `&self`.
|
||||
|
||||
---
|
||||
|
||||
## Обработчики прерываний
|
||||
|
||||
**Файл**: `src/cpu/interrupts.rs`
|
||||
|
||||
### Глобальная IDT
|
||||
|
||||
```rust
|
||||
pub static mut IDT: InterruptDescriptorTable;
|
||||
```
|
||||
|
||||
### Константы
|
||||
|
||||
`TLB_SHOOTDOWN_VECTOR: u8 = 0xFD`
|
||||
|
||||
### Ассемблерные заглушки (global_asm!)
|
||||
|
||||
#### `page_fault_stub`
|
||||
|
||||
```asm
|
||||
save all registers (push rax, rcx, rdx, rbx, rbp, rsi, rdi, r8-r15)
|
||||
mov rdi, [rsp + 15*8] ; error code из стека → 1-й аргумент
|
||||
call rust_page_fault_handler
|
||||
restore all registers
|
||||
add rsp, 8 ; убрать error code
|
||||
iretq
|
||||
```
|
||||
|
||||
#### `tlb_shootdown_stub`
|
||||
|
||||
```asm
|
||||
save all registers
|
||||
call rust_tlb_shootdown_handler
|
||||
restore all registers
|
||||
iretq
|
||||
```
|
||||
|
||||
### `init_idt()`
|
||||
|
||||
- Устанавливает вектор 14 → `page_fault_stub`
|
||||
- Устанавливает вектор `TLB_SHOOTDOWN_VECTOR` → `tlb_shootdown_stub`
|
||||
- `IDT.load()`
|
||||
|
||||
### `process_deferred_mmu_events()`
|
||||
|
||||
Лочит `KERNEL_SPACE`, вызывает `process_pending_revocations()` — дренаж очереди отзыва.
|
||||
|
||||
### `rust_page_fault_handler(error_code: u64)`
|
||||
|
||||
`#[unsafe(no_mangle)] pub extern "C" fn`
|
||||
|
||||
1. Читает CR2 (адрес fault)
|
||||
2. Из error_code: бит 1 = write, бит 0 = present
|
||||
3. **Сначала** `process_deferred_mmu_events()` — обработка ревокаций до захвата VMM (deadlock prevention)
|
||||
4. Лочит `KERNEL_SPACE`, вызывает `handle_fault(virt_addr, write)`
|
||||
5. При неудаче: паника с деталями (address, read/write, present, error_code)
|
||||
6. При успехе: возврат — инструкция будет перезапущена
|
||||
|
||||
### `rust_tlb_shootdown_handler()`
|
||||
|
||||
1. `handle_tlb_shootdown_ipi()` — локальный сброс TLB
|
||||
2. `send_eoi()` — подтверждение LAPIC
|
||||
|
||||
---
|
||||
|
||||
## Local APIC
|
||||
|
||||
**Файл**: `src/cpu/lapic.rs`
|
||||
|
||||
Драйвер Local APIC (Advanced Programmable Interrupt Controller).
|
||||
|
||||
### Константы
|
||||
|
||||
```rust
|
||||
LAPIC_DEFAULT_BASE = 0xFEE0_0000 // стандартный MMIO адрес LAPIC
|
||||
LAPIC_EOI = 0x0B0 // End-Of-Interrupt регистр
|
||||
LAPIC_ICR_LOW = 0x300 // Interrupt Command Register (low)
|
||||
```
|
||||
|
||||
### `LAPIC_VIRT_BASE: AtomicU64`
|
||||
|
||||
Кэшированный виртуальный адрес LAPIC MMIO (HHDM + default base).
|
||||
|
||||
### Функции
|
||||
|
||||
| Функция | Описание |
|
||||
|---|---|
|
||||
| `init(hhdm_offset)` | Вычисляет и сохраняет `LAPIC_VIRT_BASE = 0xFEE0_0000 + hhdm_offset` |
|
||||
| `current_core_id() -> u32` | CPUID leaf 1, EBX[31:24] — Initial APIC ID |
|
||||
| `write_lapic_reg(offset, value)` | Volatile write u32 в LAPIC MMIO |
|
||||
| `send_eoi()` | `write_lapic_reg(LAPIC_EOI, 0)` — подтверждение прерывания |
|
||||
| `broadcast_ipi_exclude_self(vector)` | IPI всем остальным ядрам через ICR (destination shorthand = 10b = all except self) |
|
||||
98
kernel/docs/main.md
Normal file
98
kernel/docs/main.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Точка входа: `src/main.rs`
|
||||
|
||||
Точка входа ядра (`kmain`). Инициализирует оборудование, подсистемы памяти, запускает тесты capability и PMActor.
|
||||
|
||||
## Атрибуты крейта
|
||||
|
||||
```rust
|
||||
#![no_std] // без стандартной библиотеки
|
||||
#![no_main] // без точки входа из libc
|
||||
extern crate alloc; // аллокатор кучи
|
||||
```
|
||||
|
||||
## Константы
|
||||
|
||||
- `KERNEL_FONT: &[u8]` — встроенный PSF2 шрифт 8×16 на 512 глифов (включён через `include_bytes!("font.psf")`)
|
||||
|
||||
## Limine Boot Protocol Request
|
||||
|
||||
Статические переменные в секции `.requests` (через `#[unsafe(link_section = ".requests")]`):
|
||||
|
||||
| Переменная | Тип | Назначение |
|
||||
|---|---|---|
|
||||
| `MEMORY_MAP_REQUEST` | `MemoryMapRequest` | Карта физической памяти |
|
||||
| `HHDM_REQUEST` | `HhdmRequest` | Higher Half Direct Map offset |
|
||||
| `KERNEL_ADDR_REQUEST` | `ExecutableAddressRequest` | Базовые адреса ядра (physical + virtual) |
|
||||
| `BASE_REVISION` | `BaseRevision` | Проверка версии Limine |
|
||||
| `FRAMEBUFFER_REQUEST` | `FramebufferRequest` | Линейный фреймбуфер |
|
||||
| `_START_MARKER` | `RequestsStartMarker` | Маркер начала секции requests |
|
||||
| `_END_MARKER` | `RequestsEndMarker` | Маркер конца секции requests |
|
||||
|
||||
---
|
||||
|
||||
## `FramebufferDisplay<'a>`
|
||||
|
||||
Низкоуровневый рендерер пикселей, обёртка над `limine::framebuffer::Framebuffer`.
|
||||
|
||||
**impl `DrawTarget` (embedded_graphics):**
|
||||
- `Color = Rgb888`, `Error = Infallible`
|
||||
- `draw_iter(pixels)` — запись пикселей в память фреймбуфера с проверкой границ
|
||||
|
||||
**impl `OriginDimensions`:**
|
||||
- `size()` — размеры фреймбуфера
|
||||
|
||||
---
|
||||
|
||||
## `Console<'a>` (встроенный, НЕ используется)
|
||||
|
||||
Обёртка над FramebufferDisplay. **В `kmain` используется `tty::Console`**, этот — мёртвый код.
|
||||
|
||||
Поля: `display`, `x`, `y`, `current_color`.
|
||||
Методы: `new()`, `set_color()`, `clear()`, `scroll()`, `write_str()`.
|
||||
|
||||
---
|
||||
|
||||
## `kmain()` — точка входа
|
||||
|
||||
`unsafe extern "C" fn() -> !`
|
||||
|
||||
1. **Проверка `BASE_REVISION.is_supported()`** — убеждается, что загрузчик поддерживает нужную версию Limine
|
||||
2. **Получение ответов Limine**: фреймбуфер, карта памяти, HHDM offset, адреса ядра
|
||||
3. **Инициализация `tty::Console`** — создаёт консоль на первом фреймбуфере, чистит экран
|
||||
4. **Инициализация Serial** — `debug::serial::init_global()` для отладки через COM1
|
||||
5. **Вывод "BOOT"** через `info!` макрос
|
||||
6. **Инициализация BitmapPMM** — из карты памяти и HHDM смещения
|
||||
7. **Инициализация LAPIC** — `cpu::lapic::init(hhdm_offset)` для MMIO доступа к Local APIC
|
||||
8. **Создание P4 (PML4)** — выделяет фрейм, зануляет его
|
||||
9. **Картирование HHDM** — все non-reserved entry памяти маппятся identity + HHDM
|
||||
10. **Картирование .text секции ядра** — по виртуальному базовому адресу
|
||||
11. **Активация страничных таблиц** — `p4.activate(p4_phys)` — запись CR3
|
||||
12. **Выделение кучи 8 MiB** — постранично маппится в `0xFFFF_9000_0000_0000`
|
||||
13. **Инициализация SlabAllocator** — `ALLOCATOR.init()` с диапазоном кучи
|
||||
14. **`init_cpu_features()`** — обнаружение INVPCID инструкции
|
||||
15. **Регистрация адресного пространства ядра** — `vmm::init_kernel_space()`
|
||||
16. **Инициализация IDT** — регистрация обработчиков page fault и TLB shootdown
|
||||
17. **Инициализация PMRouter** — 65536 каналов маршрутизации
|
||||
18. **Включение прерываний** — `sti` инструкция
|
||||
19. **Тест capability системы**: создание CNode, insert/mint/revoke
|
||||
20. **PMActor + Buddy тест** (строки 257-359): создание актора, аллокация, OOM, carve, free+coalescing
|
||||
21. **Вывод ASCII-арт логотипа**
|
||||
22. **Бесконечный HLT** — `hcf()`
|
||||
|
||||
---
|
||||
|
||||
## `hcf()`
|
||||
|
||||
```rust
|
||||
fn hcf() -> ! {
|
||||
loop { x86_64::instructions::hlt(); }
|
||||
}
|
||||
```
|
||||
|
||||
Бесконечный цикл HLT — снижает энергопотребление, процессор ждёт следующего прерывания.
|
||||
|
||||
---
|
||||
|
||||
## `rust_panic(info: &PanicInfo) -> !`
|
||||
|
||||
Обработчик паники: пишет информацию в serial, затем вызывает `hcf()`.
|
||||
243
kernel/docs/memory-management.md
Normal file
243
kernel/docs/memory-management.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Управление физической памятью
|
||||
|
||||
## Содержание
|
||||
|
||||
1. [Адресация (PhysAddr/VirtAddr)](#physaddr--virtaddr)
|
||||
2. [BitmapPMM — менеджер физических фреймов](#bitmappmm)
|
||||
3. [BuddyAllocator — блочный аллокатор](#buddyallocator)
|
||||
4. [Page Tables — страничные таблицы x86-64](#page-tables)
|
||||
|
||||
---
|
||||
|
||||
## PhysAddr / VirtAddr
|
||||
|
||||
**Файл**: `src/mem/address.rs`
|
||||
|
||||
Типы-обёртки над `u64` с HHDM-трансляцией.
|
||||
|
||||
### `PhysAddr(pub u64)` — `#[repr(transparent)]`
|
||||
|
||||
| Метод | Описание |
|
||||
|---|---|
|
||||
| `to_virt(hhdm_offset) -> VirtAddr` | `PhysAddr + hhdm_offset` — вход в HHDM |
|
||||
| `is_aligned() -> bool` | Проверка 4 KiB выравнивания |
|
||||
| `align_down() -> Self` | Выравнивание вниз до границы страницы |
|
||||
| `align_up() -> Self` | Выравнивание вверх до границы страницы |
|
||||
|
||||
### `VirtAddr(pub u64)` — `#[repr(transparent)]`
|
||||
|
||||
| Метод | Описание |
|
||||
|---|---|
|
||||
| `to_phys(hhdm_offset) -> Option<PhysAddr>` | `VirtAddr - hhdm_offset`, возвращает None если адрес ниже HHDM |
|
||||
| `as_ptr::<T>() -> *const T` | Приведение к константному указателю |
|
||||
| `as_mut_ptr::<T>() -> *mut T` | Приведение к mutable указателю |
|
||||
|
||||
---
|
||||
|
||||
## BitmapPMM
|
||||
|
||||
**Файл**: `src/mem/pmm.rs`
|
||||
|
||||
Первичный менеджер физической памяти через битовую карту + счётчики ссылок.
|
||||
|
||||
### Структура
|
||||
|
||||
```rust
|
||||
pub struct BitmapPMM {
|
||||
bitmap: &'static mut [u8], // один бит на страницу (1 = занята)
|
||||
ref_counts: &'static mut [u16], // счётчики ссылок для COW/shared
|
||||
total_pages: usize, // всего физических страниц в системе
|
||||
used_pages: usize, // количество выделенных страниц
|
||||
last_byte: usize, // подсказка для следующего сканирования
|
||||
}
|
||||
```
|
||||
|
||||
Глобальная: `pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None)`.
|
||||
|
||||
### `init(mmap, hhdm_offset)`
|
||||
|
||||
Полный поток инициализации:
|
||||
|
||||
1. Вычисляет макс. физический адрес из карты памяти Limine
|
||||
2. Рассчитывает размер bitmap и ref_counts
|
||||
3. Находит USABLE регион для хранения метаданных
|
||||
4. Зануляет bitmap, все биты = 1 (все заняты), ref_counts = 1
|
||||
5. Проходит по карте памяти: все USABLE → `free_frame()`
|
||||
6. **Лочит** страницы метаданных (чтобы не выдать повторно)
|
||||
7. **Лочит** страницу 0 (зарезервирована)
|
||||
8. Сохраняет в глобальную `PMM`
|
||||
|
||||
### `free_frame(addr)`
|
||||
|
||||
- Декрементит `ref_count`
|
||||
- Если `ref_count == 0`: очищает бит, декрементит `used_pages`, обновляет `last_byte`
|
||||
|
||||
### `lock_frame(addr)`
|
||||
|
||||
- Если страница свободна: бит = 1, `ref_count = 1`, `used_pages++`
|
||||
- Если страница занята и `ref_count` был 0: `ref_count = 1` (восстановление)
|
||||
|
||||
### `inc_ref_frame(addr)`
|
||||
|
||||
- Инкрементит `ref_count` (с насыщением) — для COW
|
||||
|
||||
### `alloc_frame() -> Option<PhysAddr>`
|
||||
|
||||
**Двухпроходный сканер:**
|
||||
|
||||
1. Проход 1: от `last_byte` до конца bitmap
|
||||
2. Проход 2: от 0 до `last_byte`
|
||||
|
||||
Сканирует побайтово в поисках байта с нулевым битом (`!0xFF`), внутри байта через `trailing_ones`. Устанавливает бит, `ref_count = 1`, `used_pages++`, обновляет `last_byte`.
|
||||
|
||||
### `alloc_contiguous(count) -> Option<PhysAddr>`
|
||||
|
||||
Линейный поиск `count` последовательных свободных страниц.
|
||||
|
||||
### Функции-обёртки
|
||||
|
||||
- `alloc_frame()`, `alloc_contiguous(count)`, `free_frame(addr)`, `inc_ref_frame(addr)` — захватывают `PMM.lock()`
|
||||
- `get_stats() -> (usize, usize)` — возвращает `(used, total)`
|
||||
|
||||
---
|
||||
|
||||
## BuddyAllocator
|
||||
|
||||
**Файл**: `src/mem/buddy.rs`
|
||||
|
||||
Power-of-two блочный аллокатор для per-actor управления физической памятью.
|
||||
|
||||
### Константы
|
||||
|
||||
- `MAX_ORDER = 11` — макс. порядок (2^11 = 2048 страниц = 8 MiB)
|
||||
|
||||
### `order_for(page_count) -> usize`
|
||||
|
||||
Вычисляет минимальное k такое, что `2^k >= page_count`.
|
||||
|
||||
```rust
|
||||
order_for(1) = 0 // 2^0 = 1
|
||||
order_for(3) = 2 // 2^2 = 4 >= 3
|
||||
order_for(2048) = 11 // 2^11 = 2048
|
||||
```
|
||||
|
||||
### Структура
|
||||
|
||||
```rust
|
||||
pub struct BuddyAllocator {
|
||||
free_lists: [Vec<usize>; MAX_ORDER + 1], // списки блоков по порядкам
|
||||
total_pages: usize, // всего страниц
|
||||
free_pages: usize, // свободно страниц
|
||||
}
|
||||
```
|
||||
|
||||
Инвариант: `free_pages == Σ 2^k * len(free_lists[k])`.
|
||||
|
||||
### `new(total_pages) -> Self`
|
||||
|
||||
**Жадное разложение** диапазона на блоки максимального размера:
|
||||
|
||||
- Итерация по диапазону
|
||||
- На каждой позиции вычисляет макс. порядок (ограничен alignment через `trailing_zeros` и остатком)
|
||||
- Добавляет блок в соответствующий `free_list`
|
||||
|
||||
Пример: 6 страниц → блок 4 (order 2) + блок 2 (order 1).
|
||||
|
||||
### `alloc(order) -> Option<usize>`
|
||||
|
||||
1. Находит наименьший `k >= order` с непустым `free_list[k]`
|
||||
2. Pop с конца (O(1))
|
||||
3. **Разбивает** до нужного порядка: при каждом разбиении buddy кладётся в список порядком ниже
|
||||
4. Возвращает индекс
|
||||
|
||||
### `alloc_pages(page_count) -> Option<(usize, usize)>`
|
||||
|
||||
Округляет page_count до степени двойки, вызывает `alloc()`. Возвращает `(index, order)`.
|
||||
|
||||
### `free(block_idx, order)`
|
||||
|
||||
1. Добавляет блок в `free_list[order]`
|
||||
2. **Coalescing** на каждом уровне:
|
||||
- `buddy_idx = block_idx ^ (1 << order)` (XOR)
|
||||
- Ищет buddy в `free_list[order]` (линейно, `swap_remove`)
|
||||
- Если найден: удаляет его, объединяет, переходит на уровень выше
|
||||
- Если не найден или вне диапазона: стоп
|
||||
|
||||
### Интроспекция
|
||||
|
||||
- `free_pages()`, `total_pages()`, `is_exhausted()`, `stats()` (per-order counts)
|
||||
|
||||
---
|
||||
|
||||
## Page Tables
|
||||
|
||||
**Файл**: `src/mem/paging.rs`
|
||||
|
||||
4-уровневая страничная таблица x86-64: P4 → P3 → P2 → P1 (512 entry × 8 байт = 4 KiB каждая).
|
||||
|
||||
### `PageTableFlags` (bitflags)
|
||||
|
||||
| Флаг | Бит | Описание |
|
||||
|---|---|---|
|
||||
| `PRESENT` | 0 | Страница в памяти |
|
||||
| `WRITABLE` | 1 | Разрешена запись |
|
||||
| `USER` | 2 | Доступ из ring 3 |
|
||||
| `WRITE_THROUGH` | 3 | Write-through кэширование |
|
||||
| `NO_CACHE` | 4 | Отключение кэширования |
|
||||
| `ACCESSED` | 5 | Было обращение |
|
||||
| `DIRTY` | 6 | Была запись |
|
||||
| `HUGE_PAGE` | 7 | 2 MiB (P2) или 1 GiB (P3) |
|
||||
| `GLOBAL` | 8 | Не сбрасывается при перезагрузке CR3 |
|
||||
| `COW` | 9 | **Кастомный** — Copy-on-Write |
|
||||
| `NO_EXECUTE` | 63 | Запрет исполнения (NX bit) |
|
||||
|
||||
### `PageTable` — `#[repr(C, align(4096))]`
|
||||
|
||||
Поле: `entries: [u64; 512]`
|
||||
|
||||
### Методы
|
||||
|
||||
#### `map_page(virt, phys, flags, hhdm)`
|
||||
|
||||
1. Walk/create P4 → P3 → P2 → P1 (создаёт отсутствующие)
|
||||
2. Записывает PTE = `phys | flags`
|
||||
3. `invlpg`
|
||||
|
||||
#### `get_or_create_next_table(index, hhdm, create) -> Option<&mut PageTable>`
|
||||
|
||||
- Читает PTE, проверяет PRESENT
|
||||
- Если есть: `&mut *(hhdm + phys_addr)` (HHDM dereference)
|
||||
- Если нет и `create == true`: `pmm_alloc()`, зануляет, ставит entry с `PRESENT|WRITABLE|USER`, возвращает
|
||||
- Если нет и `create == false`: None
|
||||
|
||||
#### `walk_to_p1_mut(virt, hhdm, create) -> Option<&mut PageTable>`
|
||||
|
||||
Из virtual address вычисляет индексы P4[47:39], P3[38:30], P2[29:21], P1[20:12], проходит цепочку.
|
||||
|
||||
#### `unmap_page(virt, hhdm)`
|
||||
|
||||
Walk (без create), зануляет PTE. `invlpg`. Идемпотентен.
|
||||
|
||||
#### `translate(virt, hhdm) -> Option<PhysAddr>`
|
||||
|
||||
Walk 4 уровней. Поддерживает huge pages (1 GiB на P3, 2 MiB на P2). Возвращает PhysAddr + page offset.
|
||||
|
||||
#### `map_region(virt, phys, size, flags, hhdm)`
|
||||
|
||||
Цикл по 4 KiB страницам, каждая → `map_page()`.
|
||||
|
||||
#### `get_flags(virt, hhdm) -> Option<PageTableFlags>`
|
||||
|
||||
Walk, возвращает флаги PTE (с поддержкой huge pages).
|
||||
|
||||
#### `update_flags(virt, flags, hhdm) -> Result<(), ()>`
|
||||
|
||||
Walk, обновляет флаги, сохраняя физический адрес. `invlpg`.
|
||||
|
||||
#### `activate(phys_addr)`
|
||||
|
||||
`unsafe`: запись CR3 = phys_addr → полный сброс TLB.
|
||||
|
||||
### `pmm_alloc() -> Option<PhysAddr>`
|
||||
|
||||
Обёртка над `PMM.lock().unwrap().alloc_frame()` — разрывает циклическую зависимость между paging и pmm.
|
||||
173
kernel/docs/pmactor.md
Normal file
173
kernel/docs/pmactor.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Акторы физической памяти (PMActor) и маршрутизатор (PM Router)
|
||||
|
||||
---
|
||||
|
||||
## Формат сообщений
|
||||
|
||||
Вся коммуникация использует упаковку в `u64`:
|
||||
|
||||
```
|
||||
[8b opcode | 16b channel_id | 20b arg1 | 20b arg2]
|
||||
```
|
||||
|
||||
Константы: `OPCODE_SHIFT = 56`, `CHAN_SHIFT = 40`, `ARG1_SHIFT = 20`
|
||||
Маски: `CHAN_MASK = 0x0000_FFFF`, `ARG_MASK = 0x000F_FFFF`
|
||||
|
||||
---
|
||||
|
||||
## PMActor
|
||||
|
||||
**Файл**: `src/mem/pm_manages.rs`
|
||||
|
||||
Распределённый актор физической памяти. Каждый актор владеет фиксрованным диапазоном и управляет им через BuddyAllocator.
|
||||
|
||||
### PMRequest
|
||||
|
||||
```rust
|
||||
pub enum PMRequest {
|
||||
Allocate { size_pages: usize, token_sig: u32, channel_id: u16 },
|
||||
Free { local_frame_idx: usize, order: usize },
|
||||
Carve { offset_pages: usize, size_pages: usize, channel_id: u16 },
|
||||
None, // sentinel (не может быть отправлен)
|
||||
}
|
||||
```
|
||||
|
||||
Методы: `pack() -> u64`, `unpack(val: u64) -> Self`.
|
||||
|
||||
### PMResult
|
||||
|
||||
```rust
|
||||
pub enum PMResult {
|
||||
Allocated { cap: Capability, order: usize },
|
||||
OutOfMemory { size_pages: usize },
|
||||
Carved { cap: Capability },
|
||||
Freed { pages_returned: usize },
|
||||
}
|
||||
```
|
||||
|
||||
### PMResponse
|
||||
|
||||
```rust
|
||||
pub struct PMResponse {
|
||||
pub channel_id: u16,
|
||||
pub result: PMResult,
|
||||
}
|
||||
```
|
||||
|
||||
### PMActorQueue — Lock-free MPSC
|
||||
|
||||
Кольцевой буфер на 1024 слота (`AtomicU64`) с cache-line padding.
|
||||
|
||||
- `send(req)` — CAS loop на `tail` (multi-producer). Отклоняет None.
|
||||
- `pop()` — single-consumer. `Acquire`/`Release` ordering.
|
||||
|
||||
### PMActor
|
||||
|
||||
```rust
|
||||
pub struct PMActor {
|
||||
actor_id: u64,
|
||||
root_untyped: Capability,
|
||||
managed_range: (PhysAddr, PhysAddr), // [start, end)
|
||||
queue: PMActorQueue,
|
||||
buddy: BuddyAllocator,
|
||||
}
|
||||
```
|
||||
|
||||
#### `new(actor_id, root_cap, start, size_bytes) -> Self`
|
||||
|
||||
Конструктор.
|
||||
|
||||
#### `submit_request(req) -> Result<(), &str>`
|
||||
|
||||
Thread-safe: пушит в `queue`.
|
||||
|
||||
#### `process_messages(&mut self) -> Vec<PMResponse>`
|
||||
|
||||
Дренирует очередь, обрабатывает каждый запрос через приватные методы.
|
||||
|
||||
**Только из контекста актора** (`&mut self` гарантирует single consumer).
|
||||
|
||||
#### Приватные обработчики
|
||||
|
||||
**`handle_allocate(size_pages, token_sig, channel_id) -> Option<PMResponse>`**
|
||||
|
||||
1. `buddy.alloc_pages(size_pages)` → `(local_idx, order)`
|
||||
2. Строит сильную дочернюю Capability с уникальным `token_sig`
|
||||
3. Возвращает `Allocated { cap, order }`
|
||||
4. При неудаче: `OutOfMemory` (планируется ballooning)
|
||||
|
||||
**`handle_free(local_frame_idx, order) -> Option<PMResponse>`**
|
||||
|
||||
1. Валидация границ
|
||||
2. `buddy.free(local_frame_idx, order)`
|
||||
3. Возвращает None (маршрутизация не нужна)
|
||||
|
||||
**`handle_carve(offset_pages, size_pages, channel_id) -> Option<PMResponse>`**
|
||||
|
||||
1. Валидация диапазона
|
||||
2. Возвращает `Carved { cap }`
|
||||
3. Не взаимодействует с buddy
|
||||
|
||||
---
|
||||
|
||||
## PMRouter
|
||||
|
||||
**Файл**: `src/mem/pm_router.rs`
|
||||
|
||||
Глобальная ткань маршрутизации: 65536 lock-free каналов.
|
||||
|
||||
### Константы
|
||||
|
||||
- `CHANNEL_COUNT = 65536`
|
||||
- Состояния: `STATE_FREE = 0`, `STATE_PENDING = 1`, `STATE_READY = 2`
|
||||
|
||||
### Channel — `#[repr(align(64))]`
|
||||
|
||||
```rust
|
||||
struct Channel {
|
||||
state: AtomicU8,
|
||||
next_free: AtomicU16,
|
||||
result: UnsafeCell<Option<PMResult>>,
|
||||
}
|
||||
```
|
||||
|
||||
### PMRouter
|
||||
|
||||
```rust
|
||||
pub struct PMRouter {
|
||||
channels: Box<[Channel]>, // 65536 каналов
|
||||
free_head: AtomicU16, // голова free list (0 = sentinel)
|
||||
}
|
||||
```
|
||||
|
||||
### GlobalRouter
|
||||
|
||||
```rust
|
||||
static ROUTER: GlobalRouter; // синглтон
|
||||
```
|
||||
|
||||
### `init()`
|
||||
|
||||
1. Создаёт Vec из 65536 каналов, соединённых в linked list (каждый `next_free` указывает на следующий)
|
||||
2. Преобразует в `Box<[Channel]>`
|
||||
3. Сохраняет в `ROUTER.inner`, устанавливает `is_ready`
|
||||
|
||||
### `alloc_channel() -> Option<u16>`
|
||||
|
||||
CAS-цикл на `free_head`: извлекает голову списка, устанавливает `state = PENDING`.
|
||||
|
||||
### `route_responses(responses: Vec<PMResponse>)`
|
||||
|
||||
Для каждого PMResponse с ненулевым `channel_id`: сохраняет `result` в канале, устанавливает `state = READY`.
|
||||
|
||||
### `wait_for_response(id: u16) -> PMResult`
|
||||
|
||||
Spin-wait до `state == READY`. Забирает результат, пушит канал обратно в freelist, возвращает результат.
|
||||
|
||||
### `request_and_wait(actor, req_builder) -> PMResult`
|
||||
|
||||
Convenience: аллоцирует канал, строит запрос с channel_id, отправляет актору, ждёт ответ. (process_messages должен быть вызван внешне.)
|
||||
|
||||
### `dispatch(responses: Vec<PMResponse>)`
|
||||
|
||||
Точка входа для маршрутизации ответов через `PMActor::route_responses()` (вызов на глобальном роутере).
|
||||
212
kernel/docs/vmm.md
Normal file
212
kernel/docs/vmm.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# Виртуальная память (VMM)
|
||||
|
||||
**Файл**: `src/mem/vmm.rs`
|
||||
|
||||
Управление адресными пространствами: PML4 + VMA списки, lazy demand paging, copy-on-write, shared mappings, ASID/PCID, TLB shootdown IPI.
|
||||
|
||||
---
|
||||
|
||||
## Глобальные статики
|
||||
|
||||
| Переменная | Тип | Назначение |
|
||||
|---|---|---|
|
||||
| `ACTIVE_CPUS_MASK` | `AtomicU64` | Битовая маска активных CPU (core 0 по умолчанию) |
|
||||
| `SHOOTDOWN_LOCK` | `Locked<()>` | Блокировка TLB shootdown IPI |
|
||||
| `SHOOTDOWN_ASID` | `AtomicU16` | ASID, подлежащий сбросу |
|
||||
| `SHOOTDOWN_ACK` | `AtomicU64` | Битовая маска подтверждений удалённых ядер |
|
||||
| `INVPCID_SUPPORTED` | `AtomicBool` | Поддержка инструкции INVPCID |
|
||||
| `ASID_ALLOC` | `Locked<AsidAllocator>` | Аллокатор ASID/PCID |
|
||||
| `KERNEL_SPACE` | `Locked<Option<AddressSpace>>` | Адресное пространство ядра |
|
||||
|
||||
---
|
||||
|
||||
## Типы ошибок: `VmError`
|
||||
|
||||
- `OutOfMemory` — нет свободной физической памяти
|
||||
- `RegionOverlap` — VMA перекрывается с существующей
|
||||
- `RegionNotFound` — VMA не найдена
|
||||
- `InvalidAlignment` — неверное выравнивание
|
||||
- `InvalidRange` — некорректный диапазон
|
||||
- `PermissionDenied` — нет прав доступа
|
||||
- `UnexpectedFault` — page fault в не-lazy, не-COW регионе
|
||||
- `NonCanonical` — неканонический адрес
|
||||
- `AsidExhausted` — кончились ASID
|
||||
|
||||
---
|
||||
|
||||
## `VmaBacking` — типы подложки
|
||||
|
||||
```rust
|
||||
pub enum VmaBacking {
|
||||
Anonymous(Vec<Option<PhysAddr>>), // per-page tracking: None = ещё не выделена
|
||||
Physical(PhysAddr), // фиксированный физический диапазон (MMIO, DMA)
|
||||
Shared { owner_cap: u64, phys_base: PhysAddr }, // zero-copy borrow
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `VmaFlags` (bitflags)
|
||||
|
||||
| Флаг | Бит | Описание |
|
||||
|---|---|---|
|
||||
| `READ` | 0 | Чтение |
|
||||
| `WRITE` | 1 | Запись |
|
||||
| `EXEC` | 2 | Исполнение |
|
||||
| `USER` | 3 | Доступ из userspace |
|
||||
| `LAZY` | 4 | Demand paging (фрейм при page fault) |
|
||||
| `SHARED` | 5 | Разделяемая память |
|
||||
| `PINNED` | 6 | Защита от revoke |
|
||||
| `NOCACHE` | 7 | Отключение кэширования |
|
||||
| `MMIO` | 8 | Memory-mapped I/O |
|
||||
| `COW` | 9 | Copy-on-Write |
|
||||
|
||||
**`to_page_flags() -> PageTableFlags`**: преобразует VmaFlags в аппаратные флаги. PRESENT всегда включён, NO_EXECUTE если не EXEC.
|
||||
|
||||
---
|
||||
|
||||
## `AsidAllocator`
|
||||
|
||||
Битовая карта на 4096 ASID/PCID (128 × u32).
|
||||
|
||||
- ASID 0 (ядро) и ASID 4095 (зарезервирован) — заняты постоянно
|
||||
- `alloc() -> Option<u16>` — двухпроходный с пропуском целых слов (амортизированное O(1))
|
||||
- `free(asid)` — сброс бита
|
||||
|
||||
---
|
||||
|
||||
## `VmaRegion`
|
||||
|
||||
```rust
|
||||
pub struct VmaRegion {
|
||||
virt_start: VirtAddr,
|
||||
virt_end: VirtAddr,
|
||||
flags: VmaFlags,
|
||||
cap_token: u64,
|
||||
backing: VmaBacking,
|
||||
}
|
||||
```
|
||||
|
||||
Методы: `contains(addr)`, `size()`, `pages()`.
|
||||
|
||||
---
|
||||
|
||||
## `AddressSpace`
|
||||
|
||||
```rust
|
||||
pub struct AddressSpace {
|
||||
asid: u16,
|
||||
pml4_phys: PhysAddr,
|
||||
regions: Vec<VmaRegion>, // sorted by virt_start, non-overlapping
|
||||
hhdm: u64,
|
||||
}
|
||||
```
|
||||
|
||||
### Создание
|
||||
|
||||
| Метод | Описание |
|
||||
|---|---|
|
||||
| `new(hhdm)` | Выделяет PML4 (занулённый), аллоцирует ASID |
|
||||
| `from_active(pml4_phys, asid, hhdm)` | Обёртка для существующей PML4 (ядро, ASID=0) |
|
||||
|
||||
### `map_region(virt, phys, size, flags, cap_token) -> Result<VirtAddr>`
|
||||
|
||||
1. Валидация выравнивания (4 KiB) и проверка перекрытий (`check_overlap`)
|
||||
2. Создание `VmaBacking`:
|
||||
- **LAZY** → `Anonymous(vec![None; page_count])`
|
||||
- **Physical** (если `phys` не None и нет LAZY) → `Physical(phys)`
|
||||
- **Eager anonymous**: цикл по страницам, `pmm_alloc() + zero + map`
|
||||
3. Вставка в `regions` с сохранением сортировки (`insert_sorted`)
|
||||
|
||||
### `map_shared(virt, phys_base, page_count, flags, owner_cap)`
|
||||
|
||||
Eager маппинг физического диапазона без владения фреймами (не освобождаются при unmap).
|
||||
|
||||
### `handle_fault(fault_addr, write) -> Result<(), VmError>`
|
||||
|
||||
Обработка page fault:
|
||||
|
||||
1. `find_idx(fault_addr)` — бинарный поиск региона
|
||||
2. Проверка прав: write fault в WRITE-регионе
|
||||
3. **COW**: если флаг COW и пишем:
|
||||
- Выделяет новый фрейм
|
||||
- Копирует содержимое старого
|
||||
- Обновляет PTE: убирает COW, добавляет WRITABLE
|
||||
4. **LAZY**: если флаг LAZY:
|
||||
- Выделяет фрейм, зануляет, маппит
|
||||
- Обновляет `Vec<Option<PhysAddr>>`
|
||||
5. Иначе: `Err(UnexpectedFault)`
|
||||
|
||||
### `unmap_region(virt) -> Result<(), VmError>`
|
||||
|
||||
1. `find_idx(virt)` → регион
|
||||
2. `do_unmap(region)`: анмаппит PTE, освобождает фреймы если Anonymous
|
||||
3. Удаляет регион из списка
|
||||
4. `tlb_flush_asid(self.asid)`
|
||||
|
||||
### `revoke_by_token(cap_token)`
|
||||
|
||||
Анмаппит все VMA с совпадающим `cap_token` (кроме PINNED). Сливает TLB.
|
||||
|
||||
### `process_pending_revocations(&mut self)`
|
||||
|
||||
1. Дренирует `MMU_REVOCATION_QUEUE` (pop в цикле)
|
||||
2. Для каждого токена → `revoke_by_token(token)`
|
||||
3. TLB flush если была хотя бы одна ревокация
|
||||
|
||||
### `clone_for_fork(child_cap_token) -> Result<Self, VmError>`
|
||||
|
||||
Создание дочернего адресного пространства (fork):
|
||||
|
||||
- **Physical/Shared**: маппинг напрямую
|
||||
- **Anonymous**: **COW setup**:
|
||||
- Убирает WRITABLE из PTE родителя
|
||||
- Устанавливает COW флаг в обоих PTEs
|
||||
- `inc_ref_frame()` для всех фреймов
|
||||
|
||||
### `translate(virt) -> Option<PhysAddr>`
|
||||
|
||||
Аппаратный walk страничной таблицы (через `PageTable::translate`).
|
||||
|
||||
### `activate()`
|
||||
|
||||
`unsafe`: запись CR3 = `pml4_phys | asid | (1<<63)`. Бит 63 = NOFLUSH — сохраняет PCID-тегированные записи TLB.
|
||||
|
||||
### `Drop`
|
||||
|
||||
1. Анмаппит все регионы
|
||||
2. Освобождает PML4 фрейм через `free_frame`
|
||||
3. Возвращает ASID в пул
|
||||
|
||||
---
|
||||
|
||||
## TLB Management
|
||||
|
||||
### `local_tlb_flush_asid(asid)`
|
||||
|
||||
- INVPCID type 1 если поддерживается (single-context flush)
|
||||
- Иначе `tlb_flush_all()`
|
||||
|
||||
### `tlb_flush_asid(asid)`
|
||||
|
||||
1. `local_tlb_flush_asid(asid)` — локальный сброс
|
||||
2. Устанавливает глобальные статики: `SHOOTDOWN_ASID = asid`, `SHOOTDOWN_ACK = 0`
|
||||
3. `broadcast_ipi_exclude_self(TLB_SHOOTDOWN_VECTOR)` — IPI всем остальным ядрам
|
||||
4. Spin-loop: `while SHOOTDOWN_ACK != ACTIVE_CPUS_MASK { hlt(); }`
|
||||
|
||||
### `tlb_flush_all()`
|
||||
|
||||
Читает CR3 → перезаписывает без бита 63 → полный сброс всех non-global entries.
|
||||
|
||||
### `handle_tlb_shootdown_ipi()`
|
||||
|
||||
1. `local_tlb_flush_asid(SHOOTDOWN_ASID.load())`
|
||||
2. Устанавливает свой бит в `SHOOTDOWN_ACK` (через `fetch_or`)
|
||||
|
||||
### `init_cpu_features()`
|
||||
|
||||
CPUID leaf 7, проверка EBX[10] (INVPCID). Сохраняет в `INVPCID_SUPPORTED`.
|
||||
|
||||
### `init_kernel_space(pml4_phys, hhdm)`
|
||||
|
||||
Регистрирует PML4 ядра как `KERNEL_SPACE` с ASID 0.
|
||||
Reference in New Issue
Block a user