Глава 4. Управление задачами.
Переключение на задачу процессор производит автоматически и он сам использует дескриптор TSS и все поля из которых состоит сегмент состояния задачи.
Переключение производится командами далёкой передачи управления:
far jmp
| far call
| iret
| |
Используя задачи в своей операционной системе, вам необходимо их рассматривать как отдельные процессы, точнее - как самостоятельные программы, но ни в коем случае не как альтернативу процедурам. В принципе, можно построить всю ОС, используя для каждой сложной процедуры отдельную задачу, но такая ОС не будет эффективна. Во-первых, передача управления в далёкую процедуру с возвратом обратно занимает в 20 раз меньше времени, чем переключение на задачу и обратно. Это связано с тем, что при переключении на задачу происходит загрузка сегментных и системных регистров и для каждого процессор выполняет проверку параметров соответствующего дескриптора. Во-вторых, задачи и механизм их поддержки в процессоре предназначены для того, чтобы надёжно изолировать одну программу от остальных и реализовать настоящую мультизадачность.
Вспомните, как реализовывалась мультизадачность в режиме реальных адресов, точнее - в MS-DOS: благодаря множеству резидентных программ, "висящих" на различных прерываниях, реализуется "параллельная" работа нескольких программ, но на самом деле, весь MS-DOS со всеми его программами и резидентами можно рассматривать как одну большую программу, потому что разные "задачи" в ней не разделены между собой и могут запросто взаимодействовать с кодом и данными любой другой "задачи".
В защищённом режиме такого нет. Процессор попросту не допустит любой задаче выйти за поставленные ей рамки, которые и определяются содержимым TSS - его дескрипторами, LDT, CR3 и картой разрешения ввода/вывода. Любая попытка нарушить условия защиты приведёт к генерации исключения и операционная система будет "знать" о нарушителе всё. Что дальше с ним делать уже определяется идеологией самой ОС - можно безжалостно уничтожать задачи-нарушители, можно игнорировать нарушения, но не допускать их, а можно эмулировать выполнение запрещённых действий и "шпионить" за подозрительными задачами - например, вычислять вирусы.
Для того, чтобы переключиться с одной задачи на другую, нужно чтобы процессор уже выполнял задачу, т.е. переключиться на задачу можно только из задачи. Это значит, что, во-первых, просто запустить первую задачу не получится, нужны некоторые хитрости, о которых мы поговорим позже. А во-вторых, из задачи нельзя "вернуться" в обычную процедуру, т.е. однажды запустив мультизадачность, программа остаётся в ней до перехода в режим реальных адресов.
Переключение задач происходит следующим образом. Процессор сначала сохраняет параметры текущей задачи в её TSS, заполняя все поля, начиная с CR3. Потом он ищет в GDT дескриптор указанной новой задачи и после проверки всех параметров дескриптора, начинает загружать регистры значениями из полей нового TSS. При этом процессор контролирует значение каждого системного и сегментного регистра и если обнаруживает ошибку, то генерирует исключение. После загрузки всех значений, процессор записывает в поле Link (смещение 0 в TSS) селектор дескриптора предыдущей задачи и немного корректирует значение EFLAGS. После всего этого процессор передаёт управление по адресу CS:EIP и задача начинает свою работу (или продолжает, если она была прервана).
Как видите, переключение задачи довольно-таки трудоёмкий процесс и требует много времени на различные проверки и обращения к памяти. Кроме того, переключение на задачу не кэшируется буфером ветвлений, представленных в процессорах, начиная с Pentium и при переключении на задачу происходит сброс конвейера команд, что потребует перед выполнением первой команды задачи потерю времени на ожидание, пока заполнится линия кэша из памяти и пока команды пройдут по конвейеру. Дополнительные задержки происходят из-за того, что при чтении новых значений из TSS, происходит загрузка CR3, а это значит, что производится сброс буферов TLB и процессору понадобится обращаться к каталогу и таблицам страниц, даже если он только что с ними работал (загрузка значения в CR3, даже того же самого, что в нём и было, приводит к сбросу буферов TLB).
Наблюдая столько сложностей и проблем с этими задачами, возникает вполне логичный вопрос о целесообразности их применения, однако задачи процессором поддерживаются аппаратно и только из-за этого уже стоит их применять. К тому же, с каждым новым процессором Intel увеличивает производительность в первую очередь в таких проблемных местах, как переключение задач и в современных процессорах оно происходит очень быстро.
Из-за того, что переключение задач происходит относительно медленно и приводит к полному сбросу некоторых кэшей, переключение задач нужно использовать как можно реже и не применять отдельной задачи для одной процедуры. Например, вывод пикселя на экран не стоит делать одной задачей. То же самое относится к выводу символа или любого другого графического объекта, т.е. использование задач как альтернатива прерываниям MS-DOS (в данном случае - каждому сервису - отдельная задача) так же приведёт к неэффективному использованию мультизадачности. Такие вещи, как сервисы ОС, лучше выполнять в виде процедур или, в крайнем случае - прерываний, а задачами реализовывать прикладные программы и базовые модули ОС (например, менеджер дисковой подсистемы).
Передача управления от задачи к задаче может происходить тремя способами:
1. | Одна задача вызывает другую командой FAR CALL, а вторая задача возвращает управление первой командой IRET. |
2. | Задача переключается на другую задачу командой FAR JMP. |
3. | Происходит прерывание - либо аппаратное (IRQ), либо исключение и обработчик такого прерывания является отдельной задачей. Обработчик возвращает управление прерванной задаче командой IRET. |
Итак, команда IRET может производить переключение задач. Как вам известно, обычное назначение этой команды - возврат из прерывания, т.е. она отличается от RET тем, что извлекает из стека ещё и регистр флагов (FLAGS, если стек - 16-разрядный, EFLAGS - если 32-разрядный).
Как определить, что же именно выполнит данная команда - переключение на задачу или возврат из прерывания? Для этого в регистре флагов EFLAGS введён специальный флаг NT (Nested Task - вложенная задача). Каждый раз, когда процессор выполняет команду FAR CALL, вызывая задачу, этот флаг устанавливается в 1. При выполнении команды IRET процессор проверяет этот флаг - если он равен 1, то он переключается на предыдущую задачу (селектор которой хранится в поле Link по смещению 0 в TSS текущей задачи). Если NT = 0, происходит возврат из прерывания.
Если переключение на задачу было произведено командой FAR CALL либо оно произошло в результате прерывания, то процессор после загрузки контекста новой задачи установит флаг NT в регистре EFLAGS. Это обеспечит возврат в старую задачу командой IRET.
Обратите внимание на бит B (Busy) в дескрипторе TSS. Этот бит определяет занятость задачи. Каждый раз, когда процессор переключается на задачу, он устанавливает этот бит. Когда задача передаёт управление другой задаче командой FAR JMP или IRET, процессор сбрасывает флаг B в дескрипторе текущей задачи (той, из которой происходит передача управления).
Передача управления командой FAR CALL не сбрасывает флаг B. Если происходит прерывание, то этот флаг в прерванной задаче также не сбрасывается. Таким образом, установленный бит B показывает, что данная задача занята, т.е. она либо работает в данный момент, либо она прервана (находится в состоянии паузы).
Необходимость бита занятости B заключается в том, что процессор запрещает передачу управления занятой задаче (он генерирует исключение общей защиты). Это сделано для того, чтобы задачи не могли себя вызывать рекурсивно. Вот здесь проявляется отличие задачи от процедуры - задача, как программа, рекурсивной не бывает.
Однако, при помощи манипуляций непосредственно над содержимым дескрипторов TSS (сброс и установка флага B "вручную") можно добиться переключения на занятую задачу. Это иногда бывает необходимым, в частности, при запуске мультизадачности.
Дескриптор TSS содержит в байте прав доступа поле DPL (Descriptor Privilege Level). В данном случае, для дескриптора TSS, это поле содержит уровень привилегий, с которого процессор разрешает переключение на эту задачу. Переключения с меньшего уровня привилегий также разрешены. Другими словами, при передаче управления должно выполняться следующее условие:
CPL <= DPL,
где CPL - это Current Privilege Level - текущий уровень привилегий (т.е. тот, на котором выполняется код в данный момент).
Это ещё одно средство контроля целостности системы. Для системных задач поле DPL нужно устанавливать в 00b, для прикладных задач - в 11b, при этом системная задача, с DPL = 00b может переключаться на задачу с любым DPL, а, например, задача с DPL = 10b - на задачи с DPL = 10b и 11b.
Следующая глава | Оглавление | Вопросы? Замечания? Пишите: sasm@narod.ru |
Copyright © Александр Семенко. |