Второе издание
Скачать 3.09 Mb.
|
118 Глава 6 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, "Средства синхронизации в ядре". Переменная r t c _ i r q data содержит информацию об устройстпе RTC и обнов- ляется с помощью функции m o d t i m e r (). О таймерах рассказывается в главе 10, "Таймеры и управление временем". Последняя часть кода, окруженная спин-блокировками, выполняет функцию об- ратного вызова (callback), которая может быть установлена извне. Драйвер RTC по- зволяет устанавливать функцию обратного вызова, которая может быть зарегистри- рована пользователем и будет исполняться при каждом прерывании, приходящем от устройства RTC. В конце функция обработки прерывания возвращает значение IRQ_HANDLED, чтобы указать, что прерывание от данного устройства обработано правильно. Так как этот обработчик прерывания не поддерживает совместное использование линий прерывания и не существует механизма, посредством которого обработчик прерыва- ний RTC может обнаружить вложенные запросы на прерывание, то этот обработчик всегда возвращает значение IRQ_HANDLED. Контекст прерывания При выполнении обработчика прерывания или обработчика нижней половины, ядро находится в контексте прерывания. Вспомним, что контекст процесса — это ре- жим, в котором работает ядро, выполняя работу от имени процесса, например вы- полнение системного пызова или потока пространства ядра. В контексте процесса макрос current возвращает указатель на соответствующее задание. Более того, по- скольку в контексте процесса процесс связан с ядром, то контекст процесса может переходить в состояние ожидания или использовать функции планировщика каким- либо другим способом.. Прерывания и обработка прерываний 119 В противоположность только что рассмотренному, контекст прерывания не свя- зан ни с одним процессом. Макрос c u r r e n t в контексте прерывания является не- законным (хотя он и указывает на процесс, выполнение которого было прервано). Так как нет процесса, то контекст прерывания не может переходить в состояние ожидания (sleep) — действительно, каким образом можно перепланировать его вы- полнение? Поэтому некоторые функции ядра не могут быть вызваны из контекста прерывания. Если функция может переводить процесс в состояние ожидания, то ее нельзя вызывать в обработчике прерывания, что ограничивает набор функций, ко- торые можно использовать в обработчиках прерываний. Контекст прерывания является критичным ко времени исполнения, так как об- работчик прерывания прерывает выполнение некоторого программного кода. Код же самого обработчика должен быть простой и быстрый. Использование циклов проверки состояния чего-либо (busy loop) крайне нежелательно. Это очень важный момент. Всегда следует помнить, что обработчик прерывания прерывает работу не- которого кода (возможно, даже обработчика другой линии запроса на прерывание!). В связи со своей асинхронной природой обработчики прерыпаний должны быть как можно более быстрыми и простыми. Максимально возможную часть работы необхо- димо изъять из обработчика прерывания и переложить на обработчик нижней по- ловины, который выполняется в более подходящее время. Возможность установить стек контекста прерывания является конфигурируемой. Исторически, обработчик прерывания не имеет своего стека. Вместо этого он дол- жен был использовать стек ядра прерванного процесса 1 . Стек ядра имеет размер две страницы памяти, что обычно соответствует 8 Кбайт для 32-разрядных аппа- ратных платформ и 16 Кбайт для 64-разрядных платформ. Так как в таком случае обработчики прерываний совместно используют стек, то они должны быть очень экономными в отношении того, что они в этом стеке выделяют. Конечно, стек ядра изначально является ограниченным, поэтому любой код ядра должен принимать это во внимание. В ранних версиях ядер серии 2.6 была введена возможность ограничить размер стека ядра от двух до одной страницы памяти, что равно 4 Кбайт на 32-разрядных аппаратных платформах. Это уменьшает затраты памяти, потому что раньше каждый процесс требовал две страницы памяти ядра, которая не может быть вытеснена на диск. Чтобы иметь возможность работать со стеком уменьшенного размера, каждому обработчику прерывания выделяется свой стек, отдельный для каждого процессора. Этот стек называется стеком прерывания. Хотя общий размер стека прерывания и ра- вен половине от первоначально размера совместно используемого стека, тем не ме- нее в результате выходит, что суммарный размер стека получается большим, потому что на каждый стек прерывания выделяется целая страница памяти. Обработчик прерывания не должен зависеть от того, какие настройки стека ис- пользуются и чему равен размер стека ядра. Всегда необходимо использовать мини- мально возможное количество памяти в стеке. 1 Какой-нибудь процесс выполняется всегда. Если не выполняется никакой процесс, то выполняется холостая задача (idle task). 120 Глава 6 Реализация системы обработки прерываний Возможно, не вызовет удивления, что реализация системы обработки прерыва- ний в операционной системе Linux очень сильно зависит от аппаратной платфор- мы. Она зависит от типа процессора, типа контроллера прерываний, особенностей аппаратной платформы и устройства самой вычислительной машины. На рис. 6.1 показана диаграмма пути, который проходит запрос на прерывание в аппаратном обеспечении и в ядре. Аппаратура Функция handle_IRQ_event() Процессор прерывает работу ядра Да Есть ли обработчик для данной линии прерывания? Выполнить все обработчики данной линии прерывания Функция do_IRQ() Нет \ Функция ret_from_int() Возвратится к выполнению прерванного кода ядра Процессор Рис. 6.1. Прохождение запроса на прерывание в аппаратном обеспечении и в ядре Устройство инициирует прерывание путем отправки электрического сигнала контроллеру прерывания по аппаратной шине. Если соответствующая линия запро- са на прерывание не запрещена (линия может быть в данный момент времени за- маскирована), то контроллер прерываний отправляет прерывание процессору. Для большинства аппаратных платформ это осуществляется путем подачи сигнала на специальный вывод процессора. Если прерывания не запрещены в процессоре (мо- жет случиться, что они запрещены), то процессор немедленно прекращает ту рабо- ту, которую он выполнял, запрещает систему прерываний, осуществляет переход на специальный предопределенный адрес памяти и начинает выполнять программный код, который находится по этому адресу. Этот предопределенный адрес памяти уста- навливается ядром и является точкой входа в обработчики прерываний. Прохождение прерывания в ядре начинается из жестко определенной точки входа, так же как и в случае системных вызовов. Для каждой линии прерывания су- ществует своя уникальная точка, куда переходит процессор. Именно этим способом ядро получает информацию о номере IRQ приходящего прерывания. В точке вхо- да сначала в стеке ядра сохраняется значение номера прерывания и значения всех регистров процессора (которые соответствуют прерванному заданию). После этого ядро вызывает функцию do_IRQ (). Далее, начиная с этого момента, почти весь код обработки прерываний написан на языке программирования С, хотя несмотря на это код все же остается зависимым от аппаратной платформы. Прерывания и обработка прерываний 121 Генерация запроса на прерывание Контроллер прерываний Функция do_IRQ() определена следующим образом. unsigned int do_IRQ(struct pt_regs regs) Так как соглашение о вызовах функций в языке С предусматривает сохранение аргументов функций в вершине стека, то структура p t r e g s содержит первоначаль- ные значения всех регистров процессора, которые были сохранены ассемблерной подпрограммой в точке входа. Так как значение номера прерывания также сохраня- ется, то функция do_IRQ () может это значение восстановить. Для аппаратной плат- формы х86 код будет следующим. int irq = regs.orig_eax & 0xff; После вычисления значения номера линии прерывания, функция do_IRQ() от- правляет уведомление о получении прерывания и запрещает доставку прерываний с данной линии. Для обычных машин платформы PC, эти действия выполняются с помощью функции mask_and_ack_8295A (), которую вызывает функция do_IRQ (). Далее функция do_IRQ () выполняет проверку, что для данной линии прерывания зарегистрирован правильный обработчик прерывания, что этот обработчик разре- шен и что он не выполняется в данный момент. Если все эти условия выполнены, то вызывается функция handle_IRQ_event (), которая выполняет установленные для данной линии обработчики прерывания. Для аппаратной платформы х86 функция handle_IRQ_event () имеет следующий вид. int handle_IRQ_event (unsigned int irq, struct pt_regs *regs, struct irqaction *action) { int status = 1; if (!(action->flags & SA_INTERRUPT)) local_irq_enable (); do { status != action->flags; action->chandler (irq, action->dev_id, regs); action = action->next; } while (action); if (status & SA_SAMPLE_RANDOM) add_interrupt_randomness (irq); local_irq_disable(); return status; } Так как процессор запретил прерывания, они снова разрешаются, если не указан флаг SA_INTERRUPT при регистрации обработчика. Вспомним, что флаг SA_INTERRUPT указывает, что обработчик должен выполняться при всех запрещен- ных прерываниях. Далее в цикле вызываются все потенциальные обработчики пре- рываний. Если эта линия не является совместно используемой, то цикл заканчивает- ся после первой итерации. В противном случае вызываются все обработчики. После этого вызывается функция add_interrupt_randomness (), если при регистрации указан флаг SA_SAMPLE_RANDOM. Данная функция использует временные характери- стики прерывания, чтобы сгенерировать значение энтропии для генератора случай- ных чисел. В приложении Б, "Генератор случайных чисел ядра", приведена более подробная информация о генераторе случайных чисел ядра. 122 Глава 6 В конце прерывания снова запрещаются (для функции do_IRQ () требуется, что- бы прерывания были запрещены). Функция do_IRQ () производит очистку стека и возврат к первоначальной точке входа, откуда осуществляется переход к функции r e t _ f r o m _ i n t r ( ) . Функция r e t _ f r o m _ i n t r (), так же как и код входа, написана на языке ассембле- ра. Эта функция проверяет, есть ли ожидающий запрос на перепланирование вы- полнения процессов (следует вспомнить главу 4, "Планирование выполнения процес- сов", и флаг need_resched). Если есть запрос на перепланирование и ядро должно передать управление в пространство пользователя (т.е. прерывание прервало рабо- ту пользовательского процесса), то вызывается функция s c h e d u l e () . Если возврат производится в пространство ядра (т.е. прерывание прервало работу кода ядра), то функция s c h e d u l e () вызывается, только если значение счетчика p r e e m p t _ c o u n t равно нулю (в противном случае небезопасно производить вытеснение кода ядра), После возврата из функции s c h e d u l e () или если нет никакой ожидающей работы, восстанавливаются первоначальные значения регистров процессора и ядро продол- жает работу там, где оно было прервано. Для платформы х86, подпрограммы, написанные на языке ассемблера, находятся в файле a r c h / i 3 8 6 / k e r n e l / e n t r y . S , а соответствующие функции на языке С — в файле a r c h / i 3 8 6 / k e r n e l / i r q . с . Для других поддерживаемых аппаратных плат- форм имеются аналогичные файлы. Интерфейс /proc/interrupts Файловая система procfs— это виртуальная файловая система, которая существует только в памяти ядра и обычно монтируется на каталог /ргос. Чтение или запись файлов на файловой системе procfs приводит к вызовам функций ядра, которые имитируют чтение или запись обычных файлов. Важный пример— это файл /ргос/ i n t e r r u p t s , который содержит статистику, связанную с прерываниями в системе, Ниже приведен пример вывода из этого файла на однопроцессорном персональном компьютере. CPU0 0: 3602371 XT-PIC timer 1: 3048 XT-PIC i8042 2: 0 XT-PIC cascade 4: 2689466 XT-PIC uhci-hed, ethO 5: 0 XT-PIC EMU10K1 12: 85077 XT-PIC uhei-hcd 15: 24571 XT-PIC aic7xxx NMI: 0 LOC: 3602236 ERR: 0 Первая колонка содержит названия линий прерывания. В показанной системе присутствуют линии прерываний с номерами 0-2, 4, 5, 12 и 15. Линии, для которых не инсталлирован обработчик, не показываются. Вторая колонка — это количество запросов на прерывания с данным номером. В действительности такая колонка яв- ляется отдельной для каждого процессора, но в данной машине только один про- цессор. Прерывания и обработка прерываний 123 Как легко видеть, обработчик прерываний таймера получил 3 . 6 0 2 . 3 7 1 2 за- прос на прерывание, в то время как обработчик прерываний звукового адаптера (EMU10K1) не получил ни одного прерывания (это говорит о том, что он не исполь- зовался с того момента, как машина была загружена). Третья колонка— это кон- троллер прерываний, который обслуживает данное прерывание. Значение XT-PIC соответствует программируемому контроллеру прерываний PC (PC programmable interrupt controller). Для систем с устройством I/О APIC для большинства преры- ваний в качестве контроллера прерываний будет указано значение IO-APIC-level или IO-APIC-edge. И наконец, последняя колонка— это устройство, которое связа- но с прерыванием. Имя устройства указывается в параметре dev_name при вызове функции request_irq () , как обсуждалось ранее. Если прерывание используется со- вместно, как в случае прерывания номер 4 в этом примере, то перечисляются все устройства, зарегистрированные на данной линии прерывания. Для любопытствующих, код, связанный с файловой системой procfs, находится в файле fs/proc. Функция, которая обеспечивает работу интерфейса /proc/interrupts, называется show_interrupts () и является зависимой от аппаратной платформы. Управление прерываниями В ядре Linux реализовано семейство интерфейсов для управления состояния- ми прерываний в машине. Эти интерфейсы позволяют запрещать прерывания для текущего процессора или маскировать линию прерывания для всей машины. Эти функции очень сильно зависят от аппаратной платформы и находятся в файлах Причины, по которым необходимо управлять системой обработки прерываний, в основном, сводятся к необходимости обеспечения синхронизации. Путем запре- щения прерываний можно гарантировать, что обработчик прерывания не вытеснит текущий исполняемый код. Более того, запрещение прерываний также запрещает и вытеснение кода ядра. Однако ни запрещение доставки прерываний, ни запрещение преемптивности ядра не дают никакой защиты от конкурентного обращения других процессоров. Так как операционная система Linux поддерживает многопроцессор- ные системы, в большинстве случаев код ядра должен захватить некоторую блоки- ровку, чтобы предотвратить доступ другого процессора к совместно используемым данным. Эти блокировки обычно захватываются в комбинации с запрещением пре- рываний на текущем процессоре. Блокировка предоставляет защиту от доступа дру- гого процессора, а запрещение прерываний обеспечивает защиту от конкурентного доступа из возможного обработчика прерывания. В главах 8 и 9 обсуждаются различ- ные аспекты проблем синхронизации и решения этих проблем. Тем не менее понимание интерфейсов ядра для управления прерываниями явля- ется важным. 2 После прочтения главы 10, "Таймеры и управление временем", можно ли сказать, сколько времени (в единицах HZ) машина работала без перегрузки исходя из числа прерываний таймера? 124 Глава 6 Запрещение и разрешение прерываний Для локального запрещения прерываний на текущем процессоре (и только на текущем процессоре) и последующего разрешения можно использовать следующий код. local_irq_disable(); /* прерывания запрещены .. */ local_irq_enable(); Эти функции обычно реализуются в виде одной инструкции на языке ассембле- ра (что, конечно, зависит от аппаратной платформы). Для платформы х86 функция l o c a l _ i r q _ d i s a b l e () — это просто машинная инструкция c l i , а функция 1оса1_ i r q _ e n a b l e () — просто инструкция s t i . Для хакеров, не знакомых с платформой х86, s t i и c l i — это ассемблерные вызовы, которые соответственно позволяют уста- новить (set) или очистить (clear) флаг разрешения прерываний (allow interrupt flag). Другими словами, они разрешают или запрещают доставку прерываний на вызвав- шем их процессоре. Функция l o c a l _ i r q _ d i s a b l e () является опасной в случае, когда перед ее вызо- вом прерывания уже были запрещены. При этом соответствующий ей вызов функции l o c a l _ i r q _ e n a b l e () разрешит прерывания независимо от того, были они запре- щены первоначально (до вызова l o c a l _ i r q _ d i s a b l e ()) или нет. Для того чтобы избежать такой ситуации, необходим механизм, который позволяет восстанавливать состояние системы обработки прерывании в первоначальное значение. Это требова- ние имеет общий характер, потому что некоторый участок кода ядра в одном случае может выполняться при разрешенных прерываниях, а в другом случае— при запре- щенных, в зависимости от последовательности вызовов функций. Например, пусть показанный фрагмент кода является частью функции. Эта функция вызывается дву- мя другими функциями, и в первом случае перед вызовом прерывания запрещаются, а во втором — нет. Так как при увеличении объема кода ядра становится сложно от- слеживать все возможные варианты вызова функции, значительно безопаснее сохра- нять состояние системы прерываний перед тем, как запрещать прерывания. Вместо разрешения прерываний просто восстанавливается первоначальное состояние си- стемы обработки прерываний следующим образом. unsigned long flags; local_irq_save(flags); /* прерывания запрещены . . */ local_irq_restore (flags) ; /*состояние системы прерываний восстановлено в первоначальное значение ..*/ Нужно заметить, что эти функции являются макросами, поэтому передача пара- метра f l a g s выглядит как передача по значению. Этот параметр содержит завися- щие от аппаратной платформы данные, которые в свою очередь содержат состояние системы прерываний. Так как, по крайней мере для одной аппаратной платформы (SPARC), в этой неременной хранится информация о стеке, то параметр flags нель- зя передавать в другие функции (другими словами, он должен оставаться в одном и том же стековом фрейме). По этой причине вызовы функций сохранения и восста- новления должны выполняться в теле одной функции. Прерывания и обработка прерываний 125 |