Как перевести процессор в защищенный режим ассемблер
Когда программа переводит процессор в защищённый режим, при всём том богатом потенциале, который предоставляют 32-разрядные процессоры, она оказывается практически беспомощной. В основном это связано с тем, что в защищённом режиме совершенно другая система прерываний и воспользоваться ресурсами, предоставляемыми операционной системой, в которой вы запускаете такую программу, невозможно. Более того, недоступными окажутся прерывания BIOS и IRQ. Подробно работа прерываний описана в разделе "Прерывания в защищённом режиме", там же вы найдёте примеры использования прерываний всех типов (программные, аппаратные и исключения), но пока наша программа не сможет ими воспользоваться.
В предыдущих главах не раз упоминалась фраза "операционная система", когда шла речь о программе, работающей в защищённом режиме. Дело в том, что после перевода процессора в P-Mode, программа должна определить действия и условия для всех ситуаций, вплоть до того, что определить драйвера (т.е. управляющие процедуры) для таких устройств, как клавиатура, мышь, видеоадаптер, дисковые накопители и даже таймер. Существует два способа реализации таких драйверов - либо написать их самому (что, вообще говоря, не очень сложно), либо обращаться к BIOS-у в режиме виртуального процессора 8086. Оба этих способа будут описаны в соответствующих разделах, где будет подразумеваться, что вы разобрались с разделом "Защищённый режим".
В третьем пункте мы заставим программу вывести на экран строку "I am in protected mode. ", после чего программа зациклит процессор (компьютер повиснет). Дело в том, что возврат в режим реальных адресов немного сложнее, чем переход в защищённый режим и это требует дополнительного объяснения, поэтому в этом примере приводится только переход в P-Mode.
Вывод на экран будет происходить в текстовом режиме посредством прямой записи в видеопамять, для чего будет описана отдельная процедура - вы увидите, что код, предназначенный для выполнения в защищённом режиме не требует специальных определений (это будет 16-разрядный код; 32-разрядный определяется немного сложнее).
Остаётся добавить, что пример должен выполняться из простой операционной системы, например, MS-DOS, работающей в режиме реальных адресов. Если вы попытаетесь запустить программу из-под ОС, работающей в защищённом режиме (например, Windows), то программа не заработает, т.к. процессор уже будет работать в P-Mode и не допустит повторного входа в этот режим.
Что касается зависания процессора в конце выполнения нашего примера - так это нормальное явление при отладке программ в защищённом режиме. Этот режим тем и характерен, что допускает работу только одного "хозяина" одновременно, а наша программа, войдя в защищённый режим, как раз и станет таким "хозяином процессора".
Использование ошибок процессоров не представляется мне практичным, т.к. вариаций одной и той же модели процессоров много - десятки, хотя если вы сможете найти достойное применение ошибкам, то я буду рад опубликовать на сайте ваши статьи.
И всё же, возвращаясь к примеру, давайте определимся: мы изучаем "правильное" программирование 32-разрядных процессоров, при этом не используя никаких ошибок в архитектуре и никаких недокументированных особенностей и команд. Это нам обеспечит уверенность в том, что наши программы будут надёжно работать на любых интеловских процессорах и их клонах.
Прежде, чем мы перейдём к примеру программы, давайте определим две функции, которые мы будем в дальнейшем использовать.
В программе нам понадобится создавать 4 дескриптора - для кода, данных (где будет хранится выводимая строка), стека (он будет использоваться для вызова функции вывода текста) и видеопамяти. Все дескрипторы сегментов подобны друг другу, поэтому удобно их будет создавать отдельной функцией. Исходный код для этой функции (и для второй) поместите как макросы в отдельный файл - мы их будем использовать в дальнейшем и просто подключать к исходникам.
Функция первая, "set_descriptor" предназначенная для создания дескриптора, приводится сразу в том виде, в каком она будет находится в подключаемом файле "pmode.lib".
При вызове этой функции подразумевается, что в паре регистров DS:BX находится указатель на текущую позицию в GDT. Функция после создания дескриптора переведёт указатель на позицию для следующего дескриптора. Все необходимые параметры передаются через регистры и функция всего лишь "перетасовывает" их в нужном порядке.
Вторую функцию "putzs" добавьте в файл "pmode.lib" после описания первой. Эта функция предназначена для вывода на экран строки, оканчивающейся нулевым байтом (т.е. байтом, равным 00h). Такая строка везде на этом сайте называется ZS-строка или просто ZS (от Zero-String). Также вам будут встречаться строки другого типа - LS (от Lenght-String), длина которых будет задана первым байтом.
Функция "putzs" получила своё название от комбинации слов "Put" и "ZS", по аналогии с похожими Си-функциями. В этой функции сохраняются используемые регистры - в приводимом далее примере это никакого эффекта не вызовет - процессор сразу после вывода зависнет, но для других примеров это будет полезно.
Далее полностью приводится файл "example2.asm", который содержит пример перехода в защищённый режим, включая необходимые для tasm-а атрибуты.
При очередной загрузке дизассемблер предлагает открыть файл для дизассемблирования, после выбора он предлагает способ дизассемблирования файла из всех возможных. Когда файл и способ дизассемблирования выбран, перед нами открывается окошко с готовым текстом программы на ассемблере. В этом тексте нам порой и придётся разбираться.
Отладчик
Как было уже сказано в начале статьи, одним дизассемблером не обойтись, поэтому порой приходится прибегать к работе с отладчиком. Одним из удобных отладчиков можно назвать Turbo Debugger от компании Borland. Это классическое DOS-приложение, выглядящее следующим образом:
Опять же, с работой в отладчике мы встретимся в следующих статьях. Пока достаточно того факта, что он должен быть в нашем наборе инструментов.
Нереальный режим
Это особый подрежим реального режима. Процессор находится в реальном режиме, но адресуется к памяти путём конструкции "селектор: смещение". Таким образом доступна память выше 1 мегабайта. В дальнейшем будет рассматриваться защищённый режим и его подрежимы.
Реальный режим
В этом режиме процессор находится сразу после нажатия кнопки "Power" компьютера. Доступ к памяти в реальном режиме осуществляется конструкцией "сегмент:смещение", которая описывает логический адрес. Значение сегмента, как и смещения, лежит в пределах от 0 до 0FFFFh.
Так как адресоваться можно только в пределах одного сегмента, то максимальный размер сегмента равен 64 килобайт. Физический адрес, который выставляется на адресную шину процессора, считается по формуле:
В реальном режиме процессоров 80186 и 8086 значение сегмента лежало в пределах от 0 до 0F000h. Таким образом, максимальный выставленный адрес на адресную шину равен 0FFFFFh, что соответствует 2 20 ―1, т.е. 1 мегабайту.
Конечно, поначалу объём такой памяти казался колоссальным, но со временем одного мегабайта стало не хватать. С появлением процессора 80286 стал доступен так называемый блок памяти UMB, начинающийся с адреса 0FFFFh:0010h и заканчивающийся адресом 0FFFFh:0FFFFh (65520 байт за пределами одного мегабайта). Теперь можно было переконфигурировать операционную систему MS-DOS так, чтобы она занимала этот блок, освобождая в оперативной памяти 64 килобайт.
Режимы работы процессора Intel 80386
С появлением процессора Intel 80386 возникла архитектура IA32. Она предполагала появление нового режима работы процессора ― защищённого ("Protected Mode"). Для совместимости с предыдущими процессорами линейки Intel 80x86 процессор 80386 не запускался сразу в защищённом режиме, а работал в так называемом реальном режиме ("Real Mode"). Кроме этого, у каждого режима есть один или несколько подрежимов. Разберём их.
Защищённый режим
Этот режим имеет сложную конструкцию по сравнению с реальным. Логический адрес представляется конструкцией "селектор:смещение". Селектор находится в пределах от 0 до 0FFFFh (на самом деле, селекторов в 4 раза меньше ― об этом подробнее в следующих статьях). Смещение, в отличие от реального режима, является 32-разрядным, что позволяет адресовать сегменты размером 4 гигабайт. Логический адрес преобразуется в линейный по следующей схеме:
Линейный адрес в дальнейшем выставляется на адресную шину, если не включен режим страничной адресации. В противном случае линейный адрес преобразуется в физический, и только после этого выставляется на адресную шину. Кроме этого, защищённый режим позволяет организовать виртуальную память, достигающую размера до 64 терабайт и зависящую лишь от объёма жёсткого диска (например, тот же файл подкачки в Windows реализует виртуальную память). В защищённом режиме функционируют практически все современные операционные системы.
Режим виртуального 8086
Это также подрежим защищённого режима, который позволяет создать виртуальную машину, функционирующую как будто бы она находилась в реальном режиме, но, на самом деле, работающей в защищённом режиме.
Среда разработки Watcom C++
Эта статья представляет собой некоторое введение в разработку программ, функционирующих в защищённом режиме процессора. Здесь будут поставлены основные задачи любой программы, работающей в защищённом режиме, и приведены их решения. В основном, программы будут написаны на языке FASM.
Мультизадачный подрежим защищённого режима
Этот режим позволяет организовать мультизадачность, то есть возможность одновременного выполнения нескольких задач или многопользовательской системы.
Проверка возможности перехода в защищённый режим
- проверить нулевой бит регистра CR0;
- убедиться, что не загружена операционная система Windows.
Этот пример тоже пока не показывает реализацию 32-разрядных команд. Для этого следует ещё познакомиться с материалом последующей главы. Кроме этого, у примера есть следующий недостаток: вызываются функции DOS (int 21h), что уже начинает противоречить независимости нашей программы от операционной системы MS-DOS. В дальнейшем придётся избавиться от использования функций операционной системы и перейти к использованию функций BIOS. Но пока достаточно ограничиться и таким кодом.
В предыдущей статье была реализована программа, осуществляющая переход в защищённый режим с проверкой на возможное дальнейшее функционирование в защищённом режиме и реализующая простой цикл с использованием 16-разрядных команд. Теперь задача следующей программы – переход в защищённый режим и использование уже 32-разрядных команд. Для реализации этого следует познакомиться с новой информацией, касающейся защищённого режима.
Как говорилось раньше, сегментация памяти защищённого режима представляет собой совсем другую форму адресации: вместо привычной для реального режима адресации «сегмент:смещение» используется адресация «селектор:смещение». Рассмотрим структуру селектора:
Биты 3-15 указывают номер дескриптора в выбранной таблице дескрипторов (о таблицах чуть ниже). Таким образом, селектор может описать 213=8192 дескрипторов для одной таблицы. Каждый дескриптор в таблице описывает сегмент, при чём он не обязательно должен быть сегментом кода или данных (о типах сегментов будет написано ниже).
Бит 2 – флаг TI (Table Indicator). Этот флаг указывает, какая из двух таблиц дескрипторов будет использоваться для загрузки дескриптора. Если флаг равен нулю, то используется глобальная таблица дескрипторов (GDT – Global Descriptor Table), в другом случае используется локальная таблица дескрипторов (LDT – Local Descriptor Table). Теперь поймём, что значит фраза «загрузка дескриптора». Дело в том, что, начиная с процессора 80286, размер сегментных регистров стал равным не двум байтам, а десяти, доступными из которых остались только два младших (в них и грузится селектор). Однако при загрузке селектора в младшие два байта сегментного регистра, в старшие 8 байт автоматически загружается дескриптор из используемой таблицы, по которому и ведётся дальнейшая адресация.
Биты 0-1 содержат запрашиваемый уровень привилегий (RPL – Requested Privilege Level) доступа к сегменту, то есть с какими привилегиями программа обращается к сегменту, описанному дескриптором. О привилегиях доступа будет сказано позже, и пока будем считать их равными нулю (то есть, самыми высокими).
Пока в наших примерах достаточно пользоваться сегментными дескрипторами. Рассмотрим структуру сегментного дескриптора:
- 0 – сегмент 16-разрядный,
- 1 – сегмент 32-разрядный (этот флаг ещё называют BIG).
- Флаг D задаёт направление роста сегмента (Direction). Обычно, если D=1, то выбранный сегмент является сегментом стека и растёт «задом наперёд». Если же D=0, то выбранный сегмент является сегментом данных. На самом деле, значение D=1 практически не используется.
- Флаг W определяет возможность записи в сегмент (Writable). То есть, сброшенный флаг указывает, что сегмент данных доступен только для чтения. Если флаг установлен, то сегмент доступен также и для записи.
- Бит C называется битом подчинения (Conforming), но рассмотрен он будет позже.
- Флаг R указывает на доступность сегмента кода для чтения (Readable). То есть, если флаг сброшен, читать из сегмента кода нельзя.
Остаётся ещё один вопрос: каким образом процессор находит в памяти эти таблицы. Вспомним структуру реального режима: таблица прерываний хранится, начиная с нулевого логического адреса и занимает 400h байт. Процессору не составляло труда её обнаружить, так как на любой машине под таблицу были зарезервированы самые младшие 400h байт адресного пространства. В защищённом режиме всё несколько иначе: таблицы могут располагаться в любом месте оперативной памяти, и их адреса нужно где-то фиксировать. Для этого в процессорах Intel (начиная с модели 80286) появились новые машинно-специфичные регистры: GDTR(определяет расположение и размер таблицы GDT), LDTR (определяет селектор таблицы LDT), IDTR (определяет расположение и размер таблицы IDT). Регистры GDTR и LDTR схожи по строению: они имеют одинаковый размер (6 байт) и содержат в себе одинаковую структуру данных о расположении и размере таблицы в памяти. Регистр LDTR отличается от этих двух регистров по размеру (10 байт) и содержит в себе селектор дескриптора сегмента данных таблицы LDT и сам дескриптор, который находится в таблице GDT.
Рассмотрим структуру данных, хранящихся в регистрах GDTR и IDTR:
;переключаемся в защищенный режим
mov eax,cr0
or al,1
mov cr0,eax
;настраиваем регистры
db 0eah ;машинный код команды jmp
dw offset protect ;смещение метки перехода в сегменте команд
dw 30h ;селектор сегмента кода в таблице GDT
После перехода в защищенный режим, нужно проинициализировать сегментный регистр CS, это можно сделать выполнив команду дальнего перехода.
Вопрос 1: Зачем указывать явно код команды дальнего перехода, т.е. записывать команду дальнего перехода как последовательность данных?
В старших моделях процессоров Intel, можно написать команду косвенного дальнего перехода jmp dword ptr Address (где в переменной Address храниться селектор сегмента кода и смещение), в старых моделях мы так делать не могли. Удалось выяснить что это как то связанно с особенностями конвейера старших поколений процессоров Intel.
Вопрос 2: Чем отличаются конвейеры младших и старших поколений Intel, что появилось нового, за счет чего стало возможным выполнить такую команду сразу после перехода в защищенный режим? и из за чего на старых процессорах нельзя было так сделать?
И буду очень рад подробной информации и работе конвейеров процессоров Intel.
Работа в защищенном режиме
Настройка сегментных регистров
Вспомним, что содержимое сегментных регистров в реальном и защищенном режимах интерпретируется микропроцессором по-разному. Как только микропроцессор оказывается в защищенном режиме, первую же команду он пытается выполнить традиционно: по содержимому пары cs : ip определить ее адрес, выбрать ее и т.д. Но содержимое сs должно быть индексом, указывающим на дескриптор сегмента кода в таблице GDT. Но пока это не так, так как в данный момент сs все еще содержит физический адрес параграфа сегмента кода, как этого требуют правила формирования физического адреса в реальном режиме. То же самое происходит и с другими регистрами. Но если содержимое других сегментные регистров можно подкорректировать в программе, то в случае с регистром cs этого сделать нельзя, так как он в защищенном режиме программно недоступен.
Как? - Моделировать команду межсегментного перехода.
Вспомним, что действие команд перехода основано как раз на изменении содержимого регистров cs и ip. Команды ближнего перехода изменяют только содержимое eip\ip, а команды дальнего перехода — оба регистра cs и eip\ip. Воспользуемся этим обстоятельством, вдобавок существует и еще одно свойство команд передачи управления — они сбрасывают конвейер микропроцессора, подготавливая его тем самым к приему команд, которые сформированы уже по правилам защищенного режима. Это же обстоятельство заставляет нас впрямую моделировать команду межсегментного перехода, чтобы поместить правильное значение селектора в сегментный регистр cs. Это можно сделать так:
- Real-Mode - реальный, системная поддержка в виде BIOS;
- Protected-Mode - защищённый, поддержка Win и никсы;
- Virtual-Mode - для эмуляции реального режима, из защищённого;
- System-Management-Mode – реальный из защищённого, без эмуляции.
В Real-моде, программист полноправный хозяин системы и имеет дело только с физической памятью. Ему доступны буквально все инструкции процессора, прямой доступ к портам оборудования, и многое другое. Здесь не задают вопросов типа “Вы уверены? Да/Нет”, и за необдуманные действия ответственность несёте только вы. Бонусом идёт программирование вообще без системы, на одних только функциях биос (благо его и сейчас эмулируют все EFI). Основной задачей ОС является как-раз абстрагирование пользователя от реальных устройств, которые он может по неосторожности вывести из строя. Одним словом, зевать от скуки тут не приходится, и нужно быть всегда на чеку.
У первых процессоров 8086 шина-адреса(A) была 20-битная. Такая ширина позволяет адресовать всего FFFFF байт памяти. Запускаем виндовый калькулятор, переводим его и BIN, и введём 20 единиц. Именно такой вид имела шина(А) на физическом уровне.
Посмотрим на рисунок, где изображены проводники системной шины, как они идут с процессора. Шина эта – мультиплексная, т.е. единственная и для адреса, и для данных. Сначала на шину выставляется адрес, после которого по ней-же отправляются и данные:
Как видим, увеличение разрядности шины увеличивает кол-во адресуемых ячеек памяти. Это хорошо.. Но регистры ЦП были в то время 16-битными (без приставки ‘E’), и ими можно было дотянуться максимум до 64 Кб физической памяти (см.рис). Как вместить в 16-битный регистр 20-битный адрес? Было решено разбить всю память на логические сегменты, и адресовать её через регистровую пару в формате сегмент+смещение. Формула и вид получился такой:
Здесь видно, что три средние тетрады перекрываются. Для сегментных регистров типа СS остаются только смещённые влево 4-бита, которыми можно выбрать один из 16-ти сегментов. Под смещение выделяется как и прежде 16-бит, а это 64 Кб внутри выбранного сегмента памяти. Таким образом, размер сегмента в реальном режиме равен 64К, а всего сегментов =16. В сумме они дают: 64К х16=1М памяти.
Однако это не табу, и ничто не мешает нам перевернуть ‘пластинку’ наоборот, ведь мы имеем дело с логическим адресом. Тогда получим 65.536 сегментов, по 16-байт в каждом. Эти 16-байт назвали ‘параграф’ и это мин.размер одного сегмента в R-моде. Такая картина позволяет операционной системе (например DOS) эффективно распределять память, которой и так с гулькин-нос. Сегменты по 64К бывают только на бумажке, а на практике - это просто макс предел, который выделяет ОС под один сегмент.
Во-первых, среди одного мегабайта, 64К непрерывной памяти у системы может и не быть, а во-вторых - если у программы в сегменте-данных всего три строчки текста, зачем выделять для них 64К памяти? Система просто выравнит эти данные до 16-байтного параграфа, и вручит их пользователю. Если (в процессе работы) программе понадобится ещё доп.памяти, то программист должен запросить её у системы, и она без проблем выделит из своего резерва кусок (если таковой остался).
На рисунке выше, я выделил красным одну из линий шины-адреса и вставил туда ключ. Эту линию назвали А20 , поскольку она идёт следующей от 20-битного адреса 0-19. Программист обязательно должен включить её при переводе процессора из реального в защищённый режим работы, иначе не видать нам четырёх гигов памяти. Если линия А20 отключена (а она отключена по-умолчанию), то после 20-битного адреса 0xfffff счётчик адресов опять сбросится в нуль, не давая нам вылезти из первого мегабайта. На аппаратном уровне линией А20 управляет чипсет, через порт 92h системной логики.
Асматикам старой школы издавна не давала покоя мысль, поиметь 4 гига в реальном режиме. Это открывало большие перспективы, поскольку в R-моде нет многозадачности, и все ресурсы процессора полностью принадлежат только одному приложению. На современных процессорах, оно будет летать как истребитель. И нужно сказать, что энтузиастам это удалось! 4Gb памяти в реальном режиме это не сказка, а недокументированный режим, который назвали UnReal-Mode (можете погуглить).
Если коротко, то выделяется один сегментный регистр (как правило ES), который будет играть роль торпеды в 4Gb пространство. Для него подготавливается соответствующий дескриптор с базой(0) и лимитом в 4Gb. Запихать этот дескриптор в ES находясь в реальном режиме процессор не позволит, поэтому нужно перевести его в защищённый режим, обновить ES и вернуться обратно в R-моду. Теперь через ES можно гулять по 4G пространству, как у себя дома. Позже, когда приём вылез из хакерских нор и получил огласку, его взяли на вооружение чуть-ли не все досовские игры. В практической части мы ещё вспомним про UnReal, а пока топаем дальше..
2.1. Память защищённого режима
Процессор переводится в Рrotected-моду всего одним\нулевым битом в регистре CR0 . Сразу-же о сегментной памяти реального режима можно забыть. Хотя сегменты по-прежнему и присутствуют, они несут в себе уже другую нагрузку. Адрес теперь не состоит из двух составляющих Seg:Offs , а вся память приобретает плоский FLAT вид. В игру вступают защитные механизмы, которыми природа щедро одарила этот режим.
- CS – Code Segment – сегмент кода;
- DS – Data Segment – сегмент данных;
- SS – Stack Segment – сегмент стека;
- ES – Extended Segment – дополнительный для данных;
- GS – General Segment – добавлен для РМ-моды
- FS – Безымянный (сл.по алфавиту) для РМ-моды.
По большому счёту, возиться с дескрипторами приходится только тем, кто пишет свою ОС. В остальных случаях они уже настроены системой и трогать их не имеет смысла. Поэтому обратим внимание только на основные моменты.
Логический адрес состоит из сегмента+смещения, например CS:EIP, DS:ESI, ES:EDI и т.д.. MMU собирает эти составляющие, преобразуя лог.адрес в линейный, в диапазоне 0..N. Если в регистре CR0 включён страничный режим, то в дело вступает транслятор страниц в MMU. Если-же страничное преобразование выключено, то линейный адрес совпадает с физическим (здесь он выключен). На рисунке ниже, в сегментных регистрах лежит 2-байтный селектор сегмента, адрес процессора – это смещение, сегментацией и трансляцией занимается блок MMU процессора, РА – это PhysicalAddress:
Таким образом, память в защищённом режиме работы процессора – вся линейна. Сегментные регистры служат только для защиты доступа к памяти. В дескрипторах всех сегментных регистров, всегда устанавливается база(0) с максимальным лимитом в 4Gb, и никак иначе. Транслятор страниц постоянно включён, и разбивает большие сегменты на мелкие страницы памяти, размером по 4К-байт каждая. Любая из страниц имеет свои атрибуты защиты, не считая защиты верхнего уровня родительских сегментов. Это огромная корпорация, которая следит за всеми, и жёстко пресекает любое нарушение установленных прав. Одним словом – защищённый режим это руль! ..который позже согнём в баранку.
----------------------------------------
PS\\: Кстати в отладчике OllyDbg можно наглядно увидеть значения дескрипторов в шести сегментных регистрах. Как-видим, особняком стоит тут только FS , который система всегда держит при себе, и программисту трогать его не советует. Это указатель на системную таблицу РЕВ - Process Environment Block, куда система сбрасывает информацию о текущем процессе – т.н. окружение процесса.
Поскольку зарегистрировался на данном форуме, внесу и я свои 5-копеек в общую копилку статей. Занимаюсь по-большей частью ассемблером, то и статья на эту тему. Гуру в этой области прошу строго не судить, т.к. с изложением своих мыслей всегда имел проблемы. Поскольку пишу по-ходу дела и в свободное время, то между частями могут быть перерывы. Рассматривается работа процессоров x86 в двух режимах – реальном RM, и зашищённом РМ. Как и принято - начну издалека..
Часть 1. Общие сведения
1.1. Процессор и его регистры.
В компьютерной системе всё вращается вокруг центрального процессора CPU. Он работает под управлением какой-либо системы. Программист пишет инструкций, которые система помещает в память ОЗУ. В свою очередь процессор берёт инструкции из памяти в свои регистры, выполняет их, и помещает опять в память. Цикл повторяется до тех пор, пока пользователь не остановит программу.
CPU имеет огромное кол-во регистров, и только малая их часть предназначена непосредственно для выполнения кода – эти регистры назвали РОН, или регистры общего назначения: EAX, EBX, ECX, EDX и т.д. Гугл не напрягаясь выдаст кучу ссылок на их описание.
Но такая картина только снаружи процессора. Попав в контейнер процессорного ядра, все регистры переименовываются по типу R0, R1..RN, где N – нагрузка на процессор. Блок переименования ‘Rename’ ввели для параллельного исполнения инструкций на одном ядре процессора, и теперь не возникает путаницы, какому именно потоку принадлежит та или иная инструкция. На выходе из ядра, регистры опять приобретают свои имена EAX и прочие.
Кроме РОН, процессор имеет и следующие регистры:
- Пять регистров-управления самим процессором CR0..CR4 (Control Registers);
- Четыре регистра управления памятью GDTR, LDTR, IDTR, TR (Descriptor Table Registers);
- Восемь регистров-отладки DR0..DR7 (Debug Registers);
- Более 1000 специфических регистров MSR (Model-Specific Registers).
Для начала, рассмотрим такой ассемблерный код в виндовом отладчике ‘DEBUG’..
Здесь командой а я ассемблировал некоторый код, который потом командой u дизассемблировал:
- Всё-что выделено голубым – это команды процессора. Общее их кол-во давно перевалило за 200, и в каждом процессоре появляются всё новые и новые. Забегая вперёд скажу, что львиная их доля никому не нужна, кроме самих AMD и INTEL - мы будем использовать из них всего штук 20-30.
- Выделенное красным – это операнды команд. Слева от запятой находится приёмник, а справа – источник, который может быть регистром, константой, или значением в памяти. Если операнд является значением в памяти, то он берётся в квадратные скобки (по крайней мере в fasm’e, самом ‘правильном’ ассемблере из всех). Здесь в ход идут регистры процессора.
- Выделенное зелёным – это инструкция ассемблера, т.е. команда с операндами. Именно её исполняет CPU.
- Ну и жёлтое это то, как видит инструкции процессор. Этот поток байт (каждая пара чисел) принято называть Operation-Code, или по нашему ‘Опкод’ инструкции.
1.3. Оперативная память ОЗУ.
Другими словами, при любых обстоятельствах (хоть запись, хоть чтение) процессор обменивается с памятью только 64-байтными кэш строками ‘Cache-Line’ в независимости от того, нужен ему байт или двойное слово. Он никогда не станет запрашивать шину ради одного байта, ведь шина – это общий ресурс, который может быть занят, например если кто-то в данный момент использует прямой обмен с памятью посредством DMA. Информацию о размере своей кэш-линейки можете почерпнуть из программы CPU-Z.
Основной проблемой DDR-SDRAM является время доступа к ней. Связано это с тем, что запоминающая матрица её ядра собрана на крошечных конденсаторах, время разряда которых физически не способно превысить частоту в 200 МГц. На более высоких частотах, кондёры просто не успевают полностью разряжаться, что приводит к искажению данных.
Но DRAM дешёвая и отказываться от неё пока рановато. Поэтому инженеры пошли другим путём, и ввели понятие 2n-prefetch (предвыборка). Суть фишки - в увеличении числа линий-чтения с матрицы. N считается тут степенью двойки. Например, для DDR2 n=2, или 2^2=4 параллельные линии, для DDR3 получаем уже 2^3=8 линий, для DDR4 соотвественно 16. Предвыбранные (prefetch) данные накапливаются в промежуточном буфере, который через мультиплексор MUX может сливать информацию с ядра, на любой частоте. Таким образом удалось решить проблему века, придерживаясь опорной частоты запоминающей матрицы в 200 МГц:
Вообще идея использовать конденсаторы для хранения битов не совсем удачна, т.к. они имеют свойства со-временем разряжаться. Значит нужно периодически считывать с них заряд, и записывать его обратно. Этот процесс известен как ‘регенерация ячеек памяти’. До модулей DDR2 регенерацией строк занимался контроллёр-памяти, а начиная с DDR3 - функцию перенесли внутрь DRAM-чипов. Теперь проблема ушла на второй план и выполняется в фоне. Конроллёр просто выделяет чипам определённое время на регенерацию, которое (среди таймингов) числится в розысках под кличкой ‘tRP’:
1.4. Кэш-память процессора.
Другое дело кэш процессора, где в роли запоминающих элементов выступают не конденсаторы, а пара транзисторов, включённых по схеме триггера. Такой тип памяти получил название ‘статическая’ или SRAM. Тут не нужно регенерировать ячейки, на то она и статическая. Процессор получает доступ к ней мгновенно, т.к. его регистры собраны на таких-же триггерах. Ему без разницы от куда брать данные, с другого регистра или со-своего кэш – скорость практически одинакова, если не учитывать небольшие издержки на внутреннюю шину.
Процессор имеет трёхуровневую организацию кеш: L1, L2 и L3. Первые два уровня физически разделены пополам – половина для кода, половина - для данных. Кэш L3 является общим и имеет наибольшую ёмкость памяти. Внутри своего кэш, процессор хранит только действительно нужную информацию и ту, что может потребоваться в будущем (предварительная загрузка данных). Именно поэтому он читает из памяти не единичными байтами, а прихватывает с собой сразу и последующие 63. Практика доказала, что программисты пишут код последовательно и лишь иногда появляются циклы. Значит кэш-контроллёр действительно знает своё дело, раз читает инфу пачками.
Структура и работа кэш памяти - довольная обширная тема, которая потянет на отдельную статью. Только кэш поддерживается полностью на аппаратном уровне, и асм-программисту доступны лишь некоторые средства работы с ним - например полная его очистка, когда хотим получить время выполнения (профайл) определённого участка кода. Поэтому мы не будем копать в эту тему глубоко, а позже - двинемся дальше..
Первая программа: переход в защищённый режим
Переход в защищённый режим осуществляется установкой бита 0 регистра CR0. Переход в реальный режим осуществляется сбросом того же нулевого бита. Рассмотрим программу, которая выполняет эту операцию (язык ― Flat Assembler):
- проверить, возможно ли переключиться в защищённый режим;
- инициализировать таблицы дескрипторов;
- запретить прерывания (как маскируемые, так и не маскируемые);
- открыть линию A20;
- загрузить регистры управления памятью;
- установить нулевой бит (далее он будет называться PE) регистра CR0;
- выполнить переход на 32-битный сегмент кода, переопределив регистр CS.
- компьютер "зависает";
- компьютер перезагружается.
- программа запущена в режиме V86 (режим виртуального 8086);
- программа запущена в защищённом режиме или под конкретной операционной системой.
Читайте также: