Журнал «Компьютерра» №41 от 08 ноября 2005 года pic_15.jpg

Что же делать, если нам хочется как-то обойти это ограничение, не теряя совместимости со старыми приложениями? К счастью, инженеры, проектировавшие x86 ISA, были очень талантливыми и прозорливыми, а потому заложили в архитектуру возможность вводить перед инструкциями приставки - специальные указатели, так или иначе изменяющие значения инструкций. Скажем, приставка LOCK говорит, что инструкция должна быть выполнена в «атомном» режиме["Атомный" режим - это когда выполнение инструкции гарантированно не будет прервано каким-нибудь внешним событием. К примеру, если мы что-нибудь записываем в оперативную память, то начиная с момента исполнения и до завершения атомной инструкции никто «посторонний» не сможет ни записать в то же место оперативной памяти, ни прочитать оттуда. Используется в многопроцессорных системах для организации межпроцессорного взаимодействия], приставки 2E и 2F подсказывают процессору, произойдет условный переход или нет, а приставка 66 приказывает переключаться между 16-битным и 32-битным представлением данных в регистрах. Поэтому когда разработчикам x86-64 понадобилось добавить в архитектуру IA-32 поддержку 64-битности, они сделали очень простую и в то же время гениальную вещь, введя набор 64-битных приставок REX, которые не столько расширяют возможности инструкций, сколько служат для кодирования дополнительной информации в четырех своих полях. Поле REX.W задает «размер» обрабатываемых данных: если здесь записан нолик, то обрабатываемые регистры интерпретируются как 32-битные, если единичка - то как 64-битные; а поля REX.R, REX.X и REX.B - старшие биты, дополняющие трехбитные поля ModR/M.Reg, SIB.Index и, в зависимости от ситуации, ModR/M.R/M или SIB.Base соответственно. Знаю, что это звучит не слишком понятно, поэтому тут же поясню, что это означает. На самом деле в 64-битном режиме мы используем 4-битную кодировку регистров процессора, но три младших регистровых бита записываем на их «традиционные» места в инструкции, а старший бит - переносим в приставку REX, обходя тем самым архитектурное ограничение. А заодно, помимо поддержки восьми новых GPR-регистров R8-R15 и SSE-регистров XMM8-XMM15, получаем возможность отключить 64-битные вычисления, когда они нам не требуются, - и пропорционально сэкономить на времени исполнения и занимаемом данными месте! И все это - одним-единственным байтом!

Вторая группа усовершенствований - отказ от поддержки безнадежно устаревших и давно не использующихся возможностей IA-32. В расширенном режиме не поддерживаются Real Mode и Virtual 8086-mode[Да-да, Virtual 8086 - это именно то, о чем вы сейчас подумали, - полная имитация процессора Intel 8086. Много ли найдется людей, которые пожалеют о его отсутствии?], и почти полностью отключена сегментация[Поскольку читатель уже заскучал от обилия технических фактов, не буду утомлять его описанием сегментированной модели памяти 80286, скажу только, что сегментация - это очень упрощенный аналог виртуальной памяти] (хотя некоторые настройки сегмента CS и сегменты FS/GS все же можно использовать); не поддерживаются инструкции, работавшие с этими сегментами, и инструкции, оперировавшие BCD-числами[BCD (Binary Coded Decimal) - это когда десятичное число записывается шестнадцатеричными цифрами. Например, 54d - в виде 54h = 01010100b вместо традиционных 36h = 00110110b]. Все это позволяет заметно облегчить процессору жизнь и в перспективе - повысить его производительность. К примеру, отказ от сегментации позволяет при обращении к оперативной памяти не вычислять линейный адрес и не проверять его допустимость в рамках сегмента, а значит, эффективность этой подсистемы при переходе к x86-64 существенно возрастет.

«А как обстоят дела с совместимостью со старыми программами?» - спросит читатель. Посмотрим: если обойтись без приставки REX, то процессор будет считать, что все записанные там данные - нули, то есть новые регистры не используются, а размер операндов инструкции равен 32 битам, то есть старшие 32 бита каждого 64-разрядного регистра при подобных вычислениях явным образом забиваются нулями. Как легко догадаться, инструкция без префикса REX даже в 64-битном режиме будет работать точно так же, как она работала и в 32-битном; и если соблюдать меры предосторожности (в частности, не выходить при адресации за границу «нижних 4 Гбайт» виртуальной памяти, то даже в 64-битном режиме все программы будут работать как и в 32-битном! Красивое решение? Мне кажется, да. Если программе не требуется поддержка всяческих «64-битностей», то она может запросто продолжать работать на «физически» 64-битном процессоре в 32-битном режиме, не используя ни 64-битные указатели, ни 64-битные вычисления, зато «радуясь» удвоенному количеству регистров и другим улучшениям x86.

К сожалению, «нет в мире счастья», и по кодировке префиксы REX совпадают с шестнадцатью «сокращенными» инструкциями семейств INC и DEC (увеличение или уменьшение содержимого регистра на единичку). Вдобавок в 64-битном режиме не поддерживается ряд инструкций и «режимов» x86 (о чем речь пойдет ниже), а для нескольких инструкций изменены опкоды или их смысловая нагрузка[К примеру, инструкция 90h в классическом x86 означает XCHG EAX, EAX (поменять местами регистр EAX с регистром EAX). Поскольку от перестановки двух одинаковых регистров их содержимое не меняется, то эту комбинацию часто используют в качестве однобайтной «пустышки» (NOP), которая ничего не выполняет, зато занимает 1 байт машинного кода. Зачастую некоторые инструкции хочется «выровнять» в оперативной памяти, сделав так, чтобы они, например, не «пересекали 16-байтные границы» (если этого не сделать, то при декодировании инструкции возникнет «штраф», связанный с тем, что процессору придется «склеивать» инструкцию из нескольких 16-байтных кусочков); и если, скажем, эта инструкция - начало цикла, то непрерывная выплата «штрафа» может существенно замедлить выполнение программы. Вставка нескольких NOP’ов, «закрывающих» возникающие из-за выравнивания «дырки» в коде, - обычная практика, однако в 64-битном режиме процессор не просто переставит EAX с EAX местами, а еще и заполнит старшие 32 бита регистра RAX нулями - и наша инструкция уже не будет «настоящим» NOP’ом. Поэтому в x86-64 опкод 90h обрабатывается по-особому, всегда интерпретируясь как NOP]; так что даже в «тепличных» 32-битных условиях перекомпиляция программ для поддержки x86-64 все-таки требуется. Но и унывать по этому поводу не приходится: получить все преимущества от расширенного набора регистров без перекомпиляции все равно невозможно, а если очень хочется запустить 32-битное приложение, это можно сделать, временно переведя процессор в «режим совместимости» (Compatibility Mode), в котором полностью имитируется классический IA-32.


Перейти на страницу:
Изменить размер шрифта: