![]() Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Распределение исключений
В отличие от прерываний, которые могут возникать непредсказуемо, исключения — это прямой результат выполнения программы. Microsoft С определяет архитектуру программирования, известную как структурная обработка исключений, которая обеспечивает приложениям унифицированную реакцию на исключения. Все исключения, кроме тех, которые достаточно просты и обрабатываются непосредственно обработчиком ловушки, обслуживаются модулем ядра, диспетчером исключений (exception dispatcher) (см. рис. 7-6). Этот модуль зависит от архитектуры процессора, но написан на С. Задача диспетчера исключений состоит в том, чтобы найти обработчик исключений, который может данное исключение " устранить". Ниже приведен список машинно-независимых исключений, определенных ядром:
Некоторые из этих исключений перехватываются и обрабатываются ядром незаметно для пользовательских программ. Например, если во время выполнения отлаживаемой программы встречается точка останова, то генерируется исключение, которое ядро обрабатывает путем вызова отладчика. Некоторые другие исключения ядро обрабатывает, возвращая вызывающей программе код ошибки. Некоторым исключениям позволено " нетронутыми" выходить обратно в пользовательский режим. Например, нарушение защиты памяти или арифметическое переполнение генерируют исключения, которые ОС не обрабатывает. Для таких исключений подсистема среды или приложение могут задать при помощи специальных конструкций языка высокого уровня блочные обработчики исключений (frame based exception handlers). Microsoft С — это первый компилятор Microsoft, поддерживающий структурную обработку исключений, однако средства обработки исключений Windows NT не зависят от языка программирования. Термин блочный обозначает связь обработчика исключений с активизацией определенной процедуры. При вызове процедуры в стеке создается стековый фрейм, представляющий данную активизацию процедуры. Со стековым фреймом может быть связан один или несколько обработчиков исключений, каждый из которых защищает какой-либо блок исходного кода программы. Когда возникает исключение, ядро ищет обработчик, связанный с текущим стековым фреймом. Если его нет, то выполняется поиск обработчика для предыдущего фрейма и так далее, пока блочный обработчик исключений не будет найден. Если найти обработчик не удается, то ядро вызывает собственные обработчики исключений по умолчанию. (Обратите внимание, что обработка исключении реализована по-разному для разных процессоров. Реализация для Intel х8б применяет подход, использующий стековые фреймы, в то время как реализация для MIPS R4000 — табличный метод.) При возникновении исключения, будь оно явно возбуждено программой или неявно оборудованием, в ядре происходит цепочка событий. Управление передается обработчику ловушки, который создает кадр ловушки (так же, как это делается при обработке прерывания). Этот кадр позволяет ОС возобновить выполнение с места возникновения исключения, если последнее было успешно обработано. Кроме того, обработчик ловушки создает запись исключения, которая содержит причину исключения и привходящую информацию. Если исключение произошло в режиме ядра, диспетчер исключений просто вызывает процедуру для поиска блочного обработчика, который будет обслуживать данное исключение. Так как необработанные исключения режима ядра рассматриваются как фатальные ошибки ОС, то Вы можете считать, что диспетчер всегда находит некоторый обработчик. При возникновении исключения в пользовательском режиме диспетчер исключений выполняет более сложные действия. Подсистема среды может установить для каждого созданного ею процесса порт отладки и порт исключений. Они используются ядром при обычной обработке исключений, как показано на рис. 7-10. Часто источниками исключений являются отладочные точки останова. Поэтому первым действием диспетчера исключений становится посылка сообщения (через LPC) в порт отладки, связанный с процессом, в котором произошло исключение. Это дает возможность пользователю манипулировать структурами данных и вводить команды отладчика. Если порт отладки не зарегистрирован или отладчик не обработал исключение, диспетчер исключений переключается в пользовательский режим и вызывает процедуру поиска блочного обработчика исключений.
![]() рис. 7-10. Распределение исключения.
Если обработчик не найден или ни один из них не обработал исключение, то диспетчер исключений переключается обратно в режим ядра и снова вызывает обработчик, что бы пользователь мог предпринять дополнительные действия для отладки. Если отладчик отсутствует и блочный обработчик не найден, ядро посылает сообщение в порт исключений процесса потока. Этот порт, если он существует, был зарегистрирован подсистемой среды, управляющей данным потоком. Порт исключений даёт подсистеме среды, которая, как предполагается, его просматривает, возможность трансляции исключений NT в специфичный для данной среды сигнал или исключение. Например, когда POSIX получает от ядра сообщение о том, что один из её потоков сгенерировал исключение, эта подсистема посылает потоку, вызвавшему исключение, сигнал POSIX-типа. Хотя подсистема POSIX связывает по умолчанию порт исключений с каждым из своих процессов, другие подсистемы могут не зарезервировать порт или не предпринять никаких действий, когда получат от ядра уведомление о необработанном исключении в одном из процессов. Если обработка исключений продвинулась до этой точки и подсистема не обслужила исключение, ядро вызывает обработчик исключения, а он просто завершает процесс, поток которого вызвал исключение.
Распределение вызовов системных сервисов Как показано на рис.7-6, обработчик ловушки ядра NT распределяет прерывания, исключения и вызов системных сервисов. Вызовы системных сервисов, генерирующие ловушки, которые в NT рассматриваются как исключения, интересны с точки зрения расширяемости системы. Способ реализации системных сервисов ядром позволяет динамически добавлять к ОС новые сервисы в будущих версиях. Всякий раз, когда поток пользовательского режима вызывает системный сервис, ему вдруг разрешается выполнять привилегированный код ОС – это беда ОС. Поток пользовательского режима может “залезть” в структуры данных ОС или перепутать содержимое памяти, повредив таким образом, самой системе и другим пользователям. По этой причине процессоры обычно предоставляют специальную команду только для системных сервисов. Эта команда — syscall па процессорах MIPS и 1NT 2Eh на Intel x86 — применяется если поток пользовательского режима вызывает системный сервис. Аппаратура генерирует ловушку и переключается из пользовательского режима в режим ядра. Когда это происходит, ядро копирует значения параметров сервиса из стека пользовательского режима в стек режима ядра потока (чтобы пользователь никак не мог их изменить) и затем выполняет системный сервис. Как показано на рис. 7-11, для поиска системных сервисов ядро использует таблицу распределения системных сервисов. Эта таблица похожа на таблицу распределения прерываний, описанную выше, только каждый элемент ее содержит указатель на системный сервис, а не на процедуру обработки. Использование таблицы распределения системных сервисов делает базовые сервисы NT расширяемыми. Поддержка новых сервисов добавляется в ядро просто путем расширения этой таблицы, без изменений в ОС или в приложениях. Теоретически после создания кода нового сервиса системный администратор может просто запустить утилиту, которая динамически создает новую таблицу распределения с дополнительным элементом, указывающим на новый системный сервис. Хотя в первой версии Windows NT не присутствуют ни эта возможность, ни соответствующий интерфейс пользователя, они могут быть добавлены в будущем. Рис. 7-11. Исключения системных сервисов.
60. Многопроцессорная синхронизация Концепция взаимного исключения (mutual exclusion) является критической при разработке ОС. Она подразумевает, что в любой момент времени доступ к данному ресурсу может иметь один и только один поток. Взаимное исключение необходимо, когда ресурс самостоятельно не поддерживает совместный доступ или когда совместное использование может давать непредсказуемые результаты. Например, если два потока одновременно копируют файлы в порт принтера, результаты вывода могут перемешаться. Аналогично, если один поток считывает область памяти в тот момент, когда другой в нее записывает, первый поток получит непредсказуемые данные. В общем случае изменяемые ресурсы не могут использоваться совместно без определенных ограничений, тогда как для не модифицируемых ресурсов это допустимо. На рис. 7-12 показано, что происходит, когда два потока, выполняющиеся на разных процессорах, выполняют запись в циклическую очередь. Так как второй поток получает указатель на конец очереди до того, как первый поток его обновит, второй поток поместит данные на то же место, что и первый — затерев его данные и оставив одно место в очереди пустым. Обратите внимание, что, хотя на рисунке показан случай многопроцессорной системы, то же самое может произойти и на однопроцессорном компьютере, если ОС переключит контекст на второй поток, прежде чем первый обновит указатель хвоста очереди. Участки кода, в которых выполняется доступ к ресурсу, не поддерживающему совместное использование, называются критическими секциями (critical sections). Чтобы гарантировать корректную работу, в критической секции может одновременно выполняться не более одного потока. Пока один поток записывает данные в файл, обновляет базу данных или модифицирует совместно используемую переменную, никакому другому потоку не разрешен доступ к тому же ресурсу. Рис. 7-12. Некорректное совместное использование памяти. Код на рис. 7-12 — критическая секция, в которой некорректно, без взаимного исключения, осуществляется доступ к совместно используемым данным Вопрос взаимного исключения, существенный для всех ОС, особенно важен (и сложен) для тесно связанных (tightly-coupled) ОС с симметричной мультипроцессорной обработкой (symmetric multiprocessing), таких как Windows NT; здесь один и тот же системный код выполняется на нескольких процессорах одновременно, совместно используя структуры данных в глобальной памяти. В Windows NT предоставление механизмов, которые системный код использует для предотвращения одновременной модификации структуры данных двумя потоками, — это задача ядра. Ядро обеспечивает примитивы взаимного исключения; оно и другие компоненты исполнительной системы используют их для синхронизации доступа к глобальным структурам данных. Синхронизация на уровне ядра На различных стадиях работы ядро должно гарантировать, что в каждый момент времени в критической секции исполняется один и только один процессор. Критические секции ядра — это сегменты кода, модифицирующие глобальные структуры данных, например, базу данных диспетчера ядра или очередь DPC. Операционная система не будет работать нормально, если ядро не гарантирует, что потоки осуществляют доступ к этим данным в режиме взаимного исключения. Область, требующая самого большого внимания, — прерывания. Например, в момент изменения ядром глобальной структуры данных может возникнуть прерывание, обработчик которого также изменяет эту структуру. Простые однопроцессорные ОС иногда предотвращают такие случаи, запрещая прерывания на время доступа к глобальным данным, но ядро NT применяет более тонкое решение. Прежде чем использовать глобальный ресурс, ядро временно маскирует те прерывания, обработчики которых также используют этот ресурс. Это делается путем повышения IRQL процессора до самого высокого уровня (из тех, что используются источниками прерываний, обращающимися к глобальным данным). Например, прерывание уровня диспетчерский/DPC вызывает исполнение диспетчера, который обращается к базе данных диспетчера. Следовательно, любая другая часть ядра, использующая эту базу данных, повышает IRQL до отметки диспетчерский/DPC, маскируя прерывания этого уровня перед использованием базы данных. Такая стратегия прекрасно подходит для однопроцессорной системы, но неадекватна в многопроцессорной конфигурации. Повышение IRQL на одном процессоре не предотвращает прерываний на других. Ядру также необходимо обеспечить взаимоисключающий доступ для нескольких процессоров. Механизм, используемый ядром для достижения многопроцессорного взаимного исключения, называется спин-блокировкой (spin lock). Спин-блокировка — это механизм, связанный с глобальной структурой данных, например очередью DPC (как на рис. 7-1, 3). Прежде чем войти в показанную на рисунке критическую секцию, ядро должно получить спин-блокировку, связанную с защищенной очередью DPC. Если блокировка не свободна, то ядро пытается получить ее, пока это не увенчается успехом. Спин-блокировка названа так потому, что ядро (и, следовательно, процессор) изолировано и " вращается само по себе" ', пока не получит блокировку. Спин-блокировки, как и защищаемые ими структуры данных, располагаются в глобальной памяти. Код получения и освобождения спин-блокировки написан на языке ассемблера, чтобы повысить скорость работы и использовать механизм блокировки, предоставляемый данной архитектурой процессора. Во многих архитектурах спин-блокировка реализована при помощи команды " проверить и установить", которая одной (атомарной) операцией проверяет значение переменной блокировки и захватывает блокировку. Проверка и захват одной командой предотвращает захват блокировки вторым потоком (в интервале времени между проверкой значения переменной и захватом блокировки первым потоком). Когда поток пытается получить спин-блокировку, вся другая активность на этом процессоре приостанавливается. Поэтому поток, получивший такую блокировку, никогда не вытесняется и имеет возможность продолжать выполнение, чтобы быстрее освободить ее. Ядро использует спин-блокировки с большой осторожностью, минимизируя количество команд, выполняемых во время обладания ими. Спин-блокировки доступны другим частям исполнительной системы через набор функций ядра. Драйверы устройств, например, используют спин-блокировки для того, чтобы к регистрам устройств и другим глобальным данным в любой момент времени имела доступ только одна часть драйвера (и только с одного процессора). Рис. 7-13. Использование спин-блокировки.
|