Что такое привилегированный режим процессора

Обновлено: 06.07.2024

Операционная система для осуществления своих управляющих функций должна иметь по отношению к приложениям определенные привилегии. Поэтому аппаратура компьютера поддерживает как минимум два режима:

пользовательский режим (user mode) - для работы приложений;

привилегированный режим, он же - режим ядра (kernel mode), или режим суперви­зора (supervisor mode) - для работы ОС или ее частей.

В привилегированном режиме чаще всего работает именно ядро как основная часть ОС. Понятия «ядро» и «привилегированный режим» тесно связаны, поэтому ядро так­же можно характеризовать как часть ОС, работающую в привилегированном режиме.

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

· переключением процессора с задачи на задачу;

· управлением устройствами ввода-вывода;

· доступом к механизмам распределения и защиты памяти.

В пользовательском режиме безусловно запрещено выполнение инструкции пере­хода в привилегированный режим. Другие инструкции запрещается выполнять при определенных условиях, полностью контролируемых ОС. Например, ввод-вывод дан­ных или доступ к памяти разрешены приложению, если соответствующие ресурсы выделены только этому приложению, и запрещены, если данные (соответственно па­мять) являются общими для ОС и других приложений.

Если аппаратура (процессор) поддерживает хотя бы два уровня привилегий, то ОС может на этой основе создать программным способом сколь угодно развитую систему защиты и соответствующих прав доступа. Прямого соответствия между числом аппаратно реализуемых и программно реализуемых уровней привилегий нет. Так, на базе четырех уровней процессоров архитектуры х86 OS/2 строит трехуровневую, а Windows NT и Unix - двухуровневую систему привилегий.

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

image

Вы наверняка интуитивно догадывались, что приложения, выполняемые на Intel x86 компьютерах, ограничены в своих возможностях, и что некоторые действия могут быть выполнены исключительно операционной системой. Но знаете ли вы, как это действительно работает? В данном посте рассмотрим уровни привилегий x86 — механизм, в котором ОС и процессор действуют сообща для того, чтобы ограничить то, что могут сделать user mode приложения.

Существуют четыре уровня привилегий, они пронумерованы от 0 (наиболее привилегированный уровень), до 3 (наименее привилегированный уровень), и три типа ресурсов, в отношении которых действуют механизмы защиты процессора: память, порты ввода / вывода и возможность выполнения некоторых инструкций. В любой момент, x86 процессор работает на определенном уровне привилегий, и от этого зависит, что может и чего не может сделать код. Уровни привилегий также часто называют кольцами защиты, которые изображаются в виде вложенных окружностей. Наиболее привилегированный уровень соответствует окружности с наибольшей степенью вложенности. Большинство современных ядер для архитектуры x86 используют всего два уровня привилегий — 0 и 3.

Перед тем как мы обратимся к рассмотрению механизма защиты, давайте посмотрим как именно процессор следит за текущим уровнем привилегий. К этому имеют непосредственное отношение сегментные селекторы (segment selectors), которые мы рассмотрели в предыдущем посте. Вот они:

image

Программист использует определенные инструкции для того, чтобы загрузить сегментный селектор для data сегмента в любой из сегментных регистров для данных (например SS или DS). При этом загружается все содержимое сегментного селектора, включая поле Requested Privilege Level (RPL), предназначение которого опишем немного ниже. Что касается CS регистра, то здесь все происходит несколько иначе. Во-первых, нельзя напрямую использовать load инструкции вроде mov для того, чтобы загрузить селектор в этот регистр. Вместо этого, содержимое регистра может измениться только как результат выполнения инструкции, которая управляет потоком выполнения, например far call. Во-вторых, и это для нас очень важно, вместо поля RPL, значение которого определяется программистом, селектор в CS регистре имеет поле Current Privilege Level (CPL), значение которого выставляется и контролируется самим процессором. Двухбитовое поле CPL в CS регистре всегда отражает текущий уровень привилегий, на котором работает процессор. Интелловские доки, в том числе те, которые опубликованы на просторах Интернета, могут иметь рассхождения по этому вопросу, однако, данный факт совершенно точно отражает суть вещей. В любой момент, чтобы там не происходило в самом процессоре, взглянув на значение поля CPL в CS регистре, мы всегда узнаем текущий уровень привилегий, с которым выполняется код.

Обратите внимание, текущий уровень привилегий процессора, не имеет ничего общего с привилегиями пользователями операционной системы. Совершенно не важно из под какой учетки вы работаете — root, Администратор, guest или обычный пользователь. Код всех пользовательских приложений выполняется в кольце 3, в то время как любой код, имеющий отношение к ядру, выполняется в кольце 0. Иногда некоторые типичные для ядра задачи могут выносить в user space – так, например, реализованы некоторые драйверы в Windows Vista, но это можно рассматривать всего лишь как частный случай, когда специального рода процессы просто выполняют некоторую работу для ядра. Обычно их можно убить без каких бы то ни было серьезных последствий.

Из-за существующих ограничений на доступ к памяти и портам ввода / вывода, выполняемая в user mode программа сама по себе фактически никак не может влиять на “окружающий мир” и ничего не может сделать без содействия ядра. Такая программа не может открыть файл, послать сетевой пакет, вывести на экран строчку текста, или выделить под себя память. Можно сказать, что пользовательские процессы выполняются в своего рода песочницах с кардинально урезанными возможностями, которые для них были уготованы “богами нулевого кольца”. Таким образом, утечка памяти, если таковая и вызвана процессом, не может пережить сам процесс, точно также как файлы, открытые во время жизнедеятельности процесса, не останутся открытыми после его завершения. Все структуры данных, используемые для контроля подобных вещей – выделенной памяти, открытых файлов – недоступны для пользовательского кода; как только программа завершает выполнение, её “песочница” уничтожается ядром. Именно поэтому современные сервера могут иметь по 600 часов аптайма – если железо или ядро не заглючит, все может проработать вечно. И это, кстати, причина, по которой Windows 95 / 98 так часто падала: нет, это не потому что “M$ — отстой”, просто для обеспечения обратной совместимости некоторые важные структуры данных оставили доступными для user mode приложений. В то время, это наверное был разумный компромисс, но обошелся он весьма дорогой ценой.

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

image

Чем число больше, тем меньший уровень привилегий оно обозначает. Таким образом, функция MAX() выбирает значение, выражающее наименьший уровень привилегий (будь то CPL или RPL), которое затем сравнивается с уровнем привилегий целевого дескриптора (DPL). Если DPL больше или равен, до доступ разрешается. Весь смысл в использовании RPL в этой формуле заключается в том, что это позволяет ядру получать доступ к сегменту с умышленно заниженным уровнем привилегий, если это необходимо. Например, можно использовать селектор, в котором задан RPL равный 3 для того, ограничить некоторую операцию возможностью работать только с user mode сегментами данных. Проверка при загрузке селектора в SS регистр другая, здесь для успешного её прохождения все три значения CPL, RPL и DPL должны совпадать.
В действительности, защита на уровне сегментов особой роли не играет, так как современные ядра используют «плоскую» модель организации памяти, в которой user mode сегмент покрывает все доступное физическое адресное пространство. Сколь-нибудь полезная защита памяти осуществляется в paging unit при преобразовании линейного адреса в физический. Каждая страница памяти представляет собой последовательность байт, которая описывается записью в page table. В этой записи два поля имеют отношение к механизмам защиты, а именно supervisor флаг и read/write флаг. Supervisor флаг – основной механизм защиты, используемый современными ядрами. Когда данный флаг сброшен, к странице нельзя обратиться из кольца 3. Хотя read/write флаг не играет никакой роли при проверке привилегий, он все равно находит интересное применение. При загрузке программы в память на выполнение, страницы памяти, хранящие исполняемый образ программы, помечаются как доступные только для чтения. Это позволяет отловить некоторые ошибки при работе с указателями, если с помощью них производится попытка осуществить запись в эти страницы. Read/write флаг также используется для реализации механизма copy on write при создании дочернего процесса с помощью системного вызова fork() в Unix-подобных ОС. Когда производиться форк, страницы памяти родительского процесса помечаются как доступные только для чтения. Дочерний процесс изначально будет использовать те же самые страницы памяти, что и родительский процесс. Если кто либо из них попытается осуществить запись в страницу памяти, процессор инициирует fault, и ядро отработает его следующим образом – оно создает для процесса, попытавшегося осуществить запись, собственный экземпляр страницы, а также выставит на эту страницу read / write права.

Идем дальше. Нам необходим механизм, который позволил бы CPU переключаться между разными уровнями привилегий. Если бы код, выполняющийся в кольце 3, мог передавать управление в произвольные места в ядре, можно было бы легко обойти защитные механизмы операционной системы просто осуществив jmp по неправильному (или правильному?) адресу. Чтобы воспрепятствовать этому, нужен механизм, обеспечивающий контролируемый трансфер потока выполнения. Он реализован на основе т.н. gate дескрипторов или инструкции sysenter. Gate дескриптор – это сегментный дискриптор, имеющий тип «system». Существуют четыре его разновидности: call-gate, interrupt-gate, trap-gate и task-gate дескрипторы. Посредством сall-gate дескриптора, ядро может предоставить точку входа, которую можно использовать с помощью обычных инструкций типа far call и far jump. Call-gate дескрипторы используются нечасто, поэтому рассказывать о них не будем. Task-gate дескрипторы нам то же мало интересны (в Linux они используются только при обработке double faults, которые обычно случаются из-за проблем с ядром или железом).

Остаются два других гораздо более интересных типа дескрипторов: interrupt-gate и trap-gate, которые используются для обработки хардверных прерываний (клавиатура, таймеры, диски) и исключений (page faults, деление на ноль). Оба эти типа механизмов я буду условно называть «прерывания». Данный тип дескрипторов хранится в таблице дескрипторов прерываний (IDT). Каждому прерыванию устанавливается в соответствие идентификационный номер от 0 до 255, называемый вектором. Когда нужно определить какой дескриптор использовать для обработки прерывания, процессором использует вектор в качестве указателя на дескриптор в IDT. Формат interrupt-gate и trap-gate дескрипторов фактически идентичен. Данный формат, а также проверка привилегий, которая совершается, когда происходит прерывание, изображены на рисунке. Я заполнил некоторые поля дескриптора теми значениями, которые обычно используются ядром Linux, чтобы добавить конкретики:

image

Доступ контролируется на основании текущего СPL и DPL целевого сегмента, а точка входа определяется на основе селектора и поля Offset в gate-дескрипторе. В современных ядрах, сегментный селектор, содержащийся в соответствующем поле gate-дескриптора, обычно выбирает code сегмент ядра. Механизм прерываний устроен так, что не может быть использован для передачи управления из более привилегированного кольца в менее привилегированное кольцо. Уровень привилегий должен либо остаться на прежнем уровне, либо повыситься (так происходит, когда, например, осуществляется прерывание user mode приложения). В любом случае, новое значение CPL будет равно DPL целевого code сегмента. В ситуации, когда CPL меняется, также происходит автоматическое переключение stack сегмента. Если прерывание является программным (вызвано выполнением инструкции INT n, например), дополнительно осуществляется еще одна проверка: gate DPL должен быть равен или больше чем исходный CPL. Данная проверка призвана сделать вызов некоторых обработчиков прерываний недоступным для пользовательского кода. В Linux, все обработчики прерываний выполняются в кольце 0.

Во время инициализации, функция ядра Linux setup_idt() создает IDT таблицу без указания конкретных точек входа в дескрипторах. Затем дескрипторы будут заполнены данными в соответствии с содержанием файлов include/asm-x86/desc.h и arch/x86/kernel/traps_32.c. В терминологии Linux, дескриптор, который имеет в своем названии слово «system», доступен для использования user mode кодом, и для него устанавливается gate DPL равный 3. “System gate” – это интелловский trap-gate, доступный для использования user mode кодом. Помимо этого, отличий в терминологии больше нет. Хардверные прерывания настраиваются не здесь, а в соответствующих драйверах.

Три гейта доступны для использования в user mode: векторы 3 и 4 используются для отладки и проверки на числовые переполнения, соответственно. Затем идёт system gate, имеюший ID равный значению константы SYSCALL_VECTOR — для x86 архитектуры это 0x80. Раньше это был основной механизм для передачи управления ядру при осуществлении системного вызова. Было время, когда и я хотел себе блатной номер с символами “int 0x80”. Начиная с Pentium Pro была добавлена новая инструкция sysenter, предназначенная для более быстрого осуществления системного вызова. Данная инструкция использует специальные регистры, которые хранят информацию о code сегменте, точку входа и т.п. При выполнении инструкции sysenter не производится проверка уровня привилегий, процессор сразу же переключается в CPL 0 и загружает соответствующие значения в регистры, имеющие отношения к коду и стеку (CS, EIP, SS и ESP). Загрузка значений в регистры, используемые инструкцией sysenter, возможна только из колца 0 и осуществляется функцией enable_sep_cpu().

Наконец, когда настает время вернуться в кольцо 3, ядро использует инструкцию IRET или SYSEXIT для того, чтобы вернуть контроль после обработки прерывания или системного вызова, соответственно. В результате, мы покидаем кольцо 0, и возобнавляется выполнение user mode кода с CPL равным 3. Vim подсказывает мне, что количество слов уже близко к отметке 1900, поэтому оставим тему портов ввода / вывода на потом. Всем спасибо за внимание!

Процессоры M-профиля по режимам работы кардинально отличаются от процессоров A- и R-профилей.

Содержание

В привилегированном режиме (privileged execution) процессор может выполнять все команды и обращаться к любым областям памяти. Этот режим поддерживается в любой реализации.

Привилегированность выполняемого кода зависит от режима обработчика/потока (код режима потока всегда привилегированный) и от состояния бита nPRIV регистра CONTROL.

В режим обработчика (handler mode) процессор автоматически переходит при входе в любой обработчик прерывания, кроме сброса. В этом режиме всегда выполняется привилегированный код, использующий основной стек (указатель его вершины находится в регистре SP_main, или MSP).

В режиме потока (thread mode) выполняется код, не связанный с обработкой прерываний, а также код, получающий управление в результате сброса. Код режима потока может быть и привилегированным, и непривилегированным; он может использовать как основной стек, так и стек процесса (указатель в регистре SP_process, или PSP), что определяется битом SPSEL регистра CONTROL.

В части, касающейся использования стека, документация на ARMv7-M несколько противоречива. С одной стороны, утверждается, что в режиме обработчика всегда используется основной стек. С другой стороны, согласно приводимому псевдокоду, если в режиме обработчика регистр CONTROL задаёт использование стека процесса, поведение процессора не определено. Таким образом, для надёжности необходимо обеспечить, чтобы в режиме обработчика регистр CONTROL всегда задавал использование основного стека (т. е. не пытаться, находясь в режиме обработчика, установить бит CONTROL.SPSEL).

Процессор может переходить в состояние останова при наступлении некоторого отладочного события. Когда процессор находится в таком останове, говорят, что он находится в режиме отладки (debug mode).

Режим отладки может быть не реализован в конкретном варианте. Кроме того, хотя регистры, управляющие отладкой, доступны для самих отлаживаемых процессоров M-профиля, для процессоров A/R-профилей аналогичные регистры недоступны, поскольку отладчик для обращения к ним использует отдельную шину. Таким образом, даже без учёта разницы в системной архитектуре отладочные режимы в M- и A/R-профилях являются практически несовместимыми.

Для надежного управления ходом выполнения приложений операционная система должна иметь по отношению к приложениям определенные привилегии. Иначе некорректно работающее приложение может вмешаться в работу ОС и, например, разрушить часть ее кодов. Операционная система должна обладать исключительными полномочиями также для того, чтобы играть роль арбитра в споре приложений за ресурсы компьютера в мультипрограммном режиме. Ни одно приложение не должно иметь возможности без ведома ОС получать дополнительную область памяти, занимать процессор дольше разрешенного операционной системой периода времени, непосредственно управлять совместно используемыми внешни­ми устройствами.

Обеспечить привилегии операционной системе невозможно без специальных средств аппаратной поддержки. Аппаратура компьютера должна поддерживать как минимум два режима работы - пользовательский режим (user mode) и привилегированный режим, который также называют режимом ядра (kernel mode) или режимом супервизора (supervisor mode). Подразумевается, что операционная система или некоторые ее части работают в привилегированном режиме, а приложения - в пользовательском режиме.

Так как ядро выполняет все основные функции ОС, то чаще всего именно ядро становится той частью ОС, которая работает в привилегированном режиме. Иногда это свойство - работа в привилегированном режиме - служит основным определением понятия «ядро».

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

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

Очень важно, что механизмы защиты памяти используются операционной системой не только для защиты своих областей памяти от приложений, но и для защиты областей памяти, выделенных ОС какому-либо приложению, от остальных приложений. Говорят, что каждое приложение работает в своем адресном пространстве. Это свойство позволяет локализовать некорректно работающее приложение в собственной области памяти, так что его ошибки не оказывают влияния на остальные приложения и операционную систему. Между количеством уровней привилегий, реализуемых аппаратно, и количеством уровней привилегий, поддерживаемых ОС, нет прямого соответствия. Так, на базе четырех уровней, обеспечиваемых процессорами компании Intel, операционные системы Windows NT, UNIX и некоторые другие ограничиваются двухуровневой системой.

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

Отметим, что повышение устойчивости операционной системы, обеспечиваемое переходом ядра в привилегированный режим, достигается за счет некоторого замедления выполнения системных вызовов. Системный вызов привилегированного ядра инициирует переключение процессора из пользовательского режима в привилегированный, а при возврате к приложению - переключение из привилегированного режима в пользовательский (рис. 2.1). Во всех типах процессоров из-за дополнительной двукратной задержки переключения переход на процедуру со сменой режима выполняется медленнее, чем вызов процедуры без смены режима.




Работа приложений Пользовательский режим

Работа ядра Привилегированный режим

Время переключения режимов

Рис. 2. 1. Смена режимов при выполнении системного вызова
к привилегированному ядру

Архитектура ОС, основанная на привилегированном ядре и приложениях пользовательского режима, стала, по существу, классической. Ее используют многие популярные операционные системы, в том числе многочисленные версии UNIX, VAX VMS, IBM OS/390, OS/2, и с определенными модификациями - Windows NT.

В некоторых случаях разработчики ОС отступают от этого класси-ческого варианта архитектуры, организуя работу ядра и приложений в одном и том же режиме. Так, известная специализированная операционная система NetWare компании Novell использует привилегированный режим процессоров Intel x86/ Pentium как для работы ядра, так и для работы своих специфических приложений – загружаемых модулей NLM. При таком построении ОС обращения приложений к ядру выполняются быстрее, так как нет переключения режимов, однако при этом отсутствует надежная аппаратная защита памяти, занимаемой модулями ОС, от некорректно работающего приложения. Разработчики NetWare пошли на такое потен-циальное снижение надежности своей операционной системы, поскольку ограниченный набор ее специализированных приложений позволяет компенсировать этот архитектурный недостаток за счет тщательной отладки каждого приложения.

Читайте также: