Files
Elyz/kernel/docs/cpu/interrupts.md
2026-07-07 16:40:41 +03:00

6.9 KiB
Raw Blame History

Обработчики прерываний и исключений: 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 (макрос)

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_stubrust_page_fault_handler (вектор 14)
  • gpf_stubrust_gpf_handler (вектор 13)
  • double_fault_stubrust_double_fault_handler (вектор 8)

TLB shootdown stub

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)

.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

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)

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 для ранней загрузки

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() — перезагрузка после полной инициализации

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)

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 обработчик

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.

Константы

pub const TLB_SHOOTDOWN_VECTOR: u8 = 0xFD;

Вектор 0xFD (253) — в диапазоне пользовательских прерываний (32-255), намеренно далеко от системных векторов 0-31.