Для чего используются команды программных прерываний: НОУ ИНТУИТ | Лекция | Прерывания и особые случаи

Прерывания для самых маленьких / Хабр

Сегодня мы поговорим о прерываниях процессоров семейства 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, если это возможно. Вот последовательность действий, которые происходят при этом.
  1. Поиск вектора №0 в IDT.
  2. Сравнение уровня привилегий дескриптора и текущего уровня привилегий процессора.
  3. Если текущий уровень привилегий процессора меньше уровня привилегий дескриптора, то просто вызвать генеральную ошибку защиты и не вызывать прерывание.
  4. Происходит сохранение адреса возвращения, регистра (E)FLAGS и другой информации.
  5. Происходит переход на адрес, указанный в векторе №0 IDT.
  6. После выполнения обработчика инструкция 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, например, они висят на векторе 0x80. Во многих хобби-ОС они тоже висят на векторе 0x80. Теперь немного отсебятины — я думаю, что сисвызовы сделаны в виде прерываний из-за того, что 1) их так очень легко вызывать, 2) их можно вызвать из любого кода, работающего в ОС — IDT-то одна на всю систему.
Информация взята с OSDev Wiki.

НОУ ИНТУИТ | Лекция | Система прерываний

Аннотация: Рассматриваются основные вопросы, связанные с организацией работы ЭВМ при обработке прерываний, а также особенности системы прерываний в персональной ЭВМ.

Организация обработки прерываний в ЭВМ

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

Механизм прерывания обеспечивается соответствующими аппаратно-программными средствами компьютера.

Любая особая ситуация, вызывающая прерывание, сопровождается сигналом, называемым запросом прерывания (ЗП). Запросы прерываний от внешних устройств поступают в процессор по специальным линиям, а запросы, возникающие в процессе выполнения программы, поступают непосредственно изнутри микропроцессора. Механизмы обработки прерываний обоих типов схожи. Рассмотрим функционирование компьютера при появлении сигнала запроса прерывания, опираясь в основном на обработку аппаратных прерываний (рис. 14.1).

Выполнение прерывания в компьютере: tр - время реакции процессора на запрос прерывания; tс - время сохранения состояния прерываемой программы и вызова обработчика прерывания; tв - время восстановления прерванной программы
Рис. 14.1.
Выполнение прерывания в компьютере: tр — время реакции процессора на запрос прерывания; tс — время сохранения состояния прерываемой программы и вызова обработчика прерывания; tв — время восстановления прерванной программы

После появления сигнала запроса прерывания ЭВМ переходит к выполнению программы — обработчика прерывания. Обработчик выполняет те действия, которые необходимы в связи с возникшей особой ситуацией. Например, такой ситуацией может быть нажатие клавиши на клавиатуре компьютера. Тогда обработчик должен передать код нажатой клавиши из контроллера клавиатуры в процессор и, возможно, проанализировать этот код. По окончании работы обработчика управление передается прерванной программе.

Время реакции — это время между появлением сигнала запроса прерывания и началом выполнения прерывающей программы (обработчика прерывания ) в том случае, если данное прерывание разрешено к обслуживанию.

Время реакции зависит от момента, когда процессор определяет факт наличия запроса прерывания. Опрос запросов прерываний может проводиться либо по окончании выполнения очередного этапа команды (например, считывание команды, считывание первого операнда и т.д.), либо после завершения каждой команды программы.

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

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

Время реакции определяется для запроса с наивысшим приоритетом.

Глубина прерывания — максимальное число программ, которые могут прерывать друг друга. Глубина прерывания обычно совпадает с числом уровней приоритетов, распознаваемых системой прерываний. Работа системы прерываний при различной глубине прерываний ( n ) представлена на рис. 14.2. Здесь предполагается, что с увеличением номера запроса прерывания увеличивается его приоритет.

Работа системы прерываний при различной глубине прерываний
Рис. 14.2.
Работа системы прерываний при различной глубине прерываний

Без учета времени реакции, а также времени запоминания и времени восстановления:

Прерывание (Операционные Системы) — Национальная библиотека им. Н. Э. Баумана

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 15:27, 5 июня 2019.

Прерывание — сигнал к процессору , испускаемый аппаратными средствами или программным обеспечением, и указывающий на событие, которое требует немедленного внимания. Прерывание предупреждает процессор о высокоприоритетном состоянии, требующем прерывания текущего кода, выполняемого процессором. Процессор отвечает, приостанавливая свои текущие действия, сохраняя свое состояние и выполняя функцию, называемую обработчиком прерываний (или подпрограммой обработки прерываний, ISR) для обработки события. Это прерывание является временным, и после завершения обработки обработчика прерывания процессор возобновляет обычную работу. Существует два типа прерываний: аппаратные прерывания и программные прерывания.[1]

Каждое прерывание имеет свой собственный обработчик прерываний. Количество аппаратных прерываний ограничено числом строк запроса прерывания (IRQ) для процессора, но могут быть сотни различных программных прерываний. Прерывания — это широко используемая техника многозадачности компьютеров, в первую очередь в реальном времени. Такая система называется управляемой прерываниями.

История

Первое фактическое использование прерываний приписывают компьютеру UNIVAC 1103 в 1953 году.[2]На IBM 650 (1954) было применено впервые прерывание путём маскировки. Национальное бюро стандартов DYSEAC (1954) первым использовало прерывания для ввода / вывода. IBM 704 был первым компьютером, использующим прерывания для отладки с «передаточной ловушкой», которая может ссылаться на специальную процедуру, когда команда ветвления была имеет столкновение. Система TX-2 (1957) была первой, обеспечивающей несколько уровней приоритетных прерываний.[3]

Типы прерываний

Прерывания могут быть разделены на следующие типы:

  • Маскируемое прерывание (IRQ): аппаратное прерывание, которое можно игнорировать, устанавливая бит в битовой маске регистра маски прерываний (IMR).
  • Немаскируемое прерывание (NMI): аппаратное прерывание, в котором отсутствует связанная битовая маска, поэтому ее нельзя игнорировать. NMI используются для задач с высшим приоритетом, например таких как таймеры.
  • Межпроцессорное прерывание (IPI): особый случай прерывания, которое генерируется одним процессором для прерывания другого процессора в многопроцессорной системе.
  • Программное прерывание: прерывание, генерируемое в процессоре путем выполнения инструкции. Программные прерывания часто используются для реализации системных вызовов, поскольку они приводят к вызову подпрограммы с изменением уровня вызова ЦП.
  • Ложное прерывание: нежелательное аппаратное прерывание. Как правило, такие прерывания генерируются системными условиями, такими как электрические помехи в линии прерывания или из-за технически неправильно разработанного оборудования.

Процессоры обычно имеют внутреннюю маску прерываний, которая позволяет программному обеспечению игнорировать все внешние аппаратные прерывания, пока она установлена. Установка или очистка этой маски может быть быстрее, чем доступ к регистру маски прерываний (IMR) в PIC или отключение прерываний в самом устройстве. В некоторых случаях, например в случае архитектуры x86, отключение и включение прерываний на самом процессоре действует как барьер памяти.

Прерывание, которое оставляет машину в четко определенном состоянии, называется точным прерыванием. Такое прерывание имеет четыре свойства:

  • Счетчик программ (PC — Program Counter) сохраняется в известном месте.
  • Все инструкции перед тем, на который указывает счетчик программ, полностью выполнены.
  • Никакая инструкция, кроме той, на которую указывает счетчик программ, не была выполнена, или любые такие инструкции отменяются до обработки прерывания.
  • Состояние выполнения инструкции, на которую указывает счетчик программ, известно.

Прерывание, которое не соответствует указанным выше требованиям, называется неточным прерыванием.

Аппаратные прерывания

Аппаратные прерывания используются устройствами для передачи информации о том, что они требуют внимания со стороны операционной системы.[4] Внутренние аппаратные прерывания реализуются с использованием электронных сигналов оповещения, которые отправляются процессору от внешнего устройства, которое является частью самого компьютера, например контроллер диска, или внешнее периферийное устройство. К слову, нажатие клавиши на клавиатуре или перемещение мыши вызывают аппаратные прерывания, которые заставляют процессор считывать нажатие клавиши или положение мыши. В отличие от типа программного обеспечения, аппаратные прерывания являются асинхронными и могут произойти в середине выполнения инструкции, что требует дополнительного внимания при программировании. Акт инициирования аппаратного прерывания называется запросом прерывания (IRQ). [1]

Программные прерывания

Программное прерывание вызвано либо исключительным состоянием в самом процессоре, либо специальной инструкцией в наборе команд, которая вызывает прерывание, когда инструкция выполняется (см. рисунок 1). Первую часто называют ловушкой или исключением и используют для ошибок или событий, происходящих во время выполнения программы, которые настолько исключительны, что не могут быть обработаны в самой программе. Например, исключение деления на ноль будет выдано, если арифметическо-логическому блоку процессора приказано будет делить число на ноль, поскольку эта инструкция является ошибкой и невозможной. Операционная система поймает это исключение и сможет решить, что с этим делать: как правило, прерывать процесс и отображать соответствующее сообщение об ошибке. Инструкции программного прерывания могут функционировать аналогично вызовам подпрограмм и используются для различных целей, таких как запрос служб от драйверов устройств, например прерывания, отправляемые на контроллер диска и с контролера диска для запроса чтения или записи данных на диск и с диска. [1]

Рисунок 1 – Программное прерывание процесса

Сложность с разделением линий прерывания

Несколько устройств, совместно использующих линию прерывания (любого стиля запуска), действуют как источники паразитных прерываний по отношению друг к другу. При наличии множества устройств в одной линии рабочая нагрузка при обслуживании прерываний увеличивается пропорционально квадрату количества устройств. Поэтому предпочтительно распределять устройства равномерно по доступным линиям прерывания. Нехватка линий прерывания является проблемой в старых конструкциях системы, где линии прерывания являются различными физическими проводниками. Прерывания с сигналом сообщения, где линия прерывания является виртуальной, предпочтительны в новых системных архитектурах (таких как PCI Express) и в значительной степени решают эту проблему.

Некоторые устройства с плохо разработанным программным интерфейсом не позволяют определить, запросили ли они обслуживание. Они могут запереться или иным образом плохо себя вести, если их обслуживают, когда они этого не хотят. Такие устройства не могут терпеть паразитные прерывания, а также не могут терпеть совместное использование линии прерывания. Карты ISA , из-за зачастую дешевого дизайна и конструкции, печально известны этой проблемой. Такие устройства становятся все более редкими, поскольку аппаратная логика становится дешевле, а новые системные архитектуры требуют разделяемых прерываний. [1]

Проблемы с производительностью

Прерывания обеспечивают низкую нагрузку и хорошую задержку при низкой нагрузке, но значительно ухудшаются при высокой частоте прерываний, если не предпринимаются меры для предотвращения нескольких патологий. Это различные формы живых блокировок , когда система тратит все свое время на обработку прерываний, исключая другие необходимые задачи. В экстремальных условиях большое количество прерываний (например, очень высокий сетевой трафик) может полностью остановить систему. Чтобы избежать таких проблем, операционная система должна планировать обработку сетевых прерываний так же тщательно, как и планирование выполнения процесса.

С многоядерными процессорами, дополнительные улучшения производительности в обработке прерываний могут быть достигнуты с помощью масштабирования на стороне приема (RSS), когда используются сетевые адаптеры с несколькими очередями. Такие NIC предоставляют несколько очередей приема, связанных с отдельными прерываниями; путем маршрутизации каждого из этих прерываний на разные ядра обработка запросов на прерывание, запускаемых сетевым трафиком, полученным одним NIC, может быть распределена между несколькими ядрами. Распределение прерываний между ядрами может выполняться операционной системой автоматически, либо маршрутизация прерываний (обычно называемая привязкой IRQ) может быть настроена вручную.

Чисто программная реализация распределения принимаемого трафика, известная как управление приемными пакетами (RPS), распределяет принятый трафик между ядрами позже в тракте данных как часть функциональности обработчика прерываний. Преимущества RPS по RSS не включают требований к конкретному оборудованию, более продвинутым фильтрам распределения трафика и уменьшенной частоте прерываний, создаваемых NIC. Как недостаток, RPS увеличивает частоту межпроцессорных прерываний (IPI). Управление потоком приема (RFS) расширяет программный подход, учитывая локальность приложений; Дальнейшее улучшение производительности достигается за счет обработки запросов на прерывание теми же ядрами, на которых конкретные сетевые пакеты будут использоваться целевым приложением. [1]

Типичное использование

Типичное использование прерываний включает в себя следующее: системные таймеры, дисковый ввод / вывод, сигналы-выключение и ловушки. Существуют прерывания для передачи байтов данных с использованием UART или Ethernet; для чувствительных нажатий клавиш, управления двигателями и т.д.

Другое типичное использование состоит в том, чтобы генерировать периодические прерывания путем деления выходного сигнала кварцевого генератора и с помощью обработчика прерываний подсчитывать прерывания, чтобы процессор мог сохранять время. Эти периодические прерывания часто используются планировщиком задач ОС для перепланирования приоритетов запущенных процессов. Некоторые старые компьютеры генерировали периодические прерывания от частоты линии электропередачи, потому что она контролировалась коммунальными службами, чтобы исключить длительный дрейф электрических часов.

Например, прерывание диска сигнализирует о завершении передачи данных с или на периферийное устройство диска; процесс, ожидающий чтения или записи файла, запускается снова. В качестве другого примера — прерывание при отключении питания прогнозирует или запрашивает потерю мощности, позволяя компьютерному оборудованию выполнять упорядоченное завершение работы. Кроме того, прерывания используются в функциях «typeahead» для буферизации событий, таких как нажатия клавиш .

Прерывания используются для эмуляции инструкций, которые не реализованы на определенных моделях в компьютерной линии. Например, инструкции с плавающей запятой могут быть реализованы в аппаратных средствах в некоторых системах и эмулироваться в более дешевых системах. Выполнение невыполненной инструкции вызовет прерывание. Обработчик прерываний операционной системы распознает возникновение невыполненной инструкции, интерпретирует инструкцию в программной подпрограмме и затем вернется к программе прерывания, как если бы инструкция была выполнена. Это обеспечивает переносимость прикладного программного обеспечения по всей линии. [1]

Источники

Прерывания и особые ситуации: Типы прерываний

 

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

В общем случае все эти ситуации разделяются по принципу их обнаружения, источника возникновения и реакции на них на следующие три категории:

  • Прерывания и особые ситуации (Interrupts and Exceptions) — обнаруживаются и обрабатываются процессором в самые различные моменты времени и могут происходить как из внешних так и из внутренних источников, все другие типы экстраординарных ситуаций занимают более низкий уровень иерархии и могут обрабатываться, только если сперва заявят о своем существовании через прерывание или особую ситуацию.
  • Исключительные ситуации FPU (Floating-Point Exceptions) — могут возникать только при выполнении команд сопроцессора, команд MMX или 3DNow!-команд. Возникновение исключительной ситуации FPU, в свою очередь, может вызывать генерацию прерывания через подачу сигнала на специальные внешние выводы процессора (так называемая реакция в стиле MS-DOS) или особой ситуации (внутренний механизм процессора обеспечивает генерацию ошибки сопроцессора #MF), обработчик которой далее сам разбирается с тем, какое исключение произошло и какие действия в связи с этим следует предпринять (подробнее …).
  • Исключительные ситуации SIMD (SSE) (SIMD Floating-Point Exceptions) — могут возникать только при выполнении SIMD-команд и полностью определяются состоянием SIMD-регистров процессора. SIMD-исключения сообщают о своем возникновении через генерацию специальной особой ситуации #XM. Получив управление, обработчик особой ситуации должен сам программным путем определить причину возникновения исключения (подробнее …).

 

Прерывания и особые ситуации

Прерывания и особые ситуации (Interrupts and Exceptions) — это специальные средства, обеспечивающие быструю реакцию процессора на внешние воздействия и прочие неожиданные ситуации. При поступлении прерывания или генерации особой ситуации выполнение программы прерывается, а управление передается специальной процедуре — обработчику прерывания или особой ситуации. В большинстве случаев, когда обработка прерывания или особой ситуации заканчивается, управление может быть возвращено в прерванную программу, которая продолжит свое выполнение с той самой точки, в которой она была остановлена. Процессор производит автоматическое сохранение/восстановление контекста и состояния для обеспечения этой возможности.

Все прерывания и особые ситуации имеют уникальные идентификационные номера. Эти номера называются векторами прерываний и лежат в пределах от 0 до 255. Векторы от 0 до 31 отведены для особых ситуаций и немаскируемого прерывания, причем некоторые из них зарезервированы и не должны использоваться программами. Векторы от 32 до 255 свободны для любого использования пользовательскими программами и внешними устройствами.

Существует два источника поступления прерываний и три типа особых ситуаций. Кроме того, различают внутренние (программные) и внешние (аппаратные) источники генерации прерываний и особых ситуаций.

 

Внешние или аппаратные прерывания (External or Hardware interrupts):

  • Внешние маскируемые прерывания (Maskable Hardware Interrupts) — инициируются сигналами на внешних выводах процессора или с помощью встроенного контроллера прерываний (Advanced Programmable Interrupt Controller – APIC). Для процессоров, начиная с Pentium, встроенный APIC-контроллер является наиболее распространенным способом управления прерываниями. В этом случае выводы LINT[1:0]# программируются через локальную таблицу векторов (LVT) контроллера APIC, которая позволяет назначать соответствующий вывод для приема любого вида прерываний. Если же встроенного контроллера APIC в процессоре нет, или же он отключен, то внешние маскируемые прерывания принимаются на выводе INTR#. При этом номер поступившего прерывания должен передаваться процессору по системной шине специальным внешним контроллером прерываний (например, таким как 8259А). Как правило, встроенный APIC-контроллер также взаимодействует с системным контроллером прерываний (I/O APIC), который обеспечивает прием множества прерываний от разных источников и передает в процессор(ы) информацию о полученном прерывании по системной шине или специальной выделенной шине APIC (APIC serial bus). Если флаг разрешения прерываний не установлен (EFLAGS.IF = 0), то внешние маскируемые прерывания не обрабатываются. Выше указывалось, что для использования маскируемыми прерываниями предназначены векторы от 32 до 255. Технически, однако, возможно определить любому маскируемому прерыванию, которое принимается на выводе INTR# процессора значение вектора в диапазоне от 0 до 255, а если прием прерываний происходит через встроенный APIC-контроллер – в диапазоне от 16 до 255 (при попытке использования векторов от 0 до 15 APIC-контроллер сигнализирует о некорректном векторе прерывания).
  • Внешние немаскируемые прерывания (Nonmaskable External Interrupts) — принимаются на выводе NMI# процессора или внутренней шине APIC-контроллера, механизм запрета немаскируемых прерываний отсутствует (на них не влияет текущее значение флага EFLAGS.IF). Получив запрос на немаскируемое прерывание, процессор передает управление по вектору 2 и блокирует прием новых запросов на немаскируемые прерывания вплоть до выполнения команды IRET/IRETD. Технически вектор прерывания 2 может использоваться и для обрабтки маскируемых прерываний, но только описанный выше способ поступления запросов на немаскируемые прерывания обеспечивает особое поведение процессора при их обработке. 

 

Программные прерывания и особые ситуации (Software interrupts and Exceptions):

  • Генерируемые процессором особые ситуации (Program-Error Exceptions) — возникают в процессе и по результатам выполнения программного кода, разделяются на ошибки, ловушки и сбои. Каждая особая ситуация генерируется по набору определенных условий и ей соответствует строго определенный вектор прерывания в диапазоне от 0 до 31. Зачастую, при генерации таких особых ситуаций процессор сохраняет в стеке не только адрес возврата из прерывания, но и специальный код ошибки, который позволяет обработчику детально понять причину ошибки, внести коррективы и перезапустить команду, если это возможно. Для обозначения особых ситуаций принято использовать специальные мнемонические обозначения (#DE, #DB и т.д.). Полный перечень всех особых ситуаций, поддерживаемых разными моделями процессоров, приведен в Таблице 3.1.
  • Программные прерывания и особые ситуации (Software-Generated Interrupts and Exceptions) — могут быть вызваны командами INTO, INT 3, INT01, INT n, BOUND. При этом только команду INT n правильно относить к командам вызова программных прерываний (Software-Generated Interrupts). Команды INTO, INT 3, BOUND и INT01 по сути являются командами программной генерации особых ситуаций (Software-Generated Exceptions). Например, команда INT 40 генерирует прерывание, передавая управление по вектору номер 40, а команда BOUND edi, [ecx] может сгененрировать особую ситуацию нарушение границ (#BR). В качестве непосредственного операнда команды INT n могут использоваться любые вектора прерываний от 0 до 255. То есть она может использоваться, в том числе, и для программной эмуляции любых особых ситуаций с векторами от 0 до 31. В этом случае, однако, не происходит записи в стек кода ошибки (как это может иметь место в случае генерации особой ситуации аппаратными средствами контроля функционирования процессора). Обработчик особой ситуации, который предполагает, что процессор всегда генерирует и помещает в стек предусмотренный код ошибки, не сможет правильно обработать программный вызов командой INT n, так как выберет из стека некорректное значение адреса возврата для указателя команд EIP. Кроме того, существуют определенные отличия в обработке программных прерываний и программных особых ситуаций в режиме V86.
  • Особые ситуации генерируемые средствами самопроверки процессора (Machine-Check Exceptions) — реализованы в процессорах, начиная с Pentium. Условия генерации и типы таких особых ситуаций зависят от модели процессора. Для их обработки используется вектор прерывания 18.

 

Таблица 3.1. Типы прерываний и особых ситуаций

Название

Номер

Мне-мо-ника

Тип

Источник возникновения

Генери-руется ли код ошибки?

Процессор, в котором впервые появилось

Деление на нуль

0

#DE

Ошибка

Команды DIV, IDIV и команда AAM с нулевым непосредственным операндом

Нет

8086

Прерывание отладки

1

#DB

Ошибка/Лов.

Любые

Нет

8086

Немаскируемое прерывание

2

NMI

Прер.

Сигнал на выводе NMI# или внутренней шине APIC-контроллера

Нет

8086

Точка останова

3

#BP

Лов.

Команда INT3

Нет

8086

Переполнение

4

#OF

Лов.

Команда INTO

Нет

8086

Нарушение границ

5

#BR

Ошибка

Команда BOUND

Нет

80186

Неопределенный код операции

6

#UD

Ошибка

Команда UD2, зарезервированные коды операций, некорректные команды

Нет

Intel286

Сопроцессор отсутствует

7

#NM

Ошибка

Команды FPU, команда WAIT/FWAIT; команды MMX, 3DNow!, SIMD, когда CR0.TS = 1 и CR0.EM = 0

Нет

Intel286

Двойная ошибка

8

#DF

Сбой

Любые команды, для которых могут генерироваться особые ситуации, маскируемые и немаскируемые прерывания

Да (всегда нуль)

Intel286

Превышение сегмента сопроцессором – в Intel486 … зарезервировано

9

 

Сбой

Команды FPU, обращающиеся к памяти

Нет

только Intel286 Intel386

Неправильный TSS1

10

#TS

Ошибка

Переключение задачи или доступ к сегменту TSS.

Да

Intel286

Сегмент не присутствует1

11

#NP

Ошибка

Загрузки сегментных регистров или попытки доступа к системным сегментам (код, данные, стек, LDT, TSS)

Да

Intel286

Ошибка стека

12

#SS

Ошибка

Стековые операции и загрузки сегментного регистра SS

Да

Intel286

Общая защита

13

#GP

Ошибка/Лов.

Любые ссылки на код или данные и иные операции, предусмотренные механизмом защиты

Да

Intel286

Страничная ошибка1

14

#PF

Ошибка

Любые ссылки на код или данные в памяти

Да (спец. формат)

Intel386

Зарезервировано

15

 

 

 

 

 

Ошибка сопроцессора

16

#MF

Ошибка

Команды FPU, команда WAIT/FWAIT,команды MMX, 3DNow!, SIMD при наличии отложенных исключений FPU, когда CR0.TS = 0 и CR0.EM = 0

Нет

Intel286

Контроль выравнивания1

17

#AC

Ошибка

Любые невыровненные ссылки на данные в памяти, если активирован контроль выравнивания (CR0.AM = 1, EFLAGS.AC  = 1,  CPL  = 3)

Да (всегда нуль)

Intel486

Контроль машины

18

#MC

Сбой

Зависит от модели

Зависит от модели

Penium

SIMD-исключение

19

#XM

Ошибка

Команды SIMD (SSE)

Нет

Pentium III

Зарезервировано

20-31

 

 

 

 

 

Прерывания пользователя

32-255

Прер.

Внешние прерывания или команда INT n

Нет

8086

 

1. Указанные особые ситуации генерируются только в защищенном режиме и режиме V86.

             

 

Типы особых ситуаций и особенности их обработки

Особые ситуации, генерируемые процессором подразделяются на три типа — ошибки, ловушки и сбои. В зависимости от типа особой ситуации различается реакция процессора на ее возникновение.

  • Ошибка (Fault) — это особая ситуация, которая может быть исправлена обработчиком особой ситуации. При встрече ошибки состояние процессора сохраняется в том виде, каким оно было до начала выполнения команды, инициировавшей генерацию ошибки, а значения CS:EIP, указывающие на эту команду сохраняются в стеке обработчика. Прерванная программа после исправления ошибки может быть продолжена непосредственно с команды, вызвавшей эту ошибку.
  • Ловушка (Trap) — особая ситуация, которая генерируется после выполнения соответствующей команды. В этом случае сохраняемые в стеке значения CS:EIP, указывают на команду, которая будет выполняться вслед за командой, вызвавшей ловушку; например, если ловушка произошла во время команды JMP, то сохраненные значения CS:EIP указывают на команду, являвшуюся целью команды JMP.
  • Сбой (Abort) — это особая ситуация, которая не допускает точную локализацию вызвавшей ее команды и не допускает перезапуска. Сбои используются для сообщений о некоторых ошибках, таких как: технические неисправности и наличие некорректных значений в системных таблицах.

 

Правила функционирования механизма обработки прерываний и особых ситуаций существенно зависят от режима работы процессора и текущих установок некоторых флагов в регистре CR4. Сам этот механизм включает следующие элементы:

 

Таблица 6.55. Способы обработки процессором прерываний и особых ситуаций в различных режимах работы

Режим работы процессора

Тип прер. CR0.PE VM CR4.VME CR4.PVI IOPL IRB IDT IVT

Режим реальной адресации

П 0 0 x x 0 x +
А, О 0 0 x x 0 x +

Защищенный режим

П 1 0 x 0 x x +
А, О 1 0 x 0 x x +

Защищенный режим с поддержкой виртуальных флагов прерываний
(CR4.PVI = 1)

П 1 0 x 1 x x +
А, О 1 0 x 1 x x +
Режим V86 (CPL = 3, CR4.VME = 0) П 1 1 0 x = 3 x +
П
1 1 0 x < 3 x #GP(0)
А, О1 1 1 0 x x x +

Режим EV86 (CPL = 3, CR4.VME = 1)

П 1 1 1 x x 0 +
П 1 1 1 x = 3 1 +
П
1 1 1 x < 3 1 #GP(0)
А, О1 1 1 1 x x x +

x — Флаг или битовое поле может иметь любое значение.

+ — Возможное событие (переход к обработчику прерывания в соответствующем режиме).

1 — Включая особые ситуации, генерируемые командами INTO, INT 3, INT01, BOUND.

 

 

< Предыдущая   Следующая >

Программные прерывания — Мегаобучалка

Хотя большинство прерываний генерируется аппаратно, ядро Windows тоже может генерировать прерывания — только они являются программными. Этот вид прерываний служит для решения многих задач, в том числе:

· инициации диспетчеризации потоков;

· обработки прерываний, не критичных по времени;

· обработки событий таймеров;

· асинхронного выполнения какой-либо процедуры в контексте конкретного потока;

· поддержки асинхронного ввода-вывода. Эти задачи подробно рассматриваются ниже.

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

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

В зависимости от источника прерывания делятся на три больших класса: внешние, внутренние и программные.

Внешние (аппаратные) прерывания могут формироваться в результате действий пользователя или аппаратных устройств, например, контроллеров внешних устройств ввода-вывода, датчиков. Данный класс прерываний является асинхронным по отношению к потоку инструкций прерываемой программы. Аппаратура процессора работает так, что асинхронные прерывания возникают между выполнением двух соседних инструкций, при этом система после обработки прерывания продолжает выполнение процесса, уже начиная со следующей инструкции.



Внутренние прерывания, называемые также исключениями (exeption), происходят синхронно выполнению программы при появлении аварийной ситуации в ходе исполнения некоторой инструкции программы. Примерами исключений являются деление на ноль, ошибки защиты памяти, обращения по несуществующему адресу и т. п. Исключения возникают непосредственно в ходе выполнения тактов команды («внутри» выполнения).

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

Прерываниям приписывается приоритет, с помощью которого они ранжируются по степени важности и срочности.

Прерывания обычно обрабатываются модулями операционной системы, так как действия, выполняемые по прерыванию, относятся к управлению разделяемыми ресурсами ВС. Процедуры, вызываемые по прерываниям, обычно называют обработчиками прерываний или процедурами обслуживания прерываний (Interrupt Service Routine, ISK). Аппаратные прерывания обрабатываются драйверами соответствующих внешних устройств, исключения – специальными модулями ядра, а программные прерывания – процедурами ОС, обслуживающими системные вызовы. Кроме этих модулей в операционной системе может находиться так называемый диспетчер прерываний, который координирует работу отдельных обработчиков прерываний.

В начале прерывания использовались в основном для управления процессором устройствами ввода-вывода. Затем прерывания стали использовать для организации внутренней работы ЭВМ. В соответствии с этим существуют следующие типы прерываний:

1. Аппаратные прерывания – прерывания от устройств компьютера.

2. Программные прерывания – прерывания, которые вырабатывают процессы, находящиеся на стадии выполнения.

3. Логические прерывания – Эти прерывания вырабатывает сам процессор, когда встречается с каким-либо необходимым условием:

1. деление на 0

2. переполнение регистров микропроцессора

3. пошаговое выполнение программ

4. режим контрольных точек.

 

Каждое прерывание имеет два параметра:

1. Номер прерывания

2. Вектор прерывания.

 

Вектор прерывания– это адрес ячейки памяти, где хранится программа – обработчик прерывания.

Прерывания обозначаются — IRQ.

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

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

Диспетчеризация сводится к следующему:

· сохранение контекста текущего потока, который требуется сменить;

· загрузка контекста нового потока, выбранного в результате планирования;

· запуск нового потока на выполнение.

Поскольку операция переключения контекстов существенно влияет на производительность вычислительной системы, программные модули ОС выполняют диспетчеризацию потоков совместно с аппаратными средствами процессора.

Уровни запросов программных прерываний

Хотя контроллеры прерываний различают уровни приоритетов прерываний, Windows использует свою схему приоритетов прерываний, известную под названием уровни запросов прерываний (interrupt request levels, IRQL). Внутри ядра IRQL представляются в виде номеров 0-31 в системах x86 и 0-15 в системах x64 и IA64, причем больший номер соответствует прерыванию с более высоким приоритетом. Ядро определяет стандартный набор IRQL для программных прерываний, a HAL увязывает IRQL с номерами аппаратных прерываний. IRQL, определенные для архитектуры x86, показаны на рис. 3–3, а аналогичные сведения для архитектур x64 и IA64 — на рис. 3–4.

 

ПРИМЕЧАНИЕ Уровень SYNCH_LEVEL, используемый многопроцессорными версиями ядра для защиты доступа к индивидуальным для каждого процессора блокам PRCB (processor control blocks), не показан на этих схемах, так как его значение варьируется в разных версиях Windows. Описание SYNCH_LEVEL и его возможных значений.

 

Рис. 3–4. Уровни запросов прерываний (IRQL) в системах x64 и IA64

 

Прерывания обслуживаются в порядке их приоритета, и прерывания с более высоким приоритетом вытесняют обработку прерываний с меньшим приоритетом. При возникновении прерывания с высоким приоритетом процессор сохраняет информацию о состоянии прерванного потока и активизирует сопоставленный с данным прерыванием диспетчер ловушки. Последний повышает IRQL и вызывает процедуру обслуживания прерывания (ISR). После выполнения ISR диспетчер прерывания понижает IRQL процессора до исходного уровня и загружает сохраненные ранее данные о состоянии машины. Прерванный поток возобновляется с той точки, где он был прерван. Когда ядро понижает IRQL, могут «материализоваться» ранее замаскированные прерывания с более низким приоритетом. Тогда вышеописанный процесс повторяется ядром для обработки и этих прерываний.

Уровни приоритетов IRQL имеют совершенно иной смысл, чем приоритеты в схеме планирования потоков. Приоритет в этой схеме является атрибутом потока, тогда как IRQL — атрибутом источника прерывания, например клавиатуры или мыши. Кроме того, IRQL каждого процессора меняется во время выполнения команд операционной системы.

Значение IRQL определяет, какие прерывания может получать данный процессор. IRQL также используется для синхронизации доступа к структурам данных режима ядра (о синхронизации мы поговорим позже). При выполнении поток режима ядра повышает или понижает IRQL процессора либо напрямую (вызовом соответственно KeRaiseIrql или KeLowerIrqL), либо — что бывает гораздо чаще — опосредованно (через функции, которые обращаются к синхронизирующим объектам ядра). Как показано на рис.3–5, прерывания от источника с IRQL, превышающим текущий уровень, прерывают работу процессора, а прерывания от источников, IRQL которых меньше или равен текущему уровню, маскируются до тех пор, пока выполняемый поток не понизит IRQL.

 

Поскольку доступ к PIC — операция довольно медленная, в HAL, использующих PIC, реализован механизм оптимизации «отложенный IRQL» (lazy IRQL), который избегает обращений к PIC Когда IRQL повышается, HAL — вместо того чтобы изменять маску прерывания — просто отмечает новый IRQL. Если вслед за этим возникает прерывание с более низким приоритетом, HAL устанавливает маску прерывания в соответствии с первым и откладывает обработку прерывания с более низким приоритетом до понижения IRQL. Таким образом, если при повышенном IRQL не возникнет прерываний с более низким приоритетом, HAL не потребуется обращаться к PIC.

Поток режима ядра повышает и понижает IRQL процессора, на котором он выполняется, в зависимости от того, что именно делает этот поток. Например, обработчик ловушки (или сам процессор) при прерывании повышает IRQL процессора до IRQL источника прерывания. B результате все прерывания с более низким или равным IRQL маскируются (только на этом процессоре), что не дает прерыванию с таким же или более низким IRQL помешать процессору обработать текущее прерывание. Замаскированные прерывания либо обрабатываются другим процессором, либо откладываются до понижения IRQL. Поэтому все системные компоненты, в том числе ядро и драйверы устройств, пытаются удерживать IRQL на уровне passive («пассивный»), иногда называемом низким уровнем. Если бы IRQL долго оставался неоправданно высоким, драйверы устройств не смогли бы оперативно реагировать на аппаратные прерывания.

Прерывания от внешних устройств в системе x86. Часть 1. Эволюция контроллеров прерыванийВ данной статье хотелось бы рассмотреть механизмы доставки прерываний от внешних устройств в системе x86 и попытаться ответить на вопросы:
  • что такое PIC и для чего он нужен?
  • что такое APIC и для чего он нужен? Для чего нужны LAPIC и I/O APIC?
  • в чём отличия APIC, xAPIC и x2APIC?
  • что такое MSI? В чём отличия MSI и MSI-X?
  • как с этим связаны таблицы $PIR, MPtable, ACPI?

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

Введение


Все мы знаем, что такое прерывание. Для тех, кто нет, цитата из википедии:
Прерывание (англ. interrupt) — сигнал от программного или аппаратного обеспечения, сообщающий процессору о наступлении какого-либо события, требующего немедленного внимания. Прерывание извещает процессор о наступлении высокоприоритетного события, требующего прерывания текущего кода, выполняемого процессором. Процессор отвечает приостановкой своей текущей активности, сохраняя свое состояние и выполняя функцию, называемую обработчиком прерывания (или программой обработки прерывания), которая реагирует на событие и обслуживает его, после чего возвращает управление в прерванный код.

В зависимости от источника возникновения сигнала прерывания делятся на:

  • асинхронные, или внешние (аппаратные) — события, которые исходят от внешних аппаратных устройств (например, периферийных устройств) и могут произойти в любой произвольный момент: сигнал от таймера, сетевой карты или дискового накопителя, нажатие клавиш клавиатуры, движение мыши. Факт возникновения в системе такого прерывания трактуется как запрос на прерывание (англ. Interrupt request, IRQ) — устройства сообщают, что они требуют внимания со стороны ОС;
  • синхронные, или внутренние — события в самом процессоре как результат нарушения каких-то условий при исполнении машинного кода: деление на ноль или переполнение стека, обращение к недопустимым адресам памяти или недопустимый код операции;
В данной статье хотелось бы обсудить внешние прерывания IRQ.

Зачем они нужны? Допустим мы хотим выполнить какое-либо действие со входным пакетом для сетевой карты, когда он придёт. Чтобы не спрашивать сетевую карту постоянно «есть ли у тебя новый пакет?» и не тратить на это ресурсы процессора, можно использовать прерывание IRQ. Линия прерываний устройства соединяется с линией INTR процессора, и при получении пакета сетевая карта «дергает» эту линию. Процессор понимает, что для него есть информация и читает пакет.

Но что делать если устройств много? На все внешние устройства ножек процессора не напасёшься.

Чтобы решить эту проблему, придумали микросхему — контроллер прерываний.

PIC


(вики/osdev)

Первой была микросхема Intel 8259 PIC. 8 входных линий (IRQ0-7), и одна выходная, соединяющая контроллер с линией INTR процессора. Когда возникает прерывание от какого-либо устройства, 8259 дёргает линию INTR, процессор понимает, что какое-то устройство сигнализирует о прерывании и опрашивает PIC, чтобы понять по какой именно ножке IRQx возникло прерывание. Появляется дополнительная задержка на данный опрос, но зато количество линий прерываний увеличивается до 8.

Однако 8 линий быстро оказалось мало, и чтобы увеличить их количество стали использовать 2 контроллера 8259 (master и slave) соединённых каскадно (Dual PIC).

IRQ с 0 по 7 обрабатываются первым Intel 8259 PIC (master), а IRQ с 8 по 15 вторым 8259 PIC (slave). О возникновении прерывания CPU сигнализирует только master. Если возникло прерывание на линиях 8-15, второй PIC (slave) сигнализирует о прерывании мастеру по линии IRQ 2, и тот уже в свою очередь сигнализирует CPU. Это каскадное прерывание отнимает одну из 16 линий, но в итоге даёт 15 доступных прерываний для устройств.

Схема утвердилась, и именно её имеют ввиду, когда говорят сейчас о PIC (Programm Interrupt Controller). Впоследствии контроллеры 8259 получили некоторые улучшения, и стали называться 8259A, а эта схема вошла в состав чипсета. Во времена когда основной шиной для подключения внешних устройств была шина ISA, такой системы в целом хватало. Надо было лишь следить, чтобы разные устройства не подключались на одну линию IRQ для избежания конфликтов, так как прерывания ISA не разделяемые.

Обычно раскладка прерываний под устройства была более менее стандартная

Пример (взят отсюда):
IRQ 0 — system timer
IRQ 1 — keyboard controller
IRQ 2 — cascade (прерывание от slave контроллера)
IRQ 3 — serial port COM2
IRQ 4 — serial port COM1
IRQ 5 — parallel port 2 and 3 or sound card
IRQ 6 — floppy controller
IRQ 7 — parallel port 1
IRQ 8 — RTC timer
IRQ 9 — ACPI
IRQ 10 — open/SCSI/NIC
IRQ 11 — open/SCSI/NIC
IRQ 12 — mouse controller
IRQ 13 — math co-processor
IRQ 14 — ATA channel 1
IRQ 15 — ATA channel 2

Конфигурация и работа с микросхемами 8259 осуществляется через I/O порты:


→Документацию на 8259A можно найти тут

На смену шине ISA пришла шина PCI. И количество устройств явно стало превосходить число 15, плюс в отличие от статической шины ISA в данном случае случае устройства могут добавляться в систему динамически. Но к счастью в данной шине прерывания могут быть разделяемыми (то есть к одной линии IRQ можно подсоединить несколько устройств). В итоге чтобы решить проблему нехватки линий IRQ, прерывания ото всех PCI устройств решили группировать в линии PIRQ (Programmable Interrupt Request).

Допустим у нас 4 линии прерываний свободно на PIC контроллере, а PCI устройств 20 штук. Мы объединяем прерывания по 5 устройств на линию PIRQx и подключаем линии PIRQx к контроллеру. При возникновении прерывания на линии PIRQx процессору придётся опросить все устройства подключённые к данной линии, чтобы понять от кого именно пришло прерывание, но в целом это решает задачу. Устройство осуществляющее связывание линий прерываний PCI в линии PIRQ часто называют PIR router.

В данном методе надо следить, чтобы линии PIRQx не подсоединялись к линиям IRQx на которых уже заведены прерывания ISA (так как это вызовет конфликты), и чтобы линии PIRQx были сбалансированы (ведь чем больше устройств мы подключили к одной линии PIRQ, тем больше устройств надо будет опрашивать процессору, чтобы понять, какое именно из этих устройств вызвало прерывание).

Замечание: на рисунке маппинг PCI device -> PIR изображён абстрактно, потому что на самом деле он несколько сложнее. В реальности каждый PCI device имеет 4 линии прерываний (INTA, INTB, INTC, INTD). У каждого PCI устройства (device) может быть до 8 функций (functions) и вот каждой функции соответствует уже одно прерывание INTx. Какую именно INTx будет дёргать каждая функция устройства определяется конфигурацией чипсета.

По сути функции это отдельные логические блоки. Например в одном PCI устройстве может быть функция Smbus controller, функция SATA controller, функция LPC bridge. Со стороны ОС каждая функция — это как отдельное устройство со своим конфигурационным пространством PCI Config.

Информацию о роутинге прерываний на PIC контроллере BIOS передавал ОС с помощью таблицы $PIR и с помощью заполнения регистров 3Ch (INT_LN Interrupt Line (R/W)) и 3Dh (INT_PN Interrupt Pin (RO)) конфигурационного пространства PCI для каждой функции. Спецификация о таблице $PIR раньше была на сайте Microsoft, но сейчас её там уже нет. Содержимое строк таблицы $PIR можно понять из PCI BIOS Specification [4.2.2. Get PCI Interrupt Routing Options] или почитать вот тут

APIC


(вики, osdev)

Предыдущий метод работал пока не появились многопроцессорные системы. Дело в том, что по своему устройству PIC может передавать прерывания только на один главный процессор. А хотелось бы, чтобы нагрузка на процессоры от обработки прерываний была сбалансированной. Решением данной задачи стал новый интерфейс APIC (Advanced PIC).

Для каждого процессора добавляется специальный контроллер LAPIC (Local APIC) и для маршрутизации прерываний от устройств добавляется контроллер I/O APIC. Все эти контроллеры объединяются в общую шину с названием APIC (новые системы сейчас уже соединяются по стандартной системной шине).

Когда прерывание от устройства приходит на вывод I/O APIC, контроллер направляет прерывание в LAPIC одного из процессоров. Наличие I/O APIC позволяет сбалансировано распределять прерывания от внешних устройств между процессорами.

Первой микросхемой APIC был 82489DX, это был отдельный чип, соединяющий в себе LAPIC и I/O APIC. Для создания системы из 2 процессоров нужно было 3 таких микросхемы. 2 функционировали бы как LAPIC и одна как I/O APIC. Позднее функциональность LAPIC была напрямую включена в процессоры, а функциональность I/O APIC была оформлена в чип 82093AA.

I/O APIC 82093AA содержала 24 входных вывода, а архитектура APIC могла поддерживать до 16 CPU. Для поддержки совместимости со старыми системами, прерывания 0~15 отвели под старые прерывания ISA. А прерывания от PCI устройств стали выводить на линии IRQ 16-23. Теперь можно было не задумываться о конфликтах прерываний от ISA и PCI устройств. Также благодаря увеличенному количеству свободных линий прерываний возможно стало также увеличить количество линий PIRQx.

Программирование I/O APIC и LAPIC осуществляется через MMIO. Регистры LAPIC расположены обычно по адресу 0xFEE00000, регистры I/O APIC по адресу 0xFEС00000. Хотя в принципе все эти адреса возможно переконфигурировать.

Как и в случае с PIC первоначально отдельные микросхемы позже вошли в состав чипсета.

В дальнейшем архитектура APIC получила модернизацию и новый вариант получил название xAPIC (x — extended). Сохранена обратная совместимость с предыдущим вариантом. Количество возможных CPU в системе увеличилось до 256.

Следующий виток развития архитектуры получил название x2APIC. Количество возможных CPU в системе увеличилось до 2^32. Контроллеры могут работать в режиме совместимости с xAPIC, а могут в новом режиме x2APIC, где программирование LAPIC осуществляется не через MMIO, а через MSR регистры (что гораздо быстрее). Cудя по этой ссылке для работы этого режима необходима поддержка IOMMU.

Следует заметить, что в системе может быть несколько контроллеров I/O APIC. Например один на 24 прерывания в южном мосту, другой на 32 в северном. В контексте I/O APIC прерывания часто обозначаются GSI (Global System Interrupt). Так вот в такой системе будут GSI 0-55.

Есть ли в CPU встроенный LAPIC и какой именно архитектуры можно понять по бит-флагам в CPUID.
Чтобы система могла обнаружить LAPIC и I/O APIC, BIOS должен представить информацию о них системе либо через таблицу MPtable (старый метод), либо через таблицу ACPI (таблицу MADT в данном случае). Помимо общей информации, и в MPtable и в ACPI (на этот раз в таблице DSDT) должна содержаться информация о роутинге прерываний, то есть информация о том, какое устройство сидит на какой линии прерываний (аналог таблицы $PIR).

О таблице MPTable можно почитать в официальной спецификации. Раньше спецификация была на сайте Intel, а сейчас её можно найти только в архиве. Спецификация ACPI сейчас расположена на сайте UEFI (текущая версия 6.2). Следует отметить, что с помощью ACPI можно указать роутинг прерываний и для систем без APIC (вместо использования таблицы $PIR).

MSI


(вики)

Предыдущий вариант с APIC хорош, но не лишён недостатков. Все эти линии прерываний от устройств усложняют схему, и увеличивают вероятности ошибок. На смену шины PCI пришёл PCI express, в котором линии прерываний решили просто-напросто убрать. Чтобы сохранить совместимость, сигналы о возникновении прерываний (INTx#) эмулируются отдельными видами сообщений. В этой схеме логическое сложение линий прерываний, которое раньше производилось физическим соединением проводов, легло на плечи PCI мостов. Однако поддержка legacy INTx прерываний — это лишь поддержка обратной совместимости с шиной PCI. На деле PCI express предложил новый метод доставки сообщений о прерываниях — MSI (Message Signaled Interrupts). В этом методе для сигнализации о прерывании устройство просто производит запись в MMIO область отведённую под LAPIC процессора.

Если раньше на одно PCI устройство (то есть на все его функции) выделялось всего 4 прерывания, то сейчас сейчас стало возможным адресовать до 32 прерываний.

В случае с MSI нет никакого sharing для линий, каждое прерывание соответствует своему устройству.

Прерывания MSI решают также ещё одну проблему. Допустим устройство проводит memory-write транзакцию, и хочет сообщить о её завершении через прерывание. Но write транзакция может быть задержана на шине в процессе передачи (о чём устройство никак не знает), и сигнал о прерывании придёт до процессора раньше. Таким образом CPU будет читать ещё невалидные данные. В случае если используется MSI, информация об MSI передаётся также как и данные, и раньше прийти просто не сможет.

Следует заметить, что прерывания MSI не могут работать без LAPIC, но использование MSI может заменить нам I/O APIC (упрощение дизайна).

В последствии данный метод получил расширение MSI-X. Теперь каждое устройство может иметь до 2048 прерываний. И стало возможным указывать индивидуально каждому прерыванию на каком процессоре оно должно выполняться. Это может быть очень полезно для высоконагруженных устройств, например сетевых карт.

Для поддержки MSI не требуется никаких дополнительных таблиц BIOS. Но устройство должно сообщить о поддержке MSI в одной из Capability в своём PCI Config, а драйвер устройства должен поддерживать работу с MSI.

Заключение


В данной статье мы рассмотрели эволюцию контроллеров прерываний, и получили общую теоретическую информацию о доставке прерываний от внешних устройств в x86 системе.

В следующей части мы посмотрим как на практике задействовать в Linux каждый из описанных контроллеров.

Ссылки:

AVR. Учебный курс. Подпрограммы и прерывания

Подпрограммы
Когда один и тот же участок кода часто повторяется, то разумно как то его вынести и использовать многократно. Это дает просто колоссальный выйгрыш по обьему кода и удобству программирования.

Вот, например, кусок кода, передающий в регистр UDR байты с некоторой выдержкой, выдержка делается за счет вращения бесконечного цикла:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
	.CSEG
	LDI R16,Low(RAMEND)	; Инициализация стека
	OUT SPL,R16		; Обязательно!!!
 
	LDI R16,High(RAMEND)
	OUT SPH,R16
 
	.equ	Byte 	= 50
	.equ 	Delay 	= 20
 
	LDI	R16,Byte	; Загрузили значение
Start:	OUT	UDR,R16		; Выдали его в порт
 
	LDI	R17,Delay	; Загрузили длительность задержки
M1:	DEC	R17		; Уменьшили на 1
	NOP			; Пустая операция
	BRNE	M1		; Длительность не равна 0? Переход если не 0
 
	OUT	UDR,R16		; Выдали значение в порт
 
	LDI	R17,Delay	; Аналогично
M2:	DEC	R17
	NOP
	BRNE	M2
 
	OUT	UDR,R16
 
	LDI	R17,Delay
M3:	DEC	R17
	NOP
	BRNE	M3
 
	RJMP	Start		; Зациклим программу

.CSEG LDI R16,Low(RAMEND) ; Инициализация стека OUT SPL,R16 ; Обязательно!!! LDI R16,High(RAMEND) OUT SPH,R16 .equ Byte = 50 .equ Delay = 20 LDI R16,Byte ; Загрузили значение Start: OUT UDR,R16 ; Выдали его в порт LDI R17,Delay ; Загрузили длительность задержки M1: DEC R17 ; Уменьшили на 1 NOP ; Пустая операция BRNE M1 ; Длительность не равна 0? Переход если не 0 OUT UDR,R16 ; Выдали значение в порт LDI R17,Delay ; Аналогично M2: DEC R17 NOP BRNE M2 OUT UDR,R16 LDI R17,Delay M3: DEC R17 NOP BRNE M3 RJMP Start ; Зациклим программу

Сразу напрашивается повторяющийся участок кода вынести за скобки.

1
2
3
4
	LDI	R17,Delay
M2:	DEC	R17
	NOP
	BRNE	M2

LDI R17,Delay M2: DEC R17 NOP BRNE M2

Для этих целей есть группа команд перехода к подпрограмме CALL (ICALL, RCALL, CALL)
И команда возврата из подпрограммы RET

В результате получается такой код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
	.CSEG
	LDI R16,Low(RAMEND)	; Инициализация стека
	OUT SPL,R16		; Обязательно!!!
 
	LDI R16,High(RAMEND)
	OUT SPH,R16
 
	.equ	Byte 	= 50
	.equ 	Delay 	= 20
 
	LDI	R16,Byte	; Загрузили значение
Start:	OUT	UDR,R16		; Выдали его в порт
 
	RCALL 	Wait
 
	OUT	UDR,R16
	RCALL 	Wait
	OUT	UDR,R16
	RCALL 	Wait	
	OUT	UDR,R16
	RCALL 	Wait
	RJMP 	Start		; Зациклим программу. 
 
 
Wait: 	LDI	R17,Delay
M1:	DEC	R17
	NOP
	BRNE	M1
	RET

.CSEG LDI R16,Low(RAMEND) ; Инициализация стека OUT SPL,R16 ; Обязательно!!! LDI R16,High(RAMEND) OUT SPH,R16 .equ Byte = 50 .equ Delay = 20 LDI R16,Byte ; Загрузили значение Start: OUT UDR,R16 ; Выдали его в порт RCALL Wait OUT UDR,R16 RCALL Wait OUT UDR,R16 RCALL Wait OUT UDR,R16 RCALL Wait RJMP Start ; Зациклим программу. Wait: LDI R17,Delay M1: DEC R17 NOP BRNE M1 RET

Как видишь, программа резко сократилась в размерах. Теперь скопируй это в студию, скомпилируй и запусти на трассировку. Я хочу показать как работает команда RCALL и RET и при чем тут стек.

Вначале программа, как обычно, инициализирует стек. Потом загружает наши данные в регистры R16 и выдает первый байт в UDR… А потом по команде RCALL перейдет по адресу который мы присвоили нашей процедуре, поставив метку Wait в ее начале. Это понятно и логично, гораздо интересней то, что произойдет в этот момент со стеком.

До выполнения RCALL

Увеличить
Адрес команды RCALL в памяти, по данным PC = 0x000006, адрес следующей команды (OUT UDR,R16), очевидно, будет 0x000007. Указатель стека SP = 0x045F — конец памяти, где ему и положено быть в этот момент.

После RCALL

Увеличить
Смотри, в стек пихнулось число 0x000007, указатель сместился на два байта и стал 0x045D, а контроллер сделал прыжок на адрес Wait.

Наша процедура спокойно выполняется, как ей и положено, а по команде RET процессор достанет из стека наш заныченный адрес 0x000007 и прыгнет сразу же на команду OUT UDR,R16

Таким образом, где бы мы не вызвали нашу процедуру Wait — мы всегда вернемся к тому же месту откуда вызвали, точнее на шаг вперед. Так как при переходах в стеке сохраняется адрес возврата. А если испортить стек? Взять и засунуть туда еще что нибудь? Подправь процедуру Wait и добавь туда немного бреда, например, такого

1
2
3
4
5
6
7
8
Wait: 	LDI	R17,Delay
M1:	DEC	R17
	NOP
	BRNE	M1
 
	PUSH	R17		; Ой, я не специально!
 
	RET

Wait: LDI R17,Delay M1: DEC R17 NOP BRNE M1 PUSH R17 ; Ой, я не специально! RET

Перекомпиль и посмотри что будет =) Заметь, компилятор тебе даже слова не скажет. Мол все путем, дерзай 🙂

До команды PUSH R17 в стеке будет адрес возврата 00 07, так как в регистре R17 ,в данный момент, ноль, и этот ноль попадет в стек, то там будет уже 00 00 07.

А потом идет команда RET… Она глупая, ей все равно! RET тупо возьмет два первых верхних байта из стека и запихает их в Programm Counter.

И куда мы перейдем? Правильно — по адресу 00 00, в самое начало проги, а не туда откуда мы ушли по RCALL. А будь в R17 не 00, а что нибудь другое и попади это что-то в стек, то мы бы перешли вообще черт знает куда с непредсказуемыми последствиями. Это и называется срыв стека.

Но это не значит, что в подпрограммах нельзя пользоваться стеком в своих грязных целях. Можно!!! Но делать это надо с умом. Класть туда данные и доставать их перед выходом. Следуя железному правилу «Сколько положил в стек — столько и достань!», чтобы на выходе из процедуры для команды RET лежал адрес возврата, а не черти что.

Мозговзрывной кодинг
Да, а еще тут возможны стековые извраты. Кто сказал, что мы должны вернуться именно туда откуда были вызываны? =))) А если условия изменились и по итогам вычислений в процедуре нам ВНЕЗАПНО туда стало не надо? Никто не запрещает тебе нужным образом подправить данные в стеке, а потом сделать RET и процессор, как миленький, забросит тебя туда куда надо. Легко!

