Перенесення 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-розрядна архітектура 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 у різних моделях програми можуть реагувати по-різному. Ці реакції можна розділити на дві основні категорії:


Отже, компілятори вирівнюють типи даних по природною лінією, це означає, що для виконання такого вирівнювання компілятор буде вставляти пробіли, як в типах 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-розрядну системи


У цьому розділі показано, як виправити звичайні проблемні місця:


Оголошення


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


Вирази


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


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


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



Числові константи


Шістнадцяткові константи зазвичай використовуються як маски або бітові значення. Шістнадцяткові константи без суфікса визначаються як 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
“);
else if (*(char *)&i == 0x78)
printf (“Little endian
“);
}

Порядок байт важливий тоді, коли:


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


Визначення типів


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



Приклад 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
“, ptr);


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


Для вирішення цієї проблеми потрібно використовувати специфікацію %p, Як показано нижче, яка буде добре працювати і на 32-розрядних, і на 64-розрядних системах.


char *ptr = &something;
printf (%p
“, ptr);


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


Існують кілька моментів, які ви повинні пам'ятати при передачі параметрів у функції:


Проблема виникає при передачі суми 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-розрядної архітектурою, може допомогти вам при написанні стерпного і ефективного коду.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*