Прерывания для самых маленьких
Сегодня мы поговорим о прерываниях процессоров семейства x86 (-64). Подробнее под катом.
Прерывания — это как бы сигнал процессору, что надо прервать выполнение (их поэтому и назвали прерываниями) текущего кода и срочно сделать то, что указано в обработчике.
Все адреса обработчиков прерываний хранятся в IDT. Это таблица, в которой хранятся 256 (можно больше или меньше, но большие значения просто игнорируются) ячеек (векторы прерываний) с типом и атрибутами прерывания, одним просто нулевым значением, собственно адресом обработчика прерываний и селектором кода в GDT или LDT, который будет использовать данный вектор прерываний. Теперь немного о типе и атрибутах.
Тип прерывания и атрибуты занимают 8 бит. Первые 4 бита занимают тип:
- 0b0101: 32-битный гейт задачи, при появлении такого прерывания происходит хардверное переключение задачи (да-да, есть и такое, но его уже давно не используют)
- 0b0110: 16-битный гейт прерывания
- 0b0111: 16-битный гейт trap’a (я не знаю, как это перевести на русский язык, извините)
- 0b1110: 32-битный гейт прерывания
- 0b1111: 32-битный гейт trap’a
Далее идут атрибуты. Первым атрибутом является 1 бит, который задан в 0 для гейтов прерывания и в 1 для остальных. Далее идет уровень привилегий дескриптора — 2 бита, задающие минимальный уровень привилегий для вызова прерываний, и 1 бит, заданный в 0 для неиспользуемых прерываний.
Теперь о том, как процессор вызывает обработчики.
Допустим, что вы вызвали инструкцию int 0
в ассемблере. Это даст сигнал процессору, что надо вызвать прерывание 0, если это возможно. Вот последовательность действий, которые происходят при этом.
- Поиск вектора №0 в IDT.
- Сравнение уровня привилегий дескриптора и текущего уровня привилегий процессора.
- Если текущий уровень привилегий процессора меньше уровня привилегий дескриптора, то просто вызвать генеральную ошибку защиты и не вызывать прерывание.
- Происходит сохранение адреса возвращения, регистра (E)FLAGS и другой информации.
- Происходит переход на адрес, указанный в векторе №0 IDT.
- После выполнения обработчика инструкция
iret
возвращает управление прерванному коду.
Еще есть прерывания, которые генерируются самим процессором при определенных обстоятельствах — исключения. Вот их список с краткими описаниями:
- Деление на ноль. Генерируется при, собственно, делении на ноль.
- Отладочное исключение. Генерироваться само не может, используется для, собственно, отладки.
- Немаскируемое прерывание. Генерируется при ошибках ОЗУ и невосстановимых ошибках «железа». Их невозможно замаскировать с помощью PIC (Programmable Interrupt Controller — программируемый контроллер прерываний), так как оно идет сразу в процессор, минуя PIC, но можно просто отключить.
- Точка останова. Тоже используется для отладки, потому что его опкод занимает всего 1 байт, в отличии от остальных INT N. Переназначалось DOS-отладчиками для своих целей.
- Переполнение. Генерируется инструкцией
INTO
, если в (E)FLAGS включен бит переполнения. - Выход за пределы. Генерируется при ошибке инструкции BOUND.
- Недопустимый опкод. Генерируется при попытке выполнения недопустимого кода операции.
- Устройство недоступно. Сейчас не используется, генерировался при попытке использования операций с плавающей точкой на процессорах без FPU.
- Double fault. Сложно перевести название. Ошибка невосстановима, происходит при невозможности вызвать обработчик исключения.
- Переполнение сегмента сопроцессора. Больше не используется.
- Недопустимый TSS. Сегмент состояния задачи задан неправильно.
- Сегмент отсутствует. Возникает при попытке загрузки сегмента с битом Present == 0.
- Ошибка сегмента стека. Возникает при попытке загрузки сегмента с битом Present == 0 или переполнении стека.
- Генеральная ошибка защиты. Генерируется в очень большом числе случаев, среди них есть ошибка сегмента, попытка выполнения инструкции без необходимых прав, запись туда, куда не надо, попытка доступа к нулевому дескриптору GDT и многое другое.
- Ошибка страницы. Происходит при чтении или записи в несуществующую страницу памяти, попытке доступа к данным без необходимых прав или другом.
- Ошибка с плавающей точкой. Происходит при выполнении инструкции FWAIT или WAIT с битом №5 в CR0 == 0.
- Ошибка при проверке на выравнивание. Происходит только в третьем кольце привилегий процессора, если эта ошибка, конечно, включена.
- Ошибка при проверке машины. Генерируется процессором при обнаружении «железных» ошибок.
- Исключение с плавающей точкой SIMD. Генерируется при ошибках с 128-битными числами с плавающей точкой.
- Ошибка виртуализации.
- Ошибка безопасности.
- Тройная ошибка. По сути исключением не является, это даже не прерывание. Происходит при невозможности вызвать Double Fault. Вызывает немедленную перезагрузку компьютера.
Существует особый тип прерываний — IRQ (Interrupt ReQuest), или же аппаратные прерывания, но я буду их для краткости называть просто IRQ. Технически они почти не отличаются от любых других прерываний, но генерируются не процессором или самим кодом, а устройствами, подключенными к компьютеру. К примеру, IRQ №0 генерируется PIT (таймер с программируемым интервалом), IRQ 1 генерируется при нажатии клавиши на клавиатуре, а IRQ 12 — при действии с PS/2-мышью.
Еще есть так называемые программные прерывания. Их, как понятно из названия, программа должна вызывать сама — никто их за нее не вызывает. Таковыми являются, например, системные вызовы в некоторых системах. В Linux, например, они висят на векторе 0×80. Во многих хобби-ОС они тоже висят на векторе 0×80. Теперь немного отсебятины — я думаю, что сисвызовы сделаны в виде прерываний из-за того, что 1) их так очень легко вызывать, 2) их можно вызвать из любого кода, работающего в ОС — IDT-то одна на всю систему.
Информация взята с OSDev Wiki.