Выбрать главу

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. Второй параметр— это обработчик прерывания, rtc_interrupt, при выполнении которого запрещены все прерывания в связи с указанием флага SA_INTERRUPT. Из четвертого параметра можно заключить, что драйвер будет иметь имя "rtc". Так как наше устройство не может использовать линию прерывания совместно с другими устройствами и обработчик прерывания не используется для каких-либо других целей, в качестве параметра dev_id передается значение 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 &= ~0xff;

 rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);

 if (rtc_status & RTC_TIMER_ON)

  mod_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_data не будет конкурентных обращений другими процессами на SMP-машине, а вторая — защищает в аналогичной ситуации параметры структуры rtc_callback. Блокировки обсуждаются в главе 9, "Средства синхронизации в ядре".

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

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

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

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

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

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

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

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

вернуться

31

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