Но кто сказал, что x86-64 - это только 64-битные указатели, команды и регистры GPR?..
IA-32 versus x86-64
Переделывая почти весь процессор и весь набор поддерживаемых им инструкций, мы все равно получаем несовместимость в 64-битном режиме со старыми программами. Так почему бы заодно не исправить ошибки предшественников и не убрать из x86 устаревшие ограничения? 64-битный режим не просто улучшенная версия IA-32 ISA, фактически это следующая версия архитектуры x86, во многом лишенная «родовых травм» этого семейства, начинавшегося с микроконтроллеров и долгое время не претендовавшего на «серьезное» использование.
Первое и, пожалуй, главное преимущество, которое получила архитектура x86-64, - долгожданная поддержка шестнадцати регистров общего назначения и шестнадцати регистров XMM. На первый взгляд это чисто количественное изменение, но на самом деле - принципиальная во всех отношениях доработка архитектуры IA-32. Судите сами: у современного IA-32 восемь регистров общего назначения (EAX, EBX, ECX, EDX, EDI, ESI, EBP и ESP), восемь 64-битных регистров MMX (MMX0-MMX7) и восемь же регистров SSE (XMM0-XMM7). Не замечаете ничего странного? Откуда вообще взялась цифра восемь? Для ответа на этот вопрос нам потребуется обратиться к «основам основ» - к формату x86-инструкций.
Каждая инструкция представляет собой длинную комбинацию из пяти составляющих: а) опкода (operation code, opcode), описывающего, что нужно сделать с данными, б) поля ModR/M, в котором записывается «подвид» инструкции, режим адресации памяти (как и у всякого CISC-процессорa, этих подвидов у x86 много) и один или два регистра, используемых инструкцией, в) поля SIB (Scale-Index-Base), в котором при некоторых режимах адресации памяти записываются еще два регистра (база и индекс) и масштаб, г) полей Displacement и Immediate, в них записываются используемые инструкцией константы, д) набора «приставок» (prefixes), позволяющих задать «нестандартные» режимы использования инструкции. Обязательным является только поле опкода, все остальные опциональны и могут отсутствовать[Теперь понимаете, что имелось в виду под «непомерной сложностью декодирования CISC-инструкций»?].
Оставим в стороне вопрос о том, для чего нужна столь сложная конструкция и как она работает, и сосредоточимся на одном-единственном интересующем нас аспекте: как в x86 кодируются используемые инструкциями операнды. Смотрите: поля Reg/Opcode[В принципе здесь может быть записан не регистр, а (для некоторых инструкций) дополнительный опкод, указывающий на ту или иную разновидность одной и той же инструкции] и R/M[Здесь тоже не всегда записывается регистр - иногда это поле используют для кодирования одной из 24 разновидностей режимов адресации памяти. В x86 много подобных «тонкостей»] занимают по три бита, поля Index и Base в SIB - тоже трехбитные. А трехбитных комбинаций, как легко посчитать, всего восемь, именно столько регистров может быть одновременно доступно любой программе. Так было задумано еще три десятка лет назад, при проектировании Intel 8086, и с тех пор ничего не изменилось, хотя Pentium 4 отличается от 8086, как небо от земли. Восемь регистров современных Athlon и Pentium не блажь разработчиков и не техническая необходимость, а фундаментальное ограничение самого набора инструкций x86.
Что же делать, если нам хочется как-то обойти это ограничение, не теряя совместимости со старыми приложениями? К счастью, инженеры, проектировавшие 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.
Какие процессоры поддерживают x86-64?
В случае AMD - все новые CPU без исключения. Athlon 64, Mobile Athlon 64, Turion и Opteron поддерживают технологию AMD64 изначально; процессоры Sempron (изначально этой поддержки лишенные) - начиная с определенного степпинга (E) или определенной даты (осени 2005 года). Отличить «новые» Sempron от старых проще всего по логотипу на коробке: у 64-разрядных Sempron’ов на упаковке стоит значок AMD64.
В случае Intel технологию EM64T поддерживают только процессоры новых степпингов (начиная с "E") в исполнении LGA775. Pentium D, Pentium eXtreme Edition и Pentium 4 семейства 6xx поддерживают EM64T изначально; процессоры Xeon - начиная c 90-нм ядра Nocona; процессоры Pentium 4 семейства 5xx и Celeron D семейства 3xx - только те модели, номер которых заканчивается на шестерку или единичку. Pentium 4 Extreme Edition 3,73 ГГц тоже поддерживает EM64T. Все остальные модели (в частности, Pentium M и процессоры в исполнении Socket 478) технологию EM64T не поддерживают и в ближайшее время эту поддержку не получат.
To 64bit or not to 64bit?
Так стоит ли переходить на x86-64 или нет? Думаю, после всего вышеизложенного ответ понятен: без сомнения, стоит! Технология x86-64 действительно предоставляет все преимущества 64-битных систем, содержит ряд качественных улучшений по сравнению с «классической» IA-32 ISA, но главное - позволяет не использовать 64-битные вычисления там, где этого не требуется, и сохраняет полную совместимость с любым 32-битным софтом. А потому единственный серьезный довод против, который до сих пор мешает широкому распространению технологии, - это необходимость поддержки x86-64 операционной системой и использования редких и порой не до конца отлаженных и «недооптимизированных» 64-битных драйверов.
Благодарим компании AMD (за предоставление тестового набора Athlon 64 X2 4800+), MSI (за материнскую плату MSI K8N SLI) и сеть магазинов «Неоторг» (за видеокарту MSI GeForce 7800GT).
64-битный Linux
Операционные системы семейства *nix и особенно их разновидности с открытым исходным кодом никогда не испытывали затруднений с портированием на самые разные архитектуры. Unix вообще задумывалась как портируемая операционная система[Недаром же стандарт на Unix-системы называется POSIX - Portable Operation System Interface for computer environments], а множество добровольных помощников - неплохой способ сократить время отладки и тестирования новой разновидности «операционки» и драйверов для нее.
Именно это и позволило юниксоидам в полной мере использовать 64-битные x86-процессоры сразу же после того, как появились первые компиляторы, поддерживающие 64-битные инструкции x86-64. Благо что при желании собрать свой «64-битный» дистрибутив может любой человек, обладающий достаточными познаниями в программировании, организации и администрировании *nix-систем; а перекомпилировать «обычную» программу для того, чтобы она получила поддержку x86-64, в большинстве случаев может и обычный пользователь. В мире Unix-систем, в отличие от Microsoft Windows, поддержка технологии x86-64 происходит гораздо естественнее - если приложение (или драйвер) распространяется в «исходниках», то в большинстве случаев его достаточно «пересобрать» (заново откомпилировать программу); а если в «бинарниках» (в заранее откомпилированном виде) - то почти всегда этот бинарник представлен в целом ряде вариантов под разные версии *nix-систем, среди которых наверняка найдется подходящая версия под вашу конкретную операционную систему. Поскольку все вышесказанное относится и к драйверам (которые могут считаться пусть и не самостоятельными, но программами), то проблем с ними тоже, как правило, не возникает[Для некоторых устройств unix-драйверов попросту не существует - ни для 32-битных, ни для 64-битных версий операционных систем. Но эту ситуацию уже никак, естественно, не исправишь].
Мы попробовали установить один из современных дистрибутивов с поддержкой x86-64 - Linux Corporate Server 3.0 от компании Mandrake. С инсталляцией и опознанием оборудования трудностей не было (разве что аудиокодек не распознался); с компиляцией тестовых приложений - тоже. В отличие от своих Windows-собратьев, скомпилированные наиболее распространенным компилятором GCC эти приложения выиграли в производительности куда больше (до 40-50%). Дело в том, что GCC - кроссплатформный компилятор, способный генерировать машинный код для двух десятков разных процессорных архитектур, а потому код для каждой из архитектур он генерирует по более простым алгоритмам, нежели «заточенные» под IA-32 компиляторы, и потому от «подводных камней» x86 страдает больше. К примеру, в x86 рекордно мало регистров общего назначения и регистров SSE по сравнению с другими процессорами, на которые GCC рассчитан. Поэтому переход к удвоенному числу регистров общего назначения так сильно упрощает GCC работу, что он перестает «по-глупому» спотыкаться - и возникает большая «дельта» между результатами одной и той же программы в 32-битном и 64-битном режиме. Разумеется, это справедливо не для всех приложений, но для очень многих, так что если ваш процессор поддерживает технологию x86-64 и вы намерены установить на него операционную UNIX-систему - лучше заранее купите 64-битный дистрибутив.
Как пересобрать *nix-приложение для поддержки x86-64
Большинство используемых в обычной жизни вариантов Linux ориентированы на работу с бинарными пакетами, и пересобирать программы в них приходится нечасто. Если вы хотите насладиться преимуществами 64-битной архитектуры, убедитесь, что заветные цифры «64» содержатся в номере версии вашего «дистро» - большинство крупных сборщиков поставляют специальные билды для этих целей. Впрочем, использовать или не использовать «продвинутые» дистрибутивы - вопрос открытый: Linux состоит из множества пакетов, и может статься, что нужная программа не собрана под 64-битную архитектуру в вашем репозитарии. Тогда можно попробовать собрать ее из исходников самостоятельно. Скорее всего, архитектура должна определиться автоматически (например, на этапе выполнения скрипта configure или при подготовке пакета из src.rpm-файла), и все нужные опции компилятора включатся без вашего участия. В особо запущенных случаях потребуется ручная правка Makefile. Здесь надо действовать по ситуации - но чаще всего будет достаточно добавить ключ -march ‹имя-архитектуры› при запуске gcc или g++[Например, -march k8 для сборки под AMD64 (полный перечень опций можно найти в документации gcс наwww.gcc.gnu.org/onlinedocs/gcc/i386-and-x86_002d64-Options.html