End main

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

32-разрядные микропроцессоры отличаются расширенным набором команд, часть которых относится к привилегированным. Для того, чтобы разрешить транслятору обрабатывать эти команды, в текст программы необходимо вклю­чить директиву ассемблера.386Р.

Программа начинается с описания структуры дескриптора сегмента. В отли­чие от реального режима, в котором сегменты определяются их базовыми адре­сами, задаваемыми программистом в явной форме, в защищенном режиме для каждого сегмента программы должен быть определен дескриптор – 8-байтовое поле, в котором в определенном формате записываются базовый адрес сегмента, его длина и некоторые другие характеристики (рисунок 1.8) [8].

Рисунок 1.8 – Дескриптор сегмента

Рисунок 1.9 – Селектор дескриптора

Теперь для обращения к требуемому сегменту программист заносит в сег­ментный регистр не сегментный адрес, а так называемый селектор (рисунок 1.9), в состав которого входит номер (индекс) соответствующего сегмента дескриптора.

Процессор по этому номеру находит нужный дескриптор, извлекает из него базовый адрес сегмента и, прибавляя к нему указанное в конкрет­ной команде смещение (от­носительный адрес), форми­рует адрес ячейки памяти. Индекс дескриптора (0, 1, 2 и т.д.) записывается в се­лектор, начиная с бита 3, что эквивалентно умножению его на 8. Таким образом, можно считать, что селекторы последовательных дескрипторов представляют со­бой числа 0, 8, 16, 24 и т.д. Другие поля селектора, которые для нашего случая принимают значения 0, будут описа­ны ниже.

Структура descr (строка 8 листинга 1.1) предоставляет шаблон для дескрипторов сегментов, облег­чающий их формирование. Сравнивая описание структуры descr в программе с рисунком 1.8, нетрудно заметить их соответствие друг другу.

Рассмотрим вкратце содержимое дескриптора. Граница (limit) сег­мента представляет собой номер последнего байта сегмента. Так, для сегмента размером 375 байт граница равна 374. Поле границы состоит из 20 бит и разбито на две части. Как видно из рисунка 1.8, младшие 16 бит границы занимают байты 0 и 1 дескриптора, а старшие 4 бита входят в байт атрибутов 2, занимая в нем биты 0...3. Получается, что размер сегмента ограничен величиной 1 Мбайт. На самом деле это не так. Граница может указываться либо в байтах (и тогда, действитель­но, максимальный размер сегмента равен 1 Мбайт), либо в блоках по 4 Кбайт (и тогда размер сегмента может достигать 4 Гбайт). В каких единицах задается гра­ница, определяет старший бит байта атрибутов 2, называемый битом дробности. Если он равен 0, граница указывается в байтах; если 1 – в блоках по 4 килобайта.

База сегмента (32 бита) определяет начальный линейный адрес сегмента в адресном пространстве процессора. Линейным называется адрес, выраженный не в виде комбинации сегмент-смещение, а просто номером байта в адресном про­странстве. Казалось бы, линейный адрес – это просто другое название физическо­го адреса. Для нашего примера это так и есть, в нем линейные адреса совпадают с физическими. Однако если в процессоре включен блок страничной организация памяти, то процедура преобразования адресов усложняется. Отдельные блоки размером 4 Кбайт (страницы) линейного адресного пространства могут произвольным образом отображаться на физические адреса, в частности и так, что большие линейные адреса отображаются на начало физической памяти, и наобо­рот. Страничная адресация осуществляется аппаратно (хотя для ее включения требуются определенные программные усилия) и действует независимо от сег­ментной организации программы. Поэтому во всех программных структурах за­щищенного режима фигурируют не физические, а линейные адреса. Если стра­ничная адресация выключена, эти линейные адреса совпадают с физическими, если включена – могут и не совпадать.

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

Поскольку в дескриптор записывается 32-битовый линейный базовый адрес (номер байта), сегмент в защищенном режиме может начинаться на любом байте, а не только на границе параграфа, и располагаться в любом месте адресного про­странства 4 Гбайт.

Поле базы, как и поле границы, разбито на 2 части: биты 0...23 занимают байты 2, 3 и 4 дескриптора, а биты 24...31 – байт 7. Для удобства программного обращения в структуре descr база описывается тремя полями: младшим словом (base_l – строка 10 листинга) и двумя байтами: средним (base_m – строка 11 листинга) и старшим (base_h – строка 15 листинга).

В байте атрибутов 1 задается ряд характеристик сегмента. Не вдаваясь пока в подробности этих характеристик, укажем, что в рассмотренном примере используются сег­менты двух типов: сегмент команд, для которого байт attr_1 (строка 12 листинга) должен иметь значе­ние 98h, и сегмент данных (или стека) с кодом 92h.

Некоторые дополнительные характеристики сегмента указываются в стар­шем полубайте байта attr_2 (в частности, бит дробности). Для всех наших сег­ментов значение этого полубайта равно 0.

