feat: docs & ARCH 2.2, 2.3, 2.4
This commit is contained in:
BIN
.PLAN.md.swp
BIN
.PLAN.md.swp
Binary file not shown.
@@ -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
|
// src/cpu/lapic.rs
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicU64, Ordering};
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use crate::mem::address::get_hhdm;
|
||||||
|
|
||||||
pub const LAPIC_DEFAULT_BASE: u64 = 0xFEE00_000;
|
pub const LAPIC_DEFAULT_BASE: u64 = 0xFEE00_000;
|
||||||
const LAPIC_EOI: u64 = 0x0B0;
|
const LAPIC_EOI: u64 = 0x0B0;
|
||||||
@@ -8,8 +9,8 @@ const LAPIC_ICR_LOW: u64 = 0x300;
|
|||||||
|
|
||||||
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
pub fn init(hhdm_offset: u64) {
|
pub fn init() {
|
||||||
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + hhdm_offset, Ordering::SeqCst);
|
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + get_hhdm(), Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use crate::mem::paging::{PageTable, PageTableFlags};
|
|||||||
use crate::mem::address::{PhysAddr, VirtAddr};
|
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||||
use crate::cap::{Relation, CapRights, Capability, CapObject};
|
use crate::cap::{Relation, CapRights, Capability, CapObject};
|
||||||
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
|
use crate::mem::pm_manages::{PMActor, PMRequest, PMResult};
|
||||||
|
use crate::mem::buddy::BuddyAllocator;
|
||||||
|
|
||||||
pub mod cap;
|
pub mod cap;
|
||||||
pub mod cpu;
|
pub mod cpu;
|
||||||
@@ -175,6 +176,7 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
let hhdm_res = HHDM_REQUEST.get_response().expect("Limine: No HHDM");
|
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 kaddr_res = KERNEL_ADDR_REQUEST.get_response().expect("Limine: No Kernel Address");
|
||||||
let hhdm_offset = hhdm_res.offset();
|
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 fb = fb_res.framebuffers().next().expect("Limine: No active framebuffer found");
|
||||||
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
let mut console = tty::Console::new(&fb, KERNEL_FONT);
|
||||||
@@ -185,17 +187,17 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
cpu::interrupts::init_early_exceptions();
|
cpu::interrupts::init_early_exceptions();
|
||||||
info!(console, "BOOT", "LIS4 Kernel Starting...");
|
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.");
|
info!(console, "MEM", "Primary Physical Memory Manager (BitmapPMM) initialized.");
|
||||||
|
|
||||||
cpu::lapic::init(hhdm_offset);
|
cpu::lapic::init();
|
||||||
info!(console, "LAPIC", "Local APIC initialized.");
|
info!(console, "LAPIC", "Local APIC initialized.");
|
||||||
|
|
||||||
// debug: locate the free page
|
// debug: locate the free page
|
||||||
info!(console, "BOOT", "alloc_frame...");
|
info!(console, "BOOT", "alloc_frame...");
|
||||||
let p4_phys = mem::pmm::alloc_frame().expect("OOM: Failed to allocate P4 table");
|
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);
|
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);
|
info!(console, "BOOT", "virt=0x{:x}", virt.0);
|
||||||
let p4 = unsafe { &mut *virt.as_mut_ptr::<PageTable>() };
|
let p4 = unsafe { &mut *virt.as_mut_ptr::<PageTable>() };
|
||||||
info!(console, "BOOT", "zeroing page...");
|
info!(console, "BOOT", "zeroing page...");
|
||||||
@@ -206,10 +208,10 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
|
|
||||||
for (i, entry) in mmap_res.entries().iter().enumerate() {
|
for (i, entry) in mmap_res.entries().iter().enumerate() {
|
||||||
let phys = PhysAddr(entry.base);
|
let phys = PhysAddr(entry.base);
|
||||||
let virt_hhdm = phys.to_virt(hhdm_offset);
|
let virt_hhdm = phys.to_virt();
|
||||||
p4.map_region(virt_hhdm, phys, entry.length, flags, hhdm_offset);
|
p4.map_region(virt_hhdm, phys, entry.length, flags);
|
||||||
if entry.entry_type != limine::memory_map::EntryType::RESERVED {
|
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()),
|
PhysAddr(kaddr_res.physical_base()),
|
||||||
0x1000 * 1024,
|
0x1000 * 1024,
|
||||||
flags,
|
flags,
|
||||||
hhdm_offset
|
|
||||||
);
|
);
|
||||||
|
|
||||||
info!(console, "MMU", "Activating Kernel Page Tables...");
|
info!(console, "MMU", "Activating Kernel Page Tables...");
|
||||||
@@ -228,7 +229,7 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
let heap_size = 8 * 1024 * 1024;
|
let heap_size = 8 * 1024 * 1024;
|
||||||
for i in (0..heap_size).step_by(4096) {
|
for i in (0..heap_size).step_by(4096) {
|
||||||
let frame = mem::pmm::alloc_frame().expect("OOM: Heap allocation failed");
|
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.");
|
info!(console, "HEAP", "Kernel Slab Allocator is online.");
|
||||||
|
|
||||||
mem::init_cpu_features();
|
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.");
|
info!(console, "VMM", "Kernel Address Space registered successfully.");
|
||||||
|
|
||||||
cpu::interrupts::init_idt();
|
cpu::interrupts::init_idt();
|
||||||
@@ -367,6 +368,74 @@ unsafe extern "C" fn kmain() -> ! {
|
|||||||
panic!("CRITICAL STATE LOSS: Memory leak detected inside PMActor context!");
|
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#"
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct PhysAddr(pub u64);
|
pub struct PhysAddr(pub u64);
|
||||||
@@ -8,8 +24,8 @@ pub struct VirtAddr(pub u64);
|
|||||||
|
|
||||||
impl PhysAddr {
|
impl PhysAddr {
|
||||||
/// Convert physical address to virtual via HHDM offset
|
/// Convert physical address to virtual via HHDM offset
|
||||||
pub fn to_virt(self, hhdm_offset: u64) -> VirtAddr {
|
pub fn to_virt(self) -> VirtAddr {
|
||||||
VirtAddr(self.0 + hhdm_offset)
|
VirtAddr(self.0 + get_hhdm())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -24,9 +40,10 @@ impl PhysAddr {
|
|||||||
|
|
||||||
impl VirtAddr {
|
impl VirtAddr {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn to_phys(self, hhdm_offset: u64) -> Option<PhysAddr> {
|
pub fn to_phys(self) -> Option<PhysAddr> {
|
||||||
if self.0 < hhdm_offset { return None; }
|
let hhdm = get_hhdm();
|
||||||
Some(PhysAddr(self.0 - hhdm_offset))
|
if self.0 < hhdm { return None; }
|
||||||
|
Some(PhysAddr(self.0 - hhdm))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@@ -1,105 +1,99 @@
|
|||||||
//! # Per-Actor Buddy Allocator (`src/mem/buddy.rs`)
|
use crate::mem::address::{PhysAddr, VirtAddr};
|
||||||
//!
|
|
||||||
//! 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.
|
|
||||||
|
|
||||||
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;
|
pub const MAX_ORDER: usize = 11;
|
||||||
|
|
||||||
/// ⌈log2(n)⌉ — the minimum order whose block size covers `page_count` pages.
|
const NEXT_SENTINEL: usize = usize::MAX;
|
||||||
///
|
|
||||||
/// ```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
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn order_for(page_count: usize) -> usize {
|
pub fn order_for(page_count: usize) -> usize {
|
||||||
if page_count <= 1 {
|
if page_count <= 1 {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
// Number of bits needed to represent (page_count - 1).
|
|
||||||
usize::BITS as usize - (page_count - 1).leading_zeros() as usize
|
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 {
|
pub struct BuddyAllocator {
|
||||||
/// `free_lists[k]` = relative page indices of free 2^k-page blocks.
|
free_heads: [usize; MAX_ORDER + 1],
|
||||||
/// 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).
|
|
||||||
total_pages: usize,
|
total_pages: usize,
|
||||||
/// Running count of free pages. Always equals `Σ (2^k × free_lists[k].len())`.
|
|
||||||
free_pages: usize,
|
free_pages: usize,
|
||||||
|
base_phys: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuddyAllocator {
|
impl BuddyAllocator {
|
||||||
//Construction
|
#[inline]
|
||||||
/// Create a new allocator over `total_pages` pages, **all initially free**.
|
fn page_virt(&self, idx: usize) -> VirtAddr {
|
||||||
///
|
PhysAddr(self.base_phys + idx as u64 * 4096).to_virt()
|
||||||
/// 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());
|
|
||||||
|
|
||||||
|
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 {
|
let mut this = Self {
|
||||||
free_lists,
|
free_heads: [NEXT_SENTINEL; MAX_ORDER + 1],
|
||||||
total_pages,
|
total_pages,
|
||||||
free_pages: 0,
|
free_pages: 0,
|
||||||
|
base_phys,
|
||||||
};
|
};
|
||||||
|
|
||||||
if total_pages == 0 {
|
if total_pages == 0 {
|
||||||
@@ -110,22 +104,19 @@ impl BuddyAllocator {
|
|||||||
while idx < total_pages {
|
while idx < total_pages {
|
||||||
let remaining = total_pages - idx;
|
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 {
|
let align_order = if idx == 0 {
|
||||||
MAX_ORDER
|
MAX_ORDER
|
||||||
} else {
|
} else {
|
||||||
(idx.trailing_zeros() as usize).min(MAX_ORDER)
|
(idx.trailing_zeros() as usize).min(MAX_ORDER)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Size constraint: 2^order ≤ remaining → order ≤ ⌊log₂(remaining)⌋.
|
|
||||||
let size_order = (usize::BITS as usize - 1)
|
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;
|
let block_size = 1usize << order;
|
||||||
|
|
||||||
this.free_lists[order].push(idx);
|
this.flist_push(order, idx);
|
||||||
this.free_pages += block_size;
|
this.free_pages += block_size;
|
||||||
idx += block_size;
|
idx += block_size;
|
||||||
}
|
}
|
||||||
@@ -133,60 +124,30 @@ impl BuddyAllocator {
|
|||||||
this
|
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> {
|
pub fn alloc(&mut self, order: usize) -> Option<usize> {
|
||||||
if order > MAX_ORDER {
|
if order > MAX_ORDER {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the smallest available order ≥ requested.
|
|
||||||
let found_order = (order..=MAX_ORDER)
|
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.flist_pop(found_order)
|
||||||
let block_idx = self.free_lists[found_order]
|
.expect("buddy: free_list non-empty but flist_pop returned None");
|
||||||
.pop()
|
|
||||||
.expect("buddy: free_list non-empty but pop() returned None");
|
|
||||||
|
|
||||||
// Debit: we removed a 2^found_order block from free.
|
|
||||||
self.free_pages -= 1 << found_order;
|
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;
|
let mut cur_order = found_order;
|
||||||
while cur_order > order {
|
while cur_order > order {
|
||||||
cur_order -= 1;
|
cur_order -= 1;
|
||||||
let buddy_idx = block_idx + (1 << cur_order);
|
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;
|
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)
|
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)> {
|
pub fn alloc_pages(&mut self, page_count: usize) -> Option<(usize, usize)> {
|
||||||
if page_count == 0 {
|
if page_count == 0 {
|
||||||
return None;
|
return None;
|
||||||
@@ -198,17 +159,6 @@ impl BuddyAllocator {
|
|||||||
self.alloc(order).map(|idx| (idx, order))
|
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) {
|
pub fn free(&mut self, mut block_idx: usize, mut order: usize) {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
order <= MAX_ORDER,
|
order <= MAX_ORDER,
|
||||||
@@ -224,67 +174,60 @@ impl BuddyAllocator {
|
|||||||
self.total_pages
|
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 {
|
while order < MAX_ORDER {
|
||||||
let buddy_idx = block_idx ^ (1 << 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) {
|
let buddy_end = match buddy_idx.checked_add(1 << order) {
|
||||||
Some(e) => e,
|
Some(e) => e,
|
||||||
None => break,
|
None => break,
|
||||||
};
|
};
|
||||||
if buddy_end > self.total_pages {
|
if buddy_end > self.total_pages {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for the buddy in the free list for this order.
|
if self.flist_contains(order, buddy_idx) {
|
||||||
// `swap_remove` is O(1) and order-preserving is not required.
|
self.flist_remove(order, buddy_idx);
|
||||||
if let Some(pos) = self.free_lists[order].iter().position(|&b| b == buddy_idx) {
|
self.free_pages -= 1 << order;
|
||||||
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.
|
|
||||||
block_idx = block_idx.min(buddy_idx);
|
block_idx = block_idx.min(buddy_idx);
|
||||||
order += 1;
|
order += 1;
|
||||||
// Continue trying to merge at the next level.
|
|
||||||
} else {
|
} 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_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]
|
#[inline]
|
||||||
pub fn free_pages(&self) -> usize {
|
pub fn free_pages(&self) -> usize {
|
||||||
self.free_pages
|
self.free_pages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Total pages in the managed range.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn total_pages(&self) -> usize {
|
pub fn total_pages(&self) -> usize {
|
||||||
self.total_pages
|
self.total_pages
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `true` if the allocator has no free pages.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_exhausted(&self) -> bool {
|
pub fn is_exhausted(&self) -> bool {
|
||||||
self.free_pages == 0
|
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] {
|
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.
|
/// Initialise the physical memory manager.
|
||||||
///
|
///
|
||||||
/// Must be called before any heap allocation or VMM operation.
|
/// Must be called before any heap allocation or VMM operation.
|
||||||
pub fn init_pmm(memmap: &limine::response::MemoryMapResponse, hhdm_offset: u64) {
|
pub fn init_pmm(memmap: &limine::response::MemoryMapResponse) {
|
||||||
unsafe { pmm::BitmapPMM::init(memmap, hhdm_offset); }
|
unsafe { pmm::BitmapPMM::init(memmap); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialise CPU features required by the VMM (INVPCID detection).
|
/// 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
|
/// Initialise the kernel address space record (after PMM, heap, and the
|
||||||
/// initial P4 page table have been set up in `kmain`).
|
/// initial P4 page table have been set up in `kmain`).
|
||||||
pub fn init_vmm(pml4_phys: address::PhysAddr, hhdm_offset: u64) {
|
pub fn init_vmm(pml4_phys: address::PhysAddr) {
|
||||||
vmm::init_kernel_space(pml4_phys, hhdm_offset);
|
vmm::init_kernel_space(pml4_phys);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Physical memory statistics: `(used_pages, total_pages)`.
|
/// Physical memory statistics: `(used_pages, total_pages)`.
|
||||||
|
|||||||
@@ -40,16 +40,15 @@ impl PageTable {
|
|||||||
phys: PhysAddr,
|
phys: PhysAddr,
|
||||||
size: u64,
|
size: u64,
|
||||||
flags: PageTableFlags,
|
flags: PageTableFlags,
|
||||||
hhdm: u64,
|
|
||||||
) {
|
) {
|
||||||
let pages = size.div_ceil(4096);
|
let pages = size.div_ceil(4096);
|
||||||
for i in 0..pages {
|
for i in 0..pages {
|
||||||
let offset = i * 4096;
|
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 p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
@@ -59,7 +58,7 @@ impl PageTable {
|
|||||||
($entry:expr) => {{
|
($entry:expr) => {{
|
||||||
let e = $entry;
|
let e = $entry;
|
||||||
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
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))
|
Some(PageTableFlags::from_bits_truncate(p1e))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_flags(&mut self, virt: VirtAddr, flags: PageTableFlags, hhdm: u64) -> Result<(), ()> {
|
pub fn update_flags(&mut self, virt: VirtAddr, flags: PageTableFlags) -> Result<(), ()> {
|
||||||
let Some(p1) = self.walk_to_p1_mut(virt, hhdm, false) else { return Err(()); };
|
let Some(p1) = self.walk_to_p1_mut(virt, false) else { return Err(()); };
|
||||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
let entry = p1.entries[p1_idx];
|
let entry = p1.entries[p1_idx];
|
||||||
|
|
||||||
@@ -107,10 +106,9 @@ impl PageTable {
|
|||||||
virt: VirtAddr,
|
virt: VirtAddr,
|
||||||
phys: PhysAddr,
|
phys: PhysAddr,
|
||||||
flags: PageTableFlags,
|
flags: PageTableFlags,
|
||||||
hhdm: u64,
|
|
||||||
) {
|
) {
|
||||||
let p1 = self
|
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");
|
.expect("map_page: OOM allocating intermediate page-table pages");
|
||||||
|
|
||||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
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
|
/// Does **not** free the underlying physical frame — that is the caller's
|
||||||
/// responsibility (e.g. `VmaBacking::do_unmap`).
|
/// responsibility (e.g. `VmaBacking::do_unmap`).
|
||||||
/// Silently returns if any level is not present (idempotent).
|
/// Silently returns if any level is not present (idempotent).
|
||||||
pub fn unmap_page(&mut self, virt: VirtAddr, hhdm: u64) {
|
pub fn unmap_page(&mut self, virt: VirtAddr) {
|
||||||
let Some(p1) = self.walk_to_p1_mut(virt, hhdm, false /* no create */) else {
|
let Some(p1) = self.walk_to_p1_mut(virt, false /* no create */) else {
|
||||||
return; // already absent — nothing to do
|
return; // already absent — nothing to do
|
||||||
};
|
};
|
||||||
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
let p1_idx = ((virt.0 >> 12) & 0x1FF) as usize;
|
||||||
@@ -144,7 +142,7 @@ impl PageTable {
|
|||||||
///
|
///
|
||||||
/// Handles 1 GiB and 2 MiB huge pages transparently.
|
/// Handles 1 GiB and 2 MiB huge pages transparently.
|
||||||
/// Returns `None` if any level is absent or the page is not present.
|
/// 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 p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
let p2_idx = ((virt.0 >> 21) & 0x1FF) as usize;
|
||||||
@@ -155,7 +153,7 @@ impl PageTable {
|
|||||||
($entry:expr) => {{
|
($entry:expr) => {{
|
||||||
let e = $entry;
|
let e = $entry;
|
||||||
if e & PageTableFlags::PRESENT.bits() == 0 { return None; }
|
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 `false` and any intermediate entry is absent, returns `None`.
|
||||||
/// If `create` is `true`, allocates missing intermediate pages from the PMM.
|
/// 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>
|
-> Option<&mut Self>
|
||||||
{
|
{
|
||||||
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
let p4_idx = ((virt.0 >> 39) & 0x1FF) as usize;
|
||||||
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
let p3_idx = ((virt.0 >> 30) & 0x1FF) as usize;
|
||||||
let p2_idx = ((virt.0 >> 21) & 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 p3 = self.get_or_create_next_table(p4_idx, create)?;
|
||||||
let p2 = p3.get_or_create_next_table(p3_idx, hhdm, create)?;
|
let p2 = p3.get_or_create_next_table(p3_idx, create)?;
|
||||||
p2.get_or_create_next_table(p2_idx, hhdm, create)
|
p2.get_or_create_next_table(p2_idx, create)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to the next-level table at `index`.
|
/// Return a mutable reference to the next-level table at `index`.
|
||||||
@@ -229,7 +227,6 @@ impl PageTable {
|
|||||||
fn get_or_create_next_table(
|
fn get_or_create_next_table(
|
||||||
&mut self,
|
&mut self,
|
||||||
index: usize,
|
index: usize,
|
||||||
hhdm: u64,
|
|
||||||
create: bool,
|
create: bool,
|
||||||
) -> Option<&mut Self> {
|
) -> Option<&mut Self> {
|
||||||
let entry = self.entries[index];
|
let entry = self.entries[index];
|
||||||
@@ -237,7 +234,7 @@ impl PageTable {
|
|||||||
if !create { return None; }
|
if !create { return None; }
|
||||||
|
|
||||||
let pt_phys = pmm_alloc().expect("VMM: OOM allocating page-table page");
|
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); }
|
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;
|
// 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);
|
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,
|
root_untyped: root_cap,
|
||||||
managed_range: (start, PhysAddr(start.0 + total_pages as u64 * PAGE_SIZE)),
|
managed_range: (start, PhysAddr(start.0 + total_pages as u64 * PAGE_SIZE)),
|
||||||
queue: PMActorQueue::new(),
|
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];
|
let channel = &self.channels[id as usize];
|
||||||
|
|
||||||
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
while channel.state.load(Ordering::Acquire) != STATE_READY {
|
||||||
core::hint::spin_loop();
|
// HLT-ожидание вместо busy-wait: CPU останавливается до ближайшего
|
||||||
// TODO: Для "Focus Mode" и полноценного планировщика:
|
// прерывания (timer tick, IPI от route_responses и т.д.).
|
||||||
// scheduler::yield_to_actor();
|
// Когда появится планировщик — заменить на yield_to_actor().
|
||||||
|
unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = unsafe {
|
let result = unsafe {
|
||||||
|
|||||||
@@ -1,19 +1,36 @@
|
|||||||
use crate::mem::address::PhysAddr;
|
use crate::mem::address::{PhysAddr, get_hhdm};
|
||||||
use crate::mem::allocator::Locked;
|
use crate::mem::allocator::Locked;
|
||||||
|
|
||||||
pub const PAGE_SIZE: u64 = 4096;
|
pub const PAGE_SIZE: u64 = 4096;
|
||||||
|
|
||||||
pub struct BitmapPMM {
|
pub struct BitmapPMM {
|
||||||
bitmap: &'static mut [u8],
|
bitmap: &'static mut [u8],
|
||||||
|
l1_bitmap: &'static mut [u64],
|
||||||
ref_counts: &'static mut [u16],
|
ref_counts: &'static mut [u16],
|
||||||
total_pages: usize,
|
total_pages: usize,
|
||||||
used_pages: usize,
|
used_pages: usize,
|
||||||
last_byte: usize,
|
last_word: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
|
pub static PMM: Locked<Option<BitmapPMM>> = Locked::new(None);
|
||||||
|
|
||||||
impl BitmapPMM {
|
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)]
|
#[allow(dead_code)]
|
||||||
pub fn used_pages(&self) -> usize { self.used_pages }
|
pub fn used_pages(&self) -> usize { self.used_pages }
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -21,7 +38,7 @@ impl BitmapPMM {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn free_pages(&self) -> usize { self.total_pages.saturating_sub(self.used_pages) }
|
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()
|
let max_addr = mmap.entries().iter()
|
||||||
.map(|e| e.base + e.length)
|
.map(|e| e.base + e.length)
|
||||||
.max()
|
.max()
|
||||||
@@ -31,7 +48,10 @@ impl BitmapPMM {
|
|||||||
|
|
||||||
let bitmap_size = total_pages.div_ceil(8);
|
let bitmap_size = total_pages.div_ceil(8);
|
||||||
let ref_counts_size = total_pages * core::mem::size_of::<u16>();
|
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()
|
let meta_phys = mmap.entries().iter()
|
||||||
.find(|e| {
|
.find(|e| {
|
||||||
@@ -41,8 +61,10 @@ impl BitmapPMM {
|
|||||||
.map(|e| e.base)
|
.map(|e| e.base)
|
||||||
.expect("PMM: no usable region large enough for metadata");
|
.expect("PMM: no usable region large enough for metadata");
|
||||||
|
|
||||||
let bitmap_ptr = (meta_phys + hhdm_offset) as *mut u8;
|
let hhdm = get_hhdm();
|
||||||
let ref_counts_ptr = (meta_phys + hhdm_offset + bitmap_size as u64) as *mut u16;
|
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) };
|
let bitmap = unsafe { core::slice::from_raw_parts_mut(bitmap_ptr, bitmap_size) };
|
||||||
bitmap.fill(0xFF);
|
bitmap.fill(0xFF);
|
||||||
@@ -50,15 +72,18 @@ impl BitmapPMM {
|
|||||||
let ref_counts = unsafe { core::slice::from_raw_parts_mut(ref_counts_ptr, total_pages) };
|
let ref_counts = unsafe { core::slice::from_raw_parts_mut(ref_counts_ptr, total_pages) };
|
||||||
ref_counts.fill(1);
|
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 {
|
let mut pmm = Self {
|
||||||
bitmap,
|
bitmap,
|
||||||
|
l1_bitmap,
|
||||||
ref_counts,
|
ref_counts,
|
||||||
total_pages,
|
total_pages,
|
||||||
used_pages: 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() {
|
for entry in mmap.entries() {
|
||||||
if entry.entry_type == limine::memory_map::EntryType::USABLE {
|
if entry.entry_type == limine::memory_map::EntryType::USABLE {
|
||||||
for addr in (entry.base..entry.base + entry.length).step_by(PAGE_SIZE as usize) {
|
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);
|
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) {
|
for addr in (meta_phys..meta_end).step_by(PAGE_SIZE as usize) {
|
||||||
pmm.lock_frame(PhysAddr(addr));
|
pmm.lock_frame(PhysAddr(addr));
|
||||||
@@ -78,8 +102,6 @@ impl BitmapPMM {
|
|||||||
*PMM.lock() = Some(pmm);
|
*PMM.lock() = Some(pmm);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Core operations
|
|
||||||
|
|
||||||
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
pub fn free_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
let idx = (phys_addr.0 / PAGE_SIZE) as usize;
|
||||||
if idx >= self.total_pages { return; }
|
if idx >= self.total_pages { return; }
|
||||||
@@ -92,8 +114,11 @@ impl BitmapPMM {
|
|||||||
|
|
||||||
if self.ref_counts[idx] == 0 {
|
if self.ref_counts[idx] == 0 {
|
||||||
self.bitmap[byte] &= !(1 << bit);
|
self.bitmap[byte] &= !(1 << bit);
|
||||||
self.used_pages -= 1;
|
self.used_pages -= 1;
|
||||||
if byte < self.last_byte { self.last_byte = byte; }
|
|
||||||
|
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 {
|
if self.bitmap[byte] & (1 << bit) == 0 {
|
||||||
self.bitmap[byte] |= 1 << bit;
|
self.bitmap[byte] |= 1 << bit;
|
||||||
self.ref_counts[idx] = 1;
|
self.ref_counts[idx] = 1;
|
||||||
self.used_pages += 1;
|
self.used_pages += 1;
|
||||||
} else if self.ref_counts[idx] == 0 {
|
} else if self.ref_counts[idx] == 0 {
|
||||||
self.ref_counts[idx] = 1;
|
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) {
|
pub fn inc_ref_frame(&mut self, phys_addr: PhysAddr) {
|
||||||
@@ -127,31 +155,45 @@ impl BitmapPMM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc_frame(&mut self) -> Option<PhysAddr> {
|
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 {
|
for pass in 0..2usize {
|
||||||
let (from, to) = if pass == 0 {
|
let (from, to) = if pass == 0 {
|
||||||
(self.last_byte, len)
|
(self.last_word / 64, l1_len)
|
||||||
} else {
|
} else {
|
||||||
(0, self.last_byte)
|
(0, self.last_word / 64)
|
||||||
};
|
};
|
||||||
|
|
||||||
for byte_idx in from..to {
|
for l1_idx in from..to {
|
||||||
if self.bitmap[byte_idx] == 0xFF { continue; }
|
let l1_word = self.l1_bitmap[l1_idx];
|
||||||
|
if l1_word == 0 { continue; }
|
||||||
|
|
||||||
for bit in 0..8u8 {
|
let word_offset = l1_word.trailing_zeros() as usize;
|
||||||
if self.bitmap[byte_idx] & (1 << bit) == 0 {
|
let word_idx = l1_idx * 64 + word_offset;
|
||||||
let page_idx = byte_idx * 8 + bit as usize;
|
|
||||||
if page_idx >= self.total_pages { return None; }
|
|
||||||
|
|
||||||
self.bitmap[byte_idx] |= 1 << bit;
|
let word = unsafe { self.read_word(word_idx) };
|
||||||
self.ref_counts[page_idx] = 1;
|
if word == !0u64 {
|
||||||
self.used_pages += 1;
|
self.l1_bitmap[l1_idx] &= !(1 << word_offset);
|
||||||
self.last_byte = byte_idx;
|
continue;
|
||||||
|
|
||||||
return Some(PhysAddr(page_idx as u64 * PAGE_SIZE));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
for i in run_start..run_start + count {
|
||||||
self.bitmap[i / 8] |= 1 << (i % 8);
|
self.bitmap[i / 8] |= 1 << (i % 8);
|
||||||
self.ref_counts[i] = 1;
|
self.ref_counts[i] = 1;
|
||||||
|
let word_idx = i / 64;
|
||||||
|
self.l1_update_word(word_idx);
|
||||||
}
|
}
|
||||||
self.used_pages += count;
|
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));
|
return Some(PhysAddr(run_start as u64 * PAGE_SIZE));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -190,8 +234,6 @@ impl BitmapPMM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Module-level convenience functions
|
|
||||||
|
|
||||||
pub fn alloc_frame() -> Option<PhysAddr> {
|
pub fn alloc_frame() -> Option<PhysAddr> {
|
||||||
PMM.lock().as_mut()?.alloc_frame()
|
PMM.lock().as_mut()?.alloc_frame()
|
||||||
}
|
}
|
||||||
@@ -219,7 +261,3 @@ pub fn get_stats() -> (usize, usize) {
|
|||||||
(0, 0)
|
(0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -306,14 +306,12 @@ pub struct AddressSpace {
|
|||||||
pub pml4_phys: PhysAddr,
|
pub pml4_phys: PhysAddr,
|
||||||
/// Sorted (by virt_start), non-overlapping VMA list.
|
/// Sorted (by virt_start), non-overlapping VMA list.
|
||||||
regions: Vec<VmaRegion>,
|
regions: Vec<VmaRegion>,
|
||||||
/// HHDM offset for dereferencing page-table pages.
|
|
||||||
hhdm: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddressSpace {
|
impl AddressSpace {
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn pml4_raw(&self) -> *mut PageTable {
|
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> {
|
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> {
|
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 child_pml4 = unsafe { &mut *child.pml4_raw() };
|
||||||
let parent_pml4 = unsafe { &mut *self.pml4_raw() };
|
let parent_pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
let hhdm = self.hhdm;
|
|
||||||
|
|
||||||
for region in &mut self.regions {
|
for region in &mut self.regions {
|
||||||
let mut child_region = VmaRegion {
|
let mut child_region = VmaRegion {
|
||||||
@@ -350,10 +347,10 @@ impl AddressSpace {
|
|||||||
|
|
||||||
match &mut region.backing {
|
match &mut region.backing {
|
||||||
VmaBacking::Physical(base) => {
|
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, .. } => {
|
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) => {
|
VmaBacking::Anonymous(frames) => {
|
||||||
let cow_needed = region.flags.contains(VmaFlags::WRITE);
|
let cow_needed = region.flags.contains(VmaFlags::WRITE);
|
||||||
@@ -377,10 +374,10 @@ impl AddressSpace {
|
|||||||
page_flags.remove(PageTableFlags::WRITABLE);
|
page_flags.remove(PageTableFlags::WRITABLE);
|
||||||
page_flags.insert(PageTableFlags::COW);
|
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);
|
child_frames[i] = Some(*frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,12 +405,12 @@ impl AddressSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unmap pages + free frames (if owned) for one region.
|
/// 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 pml4 = unsafe { &mut *pml4 };
|
||||||
let own = region.backing.owns_frames();
|
let own = region.backing.owns_frames();
|
||||||
for i in 0..region.pages() {
|
for i in 0..region.pages() {
|
||||||
let virt = VirtAddr(region.virt_start.0 + i as u64 * 4096);
|
let virt = VirtAddr(region.virt_start.0 + i as u64 * 4096);
|
||||||
pml4.unmap_page(virt, hhdm);
|
pml4.unmap_page(virt);
|
||||||
if own {
|
if own {
|
||||||
if let Some(frame) = region.backing.phys_for_page(i) {
|
if let Some(frame) = region.backing.phys_for_page(i) {
|
||||||
pmm::free_frame(frame);
|
pmm::free_frame(frame);
|
||||||
@@ -423,7 +420,6 @@ impl AddressSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn do_revoke_by_token(&mut self, cap_token: u64) {
|
fn do_revoke_by_token(&mut self, cap_token: u64) {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
|
|
||||||
let indices: Vec<usize> = self.regions
|
let indices: Vec<usize> = self.regions
|
||||||
@@ -435,7 +431,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
for idx in indices.into_iter().rev() {
|
for idx in indices.into_iter().rev() {
|
||||||
let region = self.regions.remove(idx);
|
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 {
|
impl AddressSpace {
|
||||||
/// Allocate a fresh, empty address space with a zeroed PML4.
|
/// 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)?;
|
let pml4_phys = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
unsafe {
|
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 {
|
Ok(Self {
|
||||||
asid: alloc_asid()?,
|
asid: alloc_asid()?,
|
||||||
pml4_phys,
|
pml4_phys,
|
||||||
regions: Vec::new(),
|
regions: Vec::new(),
|
||||||
hhdm,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrap an already-active PML4 (bootloader-provided kernel table).
|
/// Wrap an already-active PML4 (bootloader-provided kernel table).
|
||||||
/// ASID 0 = kernel "no PCID tagging"; it is never returned to the pool.
|
/// 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 {
|
pub fn from_active(pml4_phys: PhysAddr, asid: u16) -> Self {
|
||||||
Self { asid, pml4_phys, regions: Vec::new(), hhdm }
|
Self { asid, pml4_phys, regions: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_region(
|
pub fn map_region(
|
||||||
@@ -494,11 +489,10 @@ impl AddressSpace {
|
|||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
let page_flags = flags.to_page_flags();
|
let page_flags = flags.to_page_flags();
|
||||||
let page_count = (size / 4096) as usize;
|
let page_count = (size / 4096) as usize;
|
||||||
let hhdm = self.hhdm;
|
|
||||||
|
|
||||||
let backing = match phys {
|
let backing = match phys {
|
||||||
Some(base) => {
|
Some(base) => {
|
||||||
pml4.map_region(virt, base, size, page_flags, hhdm);
|
pml4.map_region(virt, base, size, page_flags);
|
||||||
VmaBacking::Physical(base)
|
VmaBacking::Physical(base)
|
||||||
}
|
}
|
||||||
None if flags.contains(VmaFlags::LAZY) => {
|
None if flags.contains(VmaFlags::LAZY) => {
|
||||||
@@ -511,13 +505,12 @@ impl AddressSpace {
|
|||||||
for i in 0..page_count {
|
for i in 0..page_count {
|
||||||
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
unsafe {
|
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(
|
pml4.map_page(
|
||||||
VirtAddr(virt.0 + i as u64 * 4096),
|
VirtAddr(virt.0 + i as u64 * 4096),
|
||||||
frame,
|
frame,
|
||||||
page_flags,
|
page_flags,
|
||||||
hhdm,
|
|
||||||
);
|
);
|
||||||
frames.push(Some(frame));
|
frames.push(Some(frame));
|
||||||
}
|
}
|
||||||
@@ -557,7 +550,6 @@ impl AddressSpace {
|
|||||||
|
|
||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
let pml4 = unsafe { &mut *self.pml4_raw() };
|
||||||
let page_flags = flags.to_page_flags();
|
let page_flags = flags.to_page_flags();
|
||||||
let hhdm = self.hhdm;
|
|
||||||
|
|
||||||
// Eagerly map all pages — the physical addresses are already known.
|
// Eagerly map all pages — the physical addresses are already known.
|
||||||
for i in 0..page_count {
|
for i in 0..page_count {
|
||||||
@@ -565,7 +557,6 @@ impl AddressSpace {
|
|||||||
VirtAddr(virt.0 + i as u64 * 4096),
|
VirtAddr(virt.0 + i as u64 * 4096),
|
||||||
PhysAddr(phys_base.0 + i as u64 * 4096),
|
PhysAddr(phys_base.0 + i as u64 * 4096),
|
||||||
page_flags,
|
page_flags,
|
||||||
hhdm,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +576,6 @@ impl AddressSpace {
|
|||||||
/// Returns `Ok(())` if the fault was a valid lazy demand-page (caller should
|
/// Returns `Ok(())` if the fault was a valid lazy demand-page (caller should
|
||||||
/// `iretq` to retry). Returns `Err` for illegal accesses.
|
/// `iretq` to retry). Returns `Err` for illegal accesses.
|
||||||
pub fn handle_fault(&mut self, fault_addr: VirtAddr, write: bool) -> Result<(), VmError> {
|
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 idx = self.find_idx(fault_addr).ok_or(VmError::RegionNotFound)?;
|
||||||
|
|
||||||
let (virt_start, region_flags) = {
|
let (virt_start, region_flags) = {
|
||||||
@@ -601,7 +591,7 @@ impl AddressSpace {
|
|||||||
let page_virt = VirtAddr(virt_start.0 + page_idx as u64 * 4096);
|
let page_virt = VirtAddr(virt_start.0 + page_idx as u64 * 4096);
|
||||||
let pml4 = unsafe { &mut *self.pml4_raw() };
|
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));
|
let is_cow = current_pte_flags.map_or(false, |f| f.contains(PageTableFlags::COW));
|
||||||
|
|
||||||
if write && is_cow {
|
if write && is_cow {
|
||||||
@@ -617,8 +607,8 @@ impl AddressSpace {
|
|||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
core::ptr::copy_nonoverlapping(
|
core::ptr::copy_nonoverlapping(
|
||||||
old_frame.to_virt(hhdm).as_ptr::<u8>(),
|
old_frame.to_virt().as_ptr::<u8>(),
|
||||||
new_frame.to_virt(hhdm).as_mut_ptr::<u8>(),
|
new_frame.to_virt().as_mut_ptr::<u8>(),
|
||||||
4096,
|
4096,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -629,7 +619,7 @@ impl AddressSpace {
|
|||||||
target_flags.remove(PageTableFlags::COW);
|
target_flags.remove(PageTableFlags::COW);
|
||||||
target_flags.insert(PageTableFlags::WRITABLE);
|
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);
|
pmm::free_frame(old_frame);
|
||||||
|
|
||||||
@@ -651,7 +641,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
let frame = pmm::alloc_frame().ok_or(VmError::OutOfMemory)?;
|
||||||
unsafe {
|
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);
|
frames[page_idx] = Some(frame);
|
||||||
|
|
||||||
@@ -660,7 +650,7 @@ impl AddressSpace {
|
|||||||
target_flags.remove(PageTableFlags::WRITABLE);
|
target_flags.remove(PageTableFlags::WRITABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
pml4.map_page(page_virt, frame, target_flags, hhdm);
|
pml4.map_page(page_virt, frame, target_flags);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -669,7 +659,7 @@ impl AddressSpace {
|
|||||||
let idx = self.find_idx(virt).ok_or(VmError::RegionNotFound)?;
|
let idx = self.find_idx(virt).ok_or(VmError::RegionNotFound)?;
|
||||||
let region = self.regions.remove(idx);
|
let region = self.regions.remove(idx);
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
Self::do_unmap(pml4, ®ion, self.hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
tlb_flush_asid(self.asid);
|
tlb_flush_asid(self.asid);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -679,7 +669,6 @@ impl AddressSpace {
|
|||||||
/// Hardware access is terminated before this function returns.
|
/// Hardware access is terminated before this function returns.
|
||||||
/// Shared-backed VMAs are unmapped from the PT without freeing frames.
|
/// Shared-backed VMAs are unmapped from the PT without freeing frames.
|
||||||
pub fn revoke_by_token(&mut self, cap_token: u64) {
|
pub fn revoke_by_token(&mut self, cap_token: u64) {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
|
|
||||||
let indices: Vec<usize> = self.regions
|
let indices: Vec<usize> = self.regions
|
||||||
@@ -693,7 +682,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
for idx in indices.into_iter().rev() {
|
for idx in indices.into_iter().rev() {
|
||||||
let region = self.regions.remove(idx);
|
let region = self.regions.remove(idx);
|
||||||
Self::do_unmap(pml4, ®ion, hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
}
|
}
|
||||||
|
|
||||||
tlb_flush_asid(self.asid);
|
tlb_flush_asid(self.asid);
|
||||||
@@ -703,7 +692,7 @@ impl AddressSpace {
|
|||||||
|
|
||||||
/// Walk the live page table to translate `virt` → physical address.
|
/// Walk the live page table to translate `virt` → physical address.
|
||||||
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
pub fn translate(&self, virt: VirtAddr) -> Option<PhysAddr> {
|
||||||
unsafe { (*self.pml4_raw()).translate(virt, self.hhdm) }
|
unsafe { (*self.pml4_raw()).translate(virt) }
|
||||||
}
|
}
|
||||||
|
|
||||||
//Activation
|
//Activation
|
||||||
@@ -737,10 +726,9 @@ impl AddressSpace {
|
|||||||
|
|
||||||
impl Drop for AddressSpace {
|
impl Drop for AddressSpace {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let hhdm = self.hhdm;
|
|
||||||
let pml4 = unsafe { self.pml4_raw() };
|
let pml4 = unsafe { self.pml4_raw() };
|
||||||
while let Some(region) = self.regions.pop() {
|
while let Some(region) = self.regions.pop() {
|
||||||
Self::do_unmap(pml4, ®ion, hhdm);
|
Self::do_unmap(pml4, ®ion);
|
||||||
}
|
}
|
||||||
pmm::free_frame(self.pml4_phys);
|
pmm::free_frame(self.pml4_phys);
|
||||||
// Return the ASID to the pool so it can be reused by future processes.
|
// 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).
|
/// ASID 0 = PCID 0 = kernel (no per-process PCID tagging).
|
||||||
/// Must be called after `BitmapPMM::init` and `init_cpu_features`.
|
/// Must be called after `BitmapPMM::init` and `init_cpu_features`.
|
||||||
pub fn init_kernel_space(pml4_phys: PhysAddr, hhdm: u64) {
|
pub fn init_kernel_space(pml4_phys: PhysAddr) {
|
||||||
*KERNEL_SPACE.lock() = Some(AddressSpace::from_active(pml4_phys, 0, hhdm));
|
*KERNEL_SPACE.lock() = Some(AddressSpace::from_active(pml4_phys, 0));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user