Содержание


Обслуживание периферии в коде модулей ядра: Часть 57. Регистрация обработчика прерываний

Comments

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

Инструментарий прерываний в /proc

Каждый раз, когда аппаратное прерывание обрабатывается процессором, внутренний счётчик прерываний увеличивается, предоставляя возможность контроля за подсистемой прерываний. Счётчики отображаются в /proc/interrupts (в последней колонке выводится имя обработчика, определённое параметром name при регистрации в вызове request_irq()). Ниже показана "раскладка" прерываний в архитектуре x86 при использовании устаревшего стандартного программируемого контроллера прерываний PC 8259 (если точнее, то показана схема с двумя каскадно-объединёнными по линии IRQ2 контроллерами 8259). Такую картину можно наблюдать только на компьютере с одним процессором:

$ cat /proc/interrupts
           CPU0
  0:   33675789          XT-PIC  timer
  1:      41076          XT-PIC  i8042
  2:          0          XT-PIC  cascade
  5:         18          XT-PIC  uhci_hcd:usb1, CS46XX
  6:          3          XT-PIC  floppy
  7:          0          XT-PIC  parport0
  8:          1          XT-PIC  rtc
  9:          0          XT-PIC  acpi
 11:    2153158          XT-PIC  ide2, eth0, mga@pci:0000:01:00.0

Те линии IRQ, для которых не установлены текущие обработчики прерываний, не отображаются в /proc/interrupts. Также здесь хорошо видно разделение линии IRQ 11 между тремя различными PCI-устройствами.

Ниже показана аналогичная информация, но полученная на существенно более новом компьютере с несколькими процессорами (ядрами), когда источником прерываний является усовершенствованный контроллер прерываний IO-APIC (отслеживаются прерывания как по фронту —IO-APIC-edge, так и по уровню —IO-APIC-level/IO-APIC-fasteoi):

$ cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       
  0:         49          0          0          0   IO-APIC-edge      timer
  1:          8          0          0          0   IO-APIC-edge      i8042
  4:          2          0          0          0   IO-APIC-edge
  7:          0          0          0          0   IO-APIC-edge      parport0
  8:          1          0          0          0   IO-APIC-edge      rtc0
  9:          0          0          0          0   IO-APIC-fasteoi   acpi
...
 22:        572          0          0          0   IO-APIC-fasteoi   HDA Intel
...
 27:         83          0        157          0   PCI-MSI-edge      eth0
NMI:          0          0          0          0   Non-maskable interrupts
LOC:       6646       8926       5769       5409   Local timer interrupts
...

Обратите внимание на прерывания по линии IRQ 27, когда прерывания от сетевого адаптера eth0 поочередно обрабатываются на разных процессорах. Интерес представляет и то, что некоторые линии IRQ запрограммированы в контроллере IO-APIC на срабатывание по фронту —IO-APIC-edge, а другие по уровню —IO-APIC-fasteoi.

Ещё одним источником (динамической) информации о произошедших (обработанных) прерываниях является файл /proc/stat:

$ cat /proc/stat
cpu  960 352 3120 226661 670 9 60 0 0
cpu0 265 103 367 56843 304 8 47 0 0
cpu1 246 102 824 56630 204 1 12 0 0
cpu2 224 51 863 56717 80 0 0 0 0
cpu3 224 94 1065 56470 80 0 0 0 0
intr 45959 49 8 0 0 2 0 0 0 1 0 0 0 144 0 0 0 30 0 0 12825 0 0 572 0 0 0 0 249 0 0 ...

Cтрока, начинающаяся с intr, содержит (начиная со 2-го числового значения) суммарное число обработанных прерываний по всем процессорам, для всех последовательных номеров линий IRQ (IRQ 0, IRQ 1, IRQ 2 ... — сравните с предыдущим показанным выводом, они сделаны почти одновременно).

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

# cat /proc/irq/27/smp_affinity
4
# cat /proc/interrupts | grep eth0
 27:         83          0       4102          0   PCI-MSI-edge      eth0

Как видно, уже после загрузки параметр smp_affinity (битовая маска разрешённых процессоров) для IRQ 27 был установлен системой в 4(100). Мы можем изменить это распределение на 2(10):

# echo 2 > /proc/irq/27/smp_affinity# cat /proc/irq/27/smp_affinity
2
# watch -n1 'cat /proc/interrupts | grep eth0'
 27:         83        117       4111          0   PCI-MSI-edge      eth0

Регистрация обработчика прерывания

Теперь, определившись со способом наблюдения за возникающими прерываниями, можно приступать к реализации обработчика прерываний. Первая операция, которую нам предстоит выполнить, — это регистрация обработчика (ISR, "верхней половины"). Функции и определения, реализующие интерфейс регистрации прерывания, объявлены в файле <linux/interrupt.h>.Регистрация обработчика и его освобождение по окончанию использования выполняются с помощью следующих методов:

typedef irqreturn_t (*irq_handler_t)( int, void* );
int request_irq( unsigned int irq, irq_handler_t handler, unsigned long flags,
                 const char *name, void *dev );
extern void free_irq( unsigned int irq, void *dev );

Где:

  • irq— номер линии запрашиваемого прерывания;
  • handler— указатель на функцию-обработчик типа irqreturn_t;
  • flags— битовая маска опций, связанная с управлением прерыванием;
  • name— символьная строка, используемая в /proc/interrupts для отображения владельца прерывания;
  • dev— указатель на уникальный идентификатор устройства на линии IRQ, для не разделяемых прерываний (например, шины ISA) может указываться NULL.

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

for( int i = 0; i < N; i++ ) request_irq( irq, handler, 0, const char *name, (void*)i );
...
for( int i = 0; i < N; i++ ) free_irq( irq, (void*)i );

Здесь указатель (void*)i хотя и не имеет смысла в качестве указателя, но играет вполне определённую необходимую роль. В некоторых ситуациях подобное решение является вполне подходящим. Но позже оказалось целесообразнее и удобнее использовать в качестве *dev— указатель на специфическую для устройства структуру, содержащую все характерные данные экземпляра устройства. Так как для каждого экземпляра создаётся своя копия структуры, то и указатели на них будут уникальными, что и требуется для параметра *dev. Этот же указатель будет передаваться в функцию-обработчик handler() в качестве второго параметра при каждом прерывании для передачи уникального идентификатора источника произошедшего прерывания. Сегодня увязывание обработчика прерывания со структурами данных самого устройства стало стандартной практикой.

Примечание: прототипы irq_handler_t и флаги установки обработчика могут существенно меняться в различных версиях ядра. Например, до версии ядра 2.6.19 все флаги, именующиеся сейчас как IRQF_*, именовались SA_*. Из-за этого ошибки могут возникнуть при компиляции даже относительно недавно разработанных модулей-драйверов.

Флаги установки обработчика (параметр flags):

  • группа флагов установки обработчика по уровню (level-triggered) или фронту (edge-triggered):
    #define IRQF_TRIGGER_NONE    0x00000000
    #define IRQF_TRIGGER_RISING  0x00000001
    #define IRQF_TRIGGER_FALLING 0x00000002
    #define IRQF_TRIGGER_HIGH    0x00000004
    #define IRQF_TRIGGER_LOW     0x00000008
    #define IRQF_TRIGGER_MASK ( IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW |
                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING )
    #define IRQF_TRIGGER_PROBE   0x00000010
  • другие флаги (не все, а только часто используемые):
    • IRQF_SHARED— разрешить разделение (совместное использование) линии IRQ с другими PCI устройствами;
    • IRQF_PROBE_SHARED— устанавливается вызывающим, когда он предполагает возможные проблемы с совместным использованием;
    • IRQF_TIMER— флаг, маркирующий это прерывание как таймерное;
    • IRQF_PERCPU— прерывание закреплённое монопольно за отдельным CPU;
    • IRQF_NOBALANCING— флаг, запрещающий вовлекать это прерывание в балансировку IRQ.

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

С регистрацией нового обработчика прерываний всё просто. Но существует одна маленькая тонкость (возможно, нигде не документированная), которая может вызвать затруднения в процессе создания обработчика прерывания. Параметр name вызова request_irq()— это просто указатель на константную строку имени, но эта строка никуда не копируется (как это обычно принято в API пространства пользователя), и указатель указывает на строку всё время, пока загружен модуль. Отсюда вытекают далеко идущие последствия. Вот такой вызов в функции инициализации модуля будет замечательно работать:

request_irq( irq, handler, 0, "my_interrupt", NULL );

Также работоспособным окажется и следующий код в функции инициализации модуля:

int init_module( void ) {
   char *dev = "my_interrupt";
   ...
   request_irq( irq, handler, 0, dev, NULL );
}

Но вот такой, не сильно отличающийся код выведет в /proc/interrupts бессмысленную последовательность символов.

int init_module( void ) {
   char dev[] = "my_interrupt";
   ...
   request_irq( irq, handler, 0, dev, NULL );
}

Здесь строка имени размещена и инициализирована в стеке, и после завершения функции инициализации она уже не существует, хотя сам модуль и существует. Перепишем этот фрагмент и всё опять заработает:

char dev[] = "my_interrupt";
...
int init_module( void ) {
   ...
   request_irq( irq, handler, 0, dev, NULL );
}

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

int init_module( void ) {
   int i;
   char *dev = "serial_xx";
   for( i = 0; i < num; i++ ) {
      sprintf( dev, "serial_%02d", i + 1 );
      request_irq( irq, handler, IRQF_SHARED, dev, (void*)( i + 1 ) );
   }

В этом случае мы получим несколько идентичных копий имён обработчиков (по последнему заполнению), поскольку все экземпляры обработчиков будут использовать одну копию строки имени, а это, очевидно, не то, что мы предполагали сделать:

$ cat /proc/interrupts
...
 22:       1652   IO-APIC-fasteoi   ohci_hcd:usb2, serial_04, serial_04, 
 serial_04, serial_04
...

И ещё серьёзное предупреждение относительно удаления обработчика в вызове free_irq(). В пользовательских приложениях мы можем себе позволить изрядную небрежность относительно завершающих действий программы: не выполнять close() для открытых дескрипторов файлов и не выполнять free() для динамически выделенных блоков памяти. Это становится возможным, поскольку при завершении программы выполняется так называемый эпилог, в котором система выполняет все явно не указанные действия. Если же в модуле при его завершении (выгрузке) явно не вызвать free_irq(), то почти со 100% вероятностью произойдёт следующее:

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

Заключение

В этой части мы рассмотрели подготовительные действия для включения обработчика прерываний в код модуля ядра. В следующей статье мы перейдём непосредственно к приёмам написания обработчиков прерываний.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=931973
ArticleTitle=Обслуживание периферии в коде модулей ядра: Часть 57. Регистрация обработчика прерываний
publish-date=05302013