Главная » Ядро Linux » Написание обработчика прерывания

0

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

static irqreturn_t intr_handler (int irq, void *dev_id, struct pt_regs *regs)

Заметим, что оно должно  соответствовать аргументу, который передается в функцию request_ir q () . Первый параметр, irq , — это численное значение номера  прерывания, которое  обслуживается обработчиком. Сейчас  этот параметр практически не используется, кроме разве что при печати  сообщений. Для версий  ядра, меньших

2.0, не было  параметра dev_id, поэтому  параметр ir q использовался, чтобы  различать устройства, которые обслуживаются одним  драйвером, и поэтому  используют один  и тот же обработчик прерываний (как  пример можно  рассмотреть компьютер с несколькими контроллерами жесткого  диска  одного  типа).

Второй  параметр, dev_id, — это указатель, равный значению, которое  было  передано  в функцию request_ir q ()  при  регистрации обработчика прерывания. Если значение этого  параметра является уникальным, что необходимо для поддержки совместно используемых прерываний, то его можно  использовать как  идентификатор для того, чтобы  отличать  друг от друга различные устройства, которые потенциально  могут использовать один  обработчик. В связи  с тем, что структура  (контекст) устройства (device  structure) является как уникальной, так  и, возможно, полезной при  использовании в обработчике, обычно  в качестве  параметра dev_id  передают указатель  на эту структуру.

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

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

Возвращаемое значение обработчиков прерываний имеет  специальный тип irqreturn_t . Обработчик может  возвращать два специальных значения: IRQ_NONE или  IRQ_HANDLED. Первое значение возвращается, если обработчик прерывания обнаружил, что устройство, которое  он  обслуживает, не является источником прерывания. Второе  значение возвращается, если  обработчик вызван  правильно и устройство, которое  он обслуживает, является источником прерывания. Кроме этого, может  быть использован макрос  IRQ_RETVAL (x).  Если  значение параметра х не равно  нулю, то макрос  возвращает значение IRQ_HANDLED,  иначе  возвращается значение, равное  IRQ_NONE. Эти специальные значения позволяют дать ядру информацию  о том, генерирует ли устройство паразитные (необрабатываемые) прерывания. Если  все обработчики прерывания,  которые обслуживают данную  линию, возвращают значение IRQ_NONE, то ядро  может  обнаружить проблему. Заметим, что этот странный тип  возвращаемого значения,  irqreturn_t , просто  соответствует типу int . Подстановка типа используется для того, чтобы  обеспечить совместимость с более ранними версиями ядра, у которых  не было подобной функции. До серии  ядер

2.6 обработчик прерывания имел  возвращаемое значение типа void.  В коде  новых драйверов можно  применить переопределение типа  typede f irqreturn_ t  в тип void и драйверы могут работать  с ядрами  серии  2.4 без дальнейшей модификации.

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

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

Реентерабельность и обработчики прерываний

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

Совместно используемые обработчики

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

• Флат  SA_SHIRQ должен  быть установлен в параметре flag s при  вызове функции  request_ir q () .

• Аргумент  dev_id  этой  же функции должен  быть уникальным для  каждого  зарегистрированного обработчика. Достаточным является передача  указателя на структуру, которая описывает устройство. Обычно так и поступают,  поскольку структура  контекста устройства является уникальной для  каждого  устройства, и, кроме  того, данные  этой  структуры  потенциально могут быть полезными при  выполнении обработчика. Для  совместно используемого  обработчика нельзя  присваивать параметру  dev_id значение NULL!

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

Все драйверы, которые рассчитаны на совместно используемую линию  прерывания, должны  удовлетворять указанным выше  требованиям. Если  хотя бы одно  из устройств, которые совместно используют линию  прерывания, не делает это корректно, то все  остальные устройства также  не  смогут  совместно использовать линию. Если  функция request_ir q ()  вызывается с указанием флага  SH_SHIRQ, то этот вызов будет успешным только  в том  случае, если для данной линии прерывания еще нет зарегистрированных обработчиков или  все обработчики для данной линии зарегистрированы с указанием флага  SH_SHIRQ. Заметим, что для ядер серии  2.6, в отличие  от более  ранних  серий, можно  "смешивать" совместно используемые обработчики с различными значениями флага SA_INTERRUPT.

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

Настоящий обработчик прерывания

Давайте  рассмотрим настоящий обработчик прерывания, который используется в драйвере  устройства RTC  (real-time clock, часы реального времени), находящегося в файле  drivers/char/rtc.с . Устройство RTC  есть во многих  вычислительных системах, включая персональные компьютеры (PC).  Это отдельное от системного таймера устройство, которое  используется для установки системных часов, для подачи сигналов таймера  (alarm)  или  для  реализации генераторов периодических сигналов (periodic  timer).  Установка системных часов обычно  производится путем записи значений  в специальный регистр  или диапазон адресов  (номеров портов) ввода-вывода (I/O range).  Подача  сигналов таймера  или генератор периодических сигналов обычно реализуются через  прерывания. Прерывание эквивалентно некоторому сигналу таймера:  оно  генерируется, когда  истекает период  времени сигнального таймера. При  загрузке  драйвера устройства RTC  вызывается функция r t c i n i t ()  для ини-

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

if (request_irq(RTC_IRQ, rtc_interrupt, SA_INTERRUPT, "rtc", NULL) { printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq); return -EIO;

}

Из   данного   пример а  видно,   что   номе р  лини и  прерывани я  —  это   константа RTC_IRQ,   значение   которой   определяется   отдельно   для   каждой   аппаратной  платформы   с  помощью   препроцессора.  Например,  для  персональных   компьютеров   это значение всегда  соответствует IRQ    8.  Второй параметр— это  обработчик прерывания,   rt c    interrupt ,  при  выполнении  которого  запрещены   все  прерывания  в  связи с  указанием   флага   SA_INTERRUPT.  Из  четвертого   параметра   можно   заключить,   что драйвер будет  иметь  имя  "rtc" . Так  как  наше  устройство не  может  использовать линию   прерывания  совместно   с  другими  устройствами   и  обработчик   прерывания  не используется для  каких-либо других  целей, в качестве параметра dev_i d  передается значение  NULL.

И  наконец,  собственно   сам  обработчик  прерывания.

/*

* Очень маленький обработчик прерывания. Он выполняется с

* установленным флагом SA_INTERRUPT, однако существует

* возможность конфликта с выполнением функции set_rtc_mmss ()

* (обработчик прерывания rtc и обработчик прерывания системного

* таймера могут выполняться одновременно на двух разных

* процессорах). Следовательно, необходимо сериализировать доступ

* к микросхеме с помощью спин-блокировки rtc_lock, что должно

* быть сделано для всех аппаратных платформ в коде работы с

* таймером. (Тело функции set_rtc_mmss() ищите в файлах

* ./arch/XXXX/kernel/time.c)

*/

static irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)

/*

* Прерывание может оказаться прерыванием таймера, прерыванием

* завершения обновления или периодическим прерыванием.

* Состояние (причина) прерывания хранится в самом

* младшем байте, а общее количество прерывание — в оставшейся

* части переменной rtc_irq_data

*/

spin_lock (&rtc_lock);

rtc_irq_data += 0x100;

rtc_irq_data &= ~Oxff;

rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & OxFO);

if (rtc_status & RTC_TIMER_ON)

rnod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);

spin_unlock(&rtc_lock);

/*

* Теперь выполним остальные действия

/*

spin_lock(&rtc_task_lock);

if (rtc_callback)

rtc_callback->func(rtc_callback->private_data); spin_unlock(&rtc_task_lock); wake_up_interruptible(&rtc_wait);

kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);

return IRQ_HANDLED;

}

Эта  функция вызывается всякий раз, когда  система  получает  прерывание от устройства RTC.  Прежде  всего, следует обратить  внимание на вызовы  функций работы  со спин-блокировками: первая  группа  вызовов гарантирует, что к переменной rtc_irq_dat a не будет конкурентных обращений другими  процессами на SMP-машине, а вторая — защищает в аналогичной ситуации параметры структуры  rtc_callback . Блокировки обсуждаются в главе 9, "Средства  синхронизации в ядре".

Переменная rtc_ir q  dat a содержит информацию об устройстпе RTC  и обновляется  с помощью функции modtime r () . О таймерах  рассказывается в главе 10, "Таймеры и управление временем".

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

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

Контекст прерывания

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

В противоположность только  что рассмотренному, контекст прерывания не связан ни с одним  процессом. Макрос curren t в контексте прерывания является незаконным (хотя  он и указывает на процесс, выполнение которого было  прервано). Так  как  нет  процесса, то контекст прерывания не может  переходить в состояние ожидания (sleep) — действительно, каким образом можно  перепланировать его выполнение? Поэтому некоторые функции ядра  не могут быть  вызваны из контекста прерывания. Если  функция может переводить процесс в состояние ожидания, то ее нельзя  вызывать в обработчике прерывания, что ограничивает набор  функций, которые  можно  использовать в обработчиках прерываний.

Контекст прерывания является критичным ко времени исполнения, так как обработчик прерывания прерывает выполнение некоторого программного кода.  Код же самого  обработчика должен  быть  простой и быстрый. Использование циклов проверки состояния чего-либо (busy loop)  крайне нежелательно. Это очень  важный момент. Всегда  следует помнить, что обработчик прерывания прерывает работу некоторого кода  (возможно, даже обработчика другой линии запроса на прерывание!). В связи  со своей  асинхронной природой обработчики прерыпаний должны  быть как можно  более быстрыми и простыми. Максимально возможную часть работы  необходимо  изъять  из обработчика прерывания и переложить на обработчик нижней половины, который выполняется в более подходящее время.

Возможность установить стек контекста прерывания является конфигурируемой. Исторически, обработчик прерывания не имеет  своего  стека.  Вместо  этого  он должен  был  использовать стек  ядра  прерванного процесса1. Стек  ядра  имеет  размер две страницы памяти, что  обычно  соответствует 8 Кбайт  для  32-разрядных аппаратных  платформ и  16 Кбайт  для  64-разрядных платформ. Так  как  в таком  случае обработчики прерываний совместно используют стек, то они  должны  быть  очень экономными в отношении того, что они в этом стеке выделяют. Конечно, стек ядра изначально является ограниченным, поэтому  любой  код ядра  должен  принимать это во внимание.

В ранних  версиях  ядер  серии  2.6 была  введена  возможность ограничить размер стека  ядра  от двух до одной  страницы памяти, что равно  4 Кбайт  на 32-разрядных аппаратных платформах. Это уменьшает затраты  памяти, потому что раньше  каждый процесс требовал  две страницы памяти ядра, которая не может  быть вытеснена на диск.  Чтобы  иметь  возможность работать  со стеком уменьшенного размера, каждому обработчику прерывания выделяется свой  стек, отдельный для каждого  процессора. Этот стек называется стеком прерывания. Хотя общий размер стека прерывания и равен половине от первоначально размера  совместно используемого стека, тем не менее  в результате  выходит, что суммарный размер  стека  получается большим, потому что на каждый  стек прерывания выделяется целая  страница памяти.

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

1Какой-нибудь процесс выполняется  всегда. Если не выполняется  никакой  процесс,  то выполняется холостая  задача (idle task).

Источник: Лав,  Роберт. Разработка ядра  Linux, 2-е  издание. : Пер.  с англ.  — М.  : ООО  «И.Д.  Вильяме» 2006. — 448 с. : ил. — Парал. тит. англ.

По теме:

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