Главная » Ядро Linux » Управление прерываниями

0

В ядре  Linux  реализовано семейство интерфейсов для управления состояниями прерываний в машине. Эти  интерфейсы позволяют запрещать прерывания для текущего  процессора или  маскировать линию  прерывания для всей  машины. Эти функции очень  сильно  зависят  от аппаратной платформы и  находятся  в файлах

<asm/system. h>  и <asm/irq . h>. В табл. 6.2 приведен полный список  этих интерфейсов.

Причины, по которым  необходимо управлять  системой обработки прерываний, в  основном, сводятся  к необходимости обеспечения синхронизации. Путем  запрещения  прерываний можно  гарантировать, что обработчик прерывания не вытеснит текущий  исполняемый код.  Более  того,  запрещение прерываний также  запрещает и вытеснение кода ядра.  Однако  ни запрещение доставки  прерываний, ни запрещение преемптивности ядра не дают никакой защиты  от конкурентного обращения других процессоров. Так  как  операционная система  Linux  поддерживает многопроцессорные  системы, в большинстве случаев  код  ядра должен  захватить  некоторую блокировку,   чтобы  предотвратить доступ  другого  процессора к  совместно используемым данным. Эти  блокировки обычно  захватываются в комбинации с запрещением прерываний на текущем  процессоре. Блокировка предоставляет защиту  от доступа  другого процессора, а запрещение прерываний  обеспечивает защиту  от конкурентного доступа из возможного обработчика прерывания. В главах 8 и 9 обсуждаются различные аспекты  проблем  синхронизации и решения этих проблем.

Тем не менее  понимание интерфейсов ядра для управления прерываниями является  важным.

2 После прочтени я главы  10,  "Таймеры  и управление  временем",  можно  ли сказать,  сколько  времени

(в  единицах  HZ)   машина  работала  без  перегрузки  исходя  из  числа  прерывани й таймера?

Запрещение и разрешение прерываний

Для локального запрещения  прерываний на текущем процессоре (и только на текущем процессоре)  и последующего разрешения можно использовать следующий код.

local_irq_disable();

/* прерывания запрещены .. */

local_irq_enable();

Эти функции обычно реализуются в  виде одной инструкции на языке ассемблера  (что, конечно, зависит от  аппаратной платформы). Для платформы х86  функция local_irq_disabl e   () — это просто машинная инструкция cli , а функция 1оса1_ irq_enabl e () — просто инструкция sti . Для хакеров, не знакомых с  платформой х86, st i  и cl i — это ассемблерные вызовы, которые соответственно позволяют установить (set) или очистить (clear) флаг разрешения прерываний  (allow interrupt flag). Другими словами,  они разрешают или запрещают доставку прерываний на вызвавшем их процессоре.

Функция local_irq_disabl e ()  является опасной в  случае,  когда перед ее  вызовом прерывания уже  были запрещены. При этом соответствующий ей вызов функции local_irq_enabl e  ()  разрешит прерывания независимо от  того, были они запрещены первоначально (до   вызова  local_irq_disabl e  ())  или нет. Для того чтобы избежать такой ситуации, необходим механизм, который позволяет восстанавливать состояние системы обработки прерывании в первоначальное значение. Это требование имеет общий характер, потому что   некоторый участок кода ядра в  одном случае может выполняться при разрешенных прерываниях,  а  в  другом случае— при запрещенных, в  зависимости от  последовательности вызовов функций. Например, пусть показанный фрагмент кода является частью функции.  Эта функция вызывается двумя другими функциями, и в первом случае перед вызовом прерывания запрещаются, а во  втором — нет. Так как при увеличении объема кода ядра становится сложно отслеживать все  возможные варианты вызова функции,  значительно безопаснее сохранять состояние системы прерываний перед тем, как запрещать прерывания. Вместо разрешения прерываний просто восстанавливается первоначальное состояние системы обработки прерываний следующим образом.

unsigned long flags;

local_irq_save(flags);

/* прерывания запрещены . . */

local_irq_restore(flags) ;

/*состояние системы прерываний восстановлено в первоначальное значение ..*/

Нужно заметить, что  эти функции являются макросами,  поэтому передача параметра  flag s  выглядит как передача по значению.  Этот параметр содержит зависящие от  аппаратной платформы данные, которые в свою очередь содержат состояние системы прерываний. Так как, по крайней мере для одной аппаратной платформы (SPARC), в этой неременной хранится информация о  стеке, то  параметр flag s  нельзя  передавать в  другие функции  (другими словами, он должен оставаться в  одном и том же  стековом фрейме). По этой причине вызовы функций сохранения и восстановления должны выполняться в теле одной функции.

Все  описанные   функции   могут вызываться  как  из  обработчика  прерываний,  так и  из  контекста  процесса.

Больше нет глобального вызова cli ()

Ранее ядро предоставляло функцию, с помощью которой можно было запретить прерывания на всех процессорах системы. Более того, если какой-либо процессор вызывал эту функцию, то он должен был ждать, пока прерывания не будут разрешены. Эта функция называлась c l i () , а соответствующая ей разрешающая функция — s t i () ;   очень "х86-центрично" (хотя и было доступно для всех аппаратных платформ). Эти интерфейсы были изъяты во время разработки ядер серии 2.5, и, следовательно, все операции по синхронизации обработки прерываний должны использовать комбинацию функций по управлению локальными прерываниями и функций работы со спин-блокировками (обсуждаются в главе 9, "Средства синхронизации в ядре"). Это означает, что код, который ранее должен был всего лишь глобально запретить прерывания для того, чтобы получить монопольный доступ к совместно используемым данным, теперь должен выполнить несколько больше работы.

Ранее разработчики драйверов могли считать, что если в их обработчике прерывания и в любом другом коде, который имеет доступ к совместно используемым данным, вызывается функция   c l i () , то это позволяет получить монопольный доступ. Функция c l i ()  позволяла гарантировать, что ни один из обработчиков прерываний (в том числе и другие экземпляры текущего обработчика) не выполняется. Более того, если любой другой процессор входит в участок кода, защищенный с помощью функции cl i () , то он не продолжит работу, пока первый процессор не выйдет из участка кода, защищенного с помощью функции c l i () , т.е. не вызовет функцию

st i () .

Изъятие глобальной функции c l i ()  имеет несколько преимуществ. Во-первых, это подталкивает разработчиков драйверов к использованию настоящих блокировок. Специальные блокировки на уровне мелких структурных единиц работают быстрее, чем глобальные блокировки, к  которым относится и функция c l i  ().  Во-вторых, это упрощает значительную часть кода и позволяет удалить большой объем кода. В результате система обработки прерываний стала проще и понятнее.

Запрещение определенной линии прерывания

В  предыдущем  разделе  были  рассмотрены   функции,   которые  позволяют  запретить доставку  всех прерываний на определенном процессоре. В некоторых случаях полезным может оказаться запрещение определенной линии прерывания во всей Системе.  Это называется  маскированием линии  прерывания.  Например,   может потребоваться  запрещение доставки прерывания от некоторого устройства перед манипуляциями  с  его  состоянием.   Для  этой  цели  операционная  система  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_ir q ()   не  возвращается  до  тех пор, пока  все  обработчики  прерываний,  которые  в данный  момент  выполняются,   не  закончат  работу.  Таким  образом  гарантируется  не  только то,  что прерыпания  с данной

линии не будут доставляться, но и то, что все выполняющиеся обработчики закончили работу. Функция  disable_irq_nosyn c  ()  не имеет последнего свойства.

Функция synchronize_ir q ()  будет ожидать, пока не завершится указанный обработчик, если он, конечно, выполняется.

Вызовы этих функций  должны быть сгруппированы,  т.е. каждому вызову функции disable_ir q  ()   или  disable_irq_nosyn c  ()   должен соответствовать вызов функции enable_ir q () . Только после последнего вызова функции  enable_ir q  () линия  запроса на прерывание будет снова разрешена. Например,  если функция disable_ir q ()  последовательно вызвана два  раза, то  линия запроса на прерывание не будет разрешена, пока функция enable_ir q ()  тоже не будет вызвана два  раза.

Эти три функции могут быть вызваны из контекста прерывания и из контекста процесса и не приводят к переходу в приостановленное состояние (sleep). При вызове  из контекста прерывания следует быть осторожным!  Например, нельзя разрешать линию прерывания во  время выполнения обработчика прерывания (вспомним, что линия запроса на прерывание обработчика, который в данный момент выполняется, является замаскированной).

Было бы  также плохим тоном запрещать линию прерывания, которая совместно используется несколькими обработчиками. Запрещение линии прерывания запрещает доставку прерываний для всех устройств, которые используют эту линию. Поэтому в драйверах новых устройств не рекомендуется использовать эти интерфейсы3.  Так как устройства PCI должны согласно спецификации поддерживать совместное использование линий прерываний, они вообще не должны использовать эти интерфейсы. Поэтому функция disable_ir q ()  и дружественные ей обычно используются для устаревших устройств, таких как параллельный порт персонального компьютера.

Состояние системы обработки прерываний

Часто необходимо знать состояние системы обработки прерываний (например, прерывания запрещены или разрешены, выполняется ли текущий код в  контексте прерывания или в контексте процесса).

Макрос irq_disable d () , который определен в  файле <asm/system.h>, возвращает ненулевое значение,  если обработка прерываний на локальном процессоре запрещена. В  противном случае возвращается нуль. Два следующих макроса позволяют

определить контекст, в  котором в данный момент выполняется ядро.

-

in_interrupt()

in_irq()

Наиболее полезный из них — это первый макрос. Он возвращает ненулевое значение, если ядро выполняется в контексте прерывания. Это включает выполнение как обработчика прерывания, так и обработчика нижней половины. Макрос in_irq( ) возвращает ненулевое значение,  только если ядро выполняет обработчик прерывания.

3Многие старые  устройства,  в частности  устройства  ISA,  не  предоставляют  возможности  определить, являются ли они источником  прерывания.  Из-за  этого линии  прерывания  для ISA-устройств часто не могут быть совместно  используемыми.  Поскольку  спецификация шины PCI требует обязательной поддержки  совместно  используемых прерываний, современные  устройства  PCI  поддерживают совместное  использование  прерываний.  В современных  компьютерах  практически  все линии прерываний  могут быть совместно  используемыми.

Наиболее часто необходимо проверять, выполняется ли код в контексте процесса, т.е. необходимо проверить, что код выполняется не в контексте прерывания. Это требуется достаточно часто,  когда коду необходимо выполнять что-то,  что может быть выполнено только из контекста процесса, например переход в приостановленное состояние. Если макрос in_interrup t ()  возвращает нулевое значение, то ядро выполняется в контексте процесса.

Таблица 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()

Запретить доставку прерываний на локальном процессоре

Разрешить  доставку  прерываний на локальном процессоре

Сохранить текущее состояние системы обработки прерываний на локальном процессоре и запретить прерывания

Восстановить указанное состояние системы прерываний на локальном процессоре

Запретить указанную линию прерывания с гарантией,  что после  возврата  из этой функции  не выполняется ни один обработчик данной линии

Запретить указанную  линию  прерывания

Разрешить  указанную  линию  прерываний

Возвратить ненулевое  значение,  если запрещена доставка прерываний на локальном процессоре,  в противном случае возвращается нуль

Возвратить ненулевое  значение,  если выполнение производится в контексте прерывания, и нуль — если в контексте процесса

Возвратить ненулевое значение, если выполнение производится в контексте прерывания, и нуль — в противном случае

Источник: Лав,  Роберт. Разработка ядра  Linux, 2-е  издание. : Пер.  с англ.  — М.  : ООО  «И.Д.  Вильяме» 2006. — 448 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии