Роберт Лав - Разработка ядра Linux
if (request_irq(RTC_IRQ, rtc_interrupt, SA_INTERRUPT, "rtc", NULL) {
printk(KERN_ERR "rtc: cannot register IRQ %dn" , 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-разрядных платформ. Так как в таком случае обработчики прерываний совместно используют стек, то они должны быть очень экономными в отношении того, что они в этом стеке выделяют. Конечно, стек ядра изначально является ограниченным, поэтому любой код ядра должен принимать это во внимание.
В ранних версиях ядер серии 2.6 была введена возможность ограничить размер стека ядра от двух до одной страницы памяти, что равно 4 Кбайт на 32-разрядных аппаратных платформах. Это уменьшает затраты памяти, потому что раньше каждый процесс требовал две страницы памяти ядра, которая не может быть вытеснена на диск. Чтобы иметь возможность работать со стеком уменьшенного размера, каждому обработчику прерывания выделяется свой стек, отдельный для каждого процессора. Этот стек называется стеком прерывания. Хотя общий размер стека прерывания и равен половине от первоначально размера совместно используемого стека, тем не менее в результате выходит, что суммарный размер стека получается большим, потому что на каждый стек прерывания выделяется целая страница памяти.
Обработчик прерывания не должен зависеть от того, какие настройки стека используются и чему равен размер стека ядра. Всегда необходимо использовать минимально возможное количество памяти в стеке.
Реализация системы обработки прерываний
Возможно, не вызовет удивления, что реализация системы обработки прерываний в операционной системе Linux очень сильно зависит от аппаратной платформы. Она зависит от типа процессора, типа контроллера прерываний, особенностей аппаратной платформы и устройства самой вычислительной машины.
На рис. 6.1 показана диаграмма пути, который проходит запрос на прерывание в аппаратном обеспечении и в ядре.
Рис. 6.1. Прохождение запроса на прерывание в аппаратном обеспечении и в ядре
Устройство инициирует прерывание путем отправки электрического сигнала контроллеру прерывания по аппаратной шине. Если соответствующая линия запроса на прерывание не запрещена (линия может быть в данный момент времени замаскирована), то контроллер прерываний отправляет прерывание процессору. Для большинства аппаратных платформ это осуществляется путем подачи сигнала на специальный вывод процессора. Если прерывания не запрещены в процессоре (может случиться, что они запрещены), то процессор немедленно прекращает ту работу, которую он выполнял, запрещает систему прерываний, осуществляет переход на специальный предопределенный адрес памяти и начинает выполнять программный код, который находится по этому адресу. Этот предопределенный адрес памяти устанавливается ядром и является точкой входа в обработчики прерываний.