feat: docs & ARCH 2.2, 2.3, 2.4

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

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

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

View File

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

View File

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

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

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