feat: docs & ARCH 2.2, 2.3, 2.4
This commit is contained in:
@@ -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 |
|
||||
@@ -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
|
||||
@@ -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
|
||||
152
kernel/docs/boot/introduction.md
Normal file
152
kernel/docs/boot/introduction.md
Normal 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
151
kernel/docs/boot/linker.md
Normal 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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
146
kernel/docs/capability/cnode.md
Normal file
146
kernel/docs/capability/cnode.md
Normal 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**: если очередь отзыва переполнена — паника.
|
||||
Это критический сбой подсистемы ресурсов.
|
||||
115
kernel/docs/capability/descriptors.md
Normal file
115
kernel/docs/capability/descriptors.md
Normal 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`).
|
||||
126
kernel/docs/capability/introduction.md
Normal file
126
kernel/docs/capability/introduction.md
Normal 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);
|
||||
```
|
||||
73
kernel/docs/capability/objects.md
Normal file
73
kernel/docs/capability/objects.md
Normal 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 | — |
|
||||
@@ -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
115
kernel/docs/cpu/idt.md
Normal 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 (один стек для
|
||||
всех обработчиков).
|
||||
200
kernel/docs/cpu/interrupts.md
Normal file
200
kernel/docs/cpu/interrupts.md
Normal 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.
|
||||
106
kernel/docs/cpu/introduction.md
Normal file
106
kernel/docs/cpu/introduction.md
Normal 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
115
kernel/docs/cpu/lapic.md
Normal 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 доставляет
|
||||
прерывание. Запись блокирующая (ждёт готовности шины).
|
||||
88
kernel/docs/debug/introduction.md
Normal file
88
kernel/docs/debug/introduction.md
Normal 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
110
kernel/docs/debug/serial.md
Normal 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
|
||||
}
|
||||
```
|
||||
@@ -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`.
|
||||
128
kernel/docs/events/introduction.md
Normal file
128
kernel/docs/events/introduction.md
Normal 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 дренирует очередь).
|
||||
1055
kernel/docs/glossary/terms.md
Normal file
1055
kernel/docs/glossary/terms.md
Normal file
File diff suppressed because it is too large
Load Diff
70
kernel/docs/index.md
Normal file
70
kernel/docs/index.md
Normal 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` |
|
||||
@@ -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) |
|
||||
@@ -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()`.
|
||||
@@ -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.
|
||||
103
kernel/docs/memory/address-translation.md
Normal file
103
kernel/docs/memory/address-translation.md
Normal 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.
|
||||
211
kernel/docs/memory/allocator.md
Normal file
211
kernel/docs/memory/allocator.md
Normal 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
267
kernel/docs/memory/buddy.md
Normal 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.
|
||||
82
kernel/docs/memory/introduction.md
Normal file
82
kernel/docs/memory/introduction.md
Normal 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
|
||||
```
|
||||
196
kernel/docs/memory/paging.md
Normal file
196
kernel/docs/memory/paging.md
Normal 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.
|
||||
132
kernel/docs/memory/physical-memory.md
Normal file
132
kernel/docs/memory/physical-memory.md
Normal 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 на этапе инициализации и больше к нему не
|
||||
обращаются.
|
||||
258
kernel/docs/memory/pm-actor.md
Normal file
258
kernel/docs/memory/pm-actor.md
Normal 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 глубок.
|
||||
189
kernel/docs/memory/pm-router.md
Normal file
189
kernel/docs/memory/pm-router.md
Normal 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
267
kernel/docs/memory/vmm.md
Normal 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 1–4094: пользовательские.
|
||||
|
||||
`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
|
||||
}
|
||||
```
|
||||
105
kernel/docs/overview/architecture.md
Normal file
105
kernel/docs/overview/architecture.md
Normal 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** — загружаем базовые обработчики исключений (0–31).
|
||||
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
|
||||
```
|
||||
72
kernel/docs/overview/introduction.md
Normal file
72
kernel/docs/overview/introduction.md
Normal 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`.
|
||||
@@ -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
150
kernel/docs/tty.md
Normal 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.
|
||||
@@ -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.
|
||||
@@ -1,6 +1,7 @@
|
||||
// src/cpu/lapic.rs
|
||||
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
use crate::mem::address::get_hhdm;
|
||||
|
||||
pub const LAPIC_DEFAULT_BASE: u64 = 0xFEE00_000;
|
||||
const LAPIC_EOI: u64 = 0x0B0;
|
||||
@@ -8,8 +9,8 @@ const LAPIC_ICR_LOW: u64 = 0x300;
|
||||
|
||||
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
pub fn init(hhdm_offset: u64) {
|
||||
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + hhdm_offset, Ordering::SeqCst);
|
||||
pub fn init() {
|
||||
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + get_hhdm(), Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::mem::paging::{PageTable, PageTableFlags};
|
||||
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||
use crate::cap::{Relation, CapRights, Capability, CapObject};
|
||||
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
|
||||
use crate::mem::buddy::BuddyAllocator;
|
||||
|
||||
pub mod cap;
|
||||
pub mod cpu;
|
||||
@@ -175,6 +176,7 @@ unsafe extern "C" fn kmain() -> ! {
|
||||
let hhdm_res = HHDM_REQUEST.get_response().expect("Limine: No HHDM");
|
||||
let kaddr_res = KERNEL_ADDR_REQUEST.get_response().expect("Limine: No Kernel Address");
|
||||
let hhdm_offset = hhdm_res.offset();
|
||||
crate::mem::address::init_hhdm(hhdm_offset);
|
||||
|
||||
let fb = fb_res.framebuffers().next().expect("Limine: No active framebuffer found");
|
||||
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
||||
@@ -185,17 +187,17 @@ unsafe extern "C" fn kmain() -> ! {
|
||||
cpu::interrupts::init_early_exceptions();
|
||||
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
||||
|
||||
unsafe { mem::pmm::BitmapPMM::init(&mmap_res, hhdm_offset); }
|
||||
unsafe { mem::pmm::BitmapPMM::init(&mmap_res); }
|
||||
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
|
||||
|
||||
cpu::lapic::init(hhdm_offset);
|
||||
cpu::lapic::init();
|
||||
info!(console, "LAPIC", "Local APIC initialized.");
|
||||
|
||||
// debug: locate the free page
|
||||
info!(console, "BOOT", "alloc_frame...");
|
||||
let p4_phys = mem::pmm::alloc_frame().expect("OOM: Failed to allocate P4 table");
|
||||
info!(console, "BOOT", "alloc_frame ok: phys=0x{:x}", p4_phys.0);
|
||||
let virt = p4_phys.to_virt(hhdm_offset);
|
||||
let virt = p4_phys.to_virt();
|
||||
info!(console, "BOOT", "virt=0x{:x}", virt.0);
|
||||
let p4 = unsafe { &mut *virt.as_mut_ptr::<PageTable>() };
|
||||
info!(console, "BOOT", "zeroing page...");
|
||||
@@ -206,10 +208,10 @@ unsafe extern "C" fn kmain() -> ! {
|
||||
|
||||
for (i, entry) in mmap_res.entries().iter().enumerate() {
|
||||
let phys = PhysAddr(entry.base);
|
||||
let virt_hhdm = phys.to_virt(hhdm_offset);
|
||||
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
|
||||
let virt_hhdm = phys.to_virt();
|
||||
p4.map_region(virt_hhdm, phys, entry.length, flags);
|
||||
if entry.entry_type != limine::memory_map::EntryType::RESERVED {
|
||||
p4.map_region(VirtAddr(entry.base), phys, entry.length, flags, hhdm_offset);
|
||||
p4.map_region(VirtAddr(entry.base), phys, entry.length, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +220,6 @@ unsafe extern "C" fn kmain() -> ! {
|
||||
PhysAddr(kaddr_res.physical_base()),
|
||||
0x1000 * 1024,
|
||||
flags,
|
||||
hhdm_offset
|
||||
);
|
||||
|
||||
info!(console, "MMU", "Activating Kernel Page Tables...");
|
||||
@@ -228,7 +229,7 @@ unsafe extern "C" fn kmain() -> ! {
|
||||
let heap_size = 8 * 1024 * 1024;
|
||||
for i in (0..heap_size).step_by(4096) {
|
||||
let frame = mem::pmm::alloc_frame().expect("OOM: Heap allocation failed");
|
||||
p4.map_page(VirtAddr(heap_start + i as u64), frame, flags, hhdm_offset);
|
||||
p4.map_page(VirtAddr(heap_start + i as u64), frame, flags);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -238,7 +239,7 @@ unsafe extern "C" fn kmain() -> ! {
|
||||
info!(console, "HEAP", "Kernel Slab Allocator is online.");
|
||||
|
||||
mem::init_cpu_features();
|
||||
mem::vmm::init_kernel_space(p4_phys, hhdm_offset);
|
||||
mem::vmm::init_kernel_space(p4_phys);
|
||||
info!(console, "VMM", "Kernel Address Space registered successfully.");
|
||||
|
||||
cpu::interrupts::init_idt();
|
||||
@@ -367,6 +368,74 @@ unsafe extern "C" fn kmain() -> ! {
|
||||
panic!("CRITICAL STATE LOSS: Memory leak detected inside PMActor context!");
|
||||
}
|
||||
|
||||
// === ARCH 2.2: Buddy Allocator Intrusive List — Direct Test ===
|
||||
info!(console, "PM", "=-= Buddy Allocator Direct Test (Intrusive List) =-=");
|
||||
|
||||
let buddy_test_pages = 128;
|
||||
let buddy_test_base = PhysAddr(0x5000_0000);
|
||||
let mut buddy = BuddyAllocator::new(buddy_test_pages, buddy_test_base.0);
|
||||
|
||||
let b1 = buddy.alloc(0).expect("buddy: alloc order 0");
|
||||
let b2 = buddy.alloc(0).expect("buddy: alloc order 0 #2");
|
||||
buddy.free(b1, 0);
|
||||
buddy.free(b2, 0);
|
||||
assert_eq!(buddy.free_pages(), buddy_test_pages, "buddy: pages not fully recovered after simple alloc/free");
|
||||
info!(console, "PM", " [OK] Simple alloc/free");
|
||||
|
||||
let b3 = buddy.alloc(1).expect("buddy: alloc order 1");
|
||||
assert_eq!(b3, 0, "buddy: first order 1 block at 0");
|
||||
let b4 = buddy.alloc(1).expect("buddy: alloc order 1 #2");
|
||||
assert_eq!(b4, 2, "buddy: second order 1 block at 2");
|
||||
buddy.free(b3, 1);
|
||||
buddy.free(b4, 1);
|
||||
let b5 = buddy.alloc(2).expect("buddy: order 2 after coalescing");
|
||||
assert_eq!(b5, 0, "buddy: coalesced block at 0");
|
||||
buddy.free(b5, 2);
|
||||
info!(console, "PM", " [OK] Coalescing across orders (O(1) intrusive list)");
|
||||
|
||||
let mut allocs = Vec::new();
|
||||
loop {
|
||||
match buddy.alloc(0) {
|
||||
Some(idx) => allocs.push(idx),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
assert!(buddy.is_exhausted(), "buddy: should be exhausted");
|
||||
info!(console, "PM", " [OK] Exhaustion after {} allocs", allocs.len());
|
||||
|
||||
for (i, idx) in allocs.iter().enumerate() {
|
||||
buddy.free(*idx, 0);
|
||||
}
|
||||
assert_eq!(buddy.free_pages(), buddy_test_pages, "buddy: not fully recovered");
|
||||
info!(console, "PM", " [OK] Full recovery after freeing {} pages", allocs.len());
|
||||
|
||||
info!(console, "PM", "~) BUDDY DIRECT TEST PASSED (~");
|
||||
|
||||
// === ARCH 2.3: PMM Tree Bitmap — alloc/free stress test ===
|
||||
info!(console, "PM", "=-= PMM Tree Bitmap Alloc/Free Stress Test =-=");
|
||||
|
||||
const PMM_TEST_COUNT: usize = 64;
|
||||
let mut frames = [PhysAddr(0); PMM_TEST_COUNT];
|
||||
for i in 0..PMM_TEST_COUNT {
|
||||
frames[i] = mem::pmm::alloc_frame().expect("PMM tree: OOM during alloc test");
|
||||
}
|
||||
info!(console, "PM", " [OK] Allocated {} frames", PMM_TEST_COUNT);
|
||||
|
||||
for f in &frames {
|
||||
mem::pmm::free_frame(*f);
|
||||
}
|
||||
info!(console, "PM", " [OK] Freed {} frames back", PMM_TEST_COUNT);
|
||||
|
||||
for i in 0..PMM_TEST_COUNT {
|
||||
frames[i] = mem::pmm::alloc_frame().expect("PMM tree: OOM after free cycle");
|
||||
}
|
||||
info!(console, "PM", " [OK] Re-allocated {} frames after free cycle", PMM_TEST_COUNT);
|
||||
|
||||
for f in &frames {
|
||||
mem::pmm::free_frame(*f);
|
||||
}
|
||||
info!(console, "PM", "~) PMM TREE BITMAP TEST PASSED (~");
|
||||
|
||||
let logo = r#"
|
||||
###########
|
||||
##################
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
static HHDM_OFFSET: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Store the Higher-Half Direct Map offset obtained from the bootloader.
|
||||
/// Must be called once during early boot, before any address translation.
|
||||
pub fn init_hhdm(offset: u64) {
|
||||
HHDM_OFFSET.store(offset, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Return the HHDM offset (kernel virtual base for physical memory).
|
||||
#[inline]
|
||||
pub fn get_hhdm() -> u64 {
|
||||
HHDM_OFFSET.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct PhysAddr(pub u64);
|
||||
@@ -8,8 +24,8 @@ pub struct VirtAddr(pub u64);
|
||||
|
||||
impl PhysAddr {
|
||||
/// Convert physical address to virtual via HHDM offset
|
||||
pub fn to_virt(self, hhdm_offset: u64) -> VirtAddr {
|
||||
VirtAddr(self.0 + hhdm_offset)
|
||||
pub fn to_virt(self) -> VirtAddr {
|
||||
VirtAddr(self.0 + get_hhdm())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -24,9 +40,10 @@ impl PhysAddr {
|
||||
|
||||
impl VirtAddr {
|
||||
#[allow(dead_code)]
|
||||
pub fn to_phys(self, hhdm_offset: u64) -> Option<PhysAddr> {
|
||||
if self.0 < hhdm_offset { return None; }
|
||||
Some(PhysAddr(self.0 - hhdm_offset))
|
||||
pub fn to_phys(self) -> Option<PhysAddr> {
|
||||
let hhdm = get_hhdm();
|
||||
if self.0 < hhdm { return None; }
|
||||
Some(PhysAddr(self.0 - hhdm))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -1,105 +1,99 @@
|
||||
//! # Per-Actor Buddy Allocator (`src/mem/buddy.rs`)
|
||||
//!
|
||||
//! A power-of-two page-block allocator for a fixed, pre-committed physical
|
||||
//! region. Designed for **single-consumer** use inside a [`crate::mem::pm_manages::PMActor`]:
|
||||
//! the owning actor is the only entity that ever mutates its buddy allocator, so
|
||||
//! no locking is needed — the entire subsystem is inherently lock-free from the
|
||||
//! consumer's perspective.
|
||||
//!
|
||||
//! ## Block Layout
|
||||
//!
|
||||
//! The region is subdivided into blocks of `2^order` pages (order 0 … MAX_ORDER).
|
||||
//! Every block at order *k* is aligned to `2^k` pages within the region.
|
||||
//!
|
||||
//! ```text
|
||||
//! order 0 → 1 page = 4 KiB
|
||||
//! order 1 → 2 pages = 8 KiB
|
||||
//! …
|
||||
//! order 11 → 2 048 pages = 8 MiB (MAX_ORDER)
|
||||
//! ```
|
||||
//!
|
||||
//! ## Complexity
|
||||
//!
|
||||
//! | Operation | Amortised | Worst-case |
|
||||
//! |-----------|-----------|------------|
|
||||
//! | `alloc` | O(log N) | O(MAX_ORDER · Lₖ) |
|
||||
//! | `free` | O(log N) | O(MAX_ORDER · Lₖ) |
|
||||
//!
|
||||
//! where Lₖ = `free_lists[k].len()` ≤ `total_pages / 2^k`.
|
||||
//!
|
||||
//! ## Production note
|
||||
//!
|
||||
//! For managed ranges > 1 GiB (> 256 K pages), replace the per-order `Vec<usize>`
|
||||
//! free lists with a radix tree or interval tree to bound Lₖ. For current
|
||||
//! PMActor capital sizes (typically ≤ 128 MiB = 32 K pages at order 0), the
|
||||
//! `Vec`-based implementation is fully adequate.
|
||||
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
// Constants & helpers
|
||||
/// Maximum allocation order.
|
||||
/// `2^11 × 4 096 bytes = 8 MiB` per single allocation.
|
||||
pub const MAX_ORDER: usize = 11;
|
||||
|
||||
/// ⌈log2(n)⌉ — the minimum order whose block size covers `page_count` pages.
|
||||
///
|
||||
/// ```text
|
||||
/// order_for(1) = 0 (2^0 = 1)
|
||||
/// order_for(2) = 1 (2^1 = 2)
|
||||
/// order_for(3) = 2 (2^2 = 4 ≥ 3)
|
||||
/// order_for(2048) = 11
|
||||
/// ```
|
||||
const NEXT_SENTINEL: usize = usize::MAX;
|
||||
|
||||
#[inline]
|
||||
pub fn order_for(page_count: usize) -> usize {
|
||||
if page_count <= 1 {
|
||||
0
|
||||
} else {
|
||||
// Number of bits needed to represent (page_count - 1).
|
||||
usize::BITS as usize - (page_count - 1).leading_zeros() as usize
|
||||
}
|
||||
}
|
||||
|
||||
// BuddyAllocator
|
||||
/// Buddy allocator over a contiguous, pre-committed range of physical pages.
|
||||
///
|
||||
/// All page indices stored in free lists are **relative to the start of the
|
||||
/// managed range**. The owner (`PMActor`) translates to absolute `PhysAddr`
|
||||
/// by adding `managed_range.0`.
|
||||
///
|
||||
/// # Invariants
|
||||
/// - `free_pages ≤ total_pages` at all times.
|
||||
/// - Every block in `free_lists[k]` is aligned to `2^k` pages (i.e.
|
||||
/// `idx % (1 << k) == 0`).
|
||||
/// - No block appears in more than one order's free list simultaneously.
|
||||
pub struct BuddyAllocator {
|
||||
/// `free_lists[k]` = relative page indices of free 2^k-page blocks.
|
||||
/// Ordering within each list is irrelevant; `pop()` / `swap_remove()` are used.
|
||||
free_lists: [Vec<usize>; MAX_ORDER + 1],
|
||||
/// Total pages in the managed range (need not be a power of two).
|
||||
free_heads: [usize; MAX_ORDER + 1],
|
||||
total_pages: usize,
|
||||
/// Running count of free pages. Always equals `Σ (2^k × free_lists[k].len())`.
|
||||
free_pages: usize,
|
||||
base_phys: u64,
|
||||
}
|
||||
|
||||
impl BuddyAllocator {
|
||||
//Construction
|
||||
/// Create a new allocator over `total_pages` pages, **all initially free**.
|
||||
///
|
||||
/// Uses a greedy largest-first decomposition to build the initial free lists
|
||||
/// in O(total_pages / 2^MAX_ORDER) iterations — essentially O(1) for
|
||||
/// power-of-two sizes.
|
||||
///
|
||||
/// Example: 7 pages → blocks [4, 2, 1] → free_lists[2]=[0], [1]=[4], [0]=[6].
|
||||
pub fn new(total_pages: usize) -> Self {
|
||||
// core::array::from_fn is the idiomatic way to init a non-Copy array.
|
||||
let free_lists: [Vec<usize>; MAX_ORDER + 1] =
|
||||
core::array::from_fn(|_| Vec::new());
|
||||
#[inline]
|
||||
fn page_virt(&self, idx: usize) -> VirtAddr {
|
||||
PhysAddr(self.base_phys + idx as u64 * 4096).to_virt()
|
||||
}
|
||||
|
||||
unsafe fn write_node(&self, idx: usize, next: usize, prev: usize) {
|
||||
unsafe {
|
||||
let ptr = self.page_virt(idx).as_mut_ptr::<usize>();
|
||||
ptr.write(next);
|
||||
ptr.add(1).write(prev);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn read_next(&self, idx: usize) -> usize {
|
||||
unsafe { self.page_virt(idx).as_ptr::<usize>().read() }
|
||||
}
|
||||
|
||||
unsafe fn read_prev(&self, idx: usize) -> usize {
|
||||
unsafe { self.page_virt(idx).as_ptr::<usize>().add(1).read() }
|
||||
}
|
||||
|
||||
fn flist_push(&mut self, order: usize, idx: usize) {
|
||||
let head = self.free_heads[order];
|
||||
unsafe {
|
||||
self.write_node(idx, head, NEXT_SENTINEL);
|
||||
if head != NEXT_SENTINEL {
|
||||
self.write_node(head, self.read_next(head), idx);
|
||||
}
|
||||
}
|
||||
self.free_heads[order] = idx;
|
||||
}
|
||||
|
||||
fn flist_remove(&mut self, order: usize, idx: usize) {
|
||||
unsafe {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn flist_contains(&self, order: usize, idx: usize) -> bool {
|
||||
if self.free_heads[order] == idx {
|
||||
return true;
|
||||
}
|
||||
unsafe { self.read_prev(idx) != NEXT_SENTINEL }
|
||||
}
|
||||
|
||||
pub fn new(total_pages: usize, base_phys: u64) -> Self {
|
||||
let mut this = Self {
|
||||
free_lists,
|
||||
free_heads: [NEXT_SENTINEL; MAX_ORDER + 1],
|
||||
total_pages,
|
||||
free_pages: 0,
|
||||
base_phys,
|
||||
};
|
||||
|
||||
if total_pages == 0 {
|
||||
@@ -110,22 +104,19 @@ impl BuddyAllocator {
|
||||
while idx < total_pages {
|
||||
let remaining = total_pages - idx;
|
||||
|
||||
// Alignment constraint: block at `idx` must be aligned to 2^order.
|
||||
// trailing_zeros(0) is u32::MAX, so we clamp to MAX_ORDER.
|
||||
let align_order = if idx == 0 {
|
||||
MAX_ORDER
|
||||
} else {
|
||||
(idx.trailing_zeros() as usize).min(MAX_ORDER)
|
||||
};
|
||||
|
||||
// Size constraint: 2^order ≤ remaining → order ≤ ⌊log₂(remaining)⌋.
|
||||
let size_order = (usize::BITS as usize - 1)
|
||||
- remaining.leading_zeros() as usize; // ⌊log2(remaining)⌋
|
||||
- remaining.leading_zeros() as usize;
|
||||
|
||||
let order = MAX_ORDER.min(align_order).min(size_order);
|
||||
let order = MAX_ORDER.min(align_order).min(size_order);
|
||||
let block_size = 1usize << order;
|
||||
|
||||
this.free_lists[order].push(idx);
|
||||
this.flist_push(order, idx);
|
||||
this.free_pages += block_size;
|
||||
idx += block_size;
|
||||
}
|
||||
@@ -133,60 +124,30 @@ impl BuddyAllocator {
|
||||
this
|
||||
}
|
||||
|
||||
//Allocation
|
||||
/// Allocate a 2^`order`-page block.
|
||||
///
|
||||
/// Returns the **relative** page index of the block's first page, or `None`
|
||||
/// if insufficient contiguous memory remains.
|
||||
///
|
||||
/// The returned index can be converted to a physical address:
|
||||
/// ```text
|
||||
/// phys = actor.managed_range.0.0 + (idx as u64) * PAGE_SIZE
|
||||
/// ```
|
||||
pub fn alloc(&mut self, order: usize) -> Option<usize> {
|
||||
if order > MAX_ORDER {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Find the smallest available order ≥ requested.
|
||||
let found_order = (order..=MAX_ORDER)
|
||||
.find(|&o| !self.free_lists[o].is_empty())?;
|
||||
.find(|&o| self.free_heads[o] != NEXT_SENTINEL)?;
|
||||
|
||||
// Consume one block from the found order.
|
||||
let block_idx = self.free_lists[found_order]
|
||||
.pop()
|
||||
.expect("buddy: free_list non-empty but pop() returned None");
|
||||
let block_idx = self.flist_pop(found_order)
|
||||
.expect("buddy: free_list non-empty but flist_pop returned None");
|
||||
|
||||
// Debit: we removed a 2^found_order block from free.
|
||||
self.free_pages -= 1 << found_order;
|
||||
|
||||
// Split down to the requested order, putting buddies back into free lists.
|
||||
//
|
||||
// Invariant at each iteration:
|
||||
// `block_idx` is the lower half of a 2^cur_order block.
|
||||
// The upper half (= `block_idx + 2^(cur_order-1)`) is returned to the
|
||||
// free list as an order-(cur_order-1) block.
|
||||
let mut cur_order = found_order;
|
||||
while cur_order > order {
|
||||
cur_order -= 1;
|
||||
let buddy_idx = block_idx + (1 << cur_order);
|
||||
self.free_lists[cur_order].push(buddy_idx);
|
||||
self.flist_push(cur_order, buddy_idx);
|
||||
self.free_pages += 1 << cur_order;
|
||||
}
|
||||
|
||||
// Net accounting: removed 2^found_order, added (2^found_order − 2^order).
|
||||
// Result: free_pages decreased by exactly 2^order. ✓
|
||||
Some(block_idx)
|
||||
}
|
||||
|
||||
/// Allocate `page_count` pages, rounding up to the nearest power of two.
|
||||
///
|
||||
/// Returns `(relative_page_idx, actual_order)`. The caller **must** pass the
|
||||
/// same `order` to [`BuddyAllocator::free`] — mismatched orders corrupt the
|
||||
/// allocator.
|
||||
///
|
||||
/// The wasted "rounding up" pages remain unusable until the block is freed.
|
||||
/// For tightly-packed allocations, callers should use `alloc(order)` directly.
|
||||
pub fn alloc_pages(&mut self, page_count: usize) -> Option<(usize, usize)> {
|
||||
if page_count == 0 {
|
||||
return None;
|
||||
@@ -198,17 +159,6 @@ impl BuddyAllocator {
|
||||
self.alloc(order).map(|idx| (idx, order))
|
||||
}
|
||||
|
||||
//Deallocation
|
||||
|
||||
/// Return a 2^`order`-page block at **relative** index `block_idx` to the
|
||||
/// free pool, coalescing with free buddies up the order chain.
|
||||
///
|
||||
/// # Panics (debug builds only)
|
||||
/// - `order > MAX_ORDER`
|
||||
/// - `block_idx + 2^order > total_pages`
|
||||
///
|
||||
/// In release builds the checks are elided for performance; passing incorrect
|
||||
/// arguments causes undefined bookkeeping, not UB (no unsafe indexing).
|
||||
pub fn free(&mut self, mut block_idx: usize, mut order: usize) {
|
||||
debug_assert!(
|
||||
order <= MAX_ORDER,
|
||||
@@ -224,67 +174,60 @@ impl BuddyAllocator {
|
||||
self.total_pages
|
||||
);
|
||||
|
||||
// Try to coalesce with our buddy at each order.
|
||||
//
|
||||
// The buddy of a block at relative index `i` of order `k` is at:
|
||||
// buddy_idx = i XOR 2^k
|
||||
// This works because aligned buddy pairs always differ in exactly bit k.
|
||||
while order < MAX_ORDER {
|
||||
let buddy_idx = block_idx ^ (1 << order);
|
||||
|
||||
// Buddy must lie entirely within the managed range.
|
||||
let buddy_end = match buddy_idx.checked_add(1 << order) {
|
||||
Some(e) => e,
|
||||
None => break,
|
||||
None => break,
|
||||
};
|
||||
if buddy_end > self.total_pages {
|
||||
break;
|
||||
}
|
||||
|
||||
// Search for the buddy in the free list for this order.
|
||||
// `swap_remove` is O(1) and order-preserving is not required.
|
||||
if let Some(pos) = self.free_lists[order].iter().position(|&b| b == buddy_idx) {
|
||||
self.free_lists[order].swap_remove(pos);
|
||||
self.free_pages -= 1 << order; // buddy was in free count; remove it
|
||||
|
||||
// Merged block starts at the lower address of the two.
|
||||
if self.flist_contains(order, buddy_idx) {
|
||||
self.flist_remove(order, buddy_idx);
|
||||
self.free_pages -= 1 << order;
|
||||
block_idx = block_idx.min(buddy_idx);
|
||||
order += 1;
|
||||
// Continue trying to merge at the next level.
|
||||
} else {
|
||||
break; // buddy is allocated or out-of-range; stop coalescing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Push the (possibly merged) block onto the appropriate free list.
|
||||
self.free_pages += 1 << order;
|
||||
self.free_lists[order].push(block_idx);
|
||||
self.flist_push(order, block_idx);
|
||||
}
|
||||
|
||||
//Introspection
|
||||
|
||||
/// Number of pages currently available for allocation.
|
||||
#[inline]
|
||||
pub fn free_pages(&self) -> usize {
|
||||
self.free_pages
|
||||
}
|
||||
|
||||
/// Total pages in the managed range.
|
||||
#[inline]
|
||||
pub fn total_pages(&self) -> usize {
|
||||
self.total_pages
|
||||
}
|
||||
|
||||
/// `true` if the allocator has no free pages.
|
||||
#[inline]
|
||||
pub fn is_exhausted(&self) -> bool {
|
||||
self.free_pages == 0
|
||||
}
|
||||
|
||||
/// Dump free-list statistics for each order (useful in panic handlers).
|
||||
///
|
||||
/// Returns an array `[(order, free_block_count); MAX_ORDER + 1]`.
|
||||
pub fn stats(&self) -> [(usize, usize); MAX_ORDER + 1] {
|
||||
core::array::from_fn(|o| (o, self.free_lists[o].len()))
|
||||
core::array::from_fn(|o| {
|
||||
let count = if self.free_heads[o] == NEXT_SENTINEL {
|
||||
0
|
||||
} else {
|
||||
let mut cnt = 0;
|
||||
let mut cur = self.free_heads[o];
|
||||
while cur != NEXT_SENTINEL {
|
||||
cnt += 1;
|
||||
cur = unsafe { self.read_next(cur) };
|
||||
}
|
||||
cnt
|
||||
};
|
||||
(o, count)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ pub mod pm_router;
|
||||
/// Initialise the physical memory manager.
|
||||
///
|
||||
/// Must be called before any heap allocation or VMM operation.
|
||||
pub fn init_pmm(memmap: &limine::response::MemoryMapResponse, hhdm_offset: u64) {
|
||||
unsafe { pmm::BitmapPMM::init(memmap, hhdm_offset); }
|
||||
pub fn init_pmm(memmap: &limine::response::MemoryMapResponse) {
|
||||
unsafe { pmm::BitmapPMM::init(memmap); }
|
||||
}
|
||||
|
||||
/// Initialise CPU features required by the VMM (INVPCID detection).
|
||||
@@ -25,8 +25,8 @@ pub fn init_cpu_features() {
|
||||
|
||||
/// Initialise the kernel address space record (after PMM, heap, and the
|
||||
/// initial P4 page table have been set up in `kmain`).
|
||||
pub fn init_vmm(pml4_phys: address::PhysAddr, hhdm_offset: u64) {
|
||||
vmm::init_kernel_space(pml4_phys, hhdm_offset);
|
||||
pub fn init_vmm(pml4_phys: address::PhysAddr) {
|
||||
vmm::init_kernel_space(pml4_phys);
|
||||
}
|
||||
|
||||
/// Physical memory statistics: `(used_pages, total_pages)`.
|
||||
|
||||
@@ -40,16 +40,15 @@ impl PageTable {
|
||||
phys: PhysAddr,
|
||||
size: u64,
|
||||
flags: PageTableFlags,
|
||||
hhdm: u64,
|
||||
) {
|
||||
let pages = size.div_ceil(4096);
|
||||
for i in 0..pages {
|
||||
let offset = i * 4096;
|
||||
self.map_page(VirtAddr(virt.0 + offset), PhysAddr(phys.0 + offset), flags, hhdm);
|
||||
self.map_page(VirtAddr(virt.0 + offset), PhysAddr(phys.0 + offset), flags);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_flags(&self, virt: VirtAddr, hhdm: u64) -> Option<PageTableFlags> {
|
||||
pub fn get_flags(&self, virt: VirtAddr) -> Option<PageTableFlags> {
|
||||
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;
|
||||
@@ -59,7 +58,7 @@ impl PageTable {
|
||||
($entry:expr) => {{
|
||||
let e = $entry;
|
||||
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
||||
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt(hhdm).as_mut_ptr::<PageTable>() }
|
||||
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt().as_mut_ptr::<PageTable>() }
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -82,8 +81,8 @@ impl PageTable {
|
||||
Some(PageTableFlags::from_bits_truncate(p1e))
|
||||
}
|
||||
|
||||
pub fn update_flags(&mut self, virt: VirtAddr, flags: PageTableFlags, hhdm: u64) -> Result<(), ()> {
|
||||
let Some(p1) = self.walk_to_p1_mut(virt, hhdm, false) else { return Err(()); };
|
||||
pub fn update_flags(&mut self, virt: VirtAddr, flags: PageTableFlags) -> Result<(), ()> {
|
||||
let Some(p1) = self.walk_to_p1_mut(virt, false) else { return Err(()); };
|
||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||
let entry = p1.entries[p1_idx];
|
||||
|
||||
@@ -107,10 +106,9 @@ impl PageTable {
|
||||
virt: VirtAddr,
|
||||
phys: PhysAddr,
|
||||
flags: PageTableFlags,
|
||||
hhdm: u64,
|
||||
) {
|
||||
let p1 = self
|
||||
.walk_to_p1_mut(virt, hhdm, true /* create */)
|
||||
.walk_to_p1_mut(virt, true /* create */)
|
||||
.expect("map_page: OOM allocating intermediate page-table pages");
|
||||
|
||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||
@@ -126,8 +124,8 @@ impl PageTable {
|
||||
/// Does **not** free the underlying physical frame — that is the caller's
|
||||
/// responsibility (e.g. `VmaBacking::do_unmap`).
|
||||
/// Silently returns if any level is not present (idempotent).
|
||||
pub fn unmap_page(&mut self, virt: VirtAddr, hhdm: u64) {
|
||||
let Some(p1) = self.walk_to_p1_mut(virt, hhdm, false /* no create */) else {
|
||||
pub fn unmap_page(&mut self, virt: VirtAddr) {
|
||||
let Some(p1) = self.walk_to_p1_mut(virt, false /* no create */) else {
|
||||
return; // already absent — nothing to do
|
||||
};
|
||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||
@@ -144,7 +142,7 @@ impl PageTable {
|
||||
///
|
||||
/// Handles 1 GiB and 2 MiB huge pages transparently.
|
||||
/// Returns `None` if any level is absent or the page is not present.
|
||||
pub fn translate(&self, virt: VirtAddr, hhdm: u64) -> Option<PhysAddr> {
|
||||
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
||||
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;
|
||||
@@ -155,7 +153,7 @@ impl PageTable {
|
||||
($entry:expr) => {{
|
||||
let e = $entry;
|
||||
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
||||
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt(hhdm).as_mut_ptr::<PageTable>() }
|
||||
unsafe { &*PhysAddr(e & PTE_ADDR_MASK).to_virt().as_mut_ptr::<PageTable>() }
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -209,16 +207,16 @@ impl PageTable {
|
||||
///
|
||||
/// If `create` is `false` and any intermediate entry is absent, returns `None`.
|
||||
/// If `create` is `true`, allocates missing intermediate pages from the PMM.
|
||||
fn walk_to_p1_mut(&mut self, virt: VirtAddr, hhdm: u64, create: bool)
|
||||
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, hhdm, create)?;
|
||||
let p2 = p3.get_or_create_next_table(p3_idx, hhdm, create)?;
|
||||
p2.get_or_create_next_table(p2_idx, hhdm, create)
|
||||
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)
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the next-level table at `index`.
|
||||
@@ -229,7 +227,6 @@ impl PageTable {
|
||||
fn get_or_create_next_table(
|
||||
&mut self,
|
||||
index: usize,
|
||||
hhdm: u64,
|
||||
create: bool,
|
||||
) -> Option<&mut Self> {
|
||||
let entry = self.entries[index];
|
||||
@@ -237,7 +234,7 @@ impl PageTable {
|
||||
if !create { return None; }
|
||||
|
||||
let pt_phys = pmm_alloc().expect("VMM: OOM allocating page-table page");
|
||||
let pt_virt = pt_phys.to_virt(hhdm);
|
||||
let pt_virt = pt_phys.to_virt();
|
||||
unsafe { core::ptr::write_bytes(pt_virt.as_mut_ptr::<u8>(), 0, 4096); }
|
||||
|
||||
// Install with USER so both kernel and user pages can live under it;
|
||||
@@ -247,7 +244,7 @@ impl PageTable {
|
||||
}
|
||||
|
||||
let next_phys = PhysAddr(self.entries[index] & PTE_ADDR_MASK);
|
||||
Some(unsafe { &mut *next_phys.to_virt(hhdm).as_mut_ptr::<Self>() })
|
||||
Some(unsafe { &mut *next_phys.to_virt().as_mut_ptr::<Self>() })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ impl PMActor {
|
||||
root_untyped: root_cap,
|
||||
managed_range: (start, PhysAddr(start.0 + total_pages as u64 * PAGE_SIZE)),
|
||||
queue: PMActorQueue::new(),
|
||||
buddy: BuddyAllocator::new(total_pages),
|
||||
buddy: BuddyAllocator::new(total_pages, start.0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,9 +140,10 @@ impl PMRouter {
|
||||
let channel = &self.channels[id as usize];
|
||||
|
||||
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
||||
core::hint::spin_loop();
|
||||
// TODO: Для "Focus Mode" и полноценного планировщика:
|
||||
// scheduler::yield_to_actor();
|
||||
// HLT-ожидание вместо busy-wait: CPU останавливается до ближайшего
|
||||
// прерывания (timer tick, IPI от route_responses и т.д.).
|
||||
// Когда появится планировщик — заменить на yield_to_actor().
|
||||
unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); }
|
||||
}
|
||||
|
||||
let result = unsafe {
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
use crate::mem::address::PhysAddr;
|
||||
use crate::mem::address::{PhysAddr, get_hhdm};
|
||||
use crate::mem::allocator::Locked;
|
||||
|
||||
pub const PAGE_SIZE: u64 = 4096;
|
||||
|
||||
pub struct BitmapPMM {
|
||||
bitmap: &'static mut [u8],
|
||||
l1_bitmap: &'static mut [u64],
|
||||
ref_counts: &'static mut [u16],
|
||||
total_pages: usize,
|
||||
used_pages: usize,
|
||||
last_byte: usize,
|
||||
last_word: usize,
|
||||
}
|
||||
|
||||
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
|
||||
|
||||
impl BitmapPMM {
|
||||
unsafe fn read_word(&self, word_idx: usize) -> u64 {
|
||||
let byte_off = word_idx * 8;
|
||||
unsafe { (self.bitmap.as_ptr().add(byte_off) as *const u64).read() }
|
||||
}
|
||||
|
||||
fn l1_update_word(&mut self, word_idx: usize) {
|
||||
let word = unsafe { self.read_word(word_idx) };
|
||||
let l1_idx = word_idx / 64;
|
||||
let l1_bit = word_idx % 64;
|
||||
if word != !0u64 {
|
||||
self.l1_bitmap[l1_idx] |= 1 << l1_bit;
|
||||
} else {
|
||||
self.l1_bitmap[l1_idx] &= !(1 << l1_bit);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn used_pages(&self) -> usize { self.used_pages }
|
||||
#[allow(dead_code)]
|
||||
@@ -21,7 +38,7 @@ impl BitmapPMM {
|
||||
#[allow(dead_code)]
|
||||
pub fn free_pages(&self) -> usize { self.total_pages.saturating_sub(self.used_pages) }
|
||||
|
||||
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse, hhdm_offset: u64) {
|
||||
pub unsafe fn init(mmap: &limine::response::MemoryMapResponse) {
|
||||
let max_addr = mmap.entries().iter()
|
||||
.map(|e| e.base + e.length)
|
||||
.max()
|
||||
@@ -31,7 +48,10 @@ impl BitmapPMM {
|
||||
|
||||
let bitmap_size = total_pages.div_ceil(8);
|
||||
let ref_counts_size = total_pages * core::mem::size_of::<u16>();
|
||||
let total_meta_size = bitmap_size + ref_counts_size;
|
||||
let num_words = total_pages.div_ceil(64);
|
||||
let l1_u64_count = num_words.div_ceil(64);
|
||||
let l1_byte_size = l1_u64_count * 8;
|
||||
let total_meta_size = bitmap_size + ref_counts_size + l1_byte_size;
|
||||
|
||||
let meta_phys = mmap.entries().iter()
|
||||
.find(|e| {
|
||||
@@ -41,8 +61,10 @@ impl BitmapPMM {
|
||||
.map(|e| e.base)
|
||||
.expect("PMM: no usable region large enough for metadata");
|
||||
|
||||
let bitmap_ptr = (meta_phys + hhdm_offset) as *mut u8;
|
||||
let ref_counts_ptr = (meta_phys + hhdm_offset + bitmap_size as u64) as *mut u16;
|
||||
let hhdm = get_hhdm();
|
||||
let bitmap_ptr = (meta_phys + hhdm) as *mut u8;
|
||||
let ref_counts_ptr = (meta_phys + hhdm + bitmap_size as u64) as *mut u16;
|
||||
let l1_ptr = (meta_phys + hhdm + bitmap_size as u64 + ref_counts_size as u64) as *mut u64;
|
||||
|
||||
let bitmap = unsafe { core::slice::from_raw_parts_mut(bitmap_ptr, bitmap_size) };
|
||||
bitmap.fill(0xFF);
|
||||
@@ -50,15 +72,18 @@ impl BitmapPMM {
|
||||
let ref_counts = unsafe { core::slice::from_raw_parts_mut(ref_counts_ptr, total_pages) };
|
||||
ref_counts.fill(1);
|
||||
|
||||
let l1_bitmap = unsafe { core::slice::from_raw_parts_mut(l1_ptr, l1_u64_count) };
|
||||
l1_bitmap.fill(0);
|
||||
|
||||
let mut pmm = Self {
|
||||
bitmap,
|
||||
l1_bitmap,
|
||||
ref_counts,
|
||||
total_pages,
|
||||
used_pages: total_pages,
|
||||
last_byte: 0,
|
||||
last_word: 0,
|
||||
};
|
||||
|
||||
// Free all USABLE pages (gap pages not covered by any entry stay locked).
|
||||
for entry in mmap.entries() {
|
||||
if entry.entry_type == limine::memory_map::EntryType::USABLE {
|
||||
for addr in (entry.base..entry.base + entry.length).step_by(PAGE_SIZE as usize) {
|
||||
@@ -67,7 +92,6 @@ impl BitmapPMM {
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata pages sit inside a USABLE region — lock them back.
|
||||
let meta_end = (meta_phys + total_meta_size as u64 + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
|
||||
for addr in (meta_phys..meta_end).step_by(PAGE_SIZE as usize) {
|
||||
pmm.lock_frame(PhysAddr(addr));
|
||||
@@ -78,8 +102,6 @@ impl BitmapPMM {
|
||||
*PMM.lock() = Some(pmm);
|
||||
}
|
||||
|
||||
//Core operations
|
||||
|
||||
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
||||
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
||||
if idx >= self.total_pages { return; }
|
||||
@@ -92,8 +114,11 @@ impl BitmapPMM {
|
||||
|
||||
if self.ref_counts[idx] == 0 {
|
||||
self.bitmap[byte] &= !(1 << bit);
|
||||
self.used_pages -= 1;
|
||||
if byte < self.last_byte { self.last_byte = byte; }
|
||||
self.used_pages -= 1;
|
||||
|
||||
let word_idx = idx / 64;
|
||||
self.l1_update_word(word_idx);
|
||||
if word_idx < self.last_word { self.last_word = word_idx; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,10 +133,13 @@ impl BitmapPMM {
|
||||
if self.bitmap[byte] & (1 << bit) == 0 {
|
||||
self.bitmap[byte] |= 1 << bit;
|
||||
self.ref_counts[idx] = 1;
|
||||
self.used_pages += 1;
|
||||
self.used_pages += 1;
|
||||
} else if self.ref_counts[idx] == 0 {
|
||||
self.ref_counts[idx] = 1;
|
||||
}
|
||||
|
||||
let word_idx = idx / 64;
|
||||
self.l1_update_word(word_idx);
|
||||
}
|
||||
|
||||
pub fn inc_ref_frame(&mut self, phys_addr: PhysAddr) {
|
||||
@@ -127,31 +155,45 @@ impl BitmapPMM {
|
||||
}
|
||||
|
||||
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
||||
let len = self.bitmap.len();
|
||||
let l1_len = self.l1_bitmap.len();
|
||||
|
||||
for pass in 0..2usize {
|
||||
let (from, to) = if pass == 0 {
|
||||
(self.last_byte, len)
|
||||
(self.last_word / 64, l1_len)
|
||||
} else {
|
||||
(0, self.last_byte)
|
||||
(0, self.last_word / 64)
|
||||
};
|
||||
|
||||
for byte_idx in from..to {
|
||||
if self.bitmap[byte_idx] == 0xFF { continue; }
|
||||
for l1_idx in from..to {
|
||||
let l1_word = self.l1_bitmap[l1_idx];
|
||||
if l1_word == 0 { continue; }
|
||||
|
||||
for bit in 0..8u8 {
|
||||
if self.bitmap[byte_idx] & (1 << bit) == 0 {
|
||||
let page_idx = byte_idx * 8 + bit as usize;
|
||||
if page_idx >= self.total_pages { return None; }
|
||||
let word_offset = l1_word.trailing_zeros() as usize;
|
||||
let word_idx = l1_idx * 64 + word_offset;
|
||||
|
||||
self.bitmap[byte_idx] |= 1 << bit;
|
||||
self.ref_counts[page_idx] = 1;
|
||||
self.used_pages += 1;
|
||||
self.last_byte = byte_idx;
|
||||
|
||||
return Some(PhysAddr(page_idx as u64 * PAGE_SIZE));
|
||||
}
|
||||
let word = unsafe { self.read_word(word_idx) };
|
||||
if word == !0u64 {
|
||||
self.l1_bitmap[l1_idx] &= !(1 << word_offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
let free_bit = (!word).trailing_zeros() as usize;
|
||||
let page_idx = word_idx * 64 + free_bit;
|
||||
if page_idx >= self.total_pages { continue; }
|
||||
|
||||
let byte = page_idx / 8;
|
||||
let bit = page_idx % 8;
|
||||
|
||||
self.bitmap[byte] |= 1 << bit;
|
||||
self.ref_counts[page_idx] = 1;
|
||||
self.used_pages += 1;
|
||||
self.last_word = word_idx;
|
||||
|
||||
if unsafe { self.read_word(word_idx) } == !0u64 {
|
||||
self.l1_bitmap[l1_idx] &= !(1 << word_offset);
|
||||
}
|
||||
|
||||
return Some(PhysAddr(page_idx as u64 * PAGE_SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +218,11 @@ impl BitmapPMM {
|
||||
for i in run_start..run_start + count {
|
||||
self.bitmap[i / 8] |= 1 << (i % 8);
|
||||
self.ref_counts[i] = 1;
|
||||
let word_idx = i / 64;
|
||||
self.l1_update_word(word_idx);
|
||||
}
|
||||
self.used_pages += count;
|
||||
self.last_byte = run_start / 8;
|
||||
self.last_word = run_start / 64;
|
||||
return Some(PhysAddr(run_start as u64 * PAGE_SIZE));
|
||||
}
|
||||
} else {
|
||||
@@ -190,8 +234,6 @@ impl BitmapPMM {
|
||||
}
|
||||
}
|
||||
|
||||
//Module-level convenience functions
|
||||
|
||||
pub fn alloc_frame() -> Option<PhysAddr> {
|
||||
PMM.lock().as_mut()?.alloc_frame()
|
||||
}
|
||||
@@ -219,7 +261,3 @@ pub fn get_stats() -> (usize, usize) {
|
||||
(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -306,14 +306,12 @@ pub struct AddressSpace {
|
||||
pub pml4_phys: PhysAddr,
|
||||
/// Sorted (by virt_start), non-overlapping VMA list.
|
||||
regions: Vec<VmaRegion>,
|
||||
/// HHDM offset for dereferencing page-table pages.
|
||||
hhdm: u64,
|
||||
}
|
||||
|
||||
impl AddressSpace {
|
||||
#[inline]
|
||||
unsafe fn pml4_raw(&self) -> *mut PageTable {
|
||||
self.pml4_phys.to_virt(self.hhdm).as_mut_ptr::<PageTable>()
|
||||
self.pml4_phys.to_virt().as_mut_ptr::<PageTable>()
|
||||
}
|
||||
|
||||
fn check_overlap(&self, start: VirtAddr, end: VirtAddr) -> Result<(), VmError> {
|
||||
@@ -326,10 +324,9 @@ impl AddressSpace {
|
||||
}
|
||||
|
||||
pub fn clone_for_fork(&mut self, child_cap_token: u64) -> Result<Self, VmError> {
|
||||
let mut child = AddressSpace::new(self.hhdm)?;
|
||||
let mut child = AddressSpace::new()?;
|
||||
let child_pml4 = unsafe { &mut *child.pml4_raw() };
|
||||
let parent_pml4 = unsafe { &mut *self.pml4_raw() };
|
||||
let hhdm = self.hhdm;
|
||||
|
||||
for region in &mut self.regions {
|
||||
let mut child_region = VmaRegion {
|
||||
@@ -350,10 +347,10 @@ impl AddressSpace {
|
||||
|
||||
match &mut region.backing {
|
||||
VmaBacking::Physical(base) => {
|
||||
child_pml4.map_region(region.virt_start, *base, region.size(), region.flags.to_page_flags(), hhdm);
|
||||
child_pml4.map_region(region.virt_start, *base, region.size(), region.flags.to_page_flags());
|
||||
}
|
||||
VmaBacking::Shared { phys_base, .. } => {
|
||||
child_pml4.map_region(region.virt_start, *phys_base, region.size(), region.flags.to_page_flags(), hhdm);
|
||||
child_pml4.map_region(region.virt_start, *phys_base, region.size(), region.flags.to_page_flags());
|
||||
}
|
||||
VmaBacking::Anonymous(frames) => {
|
||||
let cow_needed = region.flags.contains(VmaFlags::WRITE);
|
||||
@@ -377,10 +374,10 @@ impl AddressSpace {
|
||||
page_flags.remove(PageTableFlags::WRITABLE);
|
||||
page_flags.insert(PageTableFlags::COW);
|
||||
|
||||
let _ = parent_pml4.update_flags(virt, page_flags, hhdm);
|
||||
let _ = parent_pml4.update_flags(virt, page_flags);
|
||||
}
|
||||
|
||||
child_pml4.map_page(virt, *frame, page_flags, hhdm);
|
||||
child_pml4.map_page(virt, *frame, page_flags);
|
||||
child_frames[i] = Some(*frame);
|
||||
}
|
||||
}
|
||||
@@ -408,12 +405,12 @@ impl AddressSpace {
|
||||
}
|
||||
|
||||
/// Unmap pages + free frames (if owned) for one region.
|
||||
fn do_unmap(pml4: *mut PageTable, region: &VmaRegion, hhdm: u64) {
|
||||
fn do_unmap(pml4: *mut PageTable, region: &VmaRegion) {
|
||||
let pml4 = unsafe { &mut *pml4 };
|
||||
let own = region.backing.owns_frames();
|
||||
for i in 0..region.pages() {
|
||||
let virt = VirtAddr(region.virt_start.0 + i as u64 * 4096);
|
||||
pml4.unmap_page(virt, hhdm);
|
||||
pml4.unmap_page(virt);
|
||||
if own {
|
||||
if let Some(frame) = region.backing.phys_for_page(i) {
|
||||
pmm::free_frame(frame);
|
||||
@@ -423,7 +420,6 @@ impl AddressSpace {
|
||||
}
|
||||
|
||||
fn do_revoke_by_token(&mut self, cap_token: u64) {
|
||||
let hhdm = self.hhdm;
|
||||
let pml4 = unsafe { self.pml4_raw() };
|
||||
|
||||
let indices: Vec<usize> = self.regions
|
||||
@@ -435,7 +431,7 @@ impl AddressSpace {
|
||||
|
||||
for idx in indices.into_iter().rev() {
|
||||
let region = self.regions.remove(idx);
|
||||
Self::do_unmap(pml4, ®ion, hhdm);
|
||||
Self::do_unmap(pml4, ®ion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,23 +452,22 @@ impl AddressSpace {
|
||||
|
||||
impl AddressSpace {
|
||||
/// Allocate a fresh, empty address space with a zeroed PML4.
|
||||
pub fn new(hhdm: u64) -> Result<Self, VmError> {
|
||||
pub fn new() -> Result<Self, VmError> {
|
||||
let pml4_phys = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||
unsafe {
|
||||
core::ptr::write_bytes(pml4_phys.to_virt(hhdm).as_mut_ptr::<u8>(), 0, 4096);
|
||||
core::ptr::write_bytes(pml4_phys.to_virt().as_mut_ptr::<u8>(), 0, 4096);
|
||||
}
|
||||
Ok(Self {
|
||||
asid: alloc_asid()?,
|
||||
pml4_phys,
|
||||
regions: Vec::new(),
|
||||
hhdm,
|
||||
})
|
||||
}
|
||||
|
||||
/// Wrap an already-active PML4 (bootloader-provided kernel table).
|
||||
/// ASID 0 = kernel "no PCID tagging"; it is never returned to the pool.
|
||||
pub fn from_active(pml4_phys: PhysAddr, asid: u16, hhdm: u64) -> Self {
|
||||
Self { asid, pml4_phys, regions: Vec::new(), hhdm }
|
||||
pub fn from_active(pml4_phys: PhysAddr, asid: u16) -> Self {
|
||||
Self { asid, pml4_phys, regions: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn map_region(
|
||||
@@ -494,11 +489,10 @@ impl AddressSpace {
|
||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||
let page_flags = flags.to_page_flags();
|
||||
let page_count = (size / 4096) as usize;
|
||||
let hhdm = self.hhdm;
|
||||
|
||||
let backing = match phys {
|
||||
Some(base) => {
|
||||
pml4.map_region(virt, base, size, page_flags, hhdm);
|
||||
pml4.map_region(virt, base, size, page_flags);
|
||||
VmaBacking::Physical(base)
|
||||
}
|
||||
None if flags.contains(VmaFlags::LAZY) => {
|
||||
@@ -511,13 +505,12 @@ impl AddressSpace {
|
||||
for i in 0..page_count {
|
||||
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||
unsafe {
|
||||
core::ptr::write_bytes(frame.to_virt(hhdm).as_mut_ptr::<u8>(), 0, 4096);
|
||||
core::ptr::write_bytes(frame.to_virt().as_mut_ptr::<u8>(), 0, 4096);
|
||||
}
|
||||
pml4.map_page(
|
||||
VirtAddr(virt.0 + i as u64 * 4096),
|
||||
frame,
|
||||
page_flags,
|
||||
hhdm,
|
||||
);
|
||||
frames.push(Some(frame));
|
||||
}
|
||||
@@ -557,7 +550,6 @@ impl AddressSpace {
|
||||
|
||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||
let page_flags = flags.to_page_flags();
|
||||
let hhdm = self.hhdm;
|
||||
|
||||
// Eagerly map all pages — the physical addresses are already known.
|
||||
for i in 0..page_count {
|
||||
@@ -565,7 +557,6 @@ impl AddressSpace {
|
||||
VirtAddr(virt.0 + i as u64 * 4096),
|
||||
PhysAddr(phys_base.0 + i as u64 * 4096),
|
||||
page_flags,
|
||||
hhdm,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -585,7 +576,6 @@ impl AddressSpace {
|
||||
/// Returns `Ok(())` if the fault was a valid lazy demand-page (caller should
|
||||
/// `iretq` to retry). Returns `Err` for illegal accesses.
|
||||
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError> {
|
||||
let hhdm = self.hhdm;
|
||||
let idx = self.find_idx(fault_addr).ok_or(VmError::RegionNotFound)?;
|
||||
|
||||
let (virt_start, region_flags) = {
|
||||
@@ -601,7 +591,7 @@ impl AddressSpace {
|
||||
let page_virt = VirtAddr(virt_start.0 + page_idx as u64 * 4096);
|
||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||
|
||||
let current_pte_flags = pml4.get_flags(page_virt, hhdm);
|
||||
let current_pte_flags = pml4.get_flags(page_virt);
|
||||
let is_cow = current_pte_flags.map_or(false, |f| f.contains(PageTableFlags::COW));
|
||||
|
||||
if write && is_cow {
|
||||
@@ -617,8 +607,8 @@ impl AddressSpace {
|
||||
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
old_frame.to_virt(hhdm).as_ptr::<u8>(),
|
||||
new_frame.to_virt(hhdm).as_mut_ptr::<u8>(),
|
||||
old_frame.to_virt().as_ptr::<u8>(),
|
||||
new_frame.to_virt().as_mut_ptr::<u8>(),
|
||||
4096,
|
||||
);
|
||||
}
|
||||
@@ -629,7 +619,7 @@ impl AddressSpace {
|
||||
target_flags.remove(PageTableFlags::COW);
|
||||
target_flags.insert(PageTableFlags::WRITABLE);
|
||||
|
||||
pml4.map_page(page_virt, new_frame, target_flags, hhdm);
|
||||
pml4.map_page(page_virt, new_frame, target_flags);
|
||||
|
||||
pmm::free_frame(old_frame);
|
||||
|
||||
@@ -651,7 +641,7 @@ impl AddressSpace {
|
||||
|
||||
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||
unsafe {
|
||||
core::ptr::write_bytes(frame.to_virt(hhdm).as_mut_ptr::<u8>(), 0, 4096);
|
||||
core::ptr::write_bytes(frame.to_virt().as_mut_ptr::<u8>(), 0, 4096);
|
||||
}
|
||||
frames[page_idx] = Some(frame);
|
||||
|
||||
@@ -660,7 +650,7 @@ impl AddressSpace {
|
||||
target_flags.remove(PageTableFlags::WRITABLE);
|
||||
}
|
||||
|
||||
pml4.map_page(page_virt, frame, target_flags, hhdm);
|
||||
pml4.map_page(page_virt, frame, target_flags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -669,7 +659,7 @@ impl AddressSpace {
|
||||
let idx = self.find_idx(virt).ok_or(VmError::RegionNotFound)?;
|
||||
let region = self.regions.remove(idx);
|
||||
let pml4 = unsafe { self.pml4_raw() };
|
||||
Self::do_unmap(pml4, ®ion, self.hhdm);
|
||||
Self::do_unmap(pml4, ®ion);
|
||||
tlb_flush_asid(self.asid);
|
||||
Ok(())
|
||||
}
|
||||
@@ -679,7 +669,6 @@ impl AddressSpace {
|
||||
/// Hardware access is terminated before this function returns.
|
||||
/// Shared-backed VMAs are unmapped from the PT without freeing frames.
|
||||
pub fn revoke_by_token(&mut self, cap_token: u64) {
|
||||
let hhdm = self.hhdm;
|
||||
let pml4 = unsafe { self.pml4_raw() };
|
||||
|
||||
let indices: Vec<usize> = self.regions
|
||||
@@ -693,7 +682,7 @@ impl AddressSpace {
|
||||
|
||||
for idx in indices.into_iter().rev() {
|
||||
let region = self.regions.remove(idx);
|
||||
Self::do_unmap(pml4, ®ion, hhdm);
|
||||
Self::do_unmap(pml4, ®ion);
|
||||
}
|
||||
|
||||
tlb_flush_asid(self.asid);
|
||||
@@ -703,7 +692,7 @@ impl AddressSpace {
|
||||
|
||||
/// Walk the live page table to translate `virt` → physical address.
|
||||
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
||||
unsafe { (*self.pml4_raw()).translate(virt, self.hhdm) }
|
||||
unsafe { (*self.pml4_raw()).translate(virt) }
|
||||
}
|
||||
|
||||
//Activation
|
||||
@@ -737,10 +726,9 @@ impl AddressSpace {
|
||||
|
||||
impl Drop for AddressSpace {
|
||||
fn drop(&mut self) {
|
||||
let hhdm = self.hhdm;
|
||||
let pml4 = unsafe { self.pml4_raw() };
|
||||
while let Some(region) = self.regions.pop() {
|
||||
Self::do_unmap(pml4, ®ion, hhdm);
|
||||
Self::do_unmap(pml4, ®ion);
|
||||
}
|
||||
pmm::free_frame(self.pml4_phys);
|
||||
// Return the ASID to the pool so it can be reused by future processes.
|
||||
@@ -823,6 +811,6 @@ pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
|
||||
///
|
||||
/// ASID 0 = PCID 0 = kernel (no per-process PCID tagging).
|
||||
/// Must be called after `BitmapPMM::init` and `init_cpu_features`.
|
||||
pub fn init_kernel_space(pml4_phys: PhysAddr, hhdm: u64) {
|
||||
*KERNEL_SPACE.lock() = Some(AddressSpace::from_active(pml4_phys, 0, hhdm));
|
||||
pub fn init_kernel_space(pml4_phys: PhysAddr) {
|
||||
*KERNEL_SPACE.lock() = Some(AddressSpace::from_active(pml4_phys, 0));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user