Второе издание
Скачать 3.09 Mb.
|
Больше нет глобального вызова cli () Ранее ядро предоставляло функцию, с помощью которой можно было запретить прерывания на всех процессорах системы. Более того, если какой-либо процессор вызывал эту функцию, то он должен был ждать, пока прерывания не будут разрешены. Эта функция называлась c l i ( ) , а соответствующая ей разрешающая функция — s t i ( ) ; очень "х86-центрично" (хотя и было доступно для всех аппаратных платформ). Эти интерфейсы были изъяты во время разработ- ки ядер серии 2.5, и, следовательно, все операции по синхронизации обработки прерываний должны использовать комбинацию функций по управлению локальными прерываниями и функ- ций работы со спин-блокировками (обсуждаются в главе 9, "Средства синхронизации в ядре"). Это означает, что код, который ранее должен был всего лишь глобально запретить прерывания для того, чтобы получить монопольный доступ к совместно используемым данным, теперь дол- жен выполнить несколько больше работы. Ранее разработчики драйверов могли считать, что если в их обработчике прерывания и в лю- бом другом коде, который имеет доступ к совместно используемым данным, вызывается функ- ция c l i ( ) , то это позволяет получить монопольный доступ. Функция c l i () позволяла гаран- тировать, что ни один из обработчиков прерываний (в том числе и другие экземпляры текущего обработчика) не выполняется. Более того, если любой другой процессор входит в участок кода, защищенный с помощью функции c l i ( ) , то он не продолжит работу, пока первый процессор не выйдет из участка кода, защищенного с помощью функции c l i ( ) , т.е. не вызовет функцию s t 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); Первые две функции позволяют запретить указанную линию прерывания в кон- троллере прерываний. Это запрещает доставку данного прерывания всем процессо- рам в системе. Кроме того, функция d i s a b l e _ i r q () не возвращается до тех пор, пока все обработчики прерываний, которые в данный момент выполняются, не за- кончат работу. Таким образом гарантируется не только то, что прерыпания с данной 126 Глава 6 линии не будут доставляться, но и то, что все выполняющиеся обработчики закончи- ли работу. Функция disable_irq_nosync () не имеет последнего свойства. Функция s y n c h r o n i z e _ i r q () будет ожидать, пока не завершится указанный об- работчик, если он, конечно, выполняется. Вызовы этих функций должны быть сгруппированы, т.е. каждому вызову функ- ции d i s a b l e _ i r q () или d i s a b l e _ i r q _ n o s y n c () должен соответствовать вызов функции e n a b l e _ i r q (). Только после последнего вызова функции e n a b l e _ i r q () линия запроса на прерывание будет снова разрешена. Например, если функция d i s a b l e _ i r q () последовательно вызвана два раза, то линия запроса на прерывание не будет разрешена, пока функция e n a b l e _ i r q () тоже не будет вызвана два раза. Эти три функции могут быть вызваны из контекста прерывания и из контекста процесса и не приводят к переходу в приостановленное состояние (sleep). При вызо- ве из контекста прерывания следует быть осторожным! Например, нельзя разрешать линию прерывания во время выполнения обработчика прерывания (вспомним, что линия запроса на прерывание обработчика, который в данный момент выполняется, является замаскированной). Было бы также плохим тоном запрещать линию прерывания, которая совместно используется несколькими обработчиками. Запрещение линии прерывания запреща- ет доставку прерываний для всех устройств, которые используют эту линию. Поэтому в драйверах новых устройств не рекомендуется использовать эти интерфейсы 3 . Так как устройства PCI должны согласно спецификации поддерживать совместное ис- пользование линий прерываний, они вообще не должны использовать эти интерфей- сы. Поэтому функция d i s a b l e _ i r q () и дружественные ей обычно используются для устаревших устройств, таких как параллельный порт персонального компьютера. Состояние системы обработки прерываний Часто необходимо знать состояние системы обработки прерываний (например, прерывания запрещены или разрешены, выполняется ли текущий код в контексте прерывания или в контексте процесса). Макрос i r q _ d i s a b l e d (), который определен в файле - in_interrupt() in_irq() Наиболее полезный из них — это первый макрос. Он возвращает ненулевое значе- ние, если ядро выполняется в контексте прерывания. Это включает выполнение как обработчика прерывания, так и обработчика нижней половины. Макрос i n _ i r q ( ) возвращает ненулевое значение, только если ядро выполняет обработчик прерыва- ния. 3 Многие старые устройства, в частности устройства ISA, не предоставляют возможности опреде- лить, являются ли они источником прерывания. Из-за этого линии прерывания для ISA-устройств часто не могут быть совместно используемыми. Поскольку спецификация шины PCI требует обяза- тельной поддержки совместно используемых прерываний, современные устройства PCI поддержи- вают совместное использование прерываний. В современных компьютерах практически все линии прерываний могут быть совместно используемыми. Прерывания и обработка прерываний 127 Не нужно прерывать, мы почти закончили! В этой главе были рассмотрены прерывания, аппаратные ресурсы, которые ис- пользуются устройствами для подачи асинхронных сигналов процессору. Прерывания используются аппаратным обеспечением, чтобы прервать работу операционной си- стемы. Большинство современного аппаратного обеспечения использует прерывания, чтобы взаимодействовать с операционной системой. Драйвер устройства, который управляет некоторым оборудованием, должен зарегистрировать обработчик преры- вания, чтобы отвечать на эти прерывания и обрабатывать их. Работа, которая выпол- няется обработчиками прерываний, включает отправку подтверждения устройству о получении прерывания, инициализацию аппаратного устройства, копирование дан- 128 Глава 6 Наиболее часто необходимо проверять, выполняется ли код в контексте процес- са, т.е. необходимо проверить, что код выполняется не в контексте прерывания. Это требуется достаточно часто, когда коду необходимо выполнять что-то, что может быть выполнено только из контекста процесса, например переход в приостановлен- ное состояние. Если макрос i n _ i n t e r r u p 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() Запретить доставку прерываний на локальном процессоре Разрешить доставку прерываний на локальном процессоре Сохранить текущее состояние системы обра- ботки прерываний на локальном процессоре и запретить прерывания Восстановить указанное состояние системы прерываний на локальном процессоре Запретить указанную линию прерывания с га- рантией, что после возврата из этой функции не выполняется ни один обработчик данной линии Запретить указанную линию прерывания Разрешить указанную линию прерываний Возвратить ненулевое значение, если запреще- на доставка прерываний на локальном процес- соре, в противном случае возвращается нуль Возвратить ненулевое значение, если выпол- нение производится в контексте прерывания, и нуль — если в контексте процесса Возвратить ненулевое значение, если выпол- нение производится в контексте прерывания, и нуль — в противном случае ных из памяти устройства в память системы и, наоборот, обработку аппаратных за- просов и отправку ответов на них. Ядро предоставляет интерфейсы для регистрации и освобождения обработчиков прерываний, запрещения прерываний, маскирования линий прерываний и провер- ки состояния системы прерываний. В табл. 6.2 приведен обзор некоторых из этих функций. Так как прерывания прерывают выполнение другого кода (кода процессов, кода ядра и другие обработчики прерываний), то они должны выполняться быстро. Тем не менее часто приходится выполнять много работы. Для достижения компромисса между большим количеством работы и необходимостью быстрого выполнения об- работка прерывания делится на две половины. Верхняя половина — собственно об- работчик прерывания — рассматривается в этой главе. Теперь давайте рассмотрим нижнюю половину процесса обработки прерывания. Прерывания и обработка прерываний 129 7 Обработка нижних половин и отложенные действия В предыдущей главе были рассмотрены обработчики прерываний — механизм ядра, который позволяет решать задачи, связанные с аппаратными прерыва- ниями. Конечно, обработчики прерываний очень полезны и являются необходимой частью ядра. Однако, в связи с некоторыми ограничениями, они представляют со- бой лишь часть процесса обработки прерываний. Эти ограничения включают следу- ющие моменты. • Обработчики прерываний выполняются асинхронно и потенциально могут прерывать выполнение другого важного кода (даже другие обработчики пре- рываний). Поэтому обработчики прерываний должны выполняться как можно быстрее. • Обработчики прерываний выполняются в лучшем случае при запрещенной обрабатываемой линии прерывания и в худшем случае (когда установлен флаг SA_INTERRUPT) — при всех запрещенных линиях запросов на прерывания. И снова они должны выполняться как можно быстрее. • Обработчики прерываний очень критичны ко времени выполнения, так как они имеют дело с аппаратным обеспечением. • Обработчики прерываний не выполняются в контексте процесса, поэтому они не могут блокироваться. Теперь должно быть очевидным, что обработчики прерываний являются только частью полного решения проблемы обработки аппаратных прерываний. Конечно, необходим быстрый, простой и асинхронный обработчик, позволяющий немедлен- но отвечать на запросы оборудования и выполнять критичную ко времени выполне- ния работу. Эту функцию обработчики прерываний выполняют хорошо, но другая, менее критичная ко времени выполнения работа должна быть отложена до того мо- мента, когда прерывания будут разрешены. Для этого обработка прерывания делится на две части или половины. Первая часть обработчика прерывания (top half, верхняя половина) выполняется асинхронно и немедленно в ответ на аппаратное прерывание так, как это обсуждалось в пред- ыдущей главе. В этой главе мы рассмотрим вторую часть процесса обработки пре- рываний— нижние половины (bottom half). Нижние половины Задача обработки нижних половин — это выполнить всю связанную с прерывани- ями работу, которую не выполнил обработчик прерывания. В идеальной ситуации — это почти вся работа, так как необходимо, чтобы обработчик прерывания выполнил по возможности меньшую часть работы (т.е. выполнился максимально быстро) и по- быстрее возвратил управление. Тем не менее обработчик прерывания должен выполнить некоторые действия. Например, почти всегда обработчик прерывания должен отправить устройству уве- домление, что прерывание получено. Он также должен произвести копирование не- которых данных из аппаратного устройства. Эта работа чувствительна ко времени выполнения, поэтому есть смысл выполнить ее в самом обработчике прерывания. Практически все остальные действия будет правильным выполнить в обработ- чике нижней половины. Например, если в верхней половине было произведено ко- пирование данных из аппаратного устройства в память, то в обработчике нижней половины, конечно, имеет смысл эти данные обработать. К сожалению, не суще- ствует твердых правил, позволяющих определить, какую работу где нужно выпол- нять, — право решения остается за автором драйвера. Хотя ни одно из решений этой задачи не может быть неправшлъным, решение легко может оказаться неоптимальным. Следует помнить, что обработчики прерываний выполняются асинхронно при за- прещенной, по крайней мере, текущей линии запроса на прерывание. Минимизация этой задержки является важной. Хотя нет строгих правил по поводу того, как делить работу между обработчиками верхней и нижней половин, все же можно привести несколько полезных советов. • Если работа критична ко времени выполнения, то ее необходимо выполнять в обработчике прерыпания. • Если работа связана с аппаратным обеспечением, то ее следует выполнить в обработчике прерывания. • Если для выполнения работы необходимо гарантировать, что другое прерыва- ние (обычно с тем же номером) не прервет обработчик, то работу нужно вы- полнить в обработчике прерывания. • Для всего остального работу стоит выполнять в обработчике нижней половины. При написании собственного драйвера устройства есть смысл посмотреть на об- работчики прерываний и соответствующие им обработчики нижних половин других драйверов устройств — это может помочь. Принимая решение о разделении рабо- ты между обработчиками верхней и нижней половины, следует спросить себя: "Что должно быть в обработчике верхней половины, а что может быть в обработчике ниж- ней половины". В общем случае, чем быстрее выполняется обработчик прерывания, тем лучше. 132 Глава 7 Когда нужно использовать нижние половины Часто не просто понять, зачем нужно откладывать работу и когда именно ее нуж- но откладывать. Вам необходимо ограничить количество работы, которая выполня- ется в обработчике прерывания, потому что обработчик прерывания выполняется при запрещенной текущей линии прерывания. Хуже того, обработчики, зарегистри- рованные с указанием флага SA_INTERRUPT, выполняются при всех запрещенных линиях прерываний на локальном процессоре (плюс текущая линия прерывания за- прещена глобально). Минимизировать время, в течение которого прерывания запре- щены, важно для уменьшения времени реакции и увеличения производительности системы. Если к этому добавить, что обработчики прерываний выполняются асин- хронно по отношению к другому коду, и даже по отношению к другим обработчикам прерываний, то становится ясно, что нужно минимизировать время выполнения об- работчика прерывания. Решение — отложить некоторую часть работы на более позд- ний срок. Но что имеется в виду под более поздним сроком? Важно понять, что позже озна- чает не сейчас. Основной момент в обработке нижних половин — это не отложить ра- боту до определенного момента времени в будущем, а отложить работу до некоторого неопределенного момента времени в будущем, когда система будет не так загружена и все прерывания снова будут разрешены. Не только в операционной системе Linux, но и в других операционных систе- мах обработка аппаратных прерываний разделяется на две части. Верхняя половина выполняется быстро, когда все или некоторые прерывания запрещены. Нижняя по- ловина (если она реализована) пыполняется позже, когда все прерывания разреше- ны. Это решение позволяет поддерживать малое время реакции системы, благодаря тому что работа при запрещенных прерываниях выполняется в течение возможно малого периода времени. Многообразие нижних половин В отличие от обработчиков верхних половин, которые могут быть реализованы только в самих обработчиках прерываний, для реализации обработчиков нижних половин существует несколько механизмов. Эти механизмы представляют собой раз- личные интерфейсы и подсистемы, которые позволяют пользователю реализовать обработку нижних половин. В предыдущей главе мы рассмотрели единственный су- ществующий механизм реализации обработчиков прерываний, а в этой главе рассмо- трим несколько методов реализации обработчиков нижних половин. На самом деле за историю операционной системы Linux существовало много механизмов обработ- ки нижних половин. Иногда сбивает с толку то, что эти механизмы имеют очень схожие или очень неудачные названия. Для того чтобы придумывать названия меха- низмам обработки нижних половин, необходимы "специальные программисты". В этой главе мы рассмотрим принципы работы и реализацию механизмов обра- ботки нижних половин, которые существуют в ядрах операционной системы Linux серии 2.6. Также будет рассмотрено, как использовать эти механизмы в коде ядра, который вы можете написать. Старые и давно изъятые из употребления механизмы обработки нижних половин представляют собой историческую ценность, поэтому, где это важно, о них также будет рассказано. Обработка нижних половин и отложенные действия 133 В самом начале своего существования операционная система Linux предоставляла единственный механизм для обработки нижних половин, который так и назывался "нижние половины" ("bottom half"). Это название было понятно, так как существова- ло только одно средство для выполнения отложенной обработки. Соответствующая инфраструктура называлась "ВН" и мы ее так дальше и будем назвать, чтобы избе- жать путаницы с общим термином "bottom half (нижняя половина). Интерфейс ВН был очень простым, как и большинство вещей в те старые добрые времена. Он предоставлял статический список из 32 обработчиков нижних половин. Обработчик верхней половины должен был отметить какой из обработчиков нижних половин должен выполняться путем установки соответствующего бита в 32-разрядном целом числе. Выполнение каждого обработчика ВН синхронизировалось глобально, т.е. ни- какие два обработчика не могли выполняться одновременно, даже на разных процес- сорах. Такой механизм был простым в использовании, хотя и не гибким; простым в реализации, хотя представлял собой узкое место в плане производительности. Позже разработчики ядра предложили механизм очередей заданий (task queue) — од- новременно как средство выполнения отложенной обработки и как замена для ме- ханизма ВН. В ядре определялось семейство очередей. Каждая очередь содержала связанный список функций, которые должны были выполнять соответствующие действия. Функции, стоящие в очереди, выполнялись в определенные моменты времени, в зависимости от того, в какой очереди они находились. Драйверы могли регистрировать собственные обработчики нижних половин в соответствующих оче- редях. Этот механизм работал достаточно хорошо, но он был не настолько гибким, чтобы полностью заменить интерфейс ВН. Кроме того, он был достаточно "тяжело- весным" для обеспечения высокой производительности критичных к этому систем, таких как сетевая подсистема. Во время разработки серии ядер 2.3 разработчики ядра предложили механизм от- ложенных прерываний 1 (softirq) и механизм тасклетов (tasklet). За исключением решения проблемы совместимости с существующими драйвера- ми, механизмы отложенных прерываний и тасклетов были в состоянии полностью заменить интерфейс ВН 2 Отложенные прерывания - это набор из 32 статически определенных обра- ботчиков нижних половин, которые могут одновременно выполняться на разных процессорах, даже два обработчика одного типа могут выполняться параллельно. Тасклеты — это гибкие, динамически создаваемые обработчики нижних половин, которые являются надстройкой над механизмом отложенных прерываний и имеют ужасное название, смущающее всех 3 1 Термин softirq часто переводится, как "программное прерывание", однако, чтобы не вносить пута- ницу с синхронными программными прерываниями (исключительными ситуациями) в этом кон- тексте используется термин "отложенное прерывание". (Прим. ред.) 2 В связи с глобальным синхронизмом выполнения обработчиков ВН друг с другом, не так просто было их конвертировать для использования механизмов отложенных прерываний и тасклетов. Однако в ядрах серии 2.5 это наконец-то получилось сделать. 3 Они не имеют ничего общего с понятием task (задача). Их следует понимать как простые в исполь- зовании отложенные прерывания (softirq). 134 Глава 7 Два различных тасклета могут выполняться параллельно на разных процессорах, но при этом два тасклета одного типа не могут выполняться одновременно. Таким образом, тасклеты — это хороший компромисс между производительностью и про- стотой использования. В большинстве случаев для обработки нижних половин доста- точно использования таскелетов. Обработчики отложенных прерываний являются полезными, когда критична производительность, например, для сетевой подсисте- мы. Использование механизма отложенных прерываний требует осторожности, по- тому что два обработчика одного и того же отложенного прерывания могут выпол- няться одновременно. В дополнение к этому, отложенные прерывания должны быть зарегистрированы статически на этапе компиляции. Тасклеты, наоборот, могут быть зарегистрированы динамически. Еще больше запутывает ситуацию то, что некоторые люди говорят о всех обра- ботчиках нижних половин как о программных прерываниях, или отложенных пре- рываниях (software interrupt, или softirq). Другими словами, они называют механизм отложенных прерываний и в общем обработку нижних половин программными пре- рываниями. На таких людей лучше не обращать внимания, они из той же категории, что и те, которые придумали название "ВН" и тасклет. Во время разработки ядер серии 2.5 механизм ВН был в конце концов выброшен, потому что все пользователи этого механизма конвертировали свой код для исполь- зования других интерфейсов обработки нижних половин. В дополнение к этому, ин- терфейс очередей заданий был заменен на новый интерфейс очередей отложенных действий (work queue). Очереди отложенных действий— это простой и в то же вре- мя полезный механизм, позволяющий поставить некоторое действие в очередь для выполнения в контексте процесса в более поздний момент времени. Следовательно, сегодня ядро серии 2.6 предоставляет три механизма обработки нижних половин в ядре: отложенные прерывания, тасклеты и очереди отложенных действий. В ядре также использовались интерфейсы ВН и очередей заданий, но се- годня от них осталась только светлая память. |