Более того, я когда учился в универе и сдавал лабы по ассемблеру, то лихо взрывал мозги нашему преподу такими конструкциями (там, правда, был 8080, но разница не велика, привожу пример для AVR):

1
2
3
4
5
6
7
8
9
	LDI	R17,low(M1*2)
	PUSH	R17
	LDI	R17,High(M1*2)
	PUSH	R17
 
; потом дофига дофига другого кода... для отвлечения
; внимания, а затем, в нужном месте, ВНЕЗАПНО
 
	RET

LDI R17,low(M1*2) PUSH R17 LDI R17,High(M1*2) PUSH R17 ; потом дофига дофига другого кода… для отвлечения ; внимания, а затем, в нужном месте, ВНЕЗАПНО RET

И происходил переход на метку M1, своего рода извратский аналог RJMP M1. А точнее IJMP, только вместо Z пары мы используем данные адреса загруженные в стек из любого другого регистра, иногда пригождается. Но без особой нужды таким извратом заниматься не рекомендую — запутывает программу будь здоров.

Но побалуйся обязательно, чтобы во всей красе прочувствовать стековые переходы.

Отлаженные и выверенные подпрограммы кода можно запихать в отдельный модуль и таскать их из проекта в проект, не изобретая каждый раз по велосипеду.

Иногда подпрограммы ошибочно называют функциями. Отличие подпрограммы от функции в том, что функция всегда имеет какое то значение на входе и выдает ответ на выходе, как в математике. Ассемблерная подпрограмма же не имеет таких механизмов и их приходится изобретать самому. Например, передавая в РОН или в ячейках ОЗУ.

Подпрограммы vs Макросы
Но не стоит маникально все повторяющиеся участки заворачивать в подпрограммы. Дело в том, что переход и возврат добавляют две команды, а еще у нас идет прогрузка стека на 2 байта. Что тоже не есть гуд. И если заменяется три-четыре команды, то овчинка с CALL-RET не стоит выделки и лучше запихать все в макрос.

Прерывания

Это аппаратные события. Ведь у микроконтроллера кроме ядра есть еще дофига периферии. И она работает параллельно с контроллером. Пока контроллер занимается вычислением или гоняет байтики по памяти — АЦП может яростно оцифровывать входное напряжение, USART меланхолично передавать или принимайть байтик, а EEPROMка неспеша записывать в свои тормозные ячейки очередной байт.

А когда периферийное устройство завершает свою работу оно поднимает флаг готовности. Мол, чувак, у меня все пучком, забирай результат. Процессор может проверить этот флаг в общем цикле и как то его обработать.

Но некоторые события в принципе не могут ждать, например USART — вовремя не обработаешь входящий байт, считай провафлил передачу, т.к. передающий девайс пошлет второй, ему плевать успел ты его обработать или нет. Для таких срочных дел есть прерывания.

У AVR этих прерываний с полтора десятка наберется, на каждое переферийное устройство по прерыванию, а на некотрые и не по одному. Например, у USART их целых три — Байт пришел, Байт ушел, Передача завершена.

Как это работает

Когда случается прерывание, то процессор тут же завершает текущую команду, пихает следующий адрес в стек (точно также как и при CALL) и переходит… А куда, собственно, он переходит?

А переходит он на фиксированный вектор прерывания. За каждым аппаратным прерыванием закреплен свой именной адрес. Все вместе они образуют таблицу векторов прерывания. Расположена она в самом начале памяти программ. Для Атмега16, используемой в Pinboard таблица прерываний выглядит так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RESET		0x0000	; Reset Vector
INT0addr	0x0002	; External Interrupt Request 0
INT1addr	0x0004	; External Interrupt Request 1
OC2addr		0x0006	; Timer/Counter2 Compare Match
OVF2addr	0x0008	; Timer/Counter2 Overflow
ICP1addr	0x000a	; Timer/Counter1 Capture Event
OC1Aaddr	0x000c	; Timer/Counter1 Compare Match A
OC1Baddr	0x000e	; Timer/Counter1 Compare Match B
OVF1addr	0x0010	; Timer/Counter1 Overflow
OVF0addr	0x0012	; Timer/Counter0 Overflow
SPIaddr		0x0014	; Serial Transfer Complete
URXCaddr	0x0016	; USART, Rx Complete
UDREaddr	0x0018	; USART Data Register Empty
UTXCaddr	0x001a	; USART, Tx Complete
ADCCaddr	0x001c	; ADC Conversion Complete
ERDYaddr	0x001e	; EEPROM Ready
ACIaddr		0x0020	; Analog Comparator
TWIaddr		0x0022	; 2-wire Serial Interface
INT2addr	0x0024	; External Interrupt Request 2
OC0addr		0x0026	; Timer/Counter0 Compare Match
SPMRaddr	0x0028	; Store Program Memory Ready

RESET 0x0000 ; Reset Vector INT0addr 0x0002 ; External Interrupt Request 0 INT1addr 0x0004 ; External Interrupt Request 1 OC2addr 0x0006 ; Timer/Counter2 Compare Match OVF2addr 0x0008 ; Timer/Counter2 Overflow ICP1addr 0x000a ; Timer/Counter1 Capture Event OC1Aaddr 0x000c ; Timer/Counter1 Compare Match A OC1Baddr 0x000e ; Timer/Counter1 Compare Match B OVF1addr 0x0010 ; Timer/Counter1 Overflow OVF0addr 0x0012 ; Timer/Counter0 Overflow SPIaddr 0x0014 ; Serial Transfer Complete URXCaddr 0x0016 ; USART, Rx Complete UDREaddr 0x0018 ; USART Data Register Empty UTXCaddr 0x001a ; USART, Tx Complete ADCCaddr 0x001c ; ADC Conversion Complete ERDYaddr 0x001e ; EEPROM Ready ACIaddr 0x0020 ; Analog Comparator TWIaddr 0x0022 ; 2-wire Serial Interface INT2addr 0x0024 ; External Interrupt Request 2 OC0addr 0x0026 ; Timer/Counter0 Compare Match SPMRaddr 0x0028 ; Store Program Memory Ready

Как видишь, это первые адреса флеша. На каждый вектор отводится по два байта в которые мы можем записать любую команду. Но что попало туда обычно не вписывают, а ставят сразу JMP на какую нибудь метку, где уже можно спокойно сделать все что душе угодно, не стесняясь размеров кода.

Запишем эту бодягу в цивильной форме, через ORG и добавим немного кода.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
         .CSEG
         .ORG $000        ; (RESET) 
         RJMP   Reset
         .ORG $002
         RETI             ; (INT0) External Interrupt Request 0
         .ORG $004
         RETI             ; (INT1) External Interrupt Request 1
         .ORG $006
         RETI		    ; (TIMER2 COMP) Timer/Counter2 Compare Match
         .ORG $008
         RETI             ; (TIMER2 OVF) Timer/Counter2 Overflow
         .ORG $00A
         RETI		    ; (TIMER1 CAPT) Timer/Counter1 Capture Event
         .ORG $00C
         RETI             ; (TIMER1 COMPA) Timer/Counter1 Compare Match A
         .ORG $00E
         RETI             ; (TIMER1 COMPB) Timer/Counter1 Compare Match B
         .ORG $010
         RETI             ; (TIMER1 OVF) Timer/Counter1 Overflow
         .ORG $012
         RETI             ; (TIMER0 OVF) Timer/Counter0 Overflow
         .ORG $014
         RETI             ; (SPI,STC) Serial Transfer Complete
         .ORG $016
         RJMP   RX_OK     ; (USART,RXC) USART, Rx Complete
         .ORG $018
         RETI             ; (USART,UDRE) USART Data Register Empty
         .ORG $01A
         RETI             ; (USART,TXC) USART, Tx Complete
         .ORG $01C
         RETI		    ; (ADC) ADC Conversion Complete
         .ORG $01E
         RETI             ; (EE_RDY) EEPROM Ready
         .ORG $020
         RETI             ; (ANA_COMP) Analog Comparator
         .ORG $022
         RETI             ; (TWI) 2-wire Serial Interface
         .ORG $024
         RETI             ; (INT2) External Interrupt Request 2
         .ORG $026
         RETI             ; (TIMER0 COMP) Timer/Counter0 Compare Match
         .ORG $028
         RETI             ; (SPM_RDY) Store Program Memory Ready
 
	 .ORG   INT_VECTORS_SIZE      	; Конец таблицы прерываний
 
;----------------------------------------------------------------------
; Это обработчик прерывания. Тут, на просторе, можно наворотить сколько
; угодно кода. 
RX_OK:	 IN 	R16,UDR		; Тут мы делаем что то нужное и полезное
 
	 RETI			; Прерывание завершается командой RETI 
;----------------------------------------------------------------------
 
 
Reset:  LDI R16,Low(RAMEND)	; Инициализация стека
	 OUT SPL,R16		; Обязательно!!!
 
	 LDI R16,High(RAMEND)
	 OUT SPH,R16
 
	 SEI			; Разрешаем прерывания глобально
	 LDI   R17,(1<<RXCIE)	; Разрешаем прерывания по приему байта
	 OUT 	UCSRB,R17
 
M1:	 NOP			
	 NOP
	 NOP
	 NOP
	 RJMP M1

.CSEG .ORG $000 ; (RESET) RJMP Reset .ORG $002 RETI ; (INT0) External Interrupt Request 0 .ORG $004 RETI ; (INT1) External Interrupt Request 1 .ORG $006 RETI ; (TIMER2 COMP) Timer/Counter2 Compare Match .ORG $008 RETI ; (TIMER2 OVF) Timer/Counter2 Overflow .ORG $00A RETI ; (TIMER1 CAPT) Timer/Counter1 Capture Event .ORG $00C RETI ; (TIMER1 COMPA) Timer/Counter1 Compare Match A .ORG $00E RETI ; (TIMER1 COMPB) Timer/Counter1 Compare Match B .ORG $010 RETI ; (TIMER1 OVF) Timer/Counter1 Overflow .ORG $012 RETI ; (TIMER0 OVF) Timer/Counter0 Overflow .ORG $014 RETI ; (SPI,STC) Serial Transfer Complete .ORG $016 RJMP RX_OK ; (USART,RXC) USART, Rx Complete .ORG $018 RETI ; (USART,UDRE) USART Data Register Empty .ORG $01A RETI ; (USART,TXC) USART, Tx Complete .ORG $01C RETI ; (ADC) ADC Conversion Complete .ORG $01E RETI ; (EE_RDY) EEPROM Ready .ORG $020 RETI ; (ANA_COMP) Analog Comparator .ORG $022 RETI ; (TWI) 2-wire Serial Interface .ORG $024 RETI ; (INT2) External Interrupt Request 2 .ORG $026 RETI ; (TIMER0 COMP) Timer/Counter0 Compare Match .ORG $028 RETI ; (SPM_RDY) Store Program Memory Ready .ORG INT_VECTORS_SIZE ; Конец таблицы прерываний ;———————————————————————- ; Это обработчик прерывания. Тут, на просторе, можно наворотить сколько ; угодно кода. RX_OK: IN R16,UDR ; Тут мы делаем что то нужное и полезное RETI ; Прерывание завершается командой RETI ;———————————————————————- Reset: LDI R16,Low(RAMEND) ; Инициализация стека OUT SPL,R16 ; Обязательно!!! LDI R16,High(RAMEND) OUT SPH,R16 SEI ; Разрешаем прерывания глобально LDI R17,(1<<RXCIE) ; Разрешаем прерывания по приему байта OUT UCSRB,R17 M1: NOP NOP NOP NOP RJMP M1

Теперь разберем эту портянку. Контроллер стартует с адреса 0000, это точка входа. Там мы сразу же делаем бросок на метку RESET. Если это не сделать, то контроллер пойдет выполнять команды из таблицы векторов, а они не для того там посажены. Да и не далеко он ускачет — без инициализации стека и наличия адреса возврата в нем первый же RETI вызовет коллапс. Ведь RETI работает почти также как и RET.

Поэтому сразу уносим оттуда ноги на RESET. Где первым делом инициализируем стек. А потом, командой SEI, разрешаем прерывания. И установкой бита в регистре периферии UCSRB включаем прерывание по приему байта.

Дальше зацикливаемся и ждем когда в приемный буффер USART извне свалится байт. Запускай это дело в эмуляцию и начинай трассировать по одной команде. Сначала, как я и говорил, проц прыгнет на метку Reset, потом выставит нужные значения и наглухо зациклится на

1
2
3
4
5
M1:	 NOP			
	 NOP
	 NOP
	 NOP
	 RJMP M1

M1: NOP NOP NOP NOP RJMP M1

До прихода байта. Но как же нам осуществить этот приход байта если весь наш эксперимент не более чем симуляция виртуального процессора в отладчике? А очень просто!

Вручную вписать этот байт в регистр и вручную же протыкать флаг, словно байт пришел. За прием байта отвечает флаг RXC в регистре периферии UCSRA, раздел USART. Найди там бит RXC и тыкни его, чтобы закрасился. Все, прерывание вроде как наступило.

Нажми F11, чтобы сделать еще один шаг по программе… Опа, стрелочка улетела в таблицу векторов, как раз на вектор

1
2
	.ORG $016
        RJMP   RX_OK     ; (USART,RXC) USART, Rx Complete

.ORG $016 RJMP RX_OK ; (USART,RXC) USART, Rx Complete

А оттуда уже прыжок сразу же на метку RX_OK, где мы забираем данные из регистра UDR в R17 и выходим из прерывания, по команде RETI.

При этом процессор вернется в наш бесконечный цикл, в то место откуда прерывался.

Вот, как это было, если по коду:

Увеличить

Вот, вроде теперь вопросов по выполнению быть не должно.

Разрешение и запрещение прерываний
Прерываний много, но по умолчанию они все запрещены. Во-первых, глобально — есть в процессоре в регистре SREG (о нем чуть позже) флаг I (interrupt) когда он равен 0, то все прерывания запрещены вообще, глобально, все без исключения.

Когда он равен 1, то прерывания глобально разрешены, но могут быть запрещены локально.

Устанавливается и сбрасывается этот флаг командами

  • SEI — разрешить прерывания
  • CLI — запретить прерывания (Да, по поводу моего ника, DI это, кстати, то же самое что и CLI, но для процессора Z80 😉 )

Кроме того, у каждого прерывания есть еще свой собственный бит локального разрешения. В примере с UDR это бит RXCIE (Receive Complete Interrupt Enable) и расположены они в портах ввода вывода

По дефолту, после старта, все прерывания запрещены локально и глобально. И их надо разрешать вручную, не забыв прописать обработчик прерывания.

Если не прописать обработчик и по ошибке разрешить прерывания, то когда это ошибочное прерывание вдруг сработает, то процессор ускачет по вектору, а вот там все зависит от того, что ты туда прописал.

Если все неиспользуемые прерывания заглушены командой RETI то ничего не пройзойдет — как пришел так и вернется обратно. Если же там ничего нет, то процессор будет выполнять эту пустоту пока не доберется до живого кода. Это может быть как переход на обработчик другого прерывания (ниже по таблице векторов) так и начало основного кода, тело обработчика прерывания, какая либо процедура, записанная до метки start. Да что угодно, что первой попадется.

Сам понимаешь, что в таком случае ни о какой корректной работе говорить не придется. Кстати, почти все сишные компиляторы, неиспользуемые обработчики глушат на RESET т.е. случайный вызов несуществующего прерывания приводит к сбросу.

С одной стороны это бага, с другой фича — так называемая ситуация максимизации ошибки. Т.е. большой фатальный глюк, обрушивающий всю программу нахрен, куда безопасней мелкой ошибки потому, что вылазит сразу же. А ошибка может поджидать годами и спокойно пересидеть период отладки, а вылезти уже в готовом устройстве. И непременно перед тем как заказчик выпишет тебе бабло за разработку 🙂

Итак, повторюсь для ясности. Прерывания по дефолту запрещены локально и глобально. Разрешают их по мере надобности. Причем делают это дважды — на локальном уровне и на глобальном.

О том, что прерывание произошло извещает флаг этого прерывания в регистре ввода вывода. Пока флаг поднят считается что прерывание не обработано и процессор будет пытаться по этому прерыванию перейти (если конечно прерывания разрешены и локально, и глобально).

Флаг этого события сбрасывается либо сам, при переходе к обработчику прерывания, либо при совершении какого-либо действия. Например, чтобы сбросить флаг события прерывания RxC надо считать байт из UDR. Протрассируй программу и сам увидишь, что сброс флага RxC происходит после выполнения команды

Либо флаг скидывают вручную — записью в этот флаг единицы. Не нуля! Единицы! Подробней в даташите на конкретную периферию.

Очередность прерываний
Но что будет если произошло одно прерывание и процессор ушел на обработчик, а в этот момент произошло другое прерывание?

А ничего не будет, при уходе на прерывание происходит аппаратный глобальный запрет всех других прерываний— просто сбрасывается флаг I, а по команде RETI, при возврате, этот флаг устанавливается в 1. В этом, кстати, отличие RET от RETI

Но! Никто не запрещает нам внутри обработчика поставить SEI и разрешить прерывания. При этом мы получим вложенные прерывания. Можно, но это опасно. Черевато переполнением стека и прочими гадостями. Так что это надо делать с твердым осознанием последствий.

Что тогда? Прерывание которое пришло во время обработки первого потеряется?

Нет, не потеряется. Флаг то его события никто сам не снимет, так что только процессор выйдет из первого обработчика (разреша при этом глобальные прерывания), как это не снятый флаг сгенерирует новое прерывание и процессор его обработает.

А теперь такая ситуация — прерывания запрещены, неважно по какой причине, и тут приходит два прерывания. Поднимает каждая свой флажок и оба ждут глобального разрешения. SEI!!! Кто пойдет первым? А первым пойдет то прерывание, чей вектор меньше по адресу, ближе к началу памяти. За ним второй.

В случае когда пришло несколько прерываний одного типа. Скажем, пока мы там ковырялись в обработчике, нам сбоку тут еще таймер три раза тикнул своим флагом, то обработается только одно событие, остальные могут потеряться.

Бег по граблям
Прерывания штука мощная, но опасная. С их помощью плодятся такие глюки, по сравнению с которыми стековые срывы так — семечки.

Вся засада багов из-за кривых прерываниях в том, что их практически невозможно отследить в отладчике.

Возникает плавающий глюк, появление которого зависит от того в каком именно месте кода произойдет вызов прерывания. Что поймать, сам понимаешь, почти невозможно.

Так что если у МК то понос, то золотуха, то программа петухом поет, а то молчит как рыба — знай, в 95% копать собаку надо в районе прерываний и их обработчиков.

Но если правильно написать прерывание, то багов оно не даст. Главное понимать в чем его опасность.

Грабли первые — спасай регистры!!!
Прерывание, когда оно разрешено, вызывается ВНЕЗАПНО, между двумя произвольными инструкциями кода. Поэтому очень важно, чтобы к моменту когда мы из прерывания вернемся все осталось как было.

Все регистры, используемые в обработчике прерываний, должны быть предварительно сохранены. Также должен быть сохранен регистр флагов SREG, в котором хранится результат логических операций. Результаты проще всего сохранять в стеке.

Приведу пример: Вот есть у нас обработчик прерывания который сравнивает байт на входе в USART и если он равен 10, выдает обратно отклик ‘t’ (ten в смысле).

1
2
3
4
5
6
7
8
9
10
11
RX_OK:
 
	IN	R16,UDR
	CPI	R16,10
	BREQ	Ten
	RJMP	Exit
 
Ten:	LDI	R17,'t'
	OUT	UDR,R17
 
Exit:

RX_OK: IN R16,UDR CPI R16,10 BREQ Ten RJMP Exit Ten: LDI R17,’t’ OUT UDR,R17 Exit:

И у нас есть код. Код скопипащен от балды из какого то проекта, даже пояснять не буду, не в нем суть.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
         . . . 	
         LPM    R18,Z
         CPI    R18,0
 
         BREQ   ExitStr
 
         SUBI   R16,65
         LSL    R16
 
         LDI    ZL,Low(Ltrs*2)
         LDI    ZH,High(Ltrs*2)
 
         ADD    ZL,R16
         ADC    ZH,R1
 
         . . .

. . . LPM R18,Z CPI R18,0 BREQ ExitStr SUBI R16,65 LSL R16 LDI ZL,Low(Ltrs*2) LDI ZH,High(Ltrs*2) ADD ZL,R16 ADC ZH,R1 . . .

Согласно идеи прерывания, наш обработчик может воткнуться между двумя любыми инструкциями.
Например, так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
         . . . 	
         LPM    R18,Z
         CPI    R18,0
 
         BREQ   ExitStr
 
         SUBI   R16,65
>>>>>>>>>>>Прерывание >>>>>>>>>>>
RX_OK:
 
	IN	R16,UDR
	CPI	R16,10
	BREQ	Ten
	RJMP	Exit
 
Ten:	LDI	R17,'t'
	OUT	UDR,R17
 
Exit:	RETI
<<<<<<<<<<< Возврат <<<<<<<<<<<<<
         LSL    R16
 
         LDI    ZL,Low(Ltrs*2)
         LDI    ZH,High(Ltrs*2)
 
         ADD    ZL,R16
         ADC    ZH,R1
         . . .

. . . LPM R18,Z CPI R18,0 BREQ ExitStr SUBI R16,65 >>>>>>>>>>>Прерывание >>>>>>>>>>> RX_OK: IN R16,UDR CPI R16,10 BREQ Ten RJMP Exit Ten: LDI R17,’t’ OUT UDR,R17 Exit: RETI <<<<<<<<<<< Возврат <<<<<<<<<<<<< LSL R16 LDI ZL,Low(Ltrs*2) LDI ZH,High(Ltrs*2) ADD ZL,R16 ADC ZH,R1 . . .

До входа в прерывание, после команды SUBI R16,65 в R16 было какое то число из которого вычли 65.
И дальше с ним должны были провернуть операцию логического сдвига LSL R16, но тут вклинился обработчик, где в R16 записалось значение из UDR.

И мы выпали из обработчика перед командой LSL R16, но в R16 уже мусор какой то. Совершенно не те данные, что мы планировали.

Естественно вся логика работы от такого издевательства порушилась и возник глюк. Но стоит прерыванию прийти чуть раньше, на одну микросекунду — как глюк исчезнет, т.к. SUBI R16,65 будет уже после прерывания и логика не будет порушена, но может возникнуть другой глюк.

Полная лотерея, повезет не повезет. Может вылезти сразу, а может и через год идеальной работы, а потом также сгинет и сиди чеши репу что же это было — сбой по питанию, бага, или таракан по плате пробежал неудачно.

Чтобы такого не было в обязательно порядке надо сохранять регистры и SREG на входе в прерывание и доставать на выходе.

У нас тут, в обработчике, используется R17 и R16 и SREG (в него помещается результат работы команды CPI). Вот их и сохраним.

Выглдеть это будет так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
RX_OK:	PUSH	R16		; Сохранили R16
	IN	R16,SREG	; Достали SREG в R16
	PUSH	R16		; Утопили его в стеке
	PUSH	R17		; Туда же утопили R17
 
; Теперь можно со спокойной совестью работу работать. 
 
	IN	R16,UDR
	CPI	R16,10
	BREQ	Ten
	RJMP	Exit
 
Ten:	LDI	R17,'t'
	OUT	UDR,R17
 
