Сколько регистров в процессоре
Ключевую роль в обработке данных в процессоре играют специальные ячейки, известные как регистры . Регистры в процессоре x86-64 можно разделить на четыре категории: регистры общего назначения, специальные регистры для приложений, сегментные регистры и специальные регистры режима ядра. Здесь нас будут интересовать прежде всего регистры общего назначения (general-purpose registers), которые в основном и используются в приложениях на ассемблере.
Начнем с того, что процессор архитектуры x86 имел восемь 32-битных регистров общего назначения, регистр флагов и указатель инструкций. Регистры общего назначения:
- EAX (Accumulator): для арифметических операций
- ECX (Counter): для хранения счетчика цикла
- EDX (Data): для арифметических операций и операций ввода-вывода
- EBX (Base): указатель на данные
- ESP (Stack pointer): указатель на верхушку стека
- EBP (Base pointer): указатель на базу стека внутри функции
- ESI (Source index): указатель на источник при операциях с массивом
- EDI (Destination index): указатель на место назначения в операциях с массивами
- EIP : указатель адреса следующей инструкции для выполнения
- EFLAGS : регистр флагов, содержит биты состояния процессора
Можно получить доступ к частям 32-битных регистров с меньшей разрядностью. Например, младшие 16 бит 32-битного регистра EAX обозначаются как AX. К регистру AX можно обращаться как к отдельным байтам, используя имена AH (старший байт) и AL (младший байт).
В архитектуре х64 эти регистры были расширены до 64 бит, а новые расширенные регистры получили имена, которые начинаются с буквы R , например, RAX , RBX и т.д. Кроме того, были добавлены 8 новых 64-битных регистров R8 — R15
Для обращения к 32-, 16- и 8-битной части 64-разрядных регистров используются стандартные для архитектуры x86 имена регистров. Для доступа к подрегистрам новых 64-битных регистров R8 — R15 применяется соответствующий суффикс:
- D : для получения младших 32 бит регистра, например, R11D
- W : для получения младших 16 бит регистра, например, R11W
- B : для получения младших 8 бит регистра, например, R11B
Таким образом, в архитектуре x86-64 мы можем использовать следующие регистры общего пользования:
- Шестнадцать 64-разрядных регистров RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8, R9, R10, R11, R12, R13, R14 и R15
- Шестнадцать 32-разрядных регистров EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, R8D, R9D, R10D, R11D, R12D, R13D, R14D и R15D
- Шестнадцать 16-разрядных регистров AX, BX, CX, DX, SI, DI, BP, SP, R8W, R9W, R10W, R11W, R12W, R13W, R14W и R15W
- Шестнадцать 8-разрядных регистров AL, AH, BL, BH, CL, CH, DL, DH, DIL, SIL, BPL, SPL, R8B, R9B, R10B, R11B, R12B, R13B, R14B и R15B
Например, запись в часть 64-битного регистра, например в регистр AL, влияет только на биты этой части. В случае AL загрузка 8-битного значения изменяет младшие 8 битов RAX, оставляя остальные 56 бит без изменений.
Хотя эти регистры и называются общего назначения, но это не значит, что их можно использовать для любых целей. Все регистры x86-64 имеют свое особое назначение, которое ограничивает их использование в определенных контекстах.
Регистр флагов RFLAGS, содержит биты состояния процессора:
Флаг переноса (Carry flag):казывает, был ли при сложении перенос или заимствование при вычитании. Используется в качестве входных данных для инструкций сложения и вычитания.
Флаг четности: устанавливается, если младшие 8 битов результата содержат четное число единиц.
Флаг настройки: указывает, был ли при сложении перенос или заимствование при вычитании младших 4 битов.
Флаг нуля (Zero flag): устанавливается, если результат операции равен нулю
Флаг знака (Sign flag): устанавливается, если результат операции отрицательный.
Флаг прерывания выполнения (Trap flag): используется при одношаговой отладке.
Флаг разрешения прерывания: установка этого бита разрешает аппаратные прерывания.
Флаг направления: контролирует направление обработки. Если не установлен, то порядок от самого младшего до самого старшего адреса. Если установлен, то порядок обратный — от самого старшего до самого младшего адреса.
Флаг переполнения (Overflow flag): если устанавлен, то операция привела к переполнению со знаком.
Уровень привилегий ввода-вывода (I/O privilege level): уровень привилегий текущего выполняемого потока. IOPL 0 — это режим ядра, а 3 — пользовательский режим.
Флаг вложенной задачи (Nested task flag): управляет цепочкой прерываний.
Флаг возобновления (Resume flag): используется для обработки исключений во время отладки.
Флаг режима виртуальной машины 8086: если установлен, режим совместимости с 8086 активен. Этот режим позволяет запускать некоторые приложения MS-DOS в контексте операционной системы в защищенном режиме.
Флаг проверки выравнивания (Alignment check flag): если установлен, проверка выравнивания памяти активна. Например, если установлен флаг AC, сохранение 16-битного значения по нечетному адресу вызывает исключение проверки выравнивания. Процессоры x86 могут выполнять невыровненный доступ к памяти, когда этот флаг не установлен, но количество требуемых командных циклов может увеличиться.
Флаг виртуального прерывания (Virtual interrupt flag): виртуальная версия флага IF в виртуальном режиме 8086..
Флаг ожидания виртуального прерывания: Устанавливается, когда прерывание находится в состоянии ожидания в виртуальном режиме 8086.
Флаг ID: если этот бит установлен, то поддерживается инструкция cpuid. Эта инструкция возвращает идентификатор процессора и информацию о его функциях.
Основными являются флаги переноса, нуля, знака и переполнения, которые называют флагами состояния. Как видно, не все биты из 64-х разрядного регистра флагов имеют назначение. Неуказанные биты не используются.
В дополнение к регистрам общего назначения x86-64 предоставляет регистры специального назначения, в том числе восемь регистров для работы с числами с плавающей точкой, реализованных в модуле x87 с плавающей точкой (floating-point unit или FPU). Intel назвала эти регистры ST0 — ST7. Каждый регистр с плавающей точкой имеет ширину 80 бит. В отличие от регистров общего назначения обычная пользовательская программа не может получить к ним прямой доступ.
Также есть шестнадцать 128-битных регистров XMM (XMM0 — XMM15) и набор инструкций SSE/SSE2. Каждый регистр можно настроить как четыре 32-битных регистра с плавающей точкой; два 64-битных регистра двойной точности с плавающей точкой; или шестнадцать 8-битных, восемь 16-битных, четыре 32-битных, два 64-битных или один 128-битный целочисленный регистр. В более поздних вариантах семейства процессоров x86-64 AMD/Intel удвоили размер регистров до 256 бит каждый (переименовав их в YMM0-YMM15), добавив поддержки восьми 32-битных значений с плавающей точкой или четырех 64-битных значений с плавающей точкой двойной точности. (целочисленные операции по-прежнему ограничивались 128 битами).
Регистры процессора
Начиная с модели 80386 процессоры Intel предоставляют 16 основных регистров для пользовательских программ и ещё 11 регистров для работы с мультимедийными приложениями (MMX) и числами с плавающей точкой (FPU/NPX). Все команды так или иначе изменяют содержимое регистров. Как уже говорилось, обращаться к регистрам быстрее и удобнее, чем к памяти. Поэтому при программировании на языке Ассемблера регистры используются очень широко.
В этом разделе мы рассмотрим основные регистры процессоров Intel. Названия и состав/количество регистров для других процессоров могут отличаться. Итак, основные регистры процессоров Intel.
Таблица 2.1. Основные регистры процессора.
Название | Разрядность | Основное назначение |
EAX | 32 | Аккумулятор |
EBX | 32 | База |
ECX | 32 | Счётчик |
EDX | 32 | Регистр данных |
EBP | 32 | Указатель базы |
ESP | 32 | Указатель стека |
ESI | 32 | Индекс источника |
EDI | 32 | Индекс приёмника |
EFLAGS | 32 | Регистр флагов |
EIP | 32 | Указатель инструкции (команды) |
CS | 16 | Сегментный регистр |
DS | 16 | Сегментный регистр |
ES | 16 | Сегментный регистр |
FS | 16 | Сегментный регистр |
GS | 16 | Сегментный регистр |
SS | 16 | Сегментный регистр |
Регистры EAX, EBX, ECX, EDX – это регистры общего назначения. Они имеют определённое назначение (так уж сложилось исторически), однако в них можно хранить любую информацию.
Регистры EBP, ESP, ESI, EDI – это также регистры общего назначения. Они имеют уже более конкретное назначение. В них также можно хранить пользовательские данные, но делать это нужно уже более осторожно, чтобы не получить «неожиданный» результат.
Регистр флагов и сегментные регистры требуют отдельного описания и будут более подробно рассмотрены далее.
Пока для вас здесь слишком много непонятных слов, но со временем всё прояснится)))
Когда-то процессоры были 16-разрядными, и, соответственно, все их регистры были также 16-разрядными. Для совместимости со старыми программами, а также для удобства программирования некоторые регистры разделены на 2 или 4 «маленьких» регистра, у каждого из которых есть свои имена. В таблице 2.2 перечислены такие регистры.
Вот пример такого регистра.
Из этого следует, что вы можете написать в своей программе, например, такие команды:
MOV AX, 1 MOV EAX, 1
Обе команды поместят в регистр AX число 1. Разница будет заключаться только в том, что вторая команда обнулит старшие разряды регистра EAX, то есть после выполнения второй команды в регистре EAX будет число 1. А первая команда оставит в старших разрядах регистра EAX старые данные. И если там были данные, отличные от нуля, то после выполнения первой команды в регистре EAX будет какое-то число, но не 1. А вот в регистре AX будет число 1. Сложно? Ну это пока… Со временем вы к таким вещам привыкните.
Мы пока не говорили о разрядах (битах). Эту тему мы обсудим в разделах, посвящённых системам счисления. А сейчас пока вам достаточно знать, что нулевой разряд (бит) – это младший бит. Он крайний справа. Старший бит – крайний слева. Номер старшего бита зависит от разрядности числа/регистра. Например, в 32-разрядном регистре старшим битом является 31-й бит (потому что отсчёт начинается с 0, а не с 1).
Ниже приведён список регистров общего назначения, которые можно поделить описанным выше способом и при этом к «половинкам» и «четвертинкам» этих регистров можно обращаться в программе как к отдельному регистру.
Таблица 2.2. «Делимые» регистры..
Регистр | Старшие разряды | Имена 16-ти и 8-ми битных регистров | |
31…16 | 15…8 | 7…0 | |
EAX | . | AX | |
AH | AL | ||
EBX | . | BX | |
BH | BL | ||
ECX | . | CX | |
CH | CL | ||
EDX | . | DX | |
DH | DL | ||
ESI | . | SI | |
EDI | . | DI | |
EBP | . | BP | |
ESP | . | SP | |
EIP | . | IP |
На этом мы закончим наше краткое знакомство с регистрами. Если вам пока не всё понятно – просто прочитайте этот раздел, чтобы более-менее представлять себе, что такое регистры. По мере приобретения новых знаний вы можете вернуться к этому разделу и уже на новом уровне воспринять эту информацию. А в следующем разделе мы коротко опишем процесс выполнения команды.
Дневники чайника. Чтива 0, виток0
Регистры — это несколько байт, которые физически находятся в центральном процессоре (ЦП). Регистры нужны для вычислений и связи ЦП с внешним миром.
Большая часть кода программ состоит из команд копирования значений из оперативной памяти в регистры и обратно.
Знаю по себе: понять, зачем нужны регистры можно только через практику. Другого способа нет.
Причём где-то дней 10-15 я вообще не врубался — как оно всё. чтобы вот как-то вот так. 🙂
Но сейчас мне кажется, что регистры — самое простое, и в то же время важное, что есть в процессоре.
- Приём данных от пользователя (данные передаются в регистры).
- Вычисления (загрузили в регистр число и дали команду, как его обработать).
- Приём данных из устройств (опять они — регистры).
- Изменение состояния ЦП и периферийных устройств (только регистры! Например, регистр флагов).
- Вывод данных на устройства.
- Передача данных другим программам.
Во всех операциях программы (а я перечислил только основные) без регистров не обойтись.
И если даже данные не хранятся в регистрах, то в них обязательно будут указатели на эти данные (адреса данных в памяти), других способов работы с данными у процессора нет, только через собственные регистры. А если речь идёт исключительно о сторонних устройствах (например, звуковуха, видяха, винт и т.д.), то мы тоже используем регистры, только это уже не регистры процессора, а, например, PCI-регистры, или AGP, или SATA и так далее.
Так что тема АРХИВАЖНАЯ! Но непонятная без примеров. Их должно быть как минимум 10-15 (по одному в день).
Что-то я разболтался, давайте уже к делу.
Загрузите prax01.com в отладчик CodeView. Для этого скопируйте файл в каталог отладчика (примечание) и запустите из командной строки «cv prax01.com». Отладчик CodeView у вас должен выглядеть примерно так:
- В окне 3 — дизассемблированный код.
- В окне 5 — hex-байты. Их адреса (слева) и символы, которые они означают (справа).
- В окне 7 — регистры процессора.
- 9 — командная строка.
Итак, смотрим на код программы в памяти:
[3] Адреса Байты Имена Операнды 12BD:0100 B409 MOV AH,09
12BD — сегмент нашей программы, он может быть и другим.
Поскольку все примеры для DOS у нас значительно меньше 64Kb, они будут в одном сегменте.
Значит, как я уже говорил, до следующего витка мы можем игнорировать сегменты вообще, а уж в Win32 тем более.
У com-программ в оперативной памяти адрес первой машинной команды равен 100h.
Так происходит потому, что ДОСу первые адреса нужны для служебной информации о программе: командная строка, атрибуты и всё такое (можете посмотреть в окне[5], что там).
ДОС-программы нас уже не очень интересуют, и об этом больше говорить не будем.
А в Win-программах происходит примерно то же самое, только адрес первой машинной команды обычно находится чуть дальше. В самых простых приложениях — 00401000h. 🙂
Нужно просто прибавлять 100h, чтоб узнать адрес в памяти из адреса в com-файле. Так же, как вы складываете 8+100d=108d. Будет всё точно так же, 8+100h=108h.
Я легко посчитал адрес текстовой строки в оперативной памяти (010D) и заложил его в код программы.
Ведь мы знаем номер байта в файле, где начинается строка текста. 0D+100h — и вот он, искомый адрес в памяти.
Главная кнопка здесь будет F10, вам нужно нажимать только на неё (всё остальное можно делать мышкой). Эта кнопка с каждым нажатием будет выполнять текущую строку.
Вы уже понимаете, что отображается в окне[5], и также имеете представление, что в окне[3] — код программ. А вот окно [7] для вас пока ничего не означает, и это очень досадно, но мы сейчас исправимся.
Нажмите F10 один раз. Выполнится строка
mov ah,09
Эта команда означает: поместить значение 09 в регистр AH. Но в окне[7] нет регистра AH, а после выполнения этой строки изменится регистр EAX.
Было EAX=00000000 Стало EAX=00000900
Дело в том, что регистр AH — это часть регистра AX, а он — часть регистра EAX.
Если, предположим, мы загрузили регистр EAX значением 44332211, выглядеть он будет так:
EAX=44332211 AX= 2211 AH= 22 AL= 11
Один 32-разрядный регистр — это всего лишь 4 байта в процессоре.
Точно так же устроены ещё 3 регистра.
Схематично их отображают так:
EAX (сокращение от Accumulator) EBX (сокращение от Base) ECX (сокращение от Counter) EDX (сокращение от Data)
Можно, кстати, писать и строчными буквами, регистры не обидятся.
EAX, EBX, ECX и EDX — 32-битные регистры данных центрального процессора (от 386-го и по сей день). В эти регистры помещаются необходимые значения, и в них же чаще всего оказываются результаты вычислений. В 32 битах можно хранить hex-число от 00 00 00 00h до FF FF FF FFh. И это всё, что может там храниться. На назначения (Accumulator, Base, Counter, Data) в наших уроках можно вообще не обращать внимания. Когда вы освоите команды Ассемблера, вы сами поймете, почему Counter и почему Base.
Первая строка кода из нашей программы уже почти понятна.
mov ah,9
Имя команды операнд1,операнд2 где операнд1 - регистр AH операнд2 - цифра 9
Остаётся непонятной только сама команда.
MOV — это основная команда Ассемблера, она встречается в программах гораздо чаще остальных.
Для программирования самое важное понятие — переменная. На самом деле переменная лишь абстракция для удобства программирования. Такая же абстракция, как в алгебре X, Y, Z. Чтоб решить какую-нибудь задачу, мы говорим, что Y будет равен 5, а Z будет равен 3.
Так вот присвоение переменной Y значения 5 на Ассемблере будет выглядеть так:
mov Y,5
Я расскажу только об основных командах и только самое главное. Полную информацию всё равно нужно брать из справочника. Но каждая новая команда будет оформлена в моей статье почти как в справочнике.
Этого достаточно, чтоб использовать команду в своих программах. Посмотрите ещё раз на первые две строки prax01:
Имена Операнды комментарии команд mov ah,09 ; поместить значение 9 в регистр AH mov dx,010D ; поместить значение 010Dh в DX
Получив первую инструкцию, процессор выполнит инициализацию своего 8-битного регистра AH значением 9, после чего регистр AH будет содержать только байт 09.
При выполнении второй команды процессор поместит в свой 16-битный регистр DX значение 010Dh, после чего регистр DX будет содержать только эти два байта 01 и 0Dh.
Причём, если вы ещё раз посмотрите на устройство регистров, вы обязательно поймёте следующее:
Так как регистр DX состоит из DH и DL, то можно сказать, что после выполнения второй строки кода программы в регистре DH окажется значение 01, а в регистре DL окажется значение 0Dh.
DH DL DX=01 0D
Это просто, но важно! В 16-битный регистр (AX,BX,CX,DX) нельзя положить значение больше двух байт (FFFFh).
А в 8-битный (AH,AL, BH,BL, CH,CL, DH,DL) нельзя положить больше байта, то есть FFh.
EAX=99884433 AX= 4433 AH= 44 AL= 33
Вы должны понять, что физически есть только 4 байта (99 88 44 33h). По отдельности можно обращаться к AX за значением 4433h, или к AH за 44h, или к AL за 33h. Но 9988h находится в E-части, а у неё нет собственного имени, она не является подрегистром. Вы не можете прочитать или загрузить 2 старших байта такого регистра, не обратившись ко всему регистру. Пример:
mov EAX, 0FFFFFFFFh ; Так правильно, и EAX будет равен FFFFFFFF mov EAX, 01FFFFFFFFh ; Так НЕправильно. Значение больше регистра, ; данной операции быть не может mov EAX, 0 ; Так правильно, и EAX станет равен 00000000 mov AX, 0FFFFh ; Так правильно, и EAX будет равен 0000FFFF mov AX, 1FFFFh ; Так НЕправильно. Значение больше регистра, ; данной операции быть не может mov AX, 0 ; Так правильно, и AX станет равен 0000 mov AH, 111h ; Так НЕправильно. Значение больше регистра, ; данной операции быть не может mov AL, 100h ; Так НЕправильно. Значение больше регистра, ; данной операции быть не может mov AL, 0BBh ; Так правильно, и EAX станет равен 000000BB mov AH, AL ; так правильно, и EAX станет равен 0000BBBB
Это, думаю, понятно. А к старшей части EAX отдельно обращаться можно, например, при помощи команд сдвига битов:
shl EAX,10h ; Сделает теперь регистр EAX равным BBBB0000 shr EAX,10h ; А эта команда сделает его обратно равным 0000BBBB
О командах сдвига потом напишу подробно, сейчас я их привёл, только чтоб ответить на вопрос, как можно отдельно читать/записывать 2 старших байта E-регистров.
Итак, мы «познакомились» с четырьмя регистрами общего назначения (РОН), и есть ещё четыре.
Регистры-указатели
Они тоже входят в группу РОН. В этой программе нам они безразличны, но далее мы научимся работать и с ними. Причём на практике всё будет очень похоже на регистры данных. Да и вообще отличий между ними совсем немного. Главное отличие в том, что в регистрах-указателях нет подрегистров, есть только одна вложенная часть.
ESP=44332211 (Extended Stack Pointer) SP= 2211 (Stack Pointer)
Мы опять видим четыре байта, ведь все E-регистры 32-разрядные (32 бита — это 4 байта).
EBP включает в себя BP (Base Pointer) указатель базы ESP включает в себя SP (Stack Pointer) указатель стека ESI включает в себя SI (Source Index) индекс источника EDI включает в себя DI (Deliver Index) индекс приёмника
Эти страшные слова «указатель базы», «индекс» на самом деле для нас практически ничего не означают. Вы можете забыть о назначении этих регистров и совершенно свободно использовать регистры ESP, EBP, ESI, EDI, как вам захочется. Только нужно сохранять их содержимое на время использования и восстанавливать обратно. Поэтому сейчас не забивайте голову индексами, базами и стеками. Хотя как раз про стек мы скоро поговорим.
Я уже рассказал почти обо всех регистрах, с которыми нам придётся иметь дело. Остались 2 специальных регистра: EIP и E-flags.
Регистр адреса текущей машинной команды — EIP
Процессор берёт из памяти машинную команду и увеличивает текущий адрес так, чтобы он указывал на следующую команду. Именно для этого и существует EIP.
EIP=44332211 (Extended Instruction Pointer) IP= 2211 (Instruction Pointer)
И опять посмотрите на первые две строки кода в отладчике:
Адрес Байты Имена Операнды 0100: B409 mov ah,009 0102: BA0D01 mov dx,0010D
Обратите внимание на столбик с байтами. Первая инструкция занимает в памяти два байта (B4h — байт кода операции, 09 — значение в команде). А вторая уже 3 байта, так как значение больше. Есть инструкции, которые занимают аж 15d байт.
Упростив процес выполнения машинной команды, можно сказать так: процессор знает, что если в первом байте содержится код операции 1011 0. b (от B0h до B7h), то это означает, что машинная команда занимает два байта. Тогда он увеличивает значение регистра EIP на два. Затем, выполнив саму маш.команду, он берёт следующий байт по адресу, указанному регистром EIP, и, узнав, что там инструкция с опкодом 1011 1. b (от B8h до BFh), увеличивает значение EIP на три. И так далее.
Таким образом, когда мы смотрим на выполнение программы в отладчике, регистр EIP всегда будет содержать адрес следующей команды процессора, которая ждёт выполнения (примечание).
Есть такие команды Ассемблера, цель которых просто изменить регистр EIP, например, jmp (от слова jump — прыгать). Допустим, ЦП получает машинную команду EB 10h, тогда он выполняет изменение EIP и сразу же приступает к выполнению следующей инструкции — той, что теперь будет указана в EIP.
Теоретически все это запомнить очень сложно и, самое главное, не нужно. Потому что на практике всё станет просто как 2х2.
Мы ещё не обсудили регистр Eflags, который для нас будет очень важен. Говорить о нём без примера программы, мне кажется, глупо. Скоро и до него доберёмся.
Также вы очень часто будете натыкаться на сегментные регистры (CS,DS,ES,FS,GS,SS). Они фигурируют во всех справочниках, отображаются в отладчиках и даже указываются в командах (например: mov byte ptr DS:[EBX],01).
Но сегментные регистры нужно обсуждать вместе с сегментацией памяти, а это отдельная тема, не первой важности.
На сегодняшнем этапе развития процессоров и операционных систем понимание сегментации можно отложить до следующего витка.
Думаю, что для третьего дня теории вполне достаточно, даже, наверное, через край, вы уж извините.
Проведите prax01.com по отладчику CodeView самостоятельно (клавишей F10). Повторите этот процесс несколько раз, посмотрите, как будут меняться регистры. Найдите в окне памяти[5] код и данные программы. Чем больше времени вы будете проводить в отладчике, тем быстрее вы станете толковым программистом.
Я очень хорошо помню своё обучение. Самые сложные дни уже позади, ещё пару дней будет трудно и непонятно.
Осталось совсем немного корпеть над учебниками, скоро будут сплошные приключения, держись, юнга.
Вернуться на главную
— CodeView DOS’овый отладчик, он не любит длинных, русских или сложных путей, поэтому рекомендую держать его папку в корне диска.
Форточки будут тормозить, пока программа в отладчике. И другие вы загрузить не сможете. Похожие фокусы есть во всех дебагерах — такой уж класс программ.
Кроме того, ДОС-программы в хрюшке сами по себе здорово тормозят машину. Но для нас это не критично. Я надеюсь 🙂
Чтоб в CodeView всё выглядело именно так, нужно включить в «Options» пункт «32-bit registers». И для правильного отображения нужно выключить «Screen Swap» (если брали CV у меня — всё уже настроено).
— Регистр EIP указывает на следующую инструкцию, которая будет выполнена, только с точки зрения внешней архитектуры процессора. Если говорить о процессоре изнутри, то изменение EIP и выполнение действия нескольких инструкций может происходить параллельно (в разных блоках).
Опять же земля не плоская, но для нас в отладчике — это так. 🙂
Регистры процессора Intel 8086: от чипа к транзисторам
Intel 8086 – один из самых влиятельных из когда-либо созданных компьютерных чипов. Он создал архитектуру x86, доминирующую в современных настольных и серверных компьютерах. Я занимался реверс-инжинирингом 8086 на основе фотографий кристаллов, и в данной статье я опишу реализацию регистрового файла (набора регистров).
Кристалл 8086. Отмечено место хранения регистров. Верхние регистры используются блоком интерфейса шины для доступа к памяти, а нижние регистры общего назначения используются исполнительным блоком. Буфер команд – это 6-байтная очередь, состоящая из заранее запрошенных команд.
На фото дано изображение процессора 8086 под микроскопом. Виден металлический слой сверху чипа, под которым прячется кремний. Расположенные по внешнему краю соединительные провода соединяют площадки на кристалле с 40 внешними контактами чипа.
Рамкой обведены пятнадцать 16-битовых регистров 8086 и шесть байтов очереди предварительного запроса команд (предвыборки кода). Регистры занимают значительную часть кристалла, хотя в сумме их наберётся всего на 36 байт. Из-за ограничений по площади у первых микропроцессоров было относительно небольшое количество регистров. Для сравнения, у современных чипов процессоров есть килобайты регистров и мегабайты кэшей.
8086 был одним из самых ранних микропроцессоров, реализовавших предвыборку кода, однако у Motorola 68000 (1979) немного раньше появился буфер предвыборки на 4 байта. В мейнфреймах предвыборка существовала у IBM Stretch (1961), CDC 6600 (1964) и IBM System/360 Model 91 (1966).
Количество регистров у современного процессора подсчитать сложно. Единственное точное число, найденное мною, содержалось в книге «Анатомия высокоскоростного микропроцессора» (1997), подробно описывающей процессор AMD K6. Из-за переименования регистров у современных процессоров физических регистров гораздо больше, чем архитектурных (тех, что видны программисту), а количество физических регистров в документации не указывается. У K6, кроме восьми регистров х86 общего назначения, было 16 микроархитектурных рабочих регистров для переименования.
У процессоров с поддержкой AVX-512 есть 32 512-битовых регистра, то есть для одной этой функции зарезервировано 2 кБ регистров. В таких случаях размер регистров подсчитать ещё сложнее. Что до размера кэша, то у передовых процессоров его объём доходит до 77 МБ.
Как регистры реализуются в кремнии
Начну с описания того, как 8086 создаётся из N-МОП транзисторов. Затем объясню, как делается инвертор, как при помощи инверторов хранятся одиночные биты, и как делается регистр.
8086, как и другие чипы той эпохи, создавались на основе транзисторов типа N-МОП. Эти чипы состояли из кремниевой подложки, в которую при помощи диффузии внедряли примеси из мышьяка или бора, формировавшие транзисторы. Находящиеся над кремнием проводники из поликремния формировали вентили транзисторов и соединяли все компоненты друг с другом. Находившийся ещё выше металлический слой содержал дополнительные проводники. Для сравнения, современные процессоры используют технологию КМОП, комбинирующую N-МОП и P-МОП транзисторы, и содержат множество металлических слоёв.
На схеме ниже показан инвертор, состоящий из N-МОП транзистора и резистора. При низком входном напряжении транзистор выключен, поэтому подтягивающий резистор подтягивает выход вверх. При высоком входном напряжении транзистор включается, соединяет землю и выход, подтягивая выход вниз. Таким образом входящий сигнал инвертируется.
На самом деле, подтягивающий резистор в вентиле N-МОП представляет собой транзистор особого рода. Работающий в режиме обеднения транзистор ведёт себя, как резистор, будучи при этом более компактным и эффективным.
На схеме показано, как из транзистора и резистора получается инвертор. На фото показана реализация на чипе. Металлический слой удалён, чтобы было видно кремний и поликремний.
На фото выше видно, как физически сделан инвертор у 8086. Розоватые участки – это кремний с примесями, делающими его токопроводящим, а линии медного цвета – это поликремний сверху. Транзистор появляется там, где поликремний пересекается с кремнием. Поликремний формирует вентиль транзистора, а участки кремния с двух сторон дают исток и сток. Большой прямоугольник поликремния формирует подтягивающий резистор между +5 В и выходом. Таким образом, схема чипа совпадает со схемой инвертора. Подобные цепи можно рассмотреть под микроскопом и подвергнуть реверс-инжинирингу.
Строительный блок регистра – два инвертора в цепи обратной связи, хранящих один бит (см. ниже). Если на верхнем проводнике 0, правый инвертор выдаст 1 на нижний проводник. Затем левый инвертор выдаст 0 на верхний проводник, завершая цикл. Таким образом цепь оказывается стабильной и «помнит» 0. И наоборот, если на верхнем проводнике 1, она превратится в 0 на нижнем проводнике, и обратно в 1 на верхнем. В итоге цепь может хранить 0 или 1, формируя однобитную память.
У 8086 два спаренных инвертора хранят в регистре один бит. Схема стабильно находится в состоянии 0 или 1.
К паре инверторов добавляются три транзистора, чтобы получилась пригодная для использования ячейка регистра. Один транзистор выбирает ячейку для чтения, второй выбирает ячейку для записи, третий усиливает сигнал при чтении. В центре схемы ниже два инвертора хранят бит. Для чтения бита ток подаётся на красную шину. Это соединяет выход инвертора с разрядной шиной через усиливающий транзистор. Для записи бита ток подаётся на красную шину, соединяющую разрядную шину с инверторами. Подавая высокотоковые сигналы 0 или 1 на разрядную шину (и, соответственно, на хранящийся бит), мы заставляем инверторы переключаться на нужное значение. Отметьте, что разрядная шина используется и для чтения, и для записи.
В других процессорах используются несколько другие ячейки для хранения регистров. 6502 использует дополнительный транзистор в цепи обратной связи инвертора, чтобы разбивать цепь при записи нового значения. Z80 пишет одновременно в оба инвертора, что «облегчает» изменение, однако требует наличия двух проводников для записи. У 8086 есть усиливающий транзистор в каждой ячейке регистра для чтения, а другие процессоры считывают выходной сигнал с обоих инверторов и используют внешний дифференциальный усилитель для усиления сигнала. Базовая ячейка регистра 8086 состоит из 7 транзисторов (7Т), что больше, чем в типичной статичной ячейке RAM, использующей 6 или 4 транзистора, однако она использует только одну разрядную шину, а не две разных. Динамическая память (DRAM) работает гораздо эффективнее, и использует один транзистор и конденсатор, однако без обновления данных они будут утеряны.
Принципиальная схема хранящей бит ячейки регистра. Регистровый файл делается из массива таких ячеек.
Регистровый файл состоит из матрицы ячеек регистров, подобных описанному выше. Ширина матрицы составляет 16 ячеек, поскольку в регистрах хранятся 16-битные значения. Каждый регистр расположен по горизонтали, поэтому шина чтения или записи выбирает все ячейки определённого регистра. 16 вертикальных разрядных шин формируют единую шину, поэтому все 16 битов в выбранном регистре читаются и записываются параллельно.
На фото ниже показан увеличенный регистровый файл 8086 общего назначения, и видна матрица регистровых ячеек: 16 столбцов и 8 строк, 8 16-битных регистров. Затем дано увеличение одной регистровой ячейки в файле. Объясню, как реализована эта ячейка.
Фото кристалла 8086 с увеличением нижнего регистрового файла (восьми 16-битовых регистров), а потом и отдельной регистровой ячейки. Чтобы было видно кремниевые структуры, металлический и поликремниевый слои удалены.
8086 делается из кремния с примесями и поликремниевых проводников с металлическими проводниками сверху. На левом фото ниже показаны вертикальные металлические проводники регистровой ячейки. Отмечены земля, питание и проводники разрядной шины (оставшийся проводник пересекает регистровый файл, но не контачит с ним). На фото справа металлический слой растворён, чтобы было видно поликремний и кремний. Шины чтения и записи – это горизонтальные поликремниевые проводники. Поскольку у чипа только один металлический слой, в регистрах для вертикальных шин используется металл, а для горизонтальных – поликремний, чтобы они не пересекались друг с другом). Сквозные соединения металла и кремния видны как более яркие круги на фото с металлом и как кружки на фото с кремнием.
Ячейка хранения регистра. На фото слева показан металлический слой, а справа – соответствующие слои поликремния и кремния.
На диаграмме ниже показано соответствие физической схемы регистровой ячейки с принципиальной. Инверторы состоят из транзистора А и В и регистров. Транзисторы C, D и E формируются отмеченными кусочками поликремния. Разрядная шина не видна, поскольку располагается в металлическом слое. Отметьте, что схема ячейки памяти сильно оптимизирована для минимизации размера. Также отметьте, что транзистор А гораздо меньше других. У инвертора А довольно слабый выходной ток, поэтому разрядная шина может преодолеть его при записи.
Регистровая ячейка от 8086 с соответствующей схемой
Поддержка 8-битных регистров
Если тщательно изучить кристалл, видно, что некоторые регистровые ячейки имеют немного другую структуру. Слева расположена регистровая ячейка, которую мы уже обсудили, а справа – пара регистровых ячеек с двумя шинами записи вместо одной. На левом фото шина записи пересекает кремний в обеих регистровых ячейках. На правом фото шина «правой записи» пересекает кремний в правой части, однако проходит между участками кремния в левой. И наоборот, шина «левой записи» пересекает кремний с левой стороны и проходит между участками кремния справа. Таким образом, одна шина управляет записью правого бита, а другая – левого. В 16-битном регистре таким образом можно отдельно записывать перемежающиеся 8-битные части.
Регистровая ячейка на кристалле повторяются не единообразно – каждая вторая ячейка является зеркальным отражением предыдущей. Это увеличивает плотность регистровой ячейки – шина питания, идущая между двумя зеркальными ячейками, может питать их обе (то же касается и земли). Зеркальные повторения уменьшают количество требуемых шин питания и земли в два раза.
Хотя на блок-схемах обычно нарисовано, как 16-битные регистры делятся на левую и правую половины, в реальном исполнении биты с каждой из сторон чередуются, вместо того, чтобы всегда хранить первую 8-битовую часть слева, а вторую – справа. Такая реализация упрощает иногда возникающую задачу перемены местами двух половин 16-битного слова. Один из таких случаев – чтение или запись памяти без выравнивания. Другой – операция АЛУ, использующая верхнюю часть регистра – например, АН. Для перемены местами битов между правой и левой половинами потребовалось бы тянуть длинные проводники между всеми битами половин слов. Однако при чередующемся расположении для перемены местами двух половин слова требуется поменять местами каждую пару соседних битов, для чего длинные проводники не нужны. Иначе говоря, чередующееся расположение регистров 8086 облегчает задачу расположения проводников для перемены местами двух половин слова.
Две пары ячеек памяти с разными цепями. У ячеек слева одна шина записи, а справа – отдельные шины записи для левых и правых битов.
Почему у некоторых регистров две шины записи, а других – одна? Причина в том, что у 8086 16-битные регистры, однако к четырём из них можно обращаться и как к 8-битным, как показано ниже. К примеру к 16-битному аккумулятору А можно обращаться как к 8-битному AH (старшая часть аккумулятора) и 8-битному AL (младшая часть) регистрам. Реализация регистров с двумя шинами контроля записи позволяет вести запись в каждую половину регистра отдельно.
Если бы регистровый файл поддерживал только 16-битные регистры вместо 8-битных, процессор мог бы работать, но менее эффективно. Записи в 8-битные половины производились бы путём чтения всех 16 бит, изменения 8-битной половины и записи всех 16 бит. В результате вместо доступа к одному регистру обращение шло бы к трём. При этом регистровому файлу не нужно как-то особо поддерживать чтение 8 бит, поскольку ненужную половину можно просто игнорировать.
Регистры общего назначения в процессоре 8086. Регистры A, B, C и D можно делить на две 8-битных половины.
Многопортовые регистры
Пока что мы рассмотрели восемь «нижних регистров» общего назначения. У 8086 есть также семь «верхних регистров», используемых для доступа к памяти, включая печально известные сегментные регистры. У этих регистров схема работы более сложная, многопортовая, позволяющая одновременно вести несколько процессов чтения и записи. К примеру, многопортовый регистровый файл позволяет прочесть счётчик программы, сегментный регистр и записать другой сегментный регистр – и всё это одновременно.
Приведённая ниже блок-схема отличается от большинства блок-схем для 8086, поскольку показывает реальную физическую реализацию процессора, а не ту, что представляет себе программист. В частности, на диаграмме показано два «регистра внутренних коммуникаций» среди регистров модуля интерфейса шины (справа) вместе с сегментными регистрами, совпадающими с 7 регистрами, которые можно рассмотреть на кристалле. Временные регистры, показанные ниже, физически являются частью АЛУ, поэтому в данной статье я их не рассматриваю.
В книжке «Разработка современных процессоров» обсуждаются сложные системы регистров в процессоре, начиная с 2000-х годов. Там написано, что сложность цепей быстро выходит за рамки трёх портов, а в некоторых передовых процессорах существуют регистровые файлы с 20 или более портами.
Многопортовая регистровая ячейка ниже создана на основе той же схемы из двух инверторов, однако у неё есть три разрядных шины (а не одна, как в предыдущем случае) и пять управляющих шин (вместо двух). Три шины чтения позволяют читать содержимое регистровой ячейки по любой из трёх разрядных шин, а две шины записи позволяют разрядной шине А или С вести запись в регистровую ячейку.
Многопортовая регистровая ячейка в процессоре 8086
На первый взгляд, регистровый файл 8086 выглядит как однородный набор регистров, однако при тщательном рассмотрении видно, что каждый регистр оптимизирован в зависимости от его функции. Некоторые из них – простые 16-битные регистры, и расположены наиболее компактно. К другим 16-битным регистрам можно обращаться как к двум 8-битным, что требует ещё одной управляющей шины. У наиболее сложных регистров есть два-три порта для чтения и один-два для записи. В каждом случае физическая реализация регистровой ячейки тщательно разработана так, чтобы занимать как можно меньше места, в связи с чем форма транзисторов часто получается сложной. Инженеры Intel сжимали схему расположения регистров как можно сильнее, чтобы уместить их все на доступной им площади.
У верхних регистров есть разное количество портов для чтения и записи: два регистра с 3 шинами чтения и 2 шинами записи, один регистр с 2 шинами чтения и 2 шинами записи, четыре регистра с 2 шинами чтения и 1 шиной записи. Три первых регистра – это, вероятно, счётчик программы, непрямой временный регистр и временный регистр операнда. Последние четыре – вероятно, сегментные регистры SS, DS, SS и ES. Также там есть три регистра буфера предварительной выборки команд, каждый с одной шиной для чтения и одной для записи.
Процессор 8088, использовавшийся в оригинальных компьютерах IBM PC, был практически идентичен 8086, кроме того, что у него была 8-битная внешняя шина вместо 16-битной – так стоимость системы получалась меньше. Размер буфера предварительной выборки у 8088 был 4 байта вместо 6 – вероятно, потому, что 4-х байт было достаточно для более медленной шины памяти 8088-го.
В отличие от 8086, регистры предварительной выборки у 8088 поддерживают независимую запись в 8-битные половинки (похоже на регистры A, B, C и D 8088-го, только эти ячейки выглядят по-другому). Всё потому, что 8088 запрашивал команды по одному байту за раз, а не по одному слову, из-за узкой шины. Поэтому регистры предварительной выборки должны поддерживать побайтовую запись, в то время, как 8086-й поддерживает предварительную выборку по словам.
Файл верхних регистров, состоящий из десяти 16-битных регистров. На фото видно кремний и поликремний. Вертикальные красные линии – остатки удалённого металлического слоя. Кликабельно.
Заключение
Хотя 8086 процессору уже 42 года, он до сих пор оказывает значительное влияние на современные компьютеры, поскольку архитектура х86 до сих пор очень часто используется. Регистры 8086 до сих пор существуют в современных компьютерах х86, хотя сегодня они уже имеют длину в 64 бита, а кроме этих регистров существует и множество других.
Кристалл 8086-го очень интересно изучать, поскольку его транзисторы можно рассмотреть под микроскопом. В своё время это был сложный процессор, поскольку на нём расположено 29 000 транзисторов, однако достаточно простой для того, чтобы отследить все цепи и понять, как они работают.
- «Масштабирование кристалла: как Intel уменьшала масштаб процессора 8086»
- «Реверс-инжиниринг микропроцессора Intel 8086»
- «Анализ кристалла сдвигового регистра у математического сопроцессора 8087»