(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Перенос Linux-приложений на 64-разрядные системы

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-разрядное приложение может напрямую обратиться к 4 экзабайтам (exabyte) виртуальной памяти, а процессор Intel Itanium предоставляет непрерывное линейное адресное пространство.
  • 64-разрядный Linux разрешает файлам иметь размер до 4 экзабайт (2 в степени 63), очень значительное преимущество для серверов, обращающихся к большим базам данных.

64-разрядная архитектура Linux

К сожалению, язык программирования C не предоставляет механизм добавления новых фундаментальных типов данных. Следовательно, обеспечение 64-разрядной адресации и возможностей целочисленной арифметики включает в себя изменение связываний или отображений существующих типов данных, либо добавление новых типов данных в язык программирования.

Таблица 1. Модели 32-разрядных и 64-разрядных данных

  ILP32 LP64 LLP64 ILP64
char 8 8 8 8
short 16 16 16 16
int 32 32 32 64
long 32 64 32 64
long long 64 64 64 64
pointer 32 64 64 64

Отличие между тремя 64-разрядными моделями (LP64, LLP64 и ILP64) заключается в типах данных, не являющихся указателями. На изменение длины одного или нескольких типов данных C в различных моделях приложения могут реагировать по-разному. Эти реакции можно разделить на две основные категории:

  • Размер объектов данных . Компиляторы выравнивают типы данных на естественной границе; другими словами, 32-разрядные типы данных выравниваются по 32-битной границе на 64-разрядных системах, а 64-разрядные типы данных выравниваются по 64-битной границе на 64-разрядных системах. Это означает, что размер таких объектов данных как structure или union будет разным на 32-разрядных и 64-разрядных системах.
  • Размер фундаментальных типов данных . Обычные предположения о взаимоотношениях между фундаментальными типами данных не могут больше быть корректными в 64-разрядной модели данных. Приложения, зависящие от этих взаимоотношений, на 64-разрядной платформе компилироваться не будут. Например, предположение sizeof (int) = sizeof (long) = sizeof (pointer) верно для модели данных ILP32, но неверно для других моделей.

Итак, компиляторы выравнивают типы данных по естественной границе, это означает, что для выполнения такого выравнивания компилятор будет вставлять пробелы, как в типах C structure или union. Члены structure или union выравниваются на основе своего самого большого члена. В листинге 1 приведена эта структура.

Листинг 1. C structure

struct test {
	int i1;
	double d;
	int i2;
	long l;
}

В таблице 2 показан размер каждого члена этой структуры и размер структуры на 32-разрядной и 64-разрядной системах.

Таблица 2. Размер структуры и ее членов

Член структуры Размер на 32-разрядной системе Размер на 64-разрядной системе
struct test {    
int i1; 32 бита 32 бита
  32 бита заполнителя
double d; 64 бита 64 бита
int i2; 32 бита 32 бита
  32 бита заполнителя
long l; 32 бита 64 бита
}; Размер структуры равен 20 байт Размер структуры равен 32 байт

Обратите внимание на то, что на 32-разрядной системе компилятор может не выравнивать переменную d, даже если это 64-разрядный объект, поскольку аппаратура рассматривает его как два 32-разрядных объекта. Однако 64-разрядная система выравнивает и d, и l, добавляя два 4-байтных заполнителя.

Перенос с 32-разрядной на 64-разрядную системы

В этом разделе показано, как исправить обычные проблемные места:

  • Объявления
  • Выражения
  • Присваивания
  • Числовые константы
  • Порядок байтов (Endianism)
  • Определения типов
  • Побитный сдвиг
  • Форматирующие строки
  • Параметры функций

Объявления

Для того чтобы ваш код работал и на 32-разрядных, и на 64-разрядных системах, при работе с объявлениями обратите внимание на следующее:

  • При объявлении констант integer используйте "L" или "U" в зависимости от ситуации.
  • Для беззнаковых целых обязательно используйте unsigned int.
  • Если вам нужны переменные размером 32 бита на обеих системах, определите их как int.
  • Если переменная должна иметь длину 32 бита на 32-разрядных системах и 64 бита на 64-разрядных системах, объявите ее как long.
  • Объявляйте числовые переменные как int или long для выравнивания и лучшей производительности. Не пытайтесь сохранять байты, используя типы char или short.
  • Объявляйте символьные указатели и байты символов как unsigned, для того чтобы обойти проблемы расширения знака в 8-битных символах.

Выражения

В C/C++ выражения основаны на ассоциативности, старшинстве операторов и наборе правил арифметического расширения. Для того чтобы ваши выражения работали корректно и на 32-разрядных и на 64-разрядных системах, помните о следующих правилах:

  • Сложение двух чисел с типом signed int дает в результате signed int.
  • Сложение int и long дает в результате long.
  • Если один из операндов имеет тип unsigned int, а второй signed int, выражение становится unsigned.
  • Сложение int и double дает в результате double. int преобразуется в double перед сложением.

Присваивания

Поскольку длина pointer, int и long на 64-разрядных системах больше не одинакова, могут возникнуть проблемы, зависящие от того, как переменным присваиваются значения и как они используются в приложении. Несколько советов по этому поводу:

  • Не используйте int и long попеременно из-за возможного усечения значимых цифр. Например, не делайте так:
    int i;
    long l;
    i = l;

  • Не используйте int для хранения указателя. Следующий пример работает на 32-разрядных системах, но не работает на 64-разрядных, поскольку 32-разрядный тип int не может хранить 64-разрядный указатель. Например, не делайте так:
    unsigned int i, *ptr;
    i = (unsigned) ptr;

  • Не используйте указатель для хранения int. Например, не делайте так:
    int *ptr;
    int i;
    ptr = (int *) i;
    

  • В тех случаях, когда unsigned и signed 32-разрядные int смешиваются в выражении и присваиваются signed long, приводите тип одного из операндов в его 64-разрядный тип. Это вызовет расширение второго операнда в 64-разрядный тип, и дальнейшее преобразование при присваивании выражения не понадобится. Еще одним решением является приведение типа всего выражения, так чтобы распространение знака происходило при присваивании. Например, рассмотрим проблему, вызванную следующим:
    long n;
    int i = -2;
    unsigned k = 1;
    n = i + k;
    

    Арифметически в выделенном выше жирным шрифтом выражении результат должен быть -1. Но из-за того, что выражение имеет тип unsigned, распространения знака не происходит. Решение заключается в приведении типа одного из операндов в его 64-разрядный тип (как в первой строке ниже) или приведении типа всего выражения (как во второй строке ниже):

    n = (long) i + k;
    n = (int) (i + k);
    

Числовые константы

Шестнадцатиричные константы обычно используются как маски или битовые значения. Шестнадцатиричные константы без суффикса определяются как unsigned int, если они помещаются в 32 бита, и включен бит старшего порядка.

Например, константа OxFFFFFFFFL имеет тип signed long. На 32-разрядной системе при этом устанавливаются все биты, но на 64-разрядной системе устанавливаются только 32 бита младшего порядка, что приводит к значению 0x00000000FFFFFFFF.

Если вы хотите установить все биты, переносимый способ сделать это - определить константу signed long со значением -1. При этом включатся все биты, поскольку задействуется арифметика дополнения до двух:

long x = -1L;

Еще одной проблемой, которая может возникнуть, является установка самого старшего разряда. На 32-разрядной системе для этого используется константа 0x80000000. Но более переносимый способ сделать это - использовать выражение сдвига:

1L << ((sizeof(long) * 8) - 1);

Порядок байт (Endianism)

Порядок байт задает способ хранения данных и определяет, как адресуются байты в целочисленных типах данных и типах данных с плавающей точкой.

Прямой порядок байт (little-endian) означает, что самый младший разряд имеет самый меньший адрес в памяти, а самый старший разряд - самый старший адрес в памяти.

Обратный порядок байт (big-endian) означает, что самый старший разряд имеет самый меньший адрес в памяти, а самый младший разряд - самый старший адрес в памяти.

В таблице 3 показана примерная схема 64-разрядного типа long int.

Таблица 3. Схема 64-разрядного типа long int

  Младший адрес             Старший адрес
Прямой порядок байт (little-endian) байт 0 байт 1 байт 2 байт 3 байт 4 байт 5 байт 6 байт 7
Обратный порядок байт (big-endian) байт 7 байт 6 байт 5 байт 4 байт 3 байт 2 байт 1 байт 0

Например, 32-разрядное слово 0x12345678 будет размещаться на машине с обратным порядком байт следующим образом:

Таблица 4. 0x12345678 на системе с обратным порядком байт (big-endian)

Смещение памяти 0 1 2 3
Содержимое памяти 0x12 0x34 0x56 0x78

Если бы мы посмотрели на 0x12345678 как на два полуслова 0x1234 и 0x5678, то на машине с обратным порядком байт увидели бы следующее:

Таблица 5. 0x12345678 как два полуслова на системе с обратным порядком байт

Смещение памяти 0 2
Содержимое памяти 0x1234 0x5678

Но на машине с прямым порядком байт слово 0x12345678 размещалось бы так:

Таблица 6. 0x12345678 на системе с прямым порядком байт (little-endian)

Смещение памяти 0 1 2 3
Содержимое памяти 0x78 0x56 0x34 0x12

Аналогично, два полуслова 0x1234 и 0x5678 выглядели бы так:

Таблица 7. 0x12345678 как два полуслова на системе с прямым порядком байт

Смещение памяти 0 2
Содержимое памяти 0x3412 0x7856

Следующий пример демонстрирует различие между системами с прямым и обратным порядком байт.

Приведенная ниже C-программа выводит "Big endian", если она компилируется и запускается на машине с обратным порядком байт, и "Little endian" в противном случае.

Листинг 2. Сравнение обратного и прямого порядка байт

#include <stdio.h>
main () {
int i = 0x12345678;
if (*(char *)&i == 0x12)
printf ("Big endian\n");
else if (*(char *)&i == 0x78)
    		printf ("Little endian\n");
}

Порядок байт важен тогда, когда:

  • Используются битовые маски
  • Непрямые указатели адресуют части объекта

В C и C++ имеются битовые поля, которые помогают справиться с проблемами порядка байт. Я рекомендую использовать битовые поля вместо полей маски или шестнадцатиричных констант. Существует несколько функций, использующихся для преобразования 16-разрядных и 32-разрядных типов из "host-byte-order" в "net-byte-order". Например, htonl (3), ntohl (3) используются для преобразования 32-разрядных целых чисел. Аналогично, htons (3), ntohs (3) используются для 16-разрядных целых чисел. Однако нет стандартного набора функций для 64 разрядов. Но Linux предоставляет следующие макросы на обеих системах (с прямым и обратным порядком байт):

  • bswap_16
  • bswap_32
  • bswap_64

Определения типов

Я рекомендую вам не кодировать ваши приложения с родными типами данных C/C++, которые меняют размер на 64-разрядной операционной системе, а пользоваться определениями типов или макросами, которые явно показывают размер и тип данных, содержащихся в переменной. Некоторые определения типов помогают сделать код более переносимым.

  • ptrdiff_t:
    Тип signed integer, получаемый из вычитания двух указателей.
  • size_t:
    unsigned integer и результат оператора sizeof. Используется при передаче параметров в функции, например malloc (3), и возвращается из нескольких функций, например fred (2).
  • int32_t, uint32_t etc.:
    Определяют типы integer предопределенного размера.
  • intptr_t and uintptr_t:
    Определяют типы integer, в которые может быть преобразован любой корректный указатель на void.

Пример 1:

64-разрядное возвращаемое значение из sizeof в следующем операторе усекается до 32-разрядов во время присваивания переменой bufferSize.

int bufferSize = (int) sizeof (something);

Решением проблемы должно быть приведение типа возвращаемого значения с использованием size_t и присваивание его переменой bufferSize, объявленной как size_t:

size_t bufferSize = (size_t) sizeof (something);

Example 2:

На 32-раазрядных системах system, int и long имеют одинаковый размер. Поэтому некоторые разработчики используют их как взаимозаменяемые. Это может послужить причиной присваивания указателей типу int и наоборот. Но на 64-разрядной системе, присваивание указателя типу int вызовет усечение старших 32 бит.

Решением проблемы является хранение указателей как тип pointer или как специальных типов, определенных для этой цели, например intptr_t и uintptr_t.

Побитный сдвиг

Нетипизированные целочисленные константы имеют тип (unsigned) int. Это может привести к нежелательному усечению при побитовом сдвиге.

Например, в следующем фрагменте кода максимальным значение переменной a может быть 31. Это следует из того, что 1 << a имеет тип int.

long t = 1 << a;

Для выполнения сдвига на 64-разрядной системе нужно использовать 1L:

long t = 1L << a;

Форматирующие строки

Функция printf (3) и родственные функции могут быть источником больших проблем. Например, на 32-разрядных платформах использование %d для вывода int или long обычно будет работать, но на 64-разрядных платформах будет происходить усечение long до его младших 32 бит. Правильной спецификацией для long является %ld.

Аналогично, когда малые типы integer (char, short, int) передаются в printf (3), они будут расширены до 64 бит, и при необходимости будет распространен знак. В приведенном ниже примере printf (3) предполагает, что указатель имеет длину 32 бита.

char *ptr = &something;
printf (%x\n", ptr);

Этот фрагмент кода не будет корректен на 64-разрядных системах и будет отображать только младшие 4 байта.

Для решения этой проблемы нужно использовать спецификацию %p, как показано ниже, которая будет хорошо работать и на 32-разрядных, и на 64-разрядных системах.

char *ptr = &something;
printf (%p\n", ptr);

Параметры функций

Существуют несколько моментов, которые вы должны помнить при передаче параметров в функции:

  • В тех случаях, когда тип данных параметра определен в прототипе функции, параметр преобразуется в этот тип в соответствии со стандартными правилами.
  • Если тип параметра не указан, параметр расширяется в больший тип.
  • На 64-разрядной системе целые типы преобразуются в 64-разрядные целые типы, а типы с плавающей точкой обычной точности (single) расширяются в типы с двойной точностью (double).
  • Если тип возвращаемого значения не указан, его типом по умолчанию является int.

Проблема возникает при передаче суммы signed int и unsigned int как long. Рассмотрим следующий случай:

Листинг 3. Передача суммы signed int и unsigned int как long

long function (long l);

int main () {
	int i = -2;
	unsigned k = 1U;
	long n = function (i + k);
}

Приведенный выше фрагмент кода не будет работать на 64-разрядных системах, поскольку выражение (i + k) является 32-разрядным выражением с типом unsigned int, и при расширении в long знак не распространяется. Решением проблемы является приведение одного из операндов в его 64-разрядный тип.

Существует еще одна проблема в основанных на регистрах системах, в которых для передачи параметров в функции вместо стека используются регистры. Рассмотрим следующий пример:

float f = 1.25;
printf ("The hex value of %f is %x", f, f);

В основанной на стеке системе выводится соответствующее шестнадцатиричное значение. Но в системе, основанной на регистрах, шестнадцатиричное значение читается из целочисленного регистра, а не из регистра с плавающей точкой.

Решением проблемы является приведение типа адреса переменной с плавающей точкой в указатель на int, который затем разыменовывается следующим образом:

printf ("The hex value of %f is %x", f, *(int *)&f);

Заключение

Основные производители аппаратного обеспечения недавно расширили предложение своих 64-разрядных продуктов из-за производительности, стоимости и масштабируемости, которую могут обеспечить 64-разрядные платформы. Ограничения 32-разрядных систем, особенно потолок в 4GB виртуальной памяти, побудили компании подумать о переходе на 64-разрядные платформы. Знание того, как переносить приложений в соответствии с 64-разрядной архитектурой, может помочь вам при написании переносимого и эффективного кода.



 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 13.12.2006 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
IBM RATIONAL Rose Enterprise Floating User License + Sw Subscription & Support 12 Months
Raize Components 6
IBM DOMINO COLLABORATION EXPRESS AUTHORIZED USER ANNUAL SW SUBSCRIPTION & SUPPORT RENEWAL
SmartBear Collaborator - Concurrent User License (Includes 1 Year Maintenance)
Rational ClearQuest Floating User License
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Реестр Windows. Секреты работы на компьютере
Компьютерные книги. Рецензии и отзывы
Один день системного администратора
Компьютерная библиотека: книги, статьи, полезные ссылки
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100