-
Notifications
You must be signed in to change notification settings - Fork 2
Лекция 5
Производство x86: 1985 - ~2010 годы.
- Регистры, кроме сегментных (они всё так же 16-битные)
- Шина данных
- Шина адреса (2^32 = 4Гб ОЗУ), то есть теперь можно использовать не 2 регистра для хранения адреса, а всего 1
8086 (1978 г.) -> 80186 (1982 г.) -> 80286 (1982 г.) добавлен защищённый режим -> 80386 (1985 г.) архитектура стала 32-разрядной -> 80486 (1989 г.) -> Pentium -> … -> (современные процессоры)
- обращение к оперативной памяти происходит по реальным (действительным) адресам, трансляция адресов не используется;
- набор доступных операций не ограничен;
- защита памяти не используется.
~10 лет назад (а может быть и сейчас тоже) при включении компьютера 32-разрядные процессоры включались именно в реальном режиме работы, то есть им был доступен всего 1 Мб памяти, они могли загружать DOS.
А если ОС была более современной, чем DOS, то для её загрузки нужен специальный загрузчик, который перевёл бы процессор из реального режима работы в защищённый с помощью специальных команд и использовал бы уже все доступные возможности процессора. То есть, теперь основной режим работы процессора - защищённый, в который ОС переключает процессор сразу в начале загрузки. В таком случае процессору доступно много памяти (не 1 Мб, а вся оперативная память).
Главная фича - обеспечение нескольких уровней работы процессора с программами, то есть у процессора появилась возможность разделять программы на системные (со всеми привилегиями), прикладные (которые не должны ломать ОС или лезть в данные другой программы, чтобы что-то похитить...) и т.д.
- обращение к памяти происходит по виртуальным адресам с использованием механизмов защиты памяти;
- набор доступных операций определяется уровнем привилегий (кольца защиты): системный и пользовательский уровни
- Режим виртуального 86-го процессора
- 32-разрядный процессор может временно переключаться в него из защищённого режима в рамках какой-то задачи, чтобы выполнять старые DOS'овские программы. То есть 32-разрядный процессор может нативно поддерживать старые 16-битные приложения без перезапуска этих приложений, а просто переключая режим работы процессора. Современные 64-разрядные процессоры в основном режиме работы уже не поддерживают такую совместимость (вроде как), поэтому для запуска 16-битных приложений нам приходится использовать эмулятор (DOSBox).
- В плане архитектуры с точки зрения прикладного (не системного) программиста или обычного пользователя принципиально ничего не поменялось.
- Регистры доступны в том же самом наборе (регистры общего назначения, регистр флагов, индексные и указательные регистры, и т.д.). Только теперь они расширились до 32 бит и у их названий появилась приставка
E
(Extending). При этом всё также доступны их младшие половинки (AX
,BX
,CX
и т.д., а также их половинки -AH
,AL
,BH
,BL
,CH
,CL
и т.д.). - Сегментные регистры больше не доступны программисту. Теперь ими управляет только операционная система.
- Добавлено несколько новых групп регистров (их больше, чем исходное количество регистров).
- В процессоре может быть до нескольких десятков машинно-специфичных регистров. Их состав зависит от конкретной модели процессора.
- Аналогична системе команд 16-разрядных процессоров
- Доступны как прежние команды обработки 8- и 16-разрядных аргументов, так и 32-разрядных регистров и переменных
- Пример:
mov eax, 12345678h
xor ebx, ebx
mov bx, 1
add eax, ebx ; eax=12345679h
Так как шина адреса совпадает по размеру с шиной данных, то ничего не мешает в одном регистре (а не в двух, как раньше) хранить адрес ячейки памяти целиком.
- Плоская (самая примитивная) - код и данные используют одно и то же пространство.
- Сегментная (была и раньше) - сложение сегмента и смещения
- Страничная (основной вид в современных ОС) - виртуальные адреса отображаются на физические адреса постранично. Страница (!= сегмент) - некоторый блок памяти (более мелкий, чем сегмент), который может иметь разные размеры.
- Виртуальная память — метод управления оперативной (внутренней) памятью компьютера, позволяющий выполнять программы, требующие больше оперативной памяти, чем имеется в компьютере, путём автоматического перемещения частей программы между основной памятью и вторичным хранилищем (файл на диске, или раздел подкачки - swap)
- Основной режим для большинства современных ОС
- В x86 минимальный размер страницы - 4 Кб (может принимать и другие значения)
- Основывается на таблице страниц (аналогия с таблицей векторов прерываний в 1-ом килобайте оперативной памяти) - структуре данных (произвольного размера), используемой системой виртуальной памяти в операционной системе компьютера для хранения сопоставления между виртуальным адресом и физическим адресом (для того чтобы процессор мог по виртуальному адресу получить физический и обратиться к нужной ячейке памяти). Виртуальные адреса используются выполняющимся процессом, в то время как физические адреса используются аппаратным обеспечением (процессором). Таблица страниц является ключевым компонентом преобразования виртуальных адресов, который необходим для доступа к данным в памяти.
OFFTOP:
В Linux можно создавать и файл подкачки, и раздел подкачки. Чаще делается второе (раздел */swap*).
В Windows в корне диска C лежит большой скрытый файл с расширением .sys (очень неточная информация)
и называется вроде как swap... Короче просто загуглите...
Если программа требует больше оперативной памяти, чем есть в распоряжении компьютера, то ОС автоматически находит страницы программ, к которым давно не обращались, выгружает их из оперативной памяти, помечает их как выгруженные в специальном каталоге (чтобы к ним нельзя было обратиться, потому что их уже нет в памяти) и выгружает на жёсткий диск. Освобождённая оперативная память отдаётся другой программе, которая её запросила. В дальнейшем, если выгруженная страница потребуется (произойдёт обращение к виртуальному адресу, который попадёт в страницу, которая помечена как выгруженная), ОС найдёт какую-то другую страницу, которая не используется, перепишет её в файл подкачки, а из подкачки достанет ту страницу, к которой обратились.
- Виртуальные адреса (в верхней части картинки) тоже 32-разрядные и состоят из 3 частей (10, 10 и 12 бит соответственно).
- Конвертацией виртуальных адресов в физические занимается процессор.
Главное достижение архитектуры x86 (за период примерно с 1978 по 1985) - механизм разделения прав доступа на аппаратном уровне, разделение программ между собой и разделение операционной системы и прикладных программ. Сюда же включается конвертация виртуальных адресов в физические при каждом обращении в память.
То есть, если страничный режим работы с памятью включён, то процессор при каждом обращении к ячейке памяти выполняет такую сложную конвертацию: обращается к каталогу таблиц страниц и формирует физический адрес. Процессору нужно сделать не одно обращение по адресу в память (напрямую считать значение), а три: обращение к каталогу таблиц страниц, обращение к конкретной таблице страниц и считывание данных из полученного физического адреса.
Казалось бы, это должно было замедлить компьютер в 3 раза, но этого не произошло, потому что в процессорах реализован специальный механизм хеширования каталогов таблиц страниц и самих таблиц страниц с помощью специального буфера. Но углубляться дальше в аппаратное обеспечение мы будем уже на операционных системах с Рязановой...
- В сегментных регистрах - селекторы. Сегментные регистры теперь хранят не номера параграфов (памяти стало гораздо больше, и номер параграфа нам теперь не нужен), а селекторы, включающие в себя:
- 13-разрядный номер дескриптора
- 14-й бит: признак того, какую таблицу страниц использовать - глобальную (общесистемную) или локальную (текущей прикладной программы - текущего процесса)
- 15-й и 16-й биты: уровень привилегий запроса (0-3), который требуется для работы с текущим сегментом.
Эти числа от 0 до 3 используются для определения привилегий в защищённом режиме работы процессора и называются кольцами защиты или уровнями защиты. На 0 уровне находится операционная система, её ядро (в зависимости от того, является оно монолитным или модульным, то есть либо вся ОС работает на этом уровне привилегий, либо в некоторых случаях её драйвера выносятся на 1 и 2 уровень). На 3 уровне (минимальном) находятся все прикладные программы, то есть они не могут обращаться к памяти, требующей более высокий уровень привилегий.
- По селектору (а точнее, по номеру дескриптора) определяется запись в одной из таблиц дескрипторов сегментов
- При включённом страничном режиме - по таблице страниц определяется физический адрес страницы либо выявляется, что она выгружена из памяти, срабатывает исключение (специальный вид прерываний) и операционная система подгружает затребованную страницу из файла/раздела "подкачки" (swap)
32-разрядные процессоры в защищённом режиме отличаются от предшественников ещё и поддержкой многозадачности.
До этого мы знали 3 вида сегментов: кода, данных и стека. Теперь добавляется 4-й.
TSS (Task State Segment — сегмент состояния задачи) — специальная структура в архитектуре x86, содержащая информацию о задаче (процессе).
Используется ОС для диспетчеризации задач, в т. ч. переключения между уровнями стека. Например, между прикладными уровнями и стеком ядра или при обработке прерываний и исключений.
- Если в процессоре есть несколько ядер, то напрашивается решение "в лоб": на каждом ядре выполнять свою задачу. Но многозадачность появилась до многоядерности, да и процессов в ОС намного больше, чем ядер в процессоре, поэтому этот вариант отпадает.
- Многозадачность реализуется с помощью планировщика задач (элемент ОС), который по прерыванию таймера (с очень высокой частотой) переключает программы по некоторому алгоритму (по кругу или в зависимости от приоритетов: какие-то более важные включать чаще, а остальные реже). Таким образом создаётся иллюзия того, что все программы работают одновременно, но на самом деле они работают по очереди с очень большой частотой. Сегмент TSS как раз используется для вытеснения задач с сохранением их текущего состояния (значения регистров и т.д.).
Отказ (fault) — это исключение, которое обнаруживается и обслуживается до выполнения инструкции, вызывающей ошибку.
- После обслуживания этого исключения управление возвращается снова на ту же инструкцию (включая все префиксы), которая вызвала отказ.
- Отказы, использующиеся в системе виртуальной памяти, позволяют, например, подкачать с диска в оперативную память затребованную страницу или сегмент.
Ловушка (trap) — это исключение, которое обнаруживается и обслуживается после выполнения инструкции, его вызывающей.
- После обслуживания этого исключения управление возвращается на инструкцию, следующую за вызвавшей ловушку. К классу ловушек относятся и программные прерывания.
Аварийное завершение (abort) — это исключение, которое не позволяет точно установить инструкцию, его вызвавшую.
- Оно используется для сообщения о серьезной ошибке, такой как аппаратная ошибка или повреждение системных таблиц.
- Требует какой-то критической реакции вплоть до остановки операционной системы.
То есть этот регистр тоже стал 32-разрядным, но не все из 16 новых флагов используются.
-
GDTR
(Global Descriptor Table Register - регистр глобальной таблицы дескрипторов): 6-байтный регистр, содержит 32-битный линейный адрес начала таблицы глобальных дескрипторов (GDT) и 16-битный размер этой таблицы (лимит, уменьшенный на 1) -
IDTR
(Interrupt Descriptor Table Register - регистр таблицы дескрипторов прерываний): 6-байтный регистр, содержит 32-битный линейный адрес начала таблицы глобальных дескрипторов обработчиков прерываний (IDT) и 16-битный размер (лимит, уменьшенный на 1). Теперь таблица прерываний может располагаться где угодно (не обязательно по нулевому адресу) и описывается специальным регистромIDTR
-
LDTR
(Local Descriptor Table Register - регистр локальной таблицы дескрипторов): 10-байтный регистр, содержит 16-битный селектор для GDT и весь 8-байтный дескриптор из GDT, описывающий текущую таблицу локальных дескрипторов -
TR
(Task Register): 10-байтный регистр, содержит 16-битный селектор для GDT и весь 8-байтный дескриптор из GDT, описывающий то, где искать TSS текущей задачи
Все эти наборы регистров содержатся в каждом аппаратном ядре (ядре процессора) и управляются независимо друг от друга.
-
CR0
- флаги управления системой-
PG
- включение режима страничной адресации - Несколько флагов для управления отдельными параметрами кеша
-
WP
- запрет записи в страницы "только для чтения" -
NE
- ошибки FPU вызывают исключение, а не IRQ13 -
TS
- устанавливается процессором после переключения задачи -
PE
- включение защищённого режима (если его не выставить, то по умолчанию 32-разрядный процессор при включении будет работать в реальном режиме)
-
-
CR1
- зарезервирован (либо мы не можем обращаться к нему, либо он предназначен для хранения служебных данных на аппаратном уровне, к которым мы не имеем доступ и которые нам не нужны) -
CR2
- регистр адреса ошибки страницы - содержит линейный адрес страницы, при обращении к которой произошло исключение #PF (эта ошибка может возникнуть при обращении к выгруженной из памяти страницы и вызвать исключение, при котором процессор поместит вCR2
адрес страницы, которой нам сейчас не хватает и которую надо загрузить) -
CR3
- регистр основной таблицы страниц (содержит адрес основной таблицы страниц и режим работы этих адресов, который будет говорить о том, какого размера будут страницы - 4 Кб или больше)- 20 старших бит физического адреса начала каталога таблиц либо 27 старших бит физического адреса начала таблицы указателей на каталоги страниц, в зависимости от бита PAE в CR4
- Управление кешированием и сквозной записью страниц
-
CR4
- регистр управления новыми возможностями процессоров (начиная с процессора Pentium, т.е. с 90-х годов)
- DR0..DR3 - 32-битные линейные адреса четырёх возможных точек останова по доступу к памяти
- DR4, DR5 - зарезервированы
- DR6 (DSR) - регистр состояния отладки. Содержит причину останова
- DR7 (DCR) - регистр управления отладкой. Управляет четырьмя точками останова
Отладочные регистры нужны для отладчиков. Но мы отладчики писать не будем, поэтому подробно рассматривать эти регистры нет смысла.
Появлялись по мере развития процессоров от 386 до современных.
- Регистры управления кешем
- Регистры дополнительного управления страничной адресацией
- Регистры расширений процессора:
MMX
,SSE
и т.д.
- Выполнение ограничено, в основном, нулевым кольцом защиты
- Выполнять их может только ядро операционной системы, прикладным программам они недоступны
-
LGDT,
SGDT
- загрузка/выгрузка глобальной таблицы дескрипторов -
LLDT
,SLDT
- загрузка/выгрузка локальной таблицы дескрипторов -
LTR
,STR
- загрузка/выгрузка регистра задачи (Load Task Register / Store Task Register) -
LIDT
,SIDT
- загрузка/выгрузка таблицы дескрипторов прерываний -
MOV CR0..CR4 или DR0..DR7, <источник>
- запись данных в регистры управления процессором (только на нулевом кольце защиты), перенастройка страничной адресации и т.д. - ...
-
Линейный адрес состоит из 3 частей:
- биты 31-22 - номер таблицы страниц в каталоге
- биты 21-12 - номер страницы в выбранной таблице
- биты 11-0 - смещение от физического адреса начала страницы в памяти
- Каждое обращение к памяти требует двух дополнительных обращений!
- Необходим специальный кеш страниц - TLB
-
Каталог таблиц/таблица страниц:
- биты 31-12 - биты 31-12 физического адреса таблицы страниц либо самой страницы
- атрибуты управления страницей
Механизм защиты - ограничение доступа к сегментам или страницам в зависимости от уровня привилегий. Это ограничение реализовано на аппаратном уровне.
К типам сегментов реального режима (код, стек, данные) добавляется TSS - сегмент состояния задачи. В нём сохраняется вся информация о задаче на время приостановки её выполнения, то есть когда планировщик задач передаёт управление другому процессу. Размер - 68h байт.
- Селектор предыдущей задачи
- Регистры стека (пары
ESS:ESP
) для 0, 1, 2 уровней привилегий - Все регистры нашей прикладной программы (включая регистр стека 3-го уровня защиты, где непосредственно работает задача)
EIP
,EFLAGS
,EAX
,EBX
,ECX
,EDX
,ESP
,EBP
,ESI
,EDI
,CS
,DS
,ES
,FS
,HS
,SS
,LDTR
- флаги задачи
- битовая карта ввода-вывода (контроль доступа программы к устройствам) - структура, определяющая полномочия текущей задачи (программы) по её возможности её обращения к внешним устройствам. Там отдельными битами настраивается то, к каким устройствам / группам устройств (к какому диапазону портов ввода-вывода) наша программа может обратиться. С помощью битовой карты ОС может разрешать/запрещать программам взаимодействовать с устройствами напрямую.
x86-64 === AMD64
OFFTOP:
Почему AMD обогнал Intel?
Примерно в это же время Intel разрабатывал ещё и свою собственную архитектуру - IA64
(не имеет ничего общего с x86-64). Она предусматривала новую систему команд, практически
полностью сломали совместимость с более старыми программами, сделали более современные
процессоры, на которых почти 20 лет выпускались серверные системы. Но они не очень широко
использовались из-за проблем с совместимостью, что вынуждало переписывать (или
оптимизировать/перекомпилировать) все программы под новое железо. А это очень трудоёмкая
задача. В итоге нужное количество софта под IA64 так и не было написано, и эта архитектура
окончательно прекратила своё существование несколько лет назад. А вот расширения архитектуры
8086 (1978 года) до сих пор выпускаются. 64-разрядная архитектура AMD победила, и чуть позже
в Intel сделали такие же процессоры (можно сказать, по стандартам AMD).
- Основные режимы работы:
- Legacy mode - совместимость с 32-разрядными процессорами (32-разрядный режим работы, в который вкладываются режимы V86 и т.д.)
- Long mode – 64-разрядный режим с частичной поддержкой 32-разрядных программ (полная поддержка была бы очень накладной). Рудименты V86 и сегментной модели памяти упразднены
- Регистры:
- целочисленные 64-битных регистры общего назначения - RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP (их 32- и 16-разрядные части также доступны);
- новые целочисленные 64-битных регистры общего назначения R8 — R15
- 64-битный указатель RIP и 64-битный регистр флагов RFLAGS.
- способ передачи параметров;
- способ возврата результата из функции;
- способ возврата управления.
Соглашения о вызовах определяются в рамках отдельных языков высокого уровня, а также - различных программных API, в т. ч. API операционных систем.
В Си есть cdecl, причём как 32-, так и 64-разрядный (cdecl32, cdecl64), которые различаются не только размером, но и способом передачи параметров: в cdecl32 - через стек, а в cdecl64 - через часть регистров общего назначения (неточная информация). При чём через регистры общего назначения возвращаются только небольшие значения, большие значения возвращаются через указатели. Также есть и другие соглашения о вызовах (syscall, stdcall и др.), которые, помимо прочего, могут различаться порядком передачи параметров в подпрограммы (слева направо или справа налево).
Ассемблерная вставка - способ написания фрагмента кода на ассемблере в программе на языке высокого уровня.
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Hello World!\n");
int src = 1;
int dst;
asm ("mov %1, %0\n\t" // записали src в dst (%0 и %1 - нулевой и первый параметры)
"add $2, %0" // прибавили 2 к dst ($2 - число 2, константа)
: "=r" (dst)
: "r" (src));
printf("%d\n", dst);
return 0;
}
Пожалуйста, не бейте автора за кодстайл! Это писал Дмитрий Александрович.
$ gcc -Wall -Werror -Wextra -Wpedantic -c example.c
$ gcc example.o -o example.exe
$ ./example.exe
Hello World!
3
Если писать под Visual Studio, то у оператора asm
надо ставить фигурные скобки, а не круглые. И там можно внутри писать ассемблерные команды в такой же нотации, как у MASM'а. А если писать под GCC (под оболочкой MinGW), как в примере, то придётся использовать другую нотацию (другой синтаксис): в частности, обратный порядок аргументов.