Сегмент данных data (строка 18 листинга), который для удобства изучения функционирования программы расположен в начале программы, до сегмента команд, объявлен с ти­пом использования use16 (так же будет объявлен и сегмент команд). Этот описа­тель объявляет, что в данном сегменте будут по умолчанию использоваться 16-битовые адреса. Если бы мы готовили нашу программу для работы под управле­нием операционной системы защищенного режима, реализующей все возможно­сти микропроцессора, тип использования был бы use32. Однако наша программа будет запускаться под управлением DOS, которая работает в реальном режиме с 16-битовыми адресами и операндами.

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

Помимо единственной таблицы глобальных дескрипторов, обозначаемой GDT от Global Descriptor Table, в памяти может находиться множество таблиц локальных дескрипторов (LDT от Local Descriptor Table). Разница между ними в том, что сегменты, описываемые глобальными дескрипторами, доступны всем за­дачам, выполняемым процессором, а к сегментам, описываемым локальными де­скрипторами, может обращаться только та задача, в которой эти дескрипторы описаны. Поскольку пока мы имеем дело с однозадачным режимом, локальная таблица нам не нужна.

Поля дескрипторов для наглядности заполнены конкретными данными яв­ным образом, хотя объявление структуры descr с нулями во всех полях позволяет описать дескрипторы несколько короче [8], например:

gdt_null descr<>; Селектор 0 – обязательный нулевой дескриптор

gdt_data descr<data_size-1,,,92h>; Селектор 8 – сегмент данных

В дескрипторе gdt_data (строка 23 листинга), описывающем сегмент данных программы, заполня­ется поле границы сегмента (фактическое значение размера сегмента data_size будет вычислено компилятором, строка 30 листинга), а также байт атрибутов 1. Код 92h говорит о том, что это сегмент данных с разрешением записи и чтения. Базу сегмента, т.е. физический адрес его начала, придется вычислить программно и занести в дескриптор уже на этапе выполнения.

Дескриптор gdt­_code (строка 25 листинга) сегмента команд заполняется схожим образом. Код ат­рибута 98h обозначает, что это исполняемый сегмент, к которому, между прочим, запрещено обращение с целью чтения или записи. Таким образом, сегменты ко­манд в защищенном режиме нельзя модифицировать по ходу выполнения про­граммы.

Дескриптор gdt_stack (строка 27 листинга) сегмента стека имеет, как и любой сегмент данных, код атрибута 92h, что разрешает его чтение и запись, и явным образом заданную гра­ницу 255 байтов, что соответствует размеру стека. Базовый адрес сегмента стека так же будет вычислен на этапе выполнения программы.

Последний дескриптор gdt_screen (строка 29 листинга) описывает страницу 0 видеобуфера. Раз­мер видеостраницы, как известно, составляет 4096 байтов, поэтому в поле границы указано число 4095. Базовый физический адрес страницы известен, он равен B8000h. Младшие 16 разрядов базы (число 8000h) заполняют слово base_l дескрипто­ра, биты 16... 19 (число 0bh) – байт base_m. Биты 20...31 базового адреса равны 0, поскольку видеобуфер размещается в первом мегабайте адресного пространства.

Перед переходом в защищенный режим процессору надо будет сообщить физический адрес таблицы глобальных дескрипторов и ее размер (точнее, грани­цу). Размер GDT определяется на этапе трансляции в строке 30.

Назначение оставшихся строк сегмента данных станет ясным в процессе рас­смотрения программы.

Сегмент команд text (строка 41 листинга) начинается, как и всегда, оператором segment, в котором указывается тип использования use16, так как мы составляем 16-разрядное приложение. Указание описателя usel6. He запрещает использовать в программе 32-битовые регистры.

Фактически вся программа примера, кроме ее завершающих строк, а также фрагмента, выполняемого в защищенном режиме, посвящена подготовке перехода в защищенный режим. Прежде всего, надо завершить формирование де­скрипторов сегментов программы, в которых остались незаполненными базовые адреса сегментов. Базовые (32-битовые) адреса определяются путем умножения значений сегментных адресов на 16. Сначала производится очистка 32-разрядного аккумулятора (строка 44), так как в нем будет сформирован позднее базовый 32-разрядный адрес (фактически эта команда нужна для очистки старшего слова расширенного аккумулятора). После обычной инициализации сегментного регистра DS (строки 45, 46), которая позволит нам обращаться к полям данных программы (в реальном режиме!) выполняется сдвиг на 4 разряда содержимого регистра EАХ. Эта операция выполняется командой shl eax,4. Команда сдвигает влево содержимое 32-разрядного аккумулятора на указанное константой число бит (4). Следующая команда сохраняет получившееся 32-разрядное значение адреса в регистре ebp. После этого в регистр bx помещается смещение дескриптора сегмента кода. Следующими тремя командами (строки 53 – 55) младшее и старшее слова регистра EAX от­правляется в поля base_l и base_m дескриптора gdt_data, соответственно. Аналогично вычисляются 32-битовые адреса сегментов команд и стека, по­мещаемые в дескрипторы gdt_code (строки 57 – 63) и gdt_stack (строки 65 – 71).

Следующий этап подготовки к переходу в защищенный режим – загрузка в регистр процессора GDTR (Global Descriptor Table Register, регистр таблицы гло­бальных дескрипторов) информации о таблице глобальных дескрипторов. Эта информация включает в себя линейный базовый адрес таблицы и ее границу и размещается в 6 байтах поля данных, называемого псевдодескриптором. Для за­грузки GDTR предусмотрена специальная привилегированная команда lgdt (load global descriptor table, загрузка таблицы глобальных дескрипторов), которая тре­бует указания в качестве операнда имени псевдодескриптора. Формат псевдоде­скриптора приведен на рисунке 1.10.

Рисунок 1.10 – Формат псевдодескриптора

В нашем примере заполнение псевдодескриптора упрощается вследствие того, что таблица глобаль­ных дескрипторов расположена в на­чале сегмента данных, и ее базовый адрес совпадает с базовым адресом всего сегмента, который уже был вы­числен и помещен в дескриптор gdt_data. В строках 73, 74 ба­зовый адрес и граница помещаются в требуемые поля pdescr, а в строке 75 командой lgdt загружается регистр GDTR, со­общая, таким образом, процессору о местонахождение и размер GDT.

В принципе теперь можно перейти в защищенный режим. Однако мы запус­каем нашу программу под управлением DOS (а как ее еще можно запустить?) и естественно завершить ее также обычным образом, чтобы не нарушить работо­способность системы. Но в защищенном режиме запрещены любые обращения к функциям DOS или BIOS. Причина этого совершенно очевидна – и DOS, и BIOS являются программами реального режима, в которых широко используется сег­ментная адресация реального режима, т.е. загрузка в сегментные регистры сег­ментных адресов. В защищенном же режиме в сегментные регистры загружаются не сегментные адреса, а селекторы. Кроме того, обращение к функциям DOS и BIOS осуществляется с помощью команд int с определенными номерами, а в за­щищенном режиме эти команды приведут к совершенно другим результатам. Следовательно, программу, работающую в защищенном режиме, нельзя завершить средствами DOS. Сначала ее надо вернуть в реальный режим.

Возврат в реальный режим можно осуществить сбросом процессора. Дейст­вия процессора после сброса определяются одной из ячеек КМОП-микросхемы – байтом состояния отключения, располагаемым по адресу Fh. В частности, если в этом байте записан код Ah, после сброса управление немедленно передается по адресу, который извлекается из двухсловной ячейки 40h:67h, расположенной в области данных BIOS. Таким образом, для подготовки возврата в реальный ре­жим необходимо в ячейку 40h:67h записать адрес возврата, а в байт Fh КМОП-микросхемы занести код Ah. Приведенный способ возврата в реальный режим использовался в процессорах i286 (так как другого способа возврата в нем предусмотрено не было).

В процессорах, начиная с i386, переход из реального режима в защищенный и обратно может осуществляться с использованием управляющего регистра CR0. Так как встретить сейчас «живой» 286 процессор практически не реально, воспользуемся именно таким способом.

Всего в микропроцессорах i386 и выше имеется 4 программно адресуемых управляющих регистра CR0, CR1, CR2 и CR3 [8]. Регистр CR1 зарезервирован, регистры CR2 и CR3 управляют страничным преобразованием, которое в рассматриваемой программе не используется, а регистр CR0 содержит набор управляющих битов, из которых нам интересны биты 0 (включение и выключение защищенного режима) и 31 (разрешение страничного преобразования).

После сброса процессора оба эти бита сброшены, благодаря чему процессор начинает работать в реальном режиме с выключенным страничным преобразованием. Установка младшего бита CR0 в 1 переводит процессор в защищенный режим, а сброс его возвращает процессор в реальный режим. Следует отметить, что младшая половина регистра CR0 совпадает со словом состояния 286 процессора, поэтому команды чтения и записи в регистр CR0 аналогичны по результату командам smsw и lmsw, которые сохранены в старших процессорах из соображений совместимости.

Еще один важный шаг, который необходимо выполнить перед перехо­дом в защищенный режим, заключается в запрете всех аппаратных прерываний. Дело в том, что в защищенном режиме процессор выполняет процедуру обработки прерывания иначе, чем в реальном. При поступлении сигнала прерывания процессор не обращается к таблице векторов прерываний в первом килобайте памяти, как в ре­альном режиме, а извлекает адрес программы обработки прерывания из таблицы дескрипторов прерываний, построенной аналогично таблице глобальных дескрип­торов и располагаемой в программе пользователя (или в операционной системе). В нашем примере такой таблицы нет, и на время работы программы преры­вания придется запретить. Запрет всех аппаратных прерываний осуществляется командой cli (строка 77).

В строках 78, 79 в порт 70h засылается код 80h, который запрещает немаскируемые прерывания (которые не запрещаются командой cli).

В строках 81...83 осуществляется перевод процессора в защищенный режим. Этот перевод можно выполнить различными способами. В рассматриваемом примере для этого используется команда mov. Сначала содержимое управляющего CR0 регистра считывается в аккумулятор EAX, затем его младший бит устанавливается в 1 с помощью команды or, затем содержимое аккумулятора опять записывается в управляющий регистр CR0 с уже модифицированным младшим битом. Все после­дующие команды выполняются уже в защищенном режиме.

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

Рисунок 1.11 – Сегментные и теневые регистры

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

В процессоре для каждого из сегментных регистров име­ется так называемый теневой регистр дескриптора, который имеет формат дескриптора (рисунок 1.11). Теневые регистры недоступны программисту. Они автоматически загружа­ются процессором из таблицы дескрипторов каждый раз, ко­гда процессор загружает соот­ветствующий сегментный ре­гистр. Таким образом, в за­щищенном режиме програм­мист имеет дело с селектора­ми, т.е. номерами дескрипторов, а процессор – с самими дескрипторами, храня­щимися в теневых регистрах. Именно содержимое теневого регистра (в первую очередь, линейный адрес сегмента) определяет область памяти, к которой обра­щается процессор при выполнении конкретной команды.

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

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

Загрузить селекторы в сегментные регистры DS, SS и ES не представляет труда (строки 93 – 101). Но как загрузить селектор в регистр CS? Для этого можно воспользоваться искусственно сконструированной командой дальнего пе­рехода, которая, как известно, приводит к смене содержимого и IP, и CS. Строки 89 – 91 демонстрируют эту методику. В реальном режиме мы поместили бы во второе слово адреса сегментный адрес сегмента команд, в защищенном же мы записываем в него селектор этого сегмента (число 16).

Команда дальнего перехода, помимо загрузки в CS селектора, выполняет еще одну функцию – она очищает очередь команд в блоке предвыборки команд про­цессора. Как известно, в современных процессорах с целью повышения скорости выполнения программы используется конвейерная обработка команд программы, позволяющая совместить во времени фазы их обработки. Одновременно с вы­полнением текущей (первой) команды осуществляется выборка операндов сле­дующей (второй), дешифрация третьей и выборка из памяти четвертой команды. Таким образом, в момент перехода в защищенный режим уже могут быть рас­шифрованы несколько следующих команд и выбраны из памяти их операнды. Однако эти действия выполнялись, очевидно, по правилам реального, а не защи­щенного режима, что может привести к нарушениям в работе программы. Ко­манда перехода очищает очередь предвыборки, заставляя процессор заполнить ее заново уже в защищенном режиме.

Следующий фрагмент примера программы (строки 100 – 113) является чисто иллюстративным. В нем инициализируется (по правилам защищенного режима!) сегментный регистр ES и в видеобуфер выводится сообщение 'A message in protected mode' (зеленым цветом на синем фоне), так, что оно располагается в середине пятой строки экрана, чем подтверждается правильное функционирование программы в за­щищенном режиме.

Как уже отмечалось выше, для того, чтобы не нарушить работоспособность DOS, процессор следует вернуть в реальный режим, после чего можно будет за­вершить программу обычным образом. Перейти в реальный режим можно раз­ными способами. Можно, например, осуществить сброс процессора, заслав ко­манду FEh в порт 64h контроллера клавиатуры. Эта коман­да возбуждает сигнал на одном из выводов контроллера клавиатуры, который, в конечном счете, приводит к появлению сигнала сброса на выводе RESET микро­процессора. Этот способ (единственный для 286 процессора) неудобен тем, что для выполнения сброса необходимо несколько микросекунд. Если выполнять таким образом переход в реальный режим достаточно часто, можно потерять много времени.

В нашем примере переход в реальный режим осуществляется простым сбросом младшего бита в управляющем регистре CR0.

Если просто перейти в реальный режим сбросом бита 0 в регистре CR0, в теневых регистрах останутся дескрипторы защищенного режима, и при первом же обращении к любому сегменту программы возникнет исключение общей защиты, так как ни один из определенных ранее сегментов не имеет границы FFFh. Так как обработка исключений не производится, произойдет сброс процессора и перезагрузка компьютера, так как в данном случае не настраивался байт состояния перезагрузки, и не заполнялись соответствующие ячейки области данных BIOS. Следовательно, перед переходом в реальный режим необходимо исправить дескрипторы всех используемых сегментов: кодов, данных, стека и видеобуфера. Сегментные регистры FS и GS не использовались, поэтому о них можно не заботиться.

В строках 118 – 121 в поля границ всех четырех дескрипторов записывается значение FFFFh, а в строках 124 – 129 выполняется загрузка селекторов в сегментные регистры, что приводит к перезаписи содержимого теневых регистров. Так как сегментный регистр CS программно недоступен, его загрузку приходится опять выполнять с помощью искусственно сформированной команды дальнего перехода (строки 133 – 135).

После настройки всех использованных в защищенном режиме сегментных регистров, можно сбрасывать бит 0 управляющего регистра CR0 (строки 137 – 139). После перехода в реальный режим опять надо выполнить искусственно сформированную команды дальнего перехода для того, чтобы очистить очередь команд в блоке предвыборки процессора и загрузить в регистр CS вместо хранящегося там селектора обычный сегментный адрес регистра команд (строки 140 – 142).

Искусственно сформированная команда дальнего перехода передает управление на метку return.

Теперь процессора опять работает в реальном режиме. При этом, хотя в сегментных регистрах DS, ES и SS остались недействительные для реального режима селекторы, программа пока работает корректно, так как в теневых регистрах находятся правильные линейные адреса (оставшиеся от защищенного режима) и законные для реального режима границы (загруженные в строках 118 – 121). Однако, если в программе встретится любая команда сохранения или восстановления какого-либо сегментного регистра, нормальное выполнение программы нарушится, так как в сегментном регистре окажется не сегментный адрес, как это должно быть в реальном режиме, а селектор. Это значение будет трактоваться процессором как сегментный адрес, что приведет в дальнейшем к неверной адресации соответствующего сегмента.

Если даже в оставшемся тексте программы и нет команд чтения или записи сегментных регистров, неприятности все равно возникнут, так как простой вызов DOS'овского прерывания int 21h приведет к этим неприятностям, так как диспетчер DOS выполняет сохранение и восстановление регистров (в том числе и сегментных) при выполнении функций DOS.

Поэтому после перехода в реальный режим необходимо загрузить в используемые далее сегментные регистры соответствующие сегментные адреса. В строках 148 – 151 в регистры DS и SS записываются сегментные адреса соответствующих сегментов.

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

Для восстановления работоспособности системы следует также разрешить преры­вания (маскируемые – строка 155, немаскируемые – строки 156, 157), после чего программа может продолжаться уже в реаль­ном режиме. В рассмотренном примере для проверки работоспособности сис­темы в этом режиме на экран выводится сообщение ‘ Real mode now ’ с помощью функции DOS 09h. Для наглядности в сообщение включены Esc последовательности для смены цвета символов и фона (красные символы на зеленом фоне). Осуществлена смена цвета символов и фона может быть лишь в том случае, если в DOS установлен драйвер ANSI.SYS. Если после перехода в реальный режим при установленном драйвере ANSI.SYS сообщение будет выведено без изменения цветов, это может говорить об ошибках защищенного режима.

Перед завершением программы, она ожидает ввода с клавиатуры (функция 0 прерывания int 16h), чтобы можно было успеть увидеть содержимое экрана.

Программа завершается обычным образом функцией DOS 4Ch. Нормальное завершение программы и переход в DOS тоже в какой-то мере свидетельствует о ее правильности.


1.3 Вопросы для самопроверки

1. Какие режимы работы поддерживают 32-разрядные процессоры х86?

2. Какие регистры в 32-разрядных микропроцессорах х86 являются 16-разрядными?

3. Какие новые флаги добавились у 32-разрядных микропроцессоров х86?

4. Какие разряды управляющего регистра CR0 микропроцессора указывают состояние и режимы работы процессора?

5. Что такое бит страничного преобразования?

6. Что такое бит сопроцессора?

7. Для чего нужен бит переключения задачи?

8. Что такое бит эмуляции сопроцессора?

9. Для чего нужен бит присутствия сопроцессора?

10. Что такое бит разрешения защиты?

11. Какие регистры микропроцессора используются для поддержки страничного преобразования?

12. Что такое регистры системных адресов?

13. Для чего нужен регистр таблицы глобальных дескрипторов?

14. Для чего нужен регистр таблицы дескрипторов прерываний?

15. Для чего нужен регистр таблицы локальных дескрипторов?

16. Для чего нужен регистр состояния задачи?

17. Какие действия надо выполнить, чтобы в компилируемой программе можно было использовать 32-разрядные операнды?

18. Какие команды появились в 32-разрядных микропроцессорах (примеры)?

19. Каково главное ограничение реального режима работы процессора?

20. Какие дополнительные возможности появляются в защищенном режиме работы микропроцессора?


2 Использование 32-разрядной адресации в реальном режиме

Большое количество процессоров, используемых в настоящее время, ставит перед программистами проблемы оптимального использования ресурсов конкретного процессора в своих разработках. У изготовителей микропроцессоров стало традицией публиковать описания регистров и команд через Интернет в виде pdf-файлов, но не давать при этом рекомендаций по их применению. Хорошо, если из названия (или описания) можно сделать совершенно определенные выводы о назначении команды или регистра. А если нет?

Столь же вредная традиция — не описывать в общедоступной документации режимы работы, которых современные процессоры имеют великое множество. Без­условным чемпионом в этой области является Intel — значительная часть потен­циальных возможностей процессоров класса Pentium и последующих модифика­ций не используется потребителями, поскольку эти возможности в документации только упоминаются, но не рассматриваются. Программистам приходится искать наработки энтузиастов, которые тратят свое время на углубленное исследование режимов работы процессоров и применения конкретных, плохо описанных изготовителями, команд и регистров процессора [1].

2.1 Линейная адресация данных в реальном режиме DOS

В литературе по программированию описано три режима работы микропроцес­соров серии 80x86 — реальный режим (режим совместимости с архитектурой 8086), защищенный режим и режим виртуальных процессоров 8086 (являющий­ся неким подвидом защищенного режима).

Основной недостаток реального режима состоит в том, что адресное простран­ство имеет размер всего в 1 Мбайт и при этом сегментировано — «нарезано» на кусочки размером по 64 Кбайт. Одного мегабайта очень мало для современных ресурсоемких прикладных программ (текстовых и графических редакторов, гео­информационных систем, систем проектирования и т. д.), а сегментация не по­зволяет нормально работать с видеопамятью и большими массивами данных.

Что можно сказать о защищенном и виртуальном режимах? Многие книги и учеб­ники по микропроцессорам Intel заканчиваются главой «Переход в защищенный режим». Недостаток этого режима — необходимость заново создавать программ­ное обеспечение для работы с периферийными устройствами на низком уровне, то есть фактически полностью переписывать все основные функции DOS. Мож­но, конечно, использовать Windows, но эта операционная система предназначена для офисных целей и плохо адаптируется к решению задач оперативного управ­ления техническими системами. Кроме того, Windows забирает для собственных нужд изрядную часть ресурсов компьютера и ограничивает доступ к периферий­ным устройствам.

В некоторых случаях универсальные многозадачные операционные системы типа Windows и Unix неприменимы по причинам, не относящимся напрямую к обла­сти вычислительной техники. Первая причина — лицензионные соглашения между изготовителями и потребителями программ. Прочтите внимательно любую ли­цензию: разработчик программы не несет ответственности ни за что. Следова­тельно, за все сбои и неисправности расплачивается потребитель. Например, за
аварию в системе управления транспортом разработчикам этой системы придется отвечать по статьям Уголовного кодекса. Что касается систем военного назна­чения, то вообще сомнительно, что на таких лицензионных условиях какая-либо программа может быть официально принята в эксплуатацию на территории Рос­сии.

Вторая причина — огромный объем универсальных операционных систем — де­сятки миллионов строк на языках высокого уровня! Полностью протестировать такие системы невозможно — у фирмы Microsoft, например, хватает сил только на доскональную проверку небольшого ядра Windows! Тем более на это не спо­собен потребитель, у которого нет всей документации. Даже в случае открытой системы типа Linux, если документация есть и все исходные коды доступны — попробуйте доказать военным или банкирам, что в системе нет скрытых лову­шек и «черного хода»!

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

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

Изложенные выше причины приводят к тому, что программисты вынуждены ис­кать различные обходные пути. Один из возможных приемов — использование линейной адресации памяти. Линейная адресация — это наиболее простой, с точки зрения программиста, способ работы непосредственно с аппаратурой ЭВМ (ло­гические адреса при этом совпадают с физическими). Различия в организации памяти в реальном, защищенном и линейном режимах работы процессора иллю­стрирует рисунок 2.1.

Рисунок 2.1 – Организация адресного пространства в реальном, защищенном и линейном режимах работы процессора х86

Линейную адресацию можно использовать в специализированных программах, активно эксплуатирующих ресурсы ЭВМ — как в компьютерных играх, так и в системах автоматики, измерительных системах, системах управления, связи и т. п. Применение линейной адресации целесообразно в том случае, если проектируе­мая система предназначена для выполнения ограниченного, заранее известного набора функций и требует высокого быстродействия и надежности. Разработчики процессоров начали внедрять линейную адресацию (в качестве одного из возможных режимов работы) при переходе с 16-разрядной архитекту­ры на 32-разрядную. Фирма Intel ввела такой режим в процессоре 80386, после чего он стал фактически стандартным (поддерживается не только всеми после­дующими моделями, но и всеми клонами архитектуры х86), однако остался не­документированным (почти не описан в литературе и не рассматривается в фир­менном руководстве по программированию).

Для пользователей обычных персональных компьютеров линейная адресация в чистом виде интереса не представляет по тем же причинам, что и защищенный режим: DOS и BIOS функционируют только в реальном режиме с 64-килобайт-ными сегментами, и при переходе в любой другой режим программист оказыва­ется один на один с аппаратурой ЭВМ — без документации. Однако кроме чис­тых режимов процессоры Intel способны работать и в режимах гибридных. Еще в 1989 году Томас Роден (Thomas Roden) предложил использовать интерес­ную комбинацию сегментной (для кода и данных) и линейной (только для дан­ных) адресации [2]. Предложенный им метод позволяет, находясь в обычном режиме DOS, работать со всей доступной памятью в пределах четырехгигабайт­ного адресного пространства процессора Intel 80386. Чтобы включить режим линейной адресации данных, необходимо снять ограничения на размер сегмента в теневом регистре, соответствующем одному из дополнительных сегментных регистров FS или G5 (при необходимости описание архитектуры процессора Pen­tium можно найти в документации [3-5], размещенной в Интернете на сер­вере Intel для разработчиков). Через избранный регистр можно обращаться к любой области памяти с помощью прямой адресации или используя в качестве индексного любой 32-разрядный регистр общего назначения. После снятия ограничения запись в выделенный для линейной адресации сегмент­ный регистр выполнять нельзя, иначе нарушится информация в соответствую­щем ему теневом регистре (предел сегмента сохранится, но начальный адрес бу­дет перезаписан новым значением). Однако стандартные компиляторы и функции DOS с регистрами FS и GS не работают, и соответственно, при вызове процедур эти регистры можно вообще «не трогать» — их не нужно сохранять и восстанав­ливать. Достаточно один раз снять ограничение на размер адресного простран­ства, и после выхода из программы (до перезагрузки компьютера) линейную адресацию можно будет использовать из любой другой программы DOS, как по­ступил в своем примере Томас Роден.

Рассмотрим более подробно процедуру переключения одного из дополнительных сегментных регистров в режим линейной адресации. Каждый сегментный регистр, как указано в документации [5], состоит из видимой и невидимой (теневой) частей. Информацию в видимую часть можно записывать напрямую при помо­щи обычных команд пересылки данных (M0V и др.), а для записи в невидимую часть применяются специальные команды, которые доступны только в защищен­ном режиме. Теневая часть представляет собой так называемый дескриптор (опи­сатель) сегмента, длина которого равна 8 байтам.

При переходе от 16-разрядной архитектуры к 32-разрядной (то есть от i286 к i386) разработчики нового процессора попытались сохранить совместимость снизу вверх по структуре системных регистров, в результате чего дескрипторы сегментов при­обрели довольно уродливый (с точки зрения технической эстетики) вид — поля предела и базового адреса разделены на несколько частей. Кроме того, поле пре­дела оказалось ограничено 20 разрядами, что вынудило разработчиков применить еще один радиолюбительский трюк — ввести бит гранулярности G, чтобы можно было задавать размер сегмента, превышающий 16 Мбайт.

Рисунок 2.2 – Формат дескриптора сегмента

Формат дескриптора сегмента показан на рисунке 2.2. Дескриптор состоит из следую­щих полей.

- Базовый адрес — 32-разрядное поле, задающее начальный адрес сегмента (в ли­нейном адресном пространстве).

- Предел сегмента — 20-разрядное поле, которое определяет размер сегмента в байтах или 4-килобайтных страницах (в зависимости от значения бита грану­лярности G). Поле предела содержит значение, которое должно быть на еди­ницу меньше реального размера сегмента в байтах или страницах.

- Тип — 4-разрядное поле, определяющее тип сегмента и типы операций, кото­рые допустимо с ним выполнять.

- Бит S — признак системного объекта (0 — дескриптор описывает системный объект, 1 — назначение сегмента описывается полем типа).

- DPL — 2-разрядное поле, определяющее уровень привилегий описываемого дескриптором сегмента.

- Бит Р — признак присутствия сегмента в оперативной памяти компьютера (0 — сегмент «сброшен» на диск, 1 — сегмент присутствует в оперативной памяти).

- Бит AVL — свободный (available) бит, который может использоваться по усмот­рению системного программиста.

- Бит D — признак используемого по умолчанию режима адресации данных (0 — 16-разрядная адресация, 1 — 32-разрядная).

- Бит G — гранулярности сегмента (0 — поле предела задает размер сегмента в байтах, 1 — в 4-килобайтных страницах).

В нашем случае признак используемого по умолчанию режима адресации дан­ных D можно установить в 0 (использовать по умолчанию 16-разрядные операн­ды), но особой роли его значение не играет — в смешанном режиме сегментно-линейной адресации при работе с линейным сегментом строковые команды, ис­пользующие значение этого разряда, применять нельзя. Бит гранулярности G должен быть установлен в 1, чтобы обеспечить охват всего адресного простран­ства процессора.

Рисунок 2.3 – Формат прав доступа для сегмента данных

Для сегментов данных формат байта прав доступа (включающего поле типа сег­мента) имеет вид, показанный на рисунке 2.3. Как видно из рисунка, поле S для сег­ментов данных должно быть установлено в 1, а старший разряд поля типа дол­жен иметь значение 0. Поля Р и DPL уже упоминались выше. Бит присутствия сегмента Р следует установить в 1 (сегмент присутствует в памяти), а в поле DPL нужно установить максимальный уровень привилегий (значение 00). Бит расши­рения вниз ED для сегментов данных имеет значение 0 (в отличие от стековых сегментов, для которых ED=1). Бит разрешения записи W следует установить в единицу, чтобы можно было не только считывать, но и записывать информацию в сегмент. Бит А фиксирует обращение к сегменту и автоматически устанавлива­ется в единицу всякий раз, когда процессор производит операции считывания или записи с сегментом, описываемым данным дескриптором. При инициализации регистра бит А можно сбросить в 0.

Рисунок 2.4 – Формат управляющего регистра CR0

Осуществить загрузку теневых регистров можно только в защищенном режиме. Для переключения режимов работы процессора используется регистр управле­ния CR0, формат которого показан на рисунке 2.4 [7]. Регистр CR0 содержит флаги, отра­жающие состояние процессора и управляющие режимами его работы. Назначе­ние флагов следующее.

- РЕ (Protection Enable) —разрешение защиты Установка этого флага инструкцией LMSW или LOAD CR0 переводит процессор в защищенный режим. Сброс флага (возврат в реальный режим) возможет только по инструкции LOAD CR0. Сброс бита РЕ является частью довольно длинной последовательности инструкций, подготавливающих корректное переключение в реальный режим.

- МР (Monitor Processor Extension) — мониторинг математического сопроцессора. Позволяет вызывать исключение #NM по каждой команде WAIT при TS=1. При выполнении программ для процессоров 286/287 и 386/387 на процессорах 486 DX и старше бит MP должен быть установлен.

- ЕМ (Processor Extension Emulated) — эмуляция математического сопроцессора. Установка этого флага вызывает исключение #NM при каждой команде, относящейся к сопроцессору, что позволяет прозрачно осуществлять его программную эмуляцию.

- TS (Task Switched) — признак переключения задачи (флаг устанавливается в 1 при каждом переключении задач и проверяется перед выполнением команд математического сопроцессора).

- ЕТ (Extension Туре) — индикатор поддержки набора инструкций математического сопро­цессора (0 — выключена, 1 — включена). В процессорах Р6 флаг всегда уста­новлен в 1.

- NE (Numeric Error) — встроенный механизм контроля ошибок математического сопроцессора (0 — выключен, 1 —включен).

- WP (Write Protect) — защита от записи информации в страницы уровня пользо­вателя из процедур супервизора (0 — выключена, 1 — включена).

- АН (Alignment Mask) — разрешение контроля выравнивания (контроль выравнивания выполняется только на уровне привилегий 3 при AM =1 и флаге AC =1. (0 — запре­щен, 1 — разрешен).

- NW (Not Writethrough) — запрещение сквозной записи и циклов аннулирования. (0 — сквозная запись разрешена, 1 — запрещена).

- CD (Cache Disable) — запрет заполнения кэш-памяти (попадание в ранее заполненные строки при этом обслуживаются кэшем). (0 — использование кэш-памя­ти разрешено, 1 — запрещено).

- PG (Paging) — включение страничного преобразования памяти (0 — запрещено, 1 — разрешено).

Набор подпрограмм, необходимых для переключения сегментного регистра GS в режим линейной адресации, показан в листинге 2.1 [1]. Как сказано выше, переза­пись содержимого теневого регистра процессора возможна только в защищенном режиме, а переход в этот режим, как видно из листинга, требует ряда дополни­тельных операций, выполняемых процедурой Initialization. В частности, нужно перенастроить на специально выделенные в кодовом сегменте области данных регистры DS, SS и SP. В момент перенастройки регистров стека должны быть за­прещены прерывания, поскольку некоторые обработчики прерываний пишут ин­формацию в стек прерываемой программы.

Процедура SetLAddrModeForGS, непосредственно осуществляющая перенастройку регистра GS в режим линейной адресации, воспроизводит (с незначительными изменениями) метод Родена. Прежде чем осуществить переключение, нужно вна­чале подготовить таблицу GDT (настроить на текущие сегменты кода и данных) и загрузить ее. Затем нужно войти в защищенный режим — установить в едини­цу бит РЕ регистра CR0, а остальные разряды сохранить без изменений (в том виде, в котором они находились при работе в реальном режиме). В защищенном ре­жиме необходимо перезагрузить сегментные регистры, сняв при этом ограничения с GS, и сразу же вернуться в реальный режим DOS, сбросив в ноль бит РЕ. Дли­тельное пребывание в защищенном режиме нежелательно, поскольку переклю­чение в него выполнялось по упрощенной схеме: таблица прерываний не созда­валась, а сами прерывания были просто отключены.

После выполнения процедуры SetLAddrModeForGS обязательно следует отменить замыкание адресного пространства, то есть разблокировать адресную линию А20, которая управляется контроллером клавиатуры. Для этого необходимо по­слать в порт А контроллера соответствующую команду. Посылка команды осуществляется при помо­щи Enable_A20 и Wait8042Buffer Empty.

Листинг 2.1 – Подпрограмма, устанавливающая режим линейной адресации данных

; Порт, управляющий запретом немаскируемых прерываний


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: