netlib.narod.ru | < Назад | Оглавление | Далее > |
Действительные обработчики прерываний для порта х86 тривиальны; на самом нижнем уровне они построены посредством многократного использования макроса BUILD_IRQ (строка 1886) для построения последовательностей небольших ассемблерных функций. Сам макрос BUILD_IRQ используется макросом BI (строка 866), который в свою очередь используется макросом BUILD_16_IRQS (строка 869), который в строках с 878 по 895 применяется для создания подпрограмм ассемблера. Эта последовательность вызовов макросов предназначена всего лишь для уменьшения объема и избыточности действительного кода, который должен быть написан — вместо 16 применений макроса BUILD_16_IRQS могло бы потребоваться 256 обращений к макросу BUILD_IRQ.
Подпрограммы ассемблера выглядят подобно следующему:
IRQ0x00_interrupt: pushl 0x00-256 jmp common_interrupt
Т.е. каждая из них просто выталкивает свой номер IRQ (минус 256, по причинам, которые описаны в стоке 1879) в стек, а затем выполняет переход к общей подпрограмме прерывания.
Общая подпрограмма прерывания называется common_interrupt и также является короткой. Она образована макросом BUILD_COMMON_IRQ (строка 1871) и просто вызывает функцию do_IRQ, обеспечив, чтобы та выполнила возврат к функции ret_from_intr (строка 233), являющейся частью подпрограммы system_call, которая была исследована в главе 5. Функция do_IRQ, которая вскоре будет рассмотрена, принимает на себя ответственность проследить за тем, чтобы прерывания были обслужены.
Прежде чем исследовать весь код, не помещает на верхнем уровне рассмотреть, как отдельные части взаимодействуют во время обслуживания отдельного прерывания:
1375: Обновляет некоторые статистические данные ядра и вызывает функцию обработчика, связанную с этим IRQ. Для IRQ с низкими номерами в только что выпущенном компьютере этим обработчиком будет handle_IRQ_event.
1383: Если нижние половины активны, теперь они обрабатываются функцией do_bottom_half (строка 29126).
1292: Функция handle_IRQ_event выполняет основную часть работы для функции do_8259_IRQ (строка 821). Она вызывается также из некоторых других функций, которые не освещены в этой книге.
1302: Вопреки документации (строка 12094), флаг SA_INTERRUPT не является фиктивным. Если этот флаг не установлен, во время выполнения следующего за ним кода прерывания разрешены. Это историческое наследие различения ядром быстрых и медленных прерываний, как было описано ранее. (Код, обрабатывающий оба вида прерываний, обычно имел гораздо больше различий, но результат оставался почти таким же — код реализовался гораздо элегантнее.) Похоже, что главным образом этот флаг предназначен для использования с очень медленными устройствами — например, с дисководами гибких дисков.
1305: Выполняет итерации по очереди действий для данного IRQ (начало очереди было передано вызывающей функцией), вызывая функцию обработчика для каждого из них.
1310: Здесь случай прерывания используется для добавления некоторой случайной информации для использования устройствами /dev/random и /dev/urandom — предположительно, большинство прерываний происходит случайно.
1312: Запрещает прерывания (вызывающая функция снова разрешит их, когда будет готова).
29126: Функция do_bottom_half вызывается из трех точек в коде Linux: из строк 26707, 243 и 1384, (Две из них, как можно заметить, находятся в архитектурно-зависимых файлах; эта функция вызывается также в соответствующих точках архитектурно-зависимых файлов для портов систем, отличных от х86,) Следовательно нижние половины обслуживаются при трех условиях:
29130: Для нижних половин желательно, чтобы одновременно могла выполняться только одна из них. Это свойство поддерживается здесь, в одном из мест, где блокировка имеет значение для кода однопроцессорных систем. Во-первых, вызывается функция softing_trylock (версия для однопроцессорных систем приведена в строке 12559, а SMP-версия рассмотрена в главе 10), которая устанавливает значение local_bh_count[cpu] равным 1 и выполняет возврат, только если оно первоначально было равным 0. Благодаря строке 17479, для однопроцессорной машины значение переменной cpu всегда равно 0, и читатели должны были обратить внимание, что сама функция softing_trylock не может быть прервана, поскольку к этому моменту прерывания отключены. Функция softing_trylock и ее аналог softing_endlock (строка 12561) служат только одной цели: помочь убедиться в том, что нижние половины не прерываются другими нижними половинами (хотя они могут быть прерваны верхними половинами).
29131: Если эта блокировка была получена, функция делает еще одну попытку: harding_trylock в строке 10736. Эта функция всего лишь сообщает, находится ли выполнение внутри пары harding_enter/harding_exit (строки 10739 и 10740). Для однопроцессорной машины определения этих функций эквивалентны irq_enter и irq_exit (строки 1810 и 1811), которые используются в функции handle_IRQ_event, а также в ряде других мест. Эти макросы работают вместе, чтобы обеспечить надлежащую вложенность пар __cli и __sti — поскольку процессор не вкладывает их друг в друга, необходимо убедиться, что мы не выполняем __sti для __cli пользователя, который не ожидает этого.
29132: Никто другой не выполняет нижние половины, и функция do_bottom_half имеет право включить аппаратные прерывания. Поэтому она включает аппаратные прерывания, выполняет нижние половины, а затем снова отключает прерывания.
29135: Освобождает полученные функцией блокировки, после чего выполняет возврат.
29110: Теперь ядро готово выполнить ждущие обработки нижние половины.
29115: Сохраняет текущий набор активных — т.е. помеченных — нижних половин в локальной переменной active и с помощью макроса clear_active_bhs (строка 12481) очищает установленные разряды в глобальной переменной bh_active. Очистка этих разрядов в переменной bh_active одновременно удаляет метки со всех нижних половин.
Теперь читатели могут убедиться, что иногда нижние половины объединяются в пакеты, как и утверждалось ранее без доказательств. К этому моменту прерывания включены, поэтому если другое прерывание запускается и отмечает уже помеченную нижнюю половину раньше, чем функция run_bottom_halves успеет скопировать bh_active в active, верхняя половина будет выполнена дважды, а нижняя — только один раз. И, поскольку само прерывание также может быть прервано, верхняя половина могла бы быть выполнена трижды при однократном выполнении нижней половины, и т.д. Однако вероятность этого быстро уменьшается с увеличением количества рекурсивных прерываний.
Может ли данный код пропустить какие-либо нижние половины? Давайте предположим, что прерывание происходит в самый неудачный момент времени: между строками 29115 и 29116 — т.е. после копирования переменной bh_active но перед очисткой разрядов установки в ней. Возможны три случая:
29118: Одновременно просматривает массив bh_base и разряды в переменной active. Если самый младший разряд в active установлен, соответствующая нижняя половина вызывается; затем цикл переходит к следующей записи массива bh_base и к следующему разряду в active.
Поскольку данный цикл является циклом do/while, он выполняется по меньшей мере один раз. Частично это связанно с тем, что вызывающая функция всегда, прежде чем вызывать функцию do_bottom_half, проверяет, нуждаются ли какие-либо нижние половины в обслуживании, и поэтому цикл должен быть выполнен по меньшей мере один раз. Кстати, та проверка могла бы быть помещена и внутрь самой функции do_bottom_half, но выполнение проверки прежде вызова функции предотвращает вызов, если никакие нижние половины не нуждаются в выполнении. В любом случае цикл будет выполняться правильно, даже если никакие нижние половины не нуждаются в выполнении; это привело бы к напрасной трате времени, но не принесло бы никакого иного вреда.
29123: Цикл прерывается, когда в active не остается установленных разрядов. Поскольку по мере выполнения цикла выполняется сдвиг active, все остальные разряды проверяются одновременно, без необходимости выполнения цикла по каждому из них.
netlib.narod.ru | < Назад | Оглавление | Далее > |