Прерывания
Прерывания – это механизм, позволяющий аппаратным устройствам и программным событиям асинхронно уведомлять процессор о необходимости обработки. Прерывания лежат в основе любой событийно-ориентированно
Прерывания – это механизм, позволяющий аппаратным устройствам и программным событиям асинхронно уведомлять процессор о необходимости обработки. Прерывания лежат в основе любой событийно-ориентированной системы: они обеспечивают реакцию на нажатие клавиш, движение мыши, приход сетевых пакетов, завершение операций ввода‑вывода. Без прерываний процессор был бы вынужден постоянно опрашивать устройства (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)
- Процессор завершает текущую инструкцию.
- Сохраняет контекст: регистры, флаги, CS:EIP (или RIP).
- По вектору из IDT определяет обработчик.
- Переключает уровень привилегий на кольцо 0 (если необходимо).
- Выполняет обработчик (ISR).
- По инструкции 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");
- В чём разница между маскируемыми и немаскируемыми прерываниями?
- Зачем нужны нижние половины?
- Какой механизм отложенного выполнения в Linux следует выбрать для длительной операции, которая может спать?
- Почему в QNX обработчики часто реализуются как потоки?
- Напишите простой модуль ядра 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`.