является опасной в случае, когда перед ее вызовом прерывания уже были запрещены. При этом соответствующий ей вызов функции
local_irq_enable
разрешит прерывания независимо от того, были они запрещены первоначально (до вызова
local_irq_disable
) или нет. Для того чтобы избежать такой ситуации, необходим механизм, который позволяет восстанавливать состояние системы обработки прерывании в первоначальное значение. Это требование имеет общий характер, потому что некоторый участок кода ядра в одном случае может выполняться при разрешенных прерываниях, а в другом случае— при запрещенных, в зависимости от последовательности вызовов функций. Например, пусть показанный фрагмент кода является частью функции. Эта функция вызывается двумя другими функциями, и в первом случае перед вызовом прерывания запрещаются, а во втором — нет. Так как при увеличении объема кода ядра становится сложно отслеживать все возможные варианты вызова функции, значительно безопаснее сохранять состояние системы прерываний перед тем, как запрещать
прерывания. Вместо разрешения прерываний просто восстанавливается первоначальное состояние системы обработки прерываний следующим образом.
unsigned long flags;
local_irq_save(flags);
/* прерывания запрещены . . */
local_irq_restore(flags);
/* состояние системы прерываний восстановлено
в первоначальное значение ... */
Нужно заметить, что эти функции являются макросами, поэтому передача параметра
flags
выглядит как передача по значению. Этот параметр содержит зависящие от аппаратной платформы данные, которые в свою очередь содержат состояние системы прерываний. Так как, по крайней мере для одной аппаратной платформы (SPARC), в этой переменной хранится информация о стеке, то параметр
flags
нельзя передавать в другие функции (другими словами, он должен оставаться в одном и том же стековом фрейме). По этой причине вызовы функций сохранения и восстановления должны выполняться в теле одной функции.
Все описанные функции могут вызываться как из обработчика прерываний, так и из контекста процесса.
Больше нет глобального вызова
cli
Ранее ядро предоставляло функцию, с помощью которой можно было запретить прерывания на всех процессорах системы. Более того, если какой-либо процессор вызывал эту функцию, то он должен был ждать, пока прерывания не будут разрешены. Эта функция называлась
cli
, а соответствующая ей разрешающая функция —
sti
; очень "x86-центрично" (хотя и было доступно для всех аппаратных платформ). Эти интерфейсы были изъяты во время разработки ядер серии 2.5, и, следовательно, все операции по синхронизации обработки прерываний должны использовать комбинацию функций по управлению локальными прерываниями и функций работы со спин-блокировками (обсуждаются в главе 9, "Средства синхронизации в ядре"). Это означает, что код, который ранее должен был всего лишь глобально запретить прерывания для того, чтобы получить монопольный доступ к совместно используемым данным, теперь должен выполнить несколько больше работы.
Ранее разработчики драйверов могли считать, что если в их обработчике прерывания и в любом другом коде, который имеет доступ к совместно используемым данным, вызывается функция
cli
, то это позволяет получить монопольный доступ. Функция
cli
позволяла гарантировать, что ни один из обработчиков прерываний (в том числе и другие экземпляры текущего обработчика) не выполняется. Более того, если любой другой процессор входит в участок кода, защищенный с помощью функции
cli
, то он не продолжит работу, пока первый процессор не выйдет из участка кода, защищенного с помощью функции
cli
, т.е. не вызовет функцию
sti
.
Изъятие глобальной функции
cli
имеет несколько преимуществ. Во-первых, это подталкивает разработчиков драйверов к использованию настоящих блокировок. Специальные блокировки на уровне мелких структурных единиц работают быстрее, чем глобальные блокировки, к которым относится и функция
cli
. Во-вторых, это упрощает значительную часть кода и позволяет удалить большой объем кода. В результате система обработки прерываний стала проще и понятнее.
Запрещение определенной линии прерывания
В предыдущем разделе были рассмотрены функции, которые позволяют запретить доставку всех прерываний на определенном процессоре. В некоторых случаях полезным может оказаться запрещение определенной линии прерывания во всей системе. Это называется маскированием линии прерывания. Например, может потребоваться запрещение доставки прерывания от некоторого устройства перед манипуляциями с его состоянием. Для этой цели операционная система Linux предоставляет четыре интерфейса.
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);
Первые две функции позволяют запретить указанную линию прерывания в контроллере прерываний. Это запрещает доставку данного прерывания всем процессорам в системе. Кроме того, функция
disable_irq
не возвращается до тех пор, пока все обработчики прерываний, которые в данный момент выполняются, не закончат работу. Таким образом гарантируется не только то, что прерывания с данной линии не будут доставляться, но и то, что все выполняющиеся обработчики закончили работу. Функция
disable_irq_nosync
не имеет последнего свойства.
Функция
synchronize_irq
будет ожидать, пока не завершится указанный обработчик, если он, конечно, выполняется.
Вызовы этих функций должны быть сгруппированы, т.е. каждому вызову функции
disable_irq
или
disable_irq_nosync
должен соответствовать
вызов функции
enable_irq
. Только после последнего вызова функции
enable_irq
линия запроса на прерывание будет снова разрешена. Например, если функция
disable_irq
последовательно вызвана два раза, то линия запроса на прерывание не будет разрешена, пока функция
enable_irq
тоже не будет вызвана два раза.
Эти три функции могут быть вызваны из контекста прерывания и из контекста процесса и не приводят к переходу в приостановленное состояние (sleep). При вызове из контекста прерывания следует быть осторожным! Например, нельзя разрешать линию прерывания во время выполнения обработчика прерывания (вспомним, что линия запроса на прерывание обработчика, который в данный момент выполняется, является замаскированной).
Было бы также плохим тоном запрещать линию прерывания, которая совместно используется несколькими обработчиками. Запрещение линии прерывания запрещает доставку прерываний для всех устройств, которые используют эту линию. Поэтому в драйверах новых устройств не рекомендуется использовать эти интерфейсы [33] . Так как устройства PCI должны согласно спецификации поддерживать совместное использование линий прерываний, они вообще не должны использовать эти интерфейсы. Поэтому функция
disable_irq
и дружественные ей обычно используются для устаревших устройств, таких как параллельный порт персонального компьютера.
33
Многие старые устройства, в частности устройства ISA, не предоставляют возможности определить, являются ли они источником прерывания. Из-за этого линии прерывания для ISA-устройств часто не могут быть совместно используемыми. Поскольку спецификация шины PCI требует обязательной поддержки совместно используемых прерываний, современные устройства PCI поддерживают совместное использование прерываний. В современных компьютерах практически все линии прерываний могут быть совместно используемыми.
Состояние системы обработки прерываний
Часто необходимо знать состояние системы обработки прерываний (например, прерывания запрещены или разрешены, выполняется ли текущий код в контексте прерывания или в контексте процесса).
Макрос
irq_disabled
, который определен в файле
<asm/system.h>
, возвращает ненулевое значение, если обработка прерываний на локальном процессоре запрещена. В противном случае возвращается нуль. Два следующих макроса позволяют определить контекст, в котором в данный момент выполняется ядро.
in_interrupt
in_irq
Наиболее полезный из них — это первый макрос. Он возвращает ненулевое значение, если ядро выполняется в контексте прерывания. Это включает выполнение как обработчика прерывания, так и обработчика нижней половины. Макрос
in_irq
возвращает ненулевое значение, только если ядро выполняет обработчик прерывания.
Наиболее часто необходимо проверять, выполняется ли код в контексте процесса, т.е. необходимо проверить, что код выполняется не в контексте прерывания. Это требуется достаточно часто, когда коду необходимо выполнять что-то, что может быть выполнено только из контекста процесса, например переход в приостановленное состояние. Если макрос
in_interrupt
возвращает нулевое значение, то ядро выполняется в контексте процесса.
Таблица 6.2. Список функций управления прерываниями
Функция
Описание
local_irq_disable
Запретить доставку прерываний на локальном процессоре
local_irq_enable
Разрешить доставку прерываний на локальном процессоре
local_irq_save(unsigned long flags)
Сохранить текущее состояние системы обработки прерываний на локальном процессоре и запретить прерывания
local_irq_restore(unsigned long flags)
Восстановить указанное состояние системы прерываний на локальном процессоре
disable_irq(unsigned int irq)
Запретить указанную линию прерывания с гарантией, что после возврата из этой функции не выполняется ни один обработчик данной линии
disable_irq_nosync(unsigned int irq)
Запретить указанную линию прерывания
enable_irq(unsigned int irq)
Разрешить указанную линию прерываний
irqs_disabled
Возвратить ненулевое значение, если запрещена доставка прерываний на локальном процессоре, в противном случае возвращается нуль
in_interrupt
Возвратить ненулевое значение, если выполнение производится в контексте прерывания, и нуль — если в контексте процесса
in_irq
Возвратить ненулевое значение, если выполнение производится в контексте прерывания, и нуль — в противном случае