; А на выходе вернем все как было. 
; Достаем в обратном порядке
 
Exit:	POP	R17
	POP	R16
	OUT	SREG	R16
	POP	R16
	RETI			; Спокойно выходим. Регистры вернул как было.

RX_OK: PUSH R16 ; Сохранили R16 IN R16,SREG ; Достали SREG в R16 PUSH R16 ; Утопили его в стеке PUSH R17 ; Туда же утопили R17 ; Теперь можно со спокойной совестью работу работать. IN R16,UDR CPI R16,10 BREQ Ten RJMP Exit Ten: LDI R17,’t’ OUT UDR,R17 ; А на выходе вернем все как было. ; Достаем в обратном порядке Exit: POP R17 POP R16 OUT SREG R16 POP R16 RETI ; Спокойно выходим. Регистры вернул как было.

Как же выносить данные из прерываний? Да по разному, можно в память сохранять, можно для этого спец регистр заиметь и знать, что он может в любой момент измениться в прерывании.

Грабли вторые — не тормози!!!
Прерывания отвлекают процессор от основных дел, более того, они блокируют другие прерывания. Поэтому в прерывании главное все сделать максимально быстро и свалить. Никаких циклов задержки, никаких долгоиграющих процедур. Никаких ожиданий аппаратного события. СКОРОСТЬ! СКОРОСТЬ! СКОРОСТЬ! Вот что должно тобой руководить при написании обработчика.

Заскочил — сделал — выскочил!

Но такая красивая схема возможна далеко не всегда. Иногда бывает надо по прерыванию делать и медленные вещи. Например, прием байтов из интерфейса и запись их в какую нибудь медленную память, вроде EEPROM. Как тогда быть?

А тут делают проще. Цель прерывания — во что бы то ни стало среагировать на событие именно в тот момент, когда оно пришло, чтобы не прозевать. А вот обрабатывать его прямо сейчас обычно и не обязательно.
Заскочил в обработчик, схватил быстро тухнущие данные, перекинул их в буффер где им ничего не угрожает, поставил где-нибудь флажок отметку «мол там байт обработать надо» и вышел. Все! Остальное пусть сделает фоновая программа в порядке общей очереди.

Либо, если иначе никак нельзя, разрешай прерывания внутри обработчика, но смотри чтобы не возникли рекурсивные прерывания, когда один и тот же обработчик разрешает прерывания сам себе, образуя множественные вложенные вызовы с зарыванием в стек.

Грабли третьи — атомарный доступ
Есть ряд операций которые должны выполняться неразрывно. Например, чтение 16ти разрядных регистров таймера.

Ядро то у нас восьми-разрядное, поэтому 16ти разрядный регистр таймера считывается в два приема сначала младший байт, а потом старший. И вот в этом месте нельзя ни в коем случае допускать, чтобы между считыванием младшего и старшего было прерывание.

Т.к. после выхода из прерывания таймер уже может много чего натикать и информация уже будет не актуальная.

Поэтому перед чтением делаем CLI, а после SEI.

Также нельзя разрешать прерывания если одна и та же многоходовая операция делается и в прерывании и в главном цикле.

Например, прерывание хватает байты из АЦП и пишет их в буффер (ОЗУ), образуя связные цепочки данных. Главный же цикл периодически из этого буффера данные читает. Так вот, на момент чтения буффера из памяти, надо запрещать прерывания, чтобы никто не посмел в этот буффер что-нибудь записать.
Иначе мы можем получить невалидные данные — половина байтов из старого замера, половина из нового.

Причем, во многих случаях не обязательно глобально блокировать все прерывания вообще, достаточно заблокировать то локальное прерывание, которое может нам нагадить.

Грабли четвертые — не блокируй!!!
Третие грабли нужно внимательно обходить, но в паранойю впадать тоже не следует. Тупо запрещать прерывания везде, где мерещится бага, не стоит. Иначе можно прозевать события, а это плохо. Нет, обработчик то выполнится, но будет это уже не актуально — хороша ложка к обеду.

Обязательно погоняй в отладчике код (да хотя бы кучу NOP) с разными прерываниями. Чтобы понять и прочувствовать как ведут себя прерывания.

Старая версия статьи. Чисто поржать 🙂

внешних прерываний в системе x86. Часть 1. Эволюция контроллера прерываний / Habr В этой статье рассказывается о процессе доставки прерываний с внешних устройств в системе x86. Он пытается ответить на такие вопросы, как:
  • Что такое ПОС и для чего он нужен?
  • Что такое APIC и для чего он нужен? Какова цель LAPIC и I / O APIC?
  • В чем различия между APIC, xAPIC и x2APIC?
  • Что такое MSI? Каковы различия между MSI и MSI-X?
  • Какова роль таблиц $ PIR, MPtable и ACPI?

Если вы хотите узнать ответ на один из этих вопросов или просто хотите узнать об эволюции контроллера прерываний, пожалуйста, добро пожаловать.

Введение


Для тех, кто не знает, что такое прерывание, вот цитата из Википедии:
В системном программировании прерывание — это сигнал для процессора, испускаемый аппаратным или программным обеспечением, указывающий на событие, которое требует немедленного внимания. Прерывание предупреждает процессор о высокоприоритетном состоянии, требующем прерывания текущего кода, выполняемого процессором. Процессор отвечает, приостанавливая свои текущие действия, сохраняя свое состояние и выполняя функцию, называемую обработчиком прерываний (или подпрограммой обработки прерываний, ISR) для обработки события.Это прерывание является временным, и после завершения обработчика прерываний процессор возобновляет обычную работу.

Существует два типа прерываний: аппаратные прерывания и программные прерывания (softirqs):

  • Аппаратные прерывания используются устройствами для сообщения о том, что они требуют внимания от операционной системы. Внутренне аппаратные прерывания реализуются с использованием электронных сигналов оповещения, которые отправляются в процессор от внешнего устройства, которое является частью самого компьютера, такого как контроллер диска, или внешней периферией.Например, нажатие клавиши на клавиатуре или перемещение мыши вызывает аппаратные прерывания, которые заставляют процессор считывать нажатие клавиши или положение мыши. Акт инициирования аппаратного прерывания называется запросом прерывания (IRQ).
  • Программное прерывание вызвано либо исключительным состоянием самого процессора, либо специальной инструкцией в наборе команд, которая вызывает прерывание при его выполнении. Первый часто называют ловушкой или исключением и используется для ошибок или событий, происходящих во время выполнения программы, которые настолько исключительны, что не могут быть обработаны в самой программе.Например, возникает исключение деления на ноль, если арифметико-логическому блоку процессора предписано делить число на ноль, поскольку эта инструкция является ошибкой и невозможной.

Эта статья об аппаратных / внешних прерываниях IRQ.

Какова цель прерываний? Например, мы хотим выполнить действие с входящим пакетом от сетевой карты, как только пакет прибудет. Если вы не хотите постоянно спрашивать сетевую карту «Пришел ли мой пакет?» и тратить ваше процессорное время, вы можете использовать внешнее аппаратное прерывание IRQ.Линия прерывания от устройства должна быть подключена к линии INTR ЦПУ, и после получения каждого пакета сетевая карта будет подавать сигнал по этой линии. Процессор будет распознавать этот сигнал и знать, что сетевая карта имеет информацию для него. Только после этого процессор будет читать входящий пакет.

Но что нам делать, если много внешних устройств? Было бы очень непродуктивно делать для каждого из них тонну контактов INTR на процессоре.

Для решения этой проблемы был изобретен специальный чип — контроллер прерываний.

ПИК


(wiki / osdev)

Первым чипом контроллера прерываний был PIC Intel 8259. Он имел 8 входных линий (IRQ0-7) и 1 выходную линию (которая соединяет контроллер прерываний с линией INTR ЦПУ). При наличии прерывания от одного из устройств на его входных линиях 8259 подаст сигнал по линии INTR. После этого процессор узнает, что какое-то устройство требует его немедленного внимания, и процессор спросит PIC, какая из 8 входных линий (IRQx) была источником этого прерывания.В этом опросе есть некоторые накладные расходы, но теперь у нас есть 8 линий прерываний вместо 1.

Вскоре 8 линий стало недостаточно. Для увеличения общего количества линий прерывания два 8259 контроллеров (ведущий и ведомый) были соединены в каскад (Dual PIC).

IRQ от 0 до 7 обрабатываются с помощью первого PIC Intel 8259 (ведущий), а IRQ от 8 до 15 обрабатываются с помощью второго Intel 8259 PIC (ведомый). Только мастер подключен к ЦП и может сигнализировать о входящих прерываниях.Если в линиях 8-15 имеется прерывание, второй PIC (ведомый) сообщит об этом ведущему на линии IRQ2, и после этого мастер сообщит ЦПУ. Это каскадное прерывание убирает 1 из 16 строк, но в общей сложности делает 15 прерываний для всех внешних устройств.

Эта схема была принята сообществом, и теперь, когда кто-то говорит о PIC (Programm Interrupt Controller), он имеет в виду эту систему Dual PIC. Через некоторое время контроллеры 8259 были улучшены и получили новое имя: 8259A.С этими контроллерами в чипсет была включена система DUAL PIC. В то время, когда основной шиной для подключения внешнего устройства была ISA, этой системы было достаточно. Необходимо было только, чтобы разные устройства не подключались к одной линии IRQ, поскольку прерывания ISA не могут использоваться совместно.

Отображение прерываний устройства было в значительной степени стандартным:

Пример (отсюда):
IRQ 0 — системный таймер
IRQ 1 — клавиатурный контроллер
IRQ 2 — каскад (прерывание от подчиненного контроллера)
IRQ 3 — последовательный порт COM2
IRQ 4 — последовательный порт COM1
IRQ 5 — параллельный порт 2 и 3 или звуковая карта
IRQ 6 — контроллер дискеты
IRQ 7 — параллельный порт 1
IRQ 8 — таймер RTC
IRQ 9 — ACPI
IRQ 10 — открытый / SCSI / NIC
IRQ 11 — открытый / SCSI / NIC
IRQ 12 — контроллер мыши
IRQ 13 — математический сопроцессор
IRQ 14 — ATA канал 1
IRQ 15 — ATA канал 2

Конфигурирование и работа с чипами 8259 осуществляется с портами ввода / вывода:


Полную документацию по 8259A можно найти здесь.

Шина PCI позже заменила шину ISA. К сожалению, количество устройств стало превышать число 15. Кроме того, вместо статической шины ISA устройства в шине PCI можно динамически добавлять в систему, что потенциально может привести к еще большему количеству проблем. Но, к счастью, прерывания на шине PCI могут использоваться совместно, поэтому можно подключить множество устройств к одной линии прерываний IRQ. В конце концов, для решения проблемы отсутствия линий прерывания было решено сгруппировать прерывания от всех устройств PCI до линий PIRQ (Programmable Interrupt Request).

Например, предположим, у нас есть 4 свободных линии прерываний на контроллере PIC и 20 устройств PCI. Мы можем объединить прерывания от 5 устройств в одну линию PIRQx и подключить эти линии PIRQx к контроллеру PIC. В этом случае, если есть прерывание на одной из линий PIRQx, процессор должен будет спросить все устройства, подключенные к этой линии, о прерывании, чтобы узнать, кто за него отвечает, но в конце концов это решит проблему. Устройство, которое соединяет линии прерывания PCI с линиями PIRQ, часто называют PIR-маршрутизатором.

При использовании этого метода необходимо убедиться, что линии PIRQx не подключаются к линиям с прерываниями ISA (так как это приведет к конфликтам) и что линии PIRQx сбалансированы (чем больше устройств мы подключаем к одной линии, тем больше устройств ЦП нужно будет опрашивать, когда нужно проверить, какое устройство отвечает за прерывание).

Примечание : на изображении карта устройства PCI -> PIR изображена абстрактно, поскольку в реальном случае это немного сложнее.В реальном мире каждое устройство PCI имеет 4 линии прерывания (INTA, INTB, INTC, INTD) и до 8 функций, где каждая функция может иметь только одно из этих прерываний INTx. Какая линия INTx будет использоваться каждой функцией, определяется конфигурацией чипсета.

По своей природе функции являются отдельными логическими блоками. Например, одно устройство PCI может иметь функцию контроллера Smbus, функцию контроллера SATA и функцию моста LPC. С точки зрения операционной системы (ОС) каждая функция похожа на отдельное устройство со своим собственным пространством конфигурации (конфигурация PCI).

Информация о маршрутизации прерываний контроллера PIC отправляется в ОС BIOS с помощью таблицы $ PIR и через регистры 3Ch (линия прерывания INT_LN (R / W)) и 3Dh (вывод прерывания INT_PN (RO) ) пространства конфигурации PCI для каждой функции.

Спецификация для таблицы $ PIR недавно была на сайте Microsoft, но в настоящее время недоступна. Понять содержание таблицы можно из спецификации BIOS BIOS [4.2.2. Получить параметры маршрутизации прерывания PCI] или отсюда (последняя ссылка на русском языке, но вы можете попробовать Google «Спецификация таблицы маршрутизации PCI IRQ»)

APIC


(wiki, osdev)

Последний метод работал до появления многопроцессорных систем.По своей природе PIC может отправлять прерывания только одному ЦП, а в многопроцессорной системе желательно загружать ЦП сбалансированным образом. Решением этой проблемы стал новый интерфейс APIC (Advanced PIC).

Для каждого процессора был добавлен специальный контроллер LAPIC (локальный APIC), а также контроллер ввода / вывода APIC для маршрутизации прерываний от внешних устройств. Все эти контроллеры объединены в общую шину с именем APIC (обратите внимание, что современные системы используют стандартную системную шину вместо отдельной шины APIC для этой задачи).

Когда на вход APIC ввода / вывода поступает внешнее прерывание, контроллер отправляет сообщение прерывания на LAPIC одного из системных процессоров. Таким образом, контроллер APIC ввода / вывода помогает сбалансировать нагрузку прерывания между процессорами.

Первым чипом APIC был 82489DX, который представлял собой отдельный чип с подключенным к нему LAPIC и APIC ввода / вывода. Для двухпроцессорной системы потребовалось три таких чипа: два для LAPIC и один для ввода / вывода APIC. Позже функциональность LAPIC была непосредственно включена в процессоры, а часть ввода / вывода APIC была отделена от чипа 82093AA.

Ввод / вывод APIC 82093AA имел 24 входа, а архитектура APIC могла поддерживать до 16 процессоров. Прерывания 0-15 были оставлены для старых прерываний ISA для совместимости со старыми системами, а прерывания 16-23 предназначались для всех устройств PCI. С помощью этого разграничения можно легко избежать всех конфликтов между прерываниями ISA и PCI. С увеличением количества свободных линий прерывания также стало возможным увеличить количество линий PIRQx.

Программирование ввода / вывода APIC и LAPIC осуществляется с помощью MMIO.Регистры LAPIC обычно размещаются по адресу 0xFEE00000, а APIC ввода / вывода регистрируется по адресу 0xFEС00000, хотя их можно перенастроить.

Как и в случае с PIC, отдельные чипы вначале стали частью чипсета позже.

Архитектура APIC была позже модернизирована, и ее новый вариант получил название xAPIC (x — расширенный). Благодаря полной обратной совместимости общее количество возможных процессоров в системе было увеличено до 256.

Следующий шаг в разработке архитектуры получил название x2APIC.32. Эти контроллеры могут работать в режиме обратной совместимости с xAPIC или в новом режиме x2APIC. В этом новом режиме программирование контроллера осуществляется не через MMIO, а через регистры MSR (которые намного быстрее). По этой ссылке для этого режима необходима поддержка IOMMU.

Стоит отметить, что в системе можно использовать несколько контроллеров APIC ввода / вывода. Например, один на 24 прерывания на южном мосту, а другой на 32 прерывания на северном мосту.В контексте ввода / вывода APIC прерывания обычно называются GSI (глобальное системное прерывание). Итак, вышеупомянутая система имеет GSI 0-55.

Как мы можем определить, имеет ли ЦП внутреннюю LAPIC и какую архитектуру APIC он поддерживает? На эти вопросы можно ответить, проверив битовые флаги из CPUID.
Чтобы помочь ОС обнаружить LAPIC и APIC ввода / вывода, BIOS должен представить информацию о них либо через MPtable (старый метод), либо через таблицу ACPI (в данном случае таблицу MADT).Помимо общей информации, и MPtable, и ACPI (в данном случае таблица DSDT) должны содержать информацию о маршрутизации прерываний. Это означает информацию о том, какое устройство использует какую линию прерывания (аналогично таблице $ PIR).

Вы можете прочитать о MPtable в официальной спецификации. Ранее спецификация была на сайте Intel, но в настоящее время найти ее можно только в архивной версии. Спецификацию ACPI можно найти на веб-сайте UEFI (текущая версия 6.2). Стоит отметить, что с ACPI можно объявить маршрутизацию прерываний для систем без APIC (вместо предоставления отдельной таблицы $ PIR).

MSI


(wiki)

Последний вариант APIC был хорош, но не без минусов. Все линии прерывания от устройств сделали систему очень сложной и, таким образом, увеличили вероятность ошибки. Шина PCI Express пришла на смену шине PCI, которая полностью упростила все системы прерываний. У него вообще нет линий прерывания.Для обратной совместимости сигналы прерывания (INTx #) эмулируются с отдельным видом сообщений. С помощью линий прерывания PCI их соединение осуществлялось с помощью физических проводов. С помощью линий прерывания PCI Express логическое соединение осуществляется через мосты PCI Express. Но эта поддержка устаревших прерываний INTx существует только для обратной совместимости с шиной PCI. PCI Express представляет совершенно новый метод доставки прерываний — MSI (Message Signaled Interrupts). В этом методе устройство сигнализирует о прерывании просто путем записи в специальное место в области MMIO CPU LAPIC.

Ранее одно устройство PCI (это означает, что все его функции) могло иметь только 4 прерывания, но теперь стало возможным адресовать до 32 прерываний.

В случае MSI нет разделения линий прерываний: каждое прерывание естественно соответствует его устройству.

Прерывания MSI также решают еще одну проблему. Например, давайте представим ситуацию, когда устройство выполняет транзакцию записи в память и хочет сообщить о своем завершении через прерывание.Но транзакция записи может быть задержана на шине в процессе ее передачи (и устройство не может знать об этом). В этом случае сигнал о прерывании сначала поступит в процессор, поэтому процессор будет считывать еще не действительные данные. Если используется MSI, информация о MSI передается так же, как и сообщения данных, и поэтому не может прийти раньше.

Стоит отметить, что прерывания MSI не могут работать без LAPIC, но MSI могут заменить I / O APIC (еще одно упрощение конструкции).

Через некоторое время метод MSI был расширен до MSI-X. Теперь каждое устройство может иметь до 2048 прерываний. Также теперь можно указать, какой процессор должен обрабатывать какие прерывания. Это может быть очень полезно для высоконагруженных устройств, таких как сетевые карты, например.

Нет необходимости в отдельной таблице BIOS для поддержки MSI. Но устройство должно указать свою поддержку MSI через одну из возможностей в своем пространстве конфигурации PCI. Также драйвер устройства должен включать в себя всю необходимую поддержку для работы с MSI.

Заключение


В этой статье мы изучили информацию об эволюции контроллера прерываний и получили общие теоретические знания о доставке прерываний от внешних устройств в системе x86.

В следующей части мы пойдем на практику и посмотрим, как задействовать каждый из вышеупомянутых контроллеров прерываний в Linux.

В третьей части мы рассмотрим код начальной загрузки и посмотрим, какие настройки необходимы в чипсете для правильной маршрутизации прерываний.

Ссылки:


Благодарности

Отдельное спасибо Jacob Garber из сообщества coreboot за помощь в переводе этой статьи.,

типов прерываний | Как обрабатывать прерывания?

Прерывания:

В первые годы вычислений процессор должен ждать сигнала для обработки. Таким образом, процессор должен проверять каждую аппаратную и программную программу в системе на наличие сигнала для обработки. Этот метод проверки сигнала в системе для обработки называется методом опроса. В этом методе проблема заключается в том, что процессор должен тратить количество тактов только для проверки сигнала в системе, поскольку этот процессор будет излишне занят.Если какой-либо сигнал поступил для процесса, процессору потребуется некоторое время для обработки сигнала из-за процесса опроса в действии. Таким образом, производительность системы также будет ухудшена, и время отклика системы также уменьшится.

Итак, чтобы преодолеть эту проблему, инженеры представили новый механизм. В этом механизме процессор не будет проверять наличие какого-либо сигнала от аппаратного или программного обеспечения, но вместо этого аппаратное / программное обеспечение будет только отправлять сигнал процессору для обработки. Сигнал от аппаратного или программного обеспечения должен иметь наивысший приоритет, поскольку процессор должен покинуть текущий процесс и обработать сигнал аппаратного или программного обеспечения.Этот механизм обработки сигнала называется прерыванием системы.

Что такое прерывание?

Прерывание — это сигнал, который имеет наивысший приоритет от аппаратного или программного обеспечения, процессор должен немедленно обработать его сигнал.

Типы прерываний:

Хотя прерывания имеют наивысший приоритет по сравнению с другими сигналами, существует много типов прерываний, но основным типом прерываний являются

.
  1. Аппаратные прерывания: Если сигнал для процессора поступает от внешнего устройства или аппаратного обеспечения, оно называется аппаратными прерываниями.Пример: на клавиатуре мы будем нажимать клавишу, чтобы выполнить какое-либо действие. Это нажатие клавиши на клавиатуре сгенерирует сигнал, который подается процессору для выполнения действия, такие прерывания называются аппаратными прерываниями. Аппаратные прерывания можно классифицировать на два типа:
    • Maskable Interrupt: Аппаратные прерывания, которые могут быть задержаны, когда процессор получил прерывание с наивысшим приоритетом.
    • Немаскируемое прерывание: Аппаратное обеспечение, которое не может быть отложено и должно обрабатываться процессором немедленно.
  2. Программные прерывания: Программные прерывания также можно разделить на два типа. Они есть
    • Нормальные прерывания: прерывания, вызванные инструкциями программного обеспечения, называются инструкциями программного обеспечения.
    • Исключение: незапланированных прерываний при выполнении программы называется Исключением. Например: при выполнении программы, если мы получили значение, которое должно быть разделено на ноль, это называется исключением.

Классификация прерываний по периодичности появления:

  1. Периодическое прерывание: Если прерывания произошли с фиксированным интервалом на временной шкале, то эти прерывания называются периодическими прерываниями
  2. Апериодическое прерывание: Если возникновение прерывания не может быть предсказано, то это прерывание называется апериодическим прерыванием.

Классификация прерываний по временной зависимости от системных часов:

  1. Синхронное прерывание: Источник прерывания находится в фазе, и системные часы называются синхронным прерыванием. Другими словами, прерывания, которые зависят от системных часов. Пример: служба таймера, которая использует системные часы.
  2. Асинхронные прерывания: Если прерывания независимы или не совпадают по фазе с системными часами, это называется асинхронным прерыванием.
Обработка прерываний:

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

Если присутствует прерывание, то оно вызовет обработчик прерывания, обработчик остановит текущую инструкцию, которая обрабатывает и сохранит свою конфигурацию в регистре, и загрузит программный счетчик прерывания из местоположения, которое задается вектором прерывания. стол.После обработки прерывания обработчиком прерываний процессора загрузит инструкцию и ее конфигурацию из сохраненного регистра, процесс начнет обработку с того места, где он был оставлен. Это сохранение старой конфигурации обработки команд и загрузка новой конфигурации прерываний также называется переключением контекста.

Обработчик прерываний также называется подпрограммой обслуживания прерываний (ISR). Существуют разные типы обработчиков прерываний, которые будут обрабатывать разные прерывания.Например, для часов в системе будет иметь свой обработчик прерываний, клавиатура будет иметь свой обработчик прерываний для каждого устройства, которое будет иметь свой обработчик прерываний.

Основными функциями ISR являются

  • Прерывания могут возникать в любое время, когда они асинхронные. ISR могут вызывать асинхронные прерывания.
  • Механизм обработки прерываний может вызывать ISR из нескольких источников.
  • ISR могут обрабатывать как маскируемые, так и немаскируемые прерывания. Инструкция в программе может отключить или включить вызов обработчика прерываний.
  • ISR в начале выполнения отключит другие службы прерывания устройств. После завершения выполнения ISR он повторно инициализирует службы прерываний.
  • Вложенные прерывания разрешены в ISR для переключения на другие ISR.
Тип обработчиков прерываний:
  1. Обработчик прерываний первого уровня (FLIH) — это жесткий обработчик прерываний или быстрый обработчик прерываний. Эти обработчики прерываний имеют больше дрожания при выполнении процесса, и они в основном маскируются прерываниями
  2. Обработчик прерываний второго уровня (SLIH) — это мягкий обработчик прерываний и медленный обработчик прерываний.Эти обработчики прерываний имеют меньше дрожания.
Задержка прерывания:

Когда происходит прерывание, обслуживание прерывания посредством выполнения ISR может не начаться немедленно путем переключения контекста. Временной интервал между возникновением прерывания и началом выполнения ISR называется задержкой прерывания.

  • Tswitch = Время, необходимое для переключения контекста
  • ΣTexec = Сумма временного интервала для выполнения ISR
  • Задержка прерывания = Tswitch + ΣTexec

Ссылка по теме: Операционная система реального времени

,
аппаратных средств — может ли компьютер работать только с программными прерываниями? Переполнение стека
  1. Товары
  2. Клиенты
  3. Случаи использования
  1. Переполнение стека Публичные вопросы и ответы
  2. Команды Частные вопросы и ответы для вашей команды
  3. предприятие Частные вопросы и ответы для вашего предприятия
  4. работы Программирование и связанные с ним технические возможности карьерного роста
  5. Талант Нанимать технический талант
  6. реклама Связаться с разработчиками по всему миру

Загрузка…

  1. Авторизоваться зарегистрироваться
  2. текущее сообщество

    • Переполнение стека Помогите чат
.
клавиатурных прерываний — что означает отключение прерываний? Переполнение стека
  1. Товары
  2. Клиенты
  3. Случаи использования
  1. Переполнение стека Публичные вопросы и ответы
  2. Команды Частные вопросы и ответы для вашей команды
  3. предприятие Частные вопросы и ответы для вашего предприятия
  4. работы Программирование и связанные с ним технические возможности карьерного роста
  5. Талант Нанимать технический талант
  6. реклама Связаться с разработчиками по всему миру
,

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *