Прерывания от внешних устройств в системе x86. Часть 1. Эволюция контроллеров прерываний

  • Tutorial
В данной статье хотелось бы рассмотреть механизмы доставки прерываний от внешних устройств в системе 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 порты:
Чип Регистр I/O port
Master PIC Command 0x0020
Master PIC Data 0x0021
Slave PIC Command 0x00A0
Slave PIC Data 0x00A1

→Документацию на 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 каждый из описанных контроллеров.

Ссылки:


Поддержать автора
Поделиться публикацией

Комментарии 25

    +1
    >>— что такое PIC и для чего он нужен?
    >>— что такое APIC и для чего он нужен? Для чего нужны LAPIC и I/O APIC?
    >>— в чём отличия APIC, xAPIC и x2APIC?
    >>— что такое MSI? В чём отличия MSI и MSI-X?
    >>— как с этим связаны таблицы $PIR, MPtable, ACPI?

    Чем провинились SMI/SCI, что они не попали в список?
      0
      Безусловно важные термины в контексте разговора о прерываниях. Но в данной статье хотелось обсудить контроллеры прерываний и те прерывания, на которые драйвер устройства может повесить обработчик.
        0
        >>и те прерывания, на которые драйвер устройства может повесить обработчик.
        Как вариант, для особо беспокойных, зайти в биос из него в Shell, там повесить свой обработчик на SMI, выйти снова в биос и загрузив ДОС, пользоваться в нем своим SMI прерыванием, хоть программным, хоть железным.
      0
      Но что делать если устройств много? На все внешние устройства ножек процессора не напасёшься.

      В свое время у PDP-11 в этом плане было продуманнее.
        0
        А какой ассемблер был…
          0
          А что там было?
            0
            Там у процессора изначально несколько входов разноприоритетных прерываний (4 + немаскируемое). Несколько устройств подключенных к одной линии прерывания могут дновременно подавать на нее запрос, а процессор в ответ подает ответ на разрешение, проходщий транзитом через все устройства на этой линии. Устройство расположенное ближе в цепочке ответа на разрешение блокирует передачу разрешения дальше и по шине данных передает процессору вектор прерывания.
              0
              При этом, если Вы вынимаете из корзины плату управления периферийным устройством (для ремонта или еще для чего), то все платы, расположенные дальше по цепочке, перестают вырабатывать прерывания — очень удобно ).
                0
                Это легко преодолевается простой логикой, прозрачно для устройств
            0
            Как именно продуманнее?
              0
              Несколько устройств могло выдавать запрос прерывания на одну ножку процессора без контроллеров-расширителей прерываний. И могли иметь при этом разные вектора обработки прерывания.
            +3
            Вообще-то MSI появилось куда раньше PCI express. Уже в спецификации PCI 2.2 от 1998 года есть MSI. MSI-X описано в спецификации PCI 3.0 (которая конечно по году и совпадает с появлением PCI express, но всё же). Для самой шины что MSI, что MSI-X — обычные записи от девайса в хост, неотличимые от любых других, о том, что они сигнализируют прерывания, говорит только их специальный адрес.
              +3
              прерывания ISA не разделяемые

              Может, перед таким категоричным заявлением стоит первоисточники почитать? Например, главу “Interrupt sharing” из “IBM PC AT technical reference” (первое, что под руку попало). Еще заодно можно посмотреть схему какого-нибудь ISA адаптера (только нормального, а не совсем уж “no name” изделия — например, контроллер жесткого диска от той же IBM) и убедиться, что IRQ формируется с учетом возможности использования нескольких устройств на одной линии.
                0
                Там другая засада: 8259A (в т.ч. в составе чипсетов) конфигурировался, так, чтобы реагировать на фронт сигнала запроса прерывания, а не на уровень. То есть «посадить» несколько устройств на одну линию можно, но драйвер, который будет обрабатывать прерывания, должен знать о них всех, чтобы сбросить все запросы с этой линии, иначе контроллер перестанет сигнализировать о прерываниях на ней. Если же назначить для двух разных устройств (например, сетевых карточек) один и тот же номер IRQ (джамперами), могло работать только одно из них.
                  0
                  В том же Technical reference объясняется, как правильно садиться на прерывания с программной точки зрения. Только довольно много обработчиков этого не делали — отбирали управление полностью на себя, не парясь насчет возможных других инициаторов прерываний на той же линии.
                    0

                    Верно, а почему они так поступали? Потому что для правильного взаимодействия все обработчики на одной линии должны вызываться в цикле до тех пор, пока каждый из них не определит, что "его" устройство больше не выставляет запрос на прерывание. Только так можно гарантировать, что следующий запрос пойдет фронтом сигнала. В рамках MS/DOS реализовать такое взаимодействие очень сложно. Я уже не говорю про другие тонкости, например, чтобы 8259A поймал сигнал запроса, перед фронтом сигнала необходим некоторый промежуток “тишины“.

                      0
                      rutenis curiousGeorge — спасибо за замечания. Будет ли более правильным сказать, что «на самом деле ISA устройства могут разделять линию прерываний, при условии что они спроектированы с учётом возможности подобного разделения. Но стандарт не делает это требование обязательным, поэтому в целом подключение нескольких ISA устройств к одной линии IRQ не является безопасным»?
                +1
                Спасибо за статью, то что давно было интересно но лень было искать теперь прояснилось.
                  0

                  Спасибо за статью. Настраивал в qemu/kvm прямой доступ к GPU из гостя и говорили что MSI значительно уменьшает издержки на прерывания. Из статьи становится немного понятней почему.

                    0
                    В исследовании от Intel Reducing Interrupt Latency Through the Use of Message Signaled Interrupts говорится, что прерывание через MSI в 3 раза быстрее чем через IO-APIC и в 5 раз быстрее чем через PIC.
                      0
                      Это всё хорошо, но все эти измерения не учитывают задержки из-за обработки перерывания сначала host системой и передачу в guest. Вроде как MSI даёт серьёзный прирост в этой ситуации при использовании qemu/kvm. Возможно из-за использования mmio. Вот и хотелось услышать квалифицировонное мнение на эту тему. Виртуализация этой системы возможно за рамками данной статью, заранее прошу извинений если так.
                    +1
                    Спасибо, интересно. Прочиталось легко и было полезно.
                      +2
                      Спасибо за статью, есть небольшая поправка.
                      30 лет назад в институте у нас был стенд, на котором мы потактово разбирали работу контроллера прерываний 8259, предназначенный для процессоров Intel 8080 и 8085. Его работа была принципиально иная, нежели 8259A. Данная статья описывает именно 8259A и позднее, в принципе логично т.к. рассматривается архитектура x86, но в статье очень желательно заменить 8259->8259A как в тексте, так и слайдах.

                      Чем же так принципиально отличался первоначальный 8259? У него не было маски прерываний, он брал из своих регистров три байта и последовательно выставлял их на шину команд! CPU не делал коллбек, просто выполнял команду (вызова подпрограммы), которую ему «подсунул» контроллер. Естественно тогда ни о каких оптимизациях и конвейерах в CPU речь не шла, поэтому работало как надо ))
                      По сигналу подтверждения прерывания она выдавала на шину три байта, первый соответствовал коду операции «Вызов подпрограммы» (значение 0xCD в шестнадцатеричной системе), затем два байта адреса вызова этой подпрограммы, которые нужно было записать в соответствующие каждому источнику прерывания внутренние регистры контроллера. Для процессоров семейства x86 была выпущена микросхема 8259A, так как механизм передачи управления изменился.

                      Источник: ru.wikipedia.org/wiki/Intel_8259
                        0
                        Спецификация $PIR была на сайте Microsoft.
                          0
                          Спасибо, поправил!

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

                        Самое читаемое