Перенос Linux-приложений на 64-разрядные системыИсточник: IBM developerWorks Россия
Linux была одной из первых кросс-платформенных операционных систем, использующих 64-разрядные процессоры, а сейчас 64-разрядные системы стали применяться повсеместно для серверов и настольных компьютеров. Многие разработчики столкнулись с необходимостью переноса приложений с 32-разрядных на 64-разрядные системы. С появлением Intel Itanium и других 64-разрядных процессоров подготовка приложений для работы с ними становится все более важной. Аналогично UNIX и другим UNIX-подобным операционным системам, Linux использует стандарт LP64, согласно которому указатели и длинные целые (long integer) числа имеют 64 бита, но обычные целые (integer) остаются 32-разрядными. Хотя некоторые языки высокого уровня не чувствительны к различиям в размере, на другие это изменение может оказывать влияние (например, язык С). Работа по переносу приложения с 32 разрядов на 64 может быть как тривиальной, так и очень сложной, в зависимости от того, как были написаны эти приложения. Множество коварных деталей реализации может вызвать проблемы даже в хорошо написанном, переносимом приложении, поэтому в этой статье рассматриваются такие детали и предлагаются способы работы с ними. Преимущества 64 разрядов32-разрядные платформы имеют ряд ограничений, которые все больше расстраивают разработчиков больших приложений, например, баз данных, и особенно тех разработчиков, которые хотят пользоваться преимуществами развития аппаратного обеспечения. В то время как научные вычисления обычно основаны на математике с плавающей точкой, некоторые приложения, например финансовые вычисления, требуют более ограниченных по диапазону чисел, но с большей точностью, чем предлагаемая числами с плавающей точкой. 64-разрядная математика обеспечивает такую повышенную точность чисел с фиксированной точкой в соответствующем диапазоне. Сейчас в компьютерной индустрии ведется много дискуссий об ограничении, накладываемом 32-разрядным адресом. 32-разрядные указатели могут адресовать только 4GB виртуального адресного пространства. Это ограничение можно обойти, но разработка приложений становится более сложной, а производительность значительно уменьшается. Что касается реализации языка программирования, то современный язык C позволяет использовать тип данных "long long", имеющий длину 64 бита. Однако реализация может определить его имеющим еще больший размер. Еще одной областью, нуждающейся в улучшении, являются даты. В Linux даты выражаются как 32-разрядные целые числа, представляющие количество секунд, прошедших с 1 января 1970 года. Они станут отрицательными в 2038 году. А в 64-разрядных системах даты выражаются как 64-битные целые числа со знаком, что превышает используемый диапазон. В итоге 64-разрядная архитектура имеет следующие преимущества:
64-разрядная архитектура LinuxК сожалению, язык программирования C не предоставляет механизм добавления новых фундаментальных типов данных. Следовательно, обеспечение 64-разрядной адресации и возможностей целочисленной арифметики включает в себя изменение связываний или отображений существующих типов данных, либо добавление новых типов данных в язык программирования. Таблица 1. Модели 32-разрядных и 64-разрядных данных
Отличие между тремя 64-разрядными моделями (LP64, LLP64 и ILP64) заключается в типах данных, не являющихся указателями. На изменение длины одного или нескольких типов данных C в различных моделях приложения могут реагировать по-разному. Эти реакции можно разделить на две основные категории:
Итак, компиляторы выравнивают типы данных по естественной границе, это означает, что для выполнения такого выравнивания компилятор будет вставлять пробелы, как в типах C structure или union. Члены structure или union выравниваются на основе своего самого большого члена. В листинге 1 приведена эта структура.
В таблице 2 показан размер каждого члена этой структуры и размер структуры на 32-разрядной и 64-разрядной системах. Таблица 2. Размер структуры и ее членов
Обратите внимание на то, что на 32-разрядной системе компилятор может не выравнивать переменную Перенос с 32-разрядной на 64-разрядную системыВ этом разделе показано, как исправить обычные проблемные места:
ОбъявленияДля того чтобы ваш код работал и на 32-разрядных, и на 64-разрядных системах, при работе с объявлениями обратите внимание на следующее:
ВыраженияВ C/C++ выражения основаны на ассоциативности, старшинстве операторов и наборе правил арифметического расширения. Для того чтобы ваши выражения работали корректно и на 32-разрядных и на 64-разрядных системах, помните о следующих правилах:
ПрисваиванияПоскольку длина pointer, int и long на 64-разрядных системах больше не одинакова, могут возникнуть проблемы, зависящие от того, как переменным присваиваются значения и как они используются в приложении. Несколько советов по этому поводу:
Числовые константыШестнадцатиричные константы обычно используются как маски или битовые значения. Шестнадцатиричные константы без суффикса определяются как unsigned int, если они помещаются в 32 бита, и включен бит старшего порядка. Например, константа OxFFFFFFFFL имеет тип signed long. На 32-разрядной системе при этом устанавливаются все биты, но на 64-разрядной системе устанавливаются только 32 бита младшего порядка, что приводит к значению 0x00000000FFFFFFFF. Если вы хотите установить все биты, переносимый способ сделать это - определить константу signed long со значением -1. При этом включатся все биты, поскольку задействуется арифметика дополнения до двух:
Еще одной проблемой, которая может возникнуть, является установка самого старшего разряда. На 32-разрядной системе для этого используется константа 0x80000000. Но более переносимый способ сделать это - использовать выражение сдвига:
Порядок байт (Endianism)Порядок байт задает способ хранения данных и определяет, как адресуются байты в целочисленных типах данных и типах данных с плавающей точкой. Прямой порядок байт (little-endian) означает, что самый младший разряд имеет самый меньший адрес в памяти, а самый старший разряд - самый старший адрес в памяти. Обратный порядок байт (big-endian) означает, что самый старший разряд имеет самый меньший адрес в памяти, а самый младший разряд - самый старший адрес в памяти. В таблице 3 показана примерная схема 64-разрядного типа long int. Таблица 3. Схема 64-разрядного типа long int
Например, 32-разрядное слово 0x12345678 будет размещаться на машине с обратным порядком байт следующим образом: Таблица 4. 0x12345678 на системе с обратным порядком байт (big-endian)
Если бы мы посмотрели на 0x12345678 как на два полуслова 0x1234 и 0x5678, то на машине с обратным порядком байт увидели бы следующее: Таблица 5. 0x12345678 как два полуслова на системе с обратным порядком байт
Но на машине с прямым порядком байт слово 0x12345678 размещалось бы так: Таблица 6. 0x12345678 на системе с прямым порядком байт (little-endian)
Аналогично, два полуслова 0x1234 и 0x5678 выглядели бы так: Таблица 7. 0x12345678 как два полуслова на системе с прямым порядком байт
Следующий пример демонстрирует различие между системами с прямым и обратным порядком байт. Приведенная ниже C-программа выводит "Big endian", если она компилируется и запускается на машине с обратным порядком байт, и "Little endian" в противном случае. Листинг 2. Сравнение обратного и прямого порядка байт
Порядок байт важен тогда, когда:
В C и C++ имеются битовые поля, которые помогают справиться с проблемами порядка байт. Я рекомендую использовать битовые поля вместо полей маски или шестнадцатиричных констант. Существует несколько функций, использующихся для преобразования 16-разрядных и 32-разрядных типов из "host-byte-order" в "net-byte-order". Например,
Определения типовЯ рекомендую вам не кодировать ваши приложения с родными типами данных C/C++, которые меняют размер на 64-разрядной операционной системе, а пользоваться определениями типов или макросами, которые явно показывают размер и тип данных, содержащихся в переменной. Некоторые определения типов помогают сделать код более переносимым.
Пример 1: 64-разрядное возвращаемое значение из
Решением проблемы должно быть приведение типа возвращаемого значения с использованием
Example 2: На 32-раазрядных системах system, int и long имеют одинаковый размер. Поэтому некоторые разработчики используют их как взаимозаменяемые. Это может послужить причиной присваивания указателей типу int и наоборот. Но на 64-разрядной системе, присваивание указателя типу int вызовет усечение старших 32 бит. Решением проблемы является хранение указателей как тип pointer или как специальных типов, определенных для этой цели, например Побитный сдвигНетипизированные целочисленные константы имеют тип (unsigned) int. Это может привести к нежелательному усечению при побитовом сдвиге. Например, в следующем фрагменте кода максимальным значение переменной a может быть 31. Это следует из того, что
Для выполнения сдвига на 64-разрядной системе нужно использовать
Форматирующие строкиФункция Аналогично, когда малые типы integer (char, short, int) передаются в
Этот фрагмент кода не будет корректен на 64-разрядных системах и будет отображать только младшие 4 байта. Для решения этой проблемы нужно использовать спецификацию
Параметры функцийСуществуют несколько моментов, которые вы должны помнить при передаче параметров в функции:
Проблема возникает при передаче суммы signed int и unsigned int как long. Рассмотрим следующий случай: Листинг 3. Передача суммы signed int и unsigned int как long
Приведенный выше фрагмент кода не будет работать на 64-разрядных системах, поскольку выражение Существует еще одна проблема в основанных на регистрах системах, в которых для передачи параметров в функции вместо стека используются регистры. Рассмотрим следующий пример:
В основанной на стеке системе выводится соответствующее шестнадцатиричное значение. Но в системе, основанной на регистрах, шестнадцатиричное значение читается из целочисленного регистра, а не из регистра с плавающей точкой. Решением проблемы является приведение типа адреса переменной с плавающей точкой в указатель на int, который затем разыменовывается следующим образом:
Заключение
|