<<
>>

Обработка прерываний в MINIX

Особенности обработки прерываний во многом зависят от аппаратной платформы, но у любой системы должны быть элементы с подобной функциональностью. В 32-разрядных процессорах Intel прерывания генерируются аппаратным обеспечением и в виде электрических сигналов передаются сначала на контроллер прерываний.
Это — интегральная схема, которая умеет различать несколько подобных сигналов и для каждого из них генерировать уникальный идентификатор на шине данных процессора. Сам же процессор имеет лишь один вход для всех устройств, а следовательно, не может определить, какое именно устройство нуждается в обслуживании. У компьютеров с 32-разрядными процессорами Intel обычно имеются два контроллера прерываний, каждый из которых обслуживает 8 устройств. Но один контроллер подчинен (slave) другому, то есть сигналы с его выхода передаются на вход главного (master) контроллера. Таким образом, эта комбинация контроллеров способна обслуживать 15 различных устройств, как показано на рис. 2.18. Некоторые из 15 входов являются выделенными; например, вход таймера, IRQ 0, не подключен к какому-либо разъему, предназначенному для адаптеров.
Остальные же входы подключены к разъемам и могут использоваться так же, как и обычные устройства.

Рис. 2.18. Обработка прерываний в 32-разрядных процессорах Intel

Здесь прерывания приходят по одной из линий IRQ п, нарисованных в правой части схемы. Процессор получает сигнал о том, что произошло прерывание, по контакту INT. В ответ на это сообщение процессор посылает по контакту INTA (INTerrupt Acknowledge — подтверждение прерывания) сигнал, по которому контроллер помещает на шину данных информацию, говорящую системе о том, какой из обработчиков вызывать.

Программируются контроллеры прерываний при инициализации системы в момент, когда функция main вызывает функцию intr_init. При программировании определяется, какие данные контроллер сформирует на шине данных процессора при поступлении сигнала по каждой из входных линий контроллера, а также устанавливается ряд других параметров контроллера. Передаваемые на шину данные представляют собой 8-разрядное число, используемое далее как индекс в массиве максимум из 256 элементов. В MINIX этот массив содержит 56 элементов, из которых реально задействуются 35. Остальные зарезервированы для будущих версий процессоров Intel или расширений в MINIX 3. В 32-разрядной версии системы в записях таблицы содержатся дескрипторы шлюзов прерываний (interrupt gate descriptor). Каждый дескриптор является 8-байтовой структурой с несколькими полями.

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

int

Разница только в том, что при аппаратном прерывании адрес процедуры берется не из памяти, а из регистра контроллера.

Механизм переключения задач в 32-разрядных процессорах Intel, как ответ на прерывание, довольно сложен, и изменение значения счетчика команд — только часть этого процесса. Когда процессор, уже обрабатывающий некоторый процесс, получает прерывание, он сначала выделяет новый стек, который будет использоваться для выполнения обработчика. Положение стека определяется значением записи в сегменте состояния задания (Task State Segment, TSS). Эта структура едина для всей системы, инициализируется при вызове prot_init и модифицируется при запуске каждого процесса. В результате каждый новый стек, создаваемый прерыванием, всегда начинается от конца структуры stackframe_s в записи в таблице процессов прерванного процесса.

Затем процессор автоматически помещает в новый стек значения нескольких регистров, включая нужные для восстановления стека и счетчика команд прерванного процесса. Обработчик прерывания заносит в стек значения дополнительных регистров, заполняя кадр стека, после чего переключается на другой стек, предоставляемый ядром. Этот стек и используется для всех действий по обработке прерывания.

Завершив свою работу, обработчик прерывания переключается обратно на кадр стека в таблице процессов (не обязательно на тот, который использовался для предыдущего прерывания), затем самостоятельно извлекает из стека значения дополнительных регистров и выполняет инструкцию iretd (возврат из прерывания). Эта инструкция восстанавливает значения регистров, помещенные в стек процессором, и переключается на исходный (до прерывания) стек. Таким образом, прерывание останавливает текущий процесс, а по завершении обработки процесс запускается, причем не обязательно тот, который был приостановлен. Данная схема отличается от более простых алгоритмов, часто встречающихся во многих ассемблерных программах, тем, что в стеке прерванного процесса не сохраняется никаких данных. Более того, так как стек пересоздается в заранее заданном месте (которое определяется записью в TSS), упрощается управление несколькими параллельно работающими процессами. Все, что нужно для запуска нового процесса, — поместить в указатель стека адрес нового кадра стека, извлечь из стека значения регистров, помещаемые туда программно, и выполнить инструкцию iretd.

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

Существует механизм, позволяющий запустить в это время обработчик исключений (с помощью которого процессор реагирует на обнаружение ошибок). Исключения аналогичны прерываниям, однако их невозможно блокировать. Таким образом, исключения делают возможной обработку вложенных прерываний. В подобной ситуации новый стек не создается — необходимые регистры сохраняются в существующем стеке. Предполагается, что во время работы ядра исключение не может возникнуть; это вызвало бы хаос.

Когда при выполнении ядра процессор встречает команду iretd, он использует более простой механизм возврата, нежели в случае прерванного процесса. С помощью селектора сегмента кода, извлекаемого из стека при обработке инструкции iretd, процессор может определить, какой из ее вариантов следует выполнить.

Различные варианты реакции процессора на прерывания при выполнении кода ядра определяются упомянутыми ранее уровнями привилегий. Когда уровень привилегий прерванного кода совпадает с уровнем обработчика, действует упрощенный механизм. Тем не менее прерванный код чаще всего имеет более низкий уровень привилегий, чем код обработки прерывания. В этом случае используется механизм, ориентированный на TSS и создание нового стека. Уровень привилегий сегмента кода запоминается в его селекторе, который сохраняется в стеке, что и позволяет при возврате из прерывания определить, какой механизм будет применяться.

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

Описанное поведение может показаться непонятным непосвященным в архитектуру 32-разрядных процессоров Intel. Обычно мы будем стараться избегать подобных деталей, но чтобы понять, что происходит при возникновении прерывания и исполнении команды iretd, нужно разобраться в том, как ядро управляет переходом процесса в состояние выполнения и выходом из него (см.

рис. 2.2). То, что значительная часть работы выполняется аппаратно, упрощает жизнь программиста и, по видимости, делает систему более эффективной. В то же время такая помощь со стороны аппаратного обеспечения усложняет понимание кода.

Познакомившись с механизмом прерываний, вернемся к файлу шрх3 86 . s. В нем содержится крохотная часть ядра MINIX 3, непосредственно взаимодействующая с прерываниями. Для каждого прерывания в этом файле имеется точка входа. Так, обработчики от _hwint00 до _hwint07 (строки 6531-6560) делают вызов hwint_master (строка 6515), а обработчики от _hwint08 до _hwintl5 (строки 6583-6612) — вызов hwint_slave (строка 6566). Каждая точка входа передает вызову аргумент, указывающий, какое из устройств требует обслуживания. Фактически эти «вызовы» являются не вызовами, а макросами: имеется восемь копий макроопределения hwint_master, различающихся только параметром irq. Аналогично дело обстоит и с макросом hwint_slave. Возможно, идея кажется экстравагантной, однако в результате ассемблированный код оказывается очень компактным. Объектный код каждого из макросов занимает менее 40 байт. При обслуживании прерывания важна скорость, а использование макросов избавляет от трех действий — загрузки параметра, вызова подпрограммы и извлечения переданного параметра.

Далее в обсуждении мы будем рассматривать вызов hwint_master так, как будто это функция, а не восемь отдельных макросов. Вспомним, что перед исполнением hwint_master процессор создает новый стек в структуре stackframe_s прерванного процесса внутри его элемента в таблице процессов. В нем уже сохранены семь ключевых регистров, а все прерывания запрещены. Первое действие, выполняемое hwint_master, — вызов save (строка 6516). Эта подпрограмма помещает в стек значения вех регистров, необходимых для восстановления прерванного процесса. Ее можно было бы переписать как часть макроса, чтобы повысить скорость, но это увеличило бы размер макроса более чем в два раза, и, кроме того, подпрограмма save необходима для вызовов, осуществляемых другими функциями.

Как мы увидим, save занимается тем, что хитро манипулирует со стеком. После возврата из hwint_master используется стек, выделенный ядром, а не кадр стека в таблице процессов.

Теперь подошла очередь использования двух таблиц, объявленных в файле glo .h. Таблица _irq_handlers содержит информацию об обработчиках прерывания, включая их адреса. Номер обрабатываемого прерывания подлежит преобразованию в адрес, определяемый таблицей _irq_handlers. Этот адрес передается в стек как аргумент функции _intr_handle, которая затем и вызывается. Мы рассмотрим код этой функции позднее. Пока мы лишь скажем, что она вызывает процедуру обработки прерывания, а кроме того, устанавливает или сбрасывает флаг в массиве _irq_actids в зависимости от того, была ли обработка преры

вания успешной, и дает другим записям очереди возможность выполниться. В зависимости от конкретного обработчика, после возврата из вызова _1гЩг_Ьапс11е контроллер прерываний может быть как готовым, так и не готовым к приему следующего прерывания. Это определяется соответствующей записью в массиве

_л_гд_ас111с1з.

Ненулевое значение в _1гд_асЬ1с1з указывает на то, что процедура обработки данного прерывания не завершена. Если это так, контроллеру запрещается реагировать на прерывания, поступающие по той же линии (строки 67722-6724). Приведенные команды маскируют возможность контроллера прерываний отвечать на определенные входные данные; процессор имеет внутренний запрет реагировать на все прерывания с момента первого появления сигнала прерывания, и этот запрет продолжает действовать.

Скажем несколько слов для читателей, не знакомых с программированием на языке ассемблера. Следующая команда в строке 6521 не задает количество байтов, на которое нужно совершить скачок:

j г (К

Значение 01 не является ни шестнадцатеричным числом, ни меткой (имена обычных меток не могут начинаться с цифры). Мы имеем дело с определением локальной метки, принятым в ассемблере М1№Х 3. Аргумент 0 ± означает переход вперед на первую численную метку 0 в строке 6525. Байт, записанный в этой строке, позволяет контроллеру прерываний возобновить нормальное функционирование, возможно, с запретом прерываний текущей линии.

Интересное и, вероятно, странное наблюдение относительно метки 0 в строке 6525: такая же метка присутствует в том же файле в строке 6576 внутри макроса hwint_slave. Ситуация осложняется еще и тем, что обе метки расположены в макросах, а подстановка последних выполняется до того, как ассемблер просматривает код. В результате ассемблер обнаруживает в одном коде шестнадцать меток 0! Подобное тиражирование меток при их использовании в макросах стало причиной ввода механизма локальных меток. При разрешении последних ассемблер ищет первую метку в указанном направлении, игнорируя все остальные.

Процедура _л_п11г_Ьапс11е является аппаратно-зависимой, и детали, касающиеся ее кода, будут рассмотрены нами при изучении файла 18259.с.Тем не менее несколько общих слов о ее функционировании здесь вполне уместны. Процедура _1пЬг_Ьапс11е сканирует связанный список структур, содержащих адреса функций, обрабатывающих прерывания устройств, и номера процессов, соответствующих драйверам устройств. Связанный список требуется потому, что одна линия прерываний может совместно использоваться несколькими устройствами. Обработчик прерывания сначала проверяет, нуждается ли, на самом деле, в обслуживании его устройство. Разумеется, в случае таймера (прерывание 0) такая проверка не нужна, поскольку линия прерывания физически связана с микросхемой, генерирующей тактовые сигналы, и она является единственным устройством, способным вызвать прерывание.

Код обработчика должен быть написан так, чтобы его выполнение занимало как можно меньше времени. Если не требуется выполнять какие-либо действия либо обслуживание прерывания завершается немедленно, обработчик возвращает значение TRUE. Обработчик может выполнять такие действия, как чтение данных входного устройства и передача их в буфер для последующего доступа к ним со стороны соответствующего драйвера. Обработчик может послать драйверу сообщение, заставляющее планировать его как обычный процесс. Если работа не завершена, обработчик возвращает значение FALSE. Элемент массива _irq_act_ids представляет собой битовую карту, регистрирующую результаты работы всех обработчиков списка так, что нулевое значение имеет место в том и только в том случае, если все обработчики вернули TRUE. Иначе команды строк 6522-6524 запрещают прерывание до повторного включения контроллера в строке 6536.

Данный механизм гарантирует, что ни один из обработчиков в цепочке, принадлежащей прерыванию, не будет активирован до того, как все соответствующие драйверы устройств закончат свою работу. Очевидно, что необходим альтернативный способ разрешения прерываний; такой способ предоставляет функция enable_irq, рассмотрением которой мы займемся позднее. Пока достаточно сказать, что эта функция должна вызываться по завершении работы каждого драйвера устройства. Кроме того, очевидно, что функция enable_irq должна сначала сбросить собственный бит в элементе _irq_act_ids, соответствующий прерыванию драйвера, а затем проверить, все ли биты сброшены. Лишь после этого прерывание можно заново разрешить на контроллере.

Приведенное описание в его простейшей форме можно применить лишь к драйверу часов, поскольку часы — единственное устройство, генерирующее прерывания и являющееся частью библиотеки ядра. Адрес обработчика прерываний другого процесса не важен в контексте ядра, и функция enable_irq ядра не может быть вызвана отдельным процессом в собственном пространстве памяти. Для всех драйверов устройств, находящихся в пользовательском пространстве (то есть всех драйверов, реагирующих на аппаратные прерывания, за исключением драйвера часов), адрес общего обработчика generic_handler хранится в связанном списке обработчиков. Исходный код generic_handler размещен среди файлов системного задания, однако, поскольку последнее компилируется вместе с ядром и полученный код выполняется в ответ на прерывание, функцию generic_ handler нельзя считать частью системного задания. Помимо адреса общего обработчика, в каждом элементе связанного списка хранится номер процесса соответствующего драйвера устройства. При вызове функция generic_handler посылает драйверу сообщение, запускающее определенные его функции. Системное задание поддерживает другой конец описанной выше цепочки событий. Когда драйвер устройства завершает работу, он выполняет вызов sys_irqctl ядра, в ответ на который системное задание вызывает функцию enable_irq от имени драйвера, чтобы подготовиться к следующему прерыванию.

Возвращаясь к функции hwint_master, обратим внимание на то, что она завершается командой ret (строка 6527). Здесь происходит нечто не вполне очевидное. Если процесс был прерван, то активным является стек ядра, а не стек, созданный аппаратно в таблице процессов перед запуском hwint_master. В этом случае обработка стека подпрограммой save оставит адрес функции _restart в стеке ядра, что вызовет повторный запуск задания, драйвера, сервера или пользовательского процесса. С большой вероятностью это будет процесс, отличный от того, который выполнялся в момент прерывания. Все зависит от того, привело ли к изменению планирования процессов обслуживание сообщения, созданного процедурой обработки прерывания. Как правило, в случае аппаратного прерывания очереди процессов изменяются. Обработчики прерываний чаще всего передают драйверам устройств сообщения, а драйверы размещаются в очередях с более высоким приоритетом, чем пользовательские процессы. Таким образом, мы видим самое сердце механизма, обеспечивающего иллюзию нескольких одновременно выполняющихся процессов.

Для полноты упомянем, что в том случае, когда прерывание происходит в момент выполнения кода ядра, стек ядра уже используется. Поэтому функция save помещает в стек адрес restartl. Тогда после завершения hwint_master командой ret работа ядра восстанавливается. Это позволяет делать вложенные прерывания, которые запрещены в MINIX 3, поскольку прерывания недопустимы во время исполнения кода ядра. Тем не менее, как было отмечено, данный механизм необходим для обработки прерываний. Когда выполнение низкоуровневых процедур завершается, вызывается _restart. В результате реакции на исключение при исполнении кода ядра с высокой вероятностью следующим будет запущен процесс, отличный от прерванного. Исключение в ядре приведет к сбою, после которого система попытается завершить свою работу с минимальными повреждениями.

Процедура hwint_slave отличается от hwint_master только тем, что в ней необходимо восстанавливать два контроллера, и подчиненный, и главный, так как они оба блокируются при приеме сигнала подчиненным контроллером.

Теперь двинемся дальше и рассмотрим процедуру save (строка 6622), уже несколько раз упомянутую. Ее имя описывает ее предназначение, заключающееся в том, чтобы сохранить контекст прерванного процесса в выделенном процессором стеке (в кадре стека внутри таблицы процессов). Кроме того, для поддержки вложенных прерываний save использует переменную _k_reenter, чтобы подсчитать число вложений и определить их наличие. Если процесс был прерван, следующая инструкция (строка 6635) переключается на стек в ядре:

mov esp, k_stktop

Следующая же за ней инструкция помещает в стек адрес подпрограммы _ге- start. В противном случае, если уже используется стек ядра, в него заносится адрес restartl (строка 6642). Разумеется, прерывания здесь запрещены, однако данный механизм предназначен для обработки исключений. Независимо от того, какая ветвь алгоритма была выполнена, для возврата из процедуры не годится обычная инструкция return, так как положение стека могло быть изменено, а адрес возврата «похоронен» под помещенными в стек регистрами. Поэтому для возврата применяется инструкция

jmp RETADR-P_STACKBASE(еах)

Ее вы можете увидеть в двух точках выхода процедуры (строки 6638 и 6643). Эта инструкция восстанавливает тот адрес, который был помещен в стек перед вызовом save, и осуществляет переход по нему.

Повторный вход в ядро вызывает множество проблем, и избавление от него упростило некоторые фрагменты кода. В MINIX 3 переменная _k_reenter не теряет смысла: хотя обычные прерывания во время исполнения ядра недопустимы, исключения возможны. При нормальном функционировании команда перехода в строке 6634 никогда не выполняется, однако она необходима для обработки исключений.

В качестве отступления заметим, что при разработке MINIX 3 в процессе избавления от повторного входа в ядро программирование опередило документирование. Документирование в некоторых аспектах сложнее, чем написание программ. Если ошибки в программе можно выявить во время ее выполнения или с помощью компилятора, то обнаружить некорректные комментарии аналогичным образом невозможно. Так, в начале файла шрх3 86 . s приведен длинный комментарий, который, к сожалению, неверен. В его части, расположенной в строках 6310-6315, должно быть сказано, что повторный вход в ядро возможен лишь при появлении исключения.

Далее в файле mpx3 86.s следует процедура _s_call (строка 6649). Прежде чем вдаваться в детали, посмотрим, как она заканчивается. Здесь вы не увидите в конце инструкции ret или jmp. Фактически, выполнение продолжается с метки „restart (строка 6681). Процедура _s_call является двойником механизма обработки прерываний из числа системных вызовов. Она получает управление при возникновении программного прерывания, то есть в результате срабатывания инструкции int . Программные прерывания обрабатываются так же, как и аппаратные, за исключением того, что индекс записи в таблице дескрипторов прерываний берется из инструкции, а не передается контроллером. Таким образом, когда _s_call получает управление, процессор уже переключен на стек в таблице процессов и в него помещены значения нескольких регистров. Так как у подпрограммы _s_call нет в конце команды выхода, выполняется процедура „restart, после чего подпрограмма окончательно завершается инструкцией iretd. В результате, как и в случае с аппаратным прерыванием, запускается процесс, на который в данный момент ссылается указатель proc„ptr. На рис. 2.19 сравниваются механизмы обработки аппаратного прерывания и системного вызова, использующего программные прерывания.

Рассмотрим процедуру _s_call более подробно. Еще одна метка, _p_s_call, — специфика 16-разрядной версии MINIX 3, в которой имеются раздельные процедуры для защищенного и реального режимов. В 32-разрядной версии обращение по любой из меток приводит к одному результату. Когда программист на С программирует системный вызов, он пишет код, выглядящий как обычный вызов функции, локальной или библиотечной. Поддерживающая системные вызовы библиотечная подпрограмма подготавливает соответствующее сообщение, помещает идентификатор процесса и адрес сообщения в регистры процессора и вызывает инструкцию int SYS3 86_VECTOR. Как было отмечено ранее, такая инструкция инициирует программное прерывание и управление передается подпрограмме _з_са11, перед вызовом которой в стек (в таблице процессов) помещается ряд регистров.

а б

Рис. 2.19. Сравнение механизмов обработки аппаратного прерывания и системного вызова: а — обработка аппаратного прерывания; б — так происходит системный вызов

Первая часть процедуры _s_call напоминает код save с раскрытыми макросами. Как и в коде save, процессор переключается на стек в ядре инструкцией

mov esp, k_stktop

Аналогично разрешаются прерывания. (Сходство программных и аппаратных прерываний проявляется и в том, что в обоих случаях прерывания перед входом в обработчик запрещаются.) Далее следует вызов процедуры _sys_call (строка 6672), которую мы рассмотрим разделом позже. Сейчас мы скажем только то, что она приводит к доставке сообщения и, следовательно, запускает планировщик. Таким образом, когда _sys_call выполняет возврат, указатель proc_ptr может не указывать на процесс, сделавший системный вызов. Затем управление передается restart.

Мы видим, что вызов „restart (строка 6681) происходит в трех случаях:

1. В функции main при запуске системы.

2. При выполнении перехода в функции hwint_master или hwint_slave после аппаратного прерывания.

3. После выполнения системного вызова, за счет того, что _s_call не содержит завершающей инструкции.

На рис. 2.20 показана упрощенная схема передачи управления между процессами и ядром через вызов „restart.

Рис. 2.20. Restart — код, исполняемый как после запуска системы, так и после прерываний и системных вызовов. После него запускается следующий запланированный процесс (как правило, отличный от прерванного). На диаграмме не показаны прерывания, происходящие во время выполнения ядра

В любом из трех случаев прерывания перед вызовом _restart запрещаются. К моменту, когда выполнение кода достигает строки 6690, следующий процесс уже выбран и не может быть изменен, поскольку прерывания запрещены. Таблица процессов разработана так, чтобы начинаться с кадра стека, поэтому следующая инструкция заносит в регистр указателя стека ссылку на кадр стека в таблице процессов:

тегу еэр, (_ргос_р1:г)

Далее выполняется инструкция

Иск: Р_Ы}Т_ЗЕЬ (еэр)

Эта инструкция загружает значение регистра локальной таблицы дескрипторов из кадра стека, заставляя процессор использовать сегменты памяти, принадлежащие тому процессу, который будет запущен. Следующая инструкция загружает из записи очередного процесса адрес кадра стека, который будет задействован при возникновении следующего прерывания, и помещает этот адрес в сегмент состояния задания (ТББ).

Первая часть подпрограммы _restart не нужна в том случае, если прерывание произошло в момент работы кода ядра, поскольку стек ядра уже используется, и после завершения обработки выполнение кода должно продолжиться с прерванного места. Тем не менее в МШ1Х 3 повторное вхождение в ядро не допускается, и обычные прерывания не могут происходить во время его работы. Метка гезЬагЬ1 (строка 6694) отмечает инструкцию, с которой в данном случае необходимо продолжить выполнение при появлении исключения во время работы ядра (будем надеятся, что это никогда не произойдет). В этом месте декрементируется значение переменной к_геепЬег, обозначая тем самым новый уровень вложения прерываний, а последующие команды восстанавливают состояние процессора. Завершающие инструкции модифицируют стек так, чтобы игнорировался адрес возврата, помещенный в стек при вызове s ave. Если прерывание произошло во время выполнения пользовательского процесса, завершающая инструкция iretd передает управление следующему процессу в очереди планировщика, при этом восстанавливаются его оставшиеся регистры, в том числе указатель и адрес сегмента стека. Но если управление было передано через restartl, то есть задействован не кадр стека, а стек ядра, это означает, что после завершения совсем не нужно переходить к новому процессу, а требуется завершить выполнение прерванного кода ядра. Процессор отслеживает подобную ситуацию, когда извлекает дескриптор сегмента кода из стека при выполнении iretd, и, обнаружив ее, оставляет использоваться стек ядра.

Теперь самое время сказать несколько слов об исключениях. Исключения вызываются различными ошибками выполнения, однако это не всегда плохо. Исключения полезны, чтобы побудить систему предоставить некоторые дополнительные услуги, например выделить программе дополнительную память или загрузить в оперативную память страницу, перемещенную в область подкачки (хотя в MINIX 3 подобные услуги не предусмотрены). Иногда исключения обусловлены ошибками в программах. Когда исключение возникает внутри ядра, это вызывает серьезный сбой системы. Если исключение происходит в пользовательской программе, ее можно завершить, но такой подход неприменим к операционной системе — она должна выполняться постоянно. Обрабатывают исключения так же, как и прерывания, то есть через дескрипторы в таблице дескрипторов прерываний. В этой таблице имеется шестнадцать записей, содержащих указатели на точки входа обработчиков исключений, начиная с _divide_error и заканчивая _copr_error, которые можно увидеть в конце файла шрхЗ 86 . s (строки 6707-6769). Каждая из этих точек входа передает управление процедуре exception (строка 6774) или errexception (строка 6785) в зависимости от того, помещается ли при исключении в стек код ошибки или нет. Обработка выполняется командами ассемблера и во многом напоминает уже рассмотренный нами код: значения регистров сохраняются в стеке, и вызывается С-функция „exception (обратите внимание на знак подчеркивания перед именем). Последствия исключений могут быть различными: некоторые игнорируются, некоторые вызывают сообщение о сбое ядра, другие посылают сигналы процессам. Самой функцией „exception мы займемся в следующем разделе.

Существует еще одна точка входа, которая обрабатывается как прерывание, — это _levelO_call (строка 6714). Она используется, когда код необходимо исполнить с нулевым (максимальным) уровнем привилегий. Эта точка входа находится в файле шрх3 86. s вместе с прерываниями и исключениями потому, что она также вызывается при помощи инструкции int . Как и обработчики исключений, она выполняет вызов save, а завершается инструкцией ret, ведущей к „restart. Ее мы рассмотрим в следующем разделе, когда мы познакомимся с кодом, нуждающемся в обычно недоступных (даже ядру) привилегиях.

Наконец, в конце ассемблерного файла выделяется место для хранения некоторых данных. Определяются два различных сегмента данных.

.sect.rom

Это объявление в строке 6822 гарантирует, что указанная область памяти находится в самом начале сегмента данных ядра. Сюда компилятор помещает сигнатуру (магическое число), чтобы программа boot могла убедиться, что загружает действительно ядро. При компиляции всей системы за магическим числом будут размещены различные строковые константы. Еще одна область данных задается следующей директивой (строка 6825):

.sect.bss

Эта директива резервирует в неинициализированном пространстве ядра место для стека, а выше него — память для переменных, применяемых обработчиками прерываний. Место под стек пользовательских процессов и серверов резервируется на этапе компоновки, а ядро устанавливает нужное значение дескриптора сегмента стека при их выполнении. О себе ядро должно позаботиться само.

2.6.9.

<< | >>
Источник: Э. ТАНЕНБАУМ, А. ВУДХАЛЛ. ОПЕРАЦИОННЫЕ СИСТЕМЫ Разработка и реализация 3-е издание. 2007

Еще по теме Обработка прерываний в MINIX:

  1. 7.3.2. Прерывания
  2. 7.4.4. Как справиться с прерыванием.
  3. 7.3.2. Прерывания
  4. Прерывание нотаций:
  5. Прерывание обвинений:
  6. Прерывание криков и ругани:
  7. 7.4.4. Как справиться с прерываниями
  8. ИНФОРМАЦИЯ: ОБРАБОТКА ПОСЛЕДОВАТЕЛЬНАЯ (
  9. ИНФОРМАЦИЯ: ОБРАБОТКА ПАРАЛЛЕЛЬНАЯ
  10. Статистическая обработка.