Блог "Дети ОИТа"
/поиск

Прерывания

Прерывания – это механизм, позволяющий аппаратным устройствам и программным событиям асинхронно уведомлять процессор о необходимости обработки. Прерывания лежат в основе любой событийно-ориентированно

Прерывания – это механизм, позволяющий аппаратным устройствам и программным событиям асинхронно уведомлять процессор о необходимости обработки. Прерывания лежат в основе любой событийно-ориентированной системы: они обеспечивают реакцию на нажатие клавиш, движение мыши, приход сетевых пакетов, завершение операций ввода‑вывода. Без прерываний процессор был бы вынужден постоянно опрашивать устройства (polling), что крайне неэффективно. В этой статье рассматриваются классификация прерываний, аппаратная поддержка, методы обработки, механизмы отложенных действий, а также особенности реализации в различных ОС, включая Linux, Windows и QNX. Приводятся диаграммы последовательности и примеры кода на C.

1.1. Английские сокращения

Сокращение Английское обозначение Русское обозначение
IRQ Interrupt Request Запрос прерывания
ISR Interrupt Service Routine Обработчик прерывания
PIC Programmable Interrupt Controller Программируемый контроллер прерываний
APIC Advanced Programmable Interrupt Controller Расширенный программируемый контроллер прерываний
IDT Interrupt Descriptor Table Таблица дескрипторов прерываний
IVT Interrupt Vector Table Таблица векторов прерываний (режим реальный)
IRET Interrupt Return Инструкция возврата из прерывания
CLI/STI Clear/Set Interrupt Flag Очистка/установка флага прерываний
NMI Non‑Maskable Interrupt Немаскируемое прерывание
IPI Inter‑Processor Interrupt Межпроцессорное прерывание
MSI Message Signaled Interrupt Прерывание на основе сообщений (PCIe)
MSI‑X Extended MSI Расширенное MSI
DPC Deferred Procedure Call Отложенный вызов процедуры (Windows)
softirq Software Interrupt Программное прерывание (Linux)
tasklet Tasklet Малая отложенная задача (Linux)
workqueue Workqueue Очередь работ (Linux)
BH Bottom Half (Linux) Нижняя половина обработчика
TH Top Half (Linux) Верхняя половина обработчика
APIC Advanced Programmable Interrupt Controller Расширенный контроллер прерываний
IOAPIC I/O APIC Контроллер прерываний ввода‑вывода
LAPIC Local APIC Локальный APIC (в каждом ядре)
MSR Model‑Specific Register Регистр, зависящий от модели процессора
EOI End of Interrupt Сигнал окончания обработки прерывания
RTOS Real‑Time Operating System Операционная система реального времени

1.2. Русские сокращения

Сокращение Обозначение
ПР Прерывание
ОПР Обработчик прерывания
КПР Контроллер прерываний
ТДП Таблица дескрипторов прерываний
НМП Немаскируемое прерывание
МПП Межпроцессорное прерывание
МСП Прерывание на основе сообщений
ОПВ Отложенный вызов процедуры
ПРП Программное прерывание
ОЗД Отложенная задача
ОЧР Очередь работ
ВПЧ Верхняя половина
НПЧ Нижняя половина

2.1. По источнику

  • Аппаратные прерывания – генерируются внешними устройствами (клавиатура, таймер, сетевой адаптер, диск).
  • Программные прерывания – генерируются инструкцией `INT n` (например, системные вызовы в DOS, `int 0x80` в Linux).
  • Исключения – синхронные события, вызванные самим процессором при обнаружении ошибок (деление на ноль, страничная ошибка, недопустимая инструкция).

2.2. По маскируемости

  • Маскируемые – могут быть временно запрещены сбросом флага IF (CLI/STI).
  • Немаскируемые (NMI) – не могут быть заблокированы; используются для критических событий (сбой питания, сбой шины).

2.3. По способу доставки

  • Линейные (классические) – через отдельные линии на PIC/APIC.
  • На основе сообщений (MSI) – PCIe устройства записывают данные в специальный адрес, инициируя прерывание. Позволяют использовать больше векторов и лучше масштабируются на многоядерных системах.

3.1. Контроллеры прерываний

  • PIC (8259) – классический контроллер с 8 линиями, возможность каскадирования (до 15). Приоритеты фиксированы (IRQ0 – самый высокий). Использует «режим сдвига» для поддержки вложенности.
  • APIC – современный контроллер для многопроцессорных систем. Включает IOAPIC (на материнской плате) и LAPIC (в каждом ядре). Поддерживает расписание прерываний на разные ядра, приоритеты, доставку через шину. В 64‑битных системах используется x2APIC (расширенный APIC).

3.2. Идентификаторы прерываний

Каждому прерыванию соответствует вектор – номер от 0 до 255. Векторы 0–31 зарезервированы для исключений и NMI. Пользовательские векторы (32–255) используются для аппаратных и программных прерываний. IDT хранит дескрипторы для каждого вектора: адрес обработчика, уровень привилегий (DPL), тип (шлюз прерывания/ловушки).

3.3. Диаграмма обработки прерывания (аппаратная часть)

@startuml
title Аппаратный путь прерывания
participant "Устройство" as Device
participant "Контроллер (PIC/APIC)" as PIC
participant "Процессор" as CPU
participant "IDT" as IDT
participant "Обработчик" as ISR

Device -> PIC : сигнал IRQ
PIC -> CPU : INT (вектор)
CPU -> IDT : чтение дескриптора
IDT --> CPU : адрес ISR
CPU -> ISR : переход к обработчику
@enduml

4.1. Базовый алгоритм (x86)

  1. Процессор завершает текущую инструкцию.
  2. Сохраняет контекст: регистры, флаги, CS:EIP (или RIP).
  3. По вектору из IDT определяет обработчик.
  4. Переключает уровень привилегий на кольцо 0 (если необходимо).
  5. Выполняет обработчик (ISR).
  6. По инструкции IRET восстанавливает контекст и возвращается.

4.2. Классический подход: верхняя (Top Half) и нижняя (Bottom Half) половины

Верхняя половина (Top Half) – часть обработчика, выполняемая немедленно, с запрещёнными прерываниями. Она должна быть максимально короткой (обычно только фиксация факта, сохранение данных в буфер). Остальная логика (длительная) переносится в нижнюю половину (Bottom Half), которая выполняется позже, с разрешёнными прерываниями.

4.3. Механизмы отложенного выполнения в Linux

  • softirq – базовый механизм, выполняется в контексте прерывания (с высокой приоритетностью). Может быть вытеснено только другим softirq или аппаратным прерыванием.
  • tasklet – построены на softirq, но обеспечивают сериализацию (один tasklet не может выполняться параллельно на нескольких ядрах). Удобны для драйверов.
  • workqueue – выполняются в контексте рабочего потока (пространство пользователя), могут спать, использовать блокировки. Предпочтительны для длительных операций.

4.4. Другие ОС

  • Windows использует DPC (Deferred Procedure Call) – аналогично softirq, но с приоритетами; также есть APC (Asynchronous Procedure Call) для пользовательского режима.
  • QNX Neutrino – прерывания обрабатываются потоками; ISR минимальна, основная логика – в высокоприоритетных потоках (threaded interrupts), что упрощает синхронизацию.

5.1. Регистрация обработчика

Функция `request<sub>irq</sub>()` (или `devm<sub>request</sub>_irq()`):

  • номер IRQ,
  • указатель на функцию ISR,
  • флаги (например, `IRQF<sub>SHARED</sub>` – совместное использование),
  • имя,
  • идентификатор устройства.

Возвращает 0 при успехе.

5.2. Структура типичного ISR

static irqreturn_t my_isr(int irq, void *dev_id)
{
    // 1. Прочитать состояние устройства (статус)
    // 2. Сбросить флаг прерывания
    // 3. Переместить данные из буфера устройства
    // 4. Запланировать bottom half (tasklet/workqueue)
    // 5. Вернуть IRQ_HANDLED или IRQ_NONE
    return IRQ_HANDLED;
}

5.3. Реализация tasklet

DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
void my_tasklet_func(unsigned long data)
{
    // длительная обработка
}
// в ISR:
tasklet_schedule(&my_tasklet);

5.4. Реализация workqueue

static struct workqueue_struct *my_wq;
static DECLARE_WORK(my_work, my_work_func);

void my_work_func(struct work_struct *work)
{
    // длительная обработка, может спать
}

// инициализация:
my_wq = create_workqueue("my_wq");
// в ISR:
queue_work(my_wq, &my_work);

5.5. Современный подход: прерывания как потоки (threaded IRQ)

В Linux можно зарегистрировать обработчик, который будет выполняться в отдельном потоке ядра (IRQ thread). Это упрощает синхронизацию (можно использовать мьютексы, спать). Регистрация с флагом `IRQF<sub>ONESHOT</sub>` и функцией `request<sub>threaded</sub>_irq()`.

static irqreturn_t my_threaded_isr(int irq, void *dev_id)
{
    // выполняется в потоке
    return IRQ_HANDLED;
}
request_threaded_irq(irq, my_isr, my_threaded_isr, IRQF_ONESHOT, "my_dev", dev);
Механизм Контекст Блокировки Вытеснение Применение
Top Half (ISR) прерывания запрещены (атомарный) нет минимальная работа
softirq прерывания (контекст softirq) ограниченно (spinlock) может быть вытеснено softirq высокопроизводительная обработка (сеть)
tasklet прерывания ограниченно сериализовано драйверы
workqueue процесс/поток ядра можно спать, мьютексы да длительные операции
threaded IRQ поток ядра можно спать да современный подход

7.1. Windows

  • ISR – выполняется с высоким IRQL (запрещены прерывания).
  • DPC (Deferred Procedure Call) – аналогична softirq, выполняется при более низком IRQL.
  • APC – асинхронные вызовы в пользовательском режиме.

7.2. QNX Neutrino

  • Микроядро; обработчики прерываний пишутся как отдельные потоки.
  • ISR минимальна (только фиксация), затем планируется обработчик в виде потока (позволяет использовать все примитивы синхронизации).

7.3. FreeBSD

  • Использует `ithread` – обработчики в виде потоков ядра, аналогично threaded IRQ в Linux.
  • Поддержка совместного использования IRQ.
@startuml
title Обработка прерывания в Linux (с tasklet)
participant "Аппаратное устройство" as Dev
participant "Аппаратный контроллер" as PIC
participant "Процессор" as CPU
participant "IDT" as IDT
participant "Top Half (ISR)" as Top
participant "tasklet" as Task
participant "Процесс/поток" as Proc

Dev -> PIC : IRQ
PIC -> CPU : INT
CPU -> IDT : чтение вектора
IDT --> CPU : адрес ISR
CPU -> Top : выполнение ISR
Top -> Top : чтение статуса, сброс флага, копирование данных
Top -> Task : tasklet_schedule()
Top --> CPU : IRET (разрешение прерываний)
CPU -> Proc : возврат к пользовательскому процессу
... позже ...
CPU -> Task : выполнение tasklet (bottom half)
Task -> Task : длительная обработка
@enduml

Предположим, устройство посылает прерывание по IRQ 17 при поступлении данных.

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>

static int irq = 17;
static struct tasklet_struct my_tasklet;

static void my_tasklet_func(unsigned long data)
{
    pr_info("Tasklet: обрабатываю данные устройства\n");
    // здесь длительная работа
}

static irqreturn_t my_isr(int irq, void *dev_id)
{
    pr_info("ISR: получил прерывание на IRQ %d\n", irq);
    // чтение статуса, сброс бита
    tasklet_schedule(&my_tasklet);
    return IRQ_HANDLED;
}

static int __init my_init(void)
{
    int ret;
    tasklet_init(&my_tasklet, my_tasklet_func, 0);
    ret = request_irq(irq, my_isr, IRQF_SHARED, "my_device", &my_tasklet);
    if (ret) {
        pr_err("Не удалось зарегистрировать IRQ %d\n", irq);
        return ret;
    }
    pr_info("Драйвер загружен, IRQ %d зарегистрирован\n", irq);
    return 0;
}

static void __exit my_exit(void)
{
    free_irq(irq, &my_tasklet);
    tasklet_kill(&my_tasklet);
    pr_info("Драйвер выгружен\n");
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
  1. В чём разница между маскируемыми и немаскируемыми прерываниями?
  2. Зачем нужны нижние половины?
  3. Какой механизм отложенного выполнения в Linux следует выбрать для длительной операции, которая может спать?
  4. Почему в QNX обработчики часто реализуются как потоки?
  5. Напишите простой модуль ядра Linux, регистрирующий обработчик на пустой IRQ (заглушку) с использованием tasklet.

Обработка прерываний – ключевой механизм в операционных системах, обеспечивающий эффективное взаимодействие с оборудованием. Современные ОС предлагают гибкие методы разделения работы на быструю (верхняя половина) и отложенную (нижняя половина), что позволяет сохранять отзывчивость системы. Linux предоставляет softirq, tasklet, workqueue и threaded IRQ для разных сценариев. Выбор подхода зависит от требований к производительности, сложности и контексту выполнения. Понимание этих механизмов необходимо для разработки драйверов и оптимизации систем реального времени.

Команда Описание
`cat /proc/interrupts` Показать статистику по IRQ
`cat /proc/irq/*/smp<sub>affinity</sub>` Привязка IRQ к ядрам
`echo 1 > /proc/irq/17/smp<sub>affinity</sub>` Назначить IRQ 17 на ядро 0
`irqbalance` Демон балансировки прерываний между ядрами

Примеры кода предназначены для модулей ядра Linux и требуют прав root, установленного ядра и заголовков. В учебных целях их можно компилировать с Makefile. Код приведён для иллюстрации. Для запуска в org-babel не подходит, но может быть скопирован в файл `.c`.