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).