Прерывания¶
Что такое прерывание и какими они бывают¶
Прерывание (interrupt) — это событие, которое заставляет процессор прервать текущее выполнение и передать управление специальному обработчику (interrupt handler). После обработки процессор возвращается к прерванному коду.
Прерывания делятся на три категории:
Внешние (аппаратные, asynchronous) — поступают от периферийных устройств в любой момент:
- таймер (Timer interrupt, IRQ0) — основа вытесняющей многозадачности;
- контроллер диска — сигнализирует о завершении операции ввода-вывода;
- сетевая карта — получен пакет;
- клавиатура, мышь — нажата клавиша.
Исключения процессора (synchronous faults/traps) — генерируются самим процессором при определённых условиях:
#DE(Divide Error, вектор 0) — деление на ноль;#BP(Breakpoint, вектор 3) — инструкцияint 3;#GP(General Protection, вектор 13) — нарушение прав (привилегированная инструкция в user mode);#PF(Page Fault, вектор 14) — обращение к невалидной или отсутствующей странице;#UD(Undefined Opcode, вектор 6) — неизвестная инструкция.
Программные прерывания — вызываются инструкцией int n:
int 3— точка останова (breakpoint);int 0x80— устаревший способ системных вызовов на x86-32.
Обработка прерывания: что делает процессор¶
Когда аппаратура или исключение сигнализируют прерывание, процессор выполняет строго определённую последовательность:
User space Kernel space
────────────────── ──────────────────────────────────────────────
Процесс выполняется
│
│ ← аппаратный сигнал (IRQ) или исключение (#PF, #GP, ...)
│
▼
CPU сохраняет на kernel stack:
┌──────────────────────────────┐
│ SS (сегм. стека) │ ← если смена кольца (ring 3 → ring 0)
│ RSP (указатель стека)│
│ RFLAGS (регистр флагов) │
│ CS (сегм. кода) │
│ RIP (адрес возврата) │
│ Error code (если есть) │ ← #DF, #TS, #NP, #SS, #GP, #PF, #AC (и #VC, #CP на новых CPU)
└──────────────────────────────┘
│
│ CPU читает IDTR → находит IDT в памяти
│ берёт дескриптор IDT[вектор]
│ из дескриптора извлекает адрес обработчика
▼
┌─────────────────────┐
│ interrupt_handler │ ← ядро обрабатывает прерывание
│ (ring 0) │ (обслуживает устройство, обрабатывает fault...)
└─────────────────────┘
│
│ iret — восстанавливает RIP, CS, RFLAGS, RSP, SS
▼
Процесс продолжает выполнение (или другой процесс, если был context switch)
Таблица дескрипторов прерываний (IDT)¶
IDT (Interrupt Descriptor Table) — таблица в памяти ядра, содержащая 256 записей (дескрипторов). Каждый дескриптор указывает на обработчик конкретного прерывания или исключения.
IDTR (регистр процессора)
┌──────────────────────────────────────────┐
│ base address IDT │ limit (size-1) │
└────────────────┬─────────────────────────┘
│
▼
IDT в памяти ядра (256 × 16 байт = 4096 байт)
┌─────┬──────────────────────────────────────────────────────────┐
│ 0 │ Gate descriptor: offset handler, segment sel, type, DPL │ #DE divide error
├─────┼──────────────────────────────────────────────────────────┤
│ 1 │ Gate descriptor │ #DB debug
├─────┼──────────────────────────────────────────────────────────┤
│ 2 │ Gate descriptor │ NMI
├─────┼──────────────────────────────────────────────────────────┤
│ 3 │ Gate descriptor (DPL=3 — доступен из user mode) │ #BP int 3
├─────┼──────────────────────────────────────────────────────────┤
│ ... │ ... │
├─────┼──────────────────────────────────────────────────────────┤
│ 13 │ Gate descriptor │ #GP general protection
├─────┼──────────────────────────────────────────────────────────┤
│ 14 │ Gate descriptor │ #PF page fault
├─────┼──────────────────────────────────────────────────────────┤
│ ... │ ... │
├─────┼──────────────────────────────────────────────────────────┤
│ 32 │ Gate descriptor │ IRQ0 (timer)
├─────┼──────────────────────────────────────────────────────────┤
│ 33 │ Gate descriptor │ IRQ1 (keyboard)
├─────┼──────────────────────────────────────────────────────────┤
│ ... │ ... │
├─────┼──────────────────────────────────────────────────────────┤
│255 │ Gate descriptor │
└─────┴──────────────────────────────────────────────────────────┘
Структура одного gate descriptor (16 байт, Interrupt Gate, x86-64):
┌─────────────────────────────────────────────────────────────┐
│ байты 0..1: offset[15:0] байты 2..3: segment selector │
├─────────────────────────────────────────────────────────────┤
│ байт 4: IST (биты 0..2) | 0 (биты 3..7) │
├─────────────────────────────────────────────────────────────┤
│ байт 5: type (биты 0..3) | 0 | DPL (биты 5..6) | P (бит 7) │
├─────────────────────────────────────────────────────────────┤
│ байты 6..7: offset[31:16] │
├─────────────────────────────────────────────────────────────┤
│ байты 8..11: offset[63:32] │
├─────────────────────────────────────────────────────────────┤
│ байты 12..15: reserved (должны быть нули) │
└─────────────────────────────────────────────────────────────┘
offset — полный 64-битный адрес обработчика
type — Interrupt Gate (IF=0 при входе) или Trap Gate (IF не меняется)
DPL — минимальный уровень привилегий для программного вызова через int n
P — present bit (1 — дескриптор валиден)
Структура каждой записи IDT:
- адрес обработчика;
- уровень привилегий (ring level), с которого разрешён вызов;
- тип (Interrupt Gate, Trap Gate, Task Gate).
Адрес IDT хранится в специальном регистре IDTR, который загружается привилегированной инструкцией:
Стандартные векторы:
| Вектор | Тип | Описание |
|---|---|---|
| 0 | Fault | Divide by zero (#DE) |
| 1 | Trap | Debug |
| 2 | Interrupt | NMI (Non-Maskable Interrupt) |
| 3 | Trap | Breakpoint (#BP, int 3) |
| 6 | Fault | Invalid Opcode (#UD) |
| 13 | Fault | General Protection (#GP) |
| 14 | Fault | Page Fault (#PF) |
| 32–47 | Interrupt | IRQ0–IRQ15 (аппаратные прерывания) |
| 128 | Trap | int 0x80 (системный вызов, x86-32) |
Прерывание vs. системный вызов¶
| Аспект | Прерывание | Системный вызов |
|---|---|---|
| Источник | Аппаратура или процессор | Программа (инструкция syscall/int) |
| Асинхронность | Асинхронное (в любой момент) | Синхронное (контролируется программой) |
| Сохранение контекста | Ядро сохраняет все регистры | rcx, r11 перезаписываются |
| Задержка | Непредсказуемая | Предсказуемая |
| Примеры | Timer interrupt, Page Fault | read(), write(), fork() |
Оба механизма переключают процессор в kernel mode и используют похожую инфраструктуру (IDT), но системный вызов — это управляемый переход, тогда как прерывание — внешнее событие.
Генерация прерываний из кода¶
/* int 3 — breakpoint; доставит SIGTRAP в user mode */
void trigger_breakpoint(void) {
asm volatile("int $3");
}
/* Системный вызов write через int 0x80 (x86-32 legacy) */
void write_int80(const char *msg, int len) {
asm volatile(
"movl $4, %%eax\n\t" /* sys_write = 4 */
"movl $1, %%ebx\n\t" /* fd = stdout */
"movl %0, %%ecx\n\t" /* buffer */
"movl %1, %%edx\n\t" /* length */
"int $0x80"
: : "r"(msg), "r"(len)
: "eax", "ebx", "ecx", "edx"
);
}
В x86-64 для системных вызовов используется инструкция syscall, а не int 0x80.
Как ОС управляет процессами через прерывания¶
Основа вытесняющей многозадачности — таймерное прерывание:
- Аппаратный таймер (PIT/HPET/APIC timer) генерирует IRQ0 каждые ~10 мс (100 Гц) или чаще.
- Процессор прерывает текущий процесс и вызывает обработчик из IDT[32].
- Ядро сохраняет полный контекст прерванного процесса (все регистры,
rip,rflags,rsp) в kernel stack. - Вызывается планировщик (scheduler) — выбирает следующий процесс.
- Ядро восстанавливает контекст следующего процесса.
- Инструкция
iretвозвращает управление — но уже другому процессу.
Процесс A работает
|
| (через 10 мс)
v
Timer IRQ0 -> IDT[32] -> timer_handler()
|
| Сохранить контекст A
| Выбрать процесс B
| Восстановить контекст B
v
iret -> Процесс B продолжает
Упрощённый обработчик таймера в ядре:
void timer_interrupt_handler(struct pt_regs *regs) {
/* regs содержит сохранённый контекст прерванного процесса */
update_process_times();
struct task_struct *next = schedule();
if (next != current)
context_switch(current, next, regs);
/* iret восстановит контекст нового процесса */
}
Аналогично работают и другие прерывания: Page Fault загружает отсутствующую страницу из swap; прерывание диска разблокирует процесс, ожидавший I/O; сетевое прерывание добавляет пакет в очередь приёма.
Прерывания — механизм, которым ОС отбирает контроль у процессов и обеспечивает многозадачность.
Связанные темы¶
- Режимы процессора и системные вызовы — ring 0/3, инструкция
syscall, vDSO - Параллелизм на уровне инструкций — спекулятивное исполнение и его взаимодействие с прерываниями
- Стековые фреймы и вызовы функций — red zone и почему ядро компилируется с
-mno-red-zone
Источники¶
- Intel SDM Vol. 3A: Chapter 6 — Interrupt and Exception Handling
man 7 signal— сигналы как высокоуровневый интерфейс к прерываниям- Linux kernel documentation: interrupts — https://www.kernel.org/doc/html/latest/
- OSDev Wiki: Interrupts — https://wiki.osdev.org/Interrupts