Porting Linux applications to 64-bit systems

Linuxias의 이미지

이 글은 Porting Linux applications to 64-bit systems 을 참조하여 작성한 글입니다.

더욱 명확한 내용 확인을 원하시는 분들은 위 URL 참고하여 주시기 바랍니다.

 

 

지금은 64-bit 시스템이 서버나 데스크탑에서 흔하게 사용되지만 예전엔 아니였습니다. Linux 64-bit 프로세서들을 사용하기 위한 첫 번째 cross-platform 운영체제들 중의 하나였습니다많은 소프트웨어 개발자들은 예전에 개발했던 32-bit 기반 어플리케이션들을 64-bit 환경으로 포팅하기 위한 필요성을 느끼고 있습니다. 64-bit 프로세서의 보급화가 빠르게 이뤄지면서 이 필요성은 지속적으로 중요하게되었죠.

 

UXIX나 다른 UNIX 계열의 운영체제와 같이, 리눅스도 64-bit 환경에 대해 LP64 표준(Standard)를 사용하고 있습니다. LP64(이하 표준 생략)은 포인터와 long integer 64-bit이지만 regular integer 자료형은 32-bit를 유지하고 있습니다몇몇의 High-Level Language에선 이런 자료형의 크기 차이가 큰 영향을 미치지 않지만, C언와 같이 몇몇의 다른 언어에는 큰 영향을 미치고 있습니다.

 

32-bit 에서 64-bit 환경으로 Application을 포팅하려는 노력은 아주 작은 노력으로도 가능할수도 있지만, 매우 크고 어려운 노력이 필요할 수 있습니다이런 노력의 크기는 어떻게 어플리케이션의 코드가 작성되었으며 유지보수 되어왔는지에 의존할 것입니다아무리 잘 작성된 코드가 하더라도 매우 미묘한 이슈들이 문제를 일으킬 수 도 있습니다.

 

이 글은 이러한 이슈에 대해 논의하고, 어떻게 이 이슈들을 처리할지에 대해 제안하는 글입니다.

 

 

 

Advantages of 64 bits

본론에 앞서 32-bit 환경보다 64-bit 환경이 어떠한 장점이 있는지 살펴봅시다

32-bit 플랫폼은 database와 같이 규모가 큰 appication 개발자들에게 어려움을 주는 수많은 제약이 있었습니다.  컴퓨터 하드웨어의 향상된 성능을 충분히 이용하고 싶은 개발자들에게 말이죠.

 

과학분야에서 필요한 연산이 부동소수점 연산(floating-point mathematics)에 의존하는 것에 비해, 금융분야에서 필요한 연산은 좁은 범위의 수를 필요로 하지만 부동소수점보다는 연산의 높은 정확도를 원하고 있습니다. 64-bit 연산은 높은 정확도의 fixed-point 연산을 제공할 수 있습니다.

 

컴퓨터의 32-bit 주소에 의해 생기는 문제에 대해서도 많은 논의가 이뤄지고 있는데요, 32-bit 포인터는 오직 4GB의 가상 주소 공간(Virtual Address Space)만을 제공하기에 Application 개발자들이 이 제한적인 주소 공간을 극복하기 위해 그 이상을 원한다면 매우 복잡하고 어려운 과정이 필요할 것입니다. 그렇게 문제를 해결한다고 해도 성능이 향상되긴 커녕 점차적으로 성능이 하락하는 상황을 만날 것입니다.

 

또한 Linux date(날짜) 표현에 대해서도 문제가 있습니다. 현재 Linux에서는 1970 1 1일을 기준으로 32-bit signed-integer 자료형에 초를 저장하는 방식으로 date를 표현하고 있습니다. 32-bit 크기의 자료형에서는 2038년을 기점으로 값이 음수로 변하는 상황을 맞게됩니다. 그러나 64-bit system에서 date signed- 64-bit integer 자료형으로 표현될 것이고 사용가능한 범위가 매우 커지게 되겠죠.

 

정리하여 64-bit architecture가 가지고 있는 이점은 아래와 같습니다.

1. 64-bit Application 4 exabyte 크기의 가상 메모리를 직접 접근할 수 있습니다.

2. 64-bit Linux 4 exabyte까지 파일의 크기를 허용할 수 있습니다. Database에 접근하는 서버에겐 매우 중요한 이점입니다.

 

 

 

The Linux 64-bit architecture

안타깝게도, C 언어는 새로운 기본 자료형(fundamental data type)을 추가하는 메카니즘이 없습니다

C언어에서 64bit addressing 64bit 연산을 지원하는 것은 바인딩을 변경하거나 기존에 존재하는 자료형에 mapping 또는 새로운 자료형(기본 자료형이 아닙니다. C언어에서는 기본 자료형을 추가할 방법이 없습니다.)을 추가하는 것입니다.

 

아래 표에 64-bit standard에 대해 나열하였습니다. LP64 , LLP64, ILP64 3가지 64-bit 표준에서의 차이는 non-pointer data type입니다. 자세히 보시면, non-pointer data type들의 크기가 다른 것을 알 수 있습니다

 

 

 

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

C언어에서는 하나 이상의 자료형의 크기가 다른 모델에서 변경될 때 Application이 여러가지 영향을 받을 수 있습니다. 나타날 수 있는 2가지 영향에 대해서 알아보겠습니다.

 

1) Size of data objects

컴파일러는 32 bit 자료형을 64 bit system에서 32 bit 크기로 잡게됩니다(64 bit 자료형은 64 bit 크기로 잡습니다). 구조체(struct)나 공용체(union)과 같은 data object의 크기가 32 bit 시스템과 64 bit 시스템에서 다를 수 있음을 의미합니다.

 

2) Size of fundamental data types

기본 자료형 간의 관계(크기 등)에서 여러분들은 쉽게 추측할 수 있었지만, 64 bit data-model에선 더 이상 그 추측은 유효하지 않습니다. Application 내에 기본 자료형들간의 관계는 64-bit 플랫폼에서 컴파일 과정 중 에러가 발생할 수 있습니다.

 

예를 들어 아래의 기본자료형들 간의 크기 관계에서 32 bit 시스템(ILP32 표준)에서는 유효한 걸 모두 알고 계시지만, 다른 64 bit 시스템(LLP64, LP64, ILP64)에서는 유효하지 않습니다

 

sizeof(int) = sizeof(long) = sizeof(pointer)

 

다시 정리하면, 컴파일러는 자료형에 따라 메모리 크기를 할당 및 정리하게 됩니다. 구조체나 공용체에서 할당되는 메모리를 정리하기 위해 'padding'이 각 자료형 사이에 존재하게 됩니다. 만약 아래와 같은 구조체가 있다고 해봅시다.

 

struct test {

             int i1;

             double d;

             int i2;

             long l;

}

 

아래 표는 구조체 각 멤버에 대해 할당된 메모리크기와 padding을 보여준다.

 

Structure member

Size on 32-bit system

Size on 64-bit system

struct test {

 

 

int i1;

32-bits

32-bits

 

32-bits filler

double d;

64-bits

64-bits

int i2;

32 bits

32 bits

 

32-bits filler

long l;

32 bits

64 bits

};

Structure size 20 bytes

Structure size 32 bytes

 

 

Porting from 32-bit to 64-bit systems

이번 장에서 흔히 발생하는 문제에 대해 어떻게 해결하는지 알아봅시다.

먼저, 살펴볼 부분에 대해 나열해보면 아래와 같습니다.

Declarations

Expressions

Assignments

Numeric constants

Endianism

Type definitions

Bit shifting

Formatting strings

 

Declarations

여러분의 코드가 32-bit 64-bit 시스템 모두에서 제대로 동작하기 위해서는 아래와 같은 방식을 따라야 합니다.

정수형 상수를 사용할 때 'L' 또는 'U' 접미사를 적절히 사용해야 합니다. unsigned int 형을 사용하는게 확실한다면 sign으로 선언하는 것을 피하고 32-bit, 64-bit 시스템에서 모두 32bit 자료형이 필요하다면 int를 사용해야 합니다. 다른 자료형은 각 시스템에서 다른 크기를 가지게 될 수 있기 때문이죠.

만약 32-bit 시스템에서는 32-bit 자료형이, 64-bit 시스템에서는 64-bit 자료형이 필요하다면 자료형은 long으로 선언합니다.

또한, character pointer character byte들은 unsigned로 선언되어야 합니다. signed로 선언되어 있을 시 부호확장문제(sign extension problem)가 발생할 수 있습니다.

 

 

Expressions

C/C++에서 표현식은 기본적으로 연산자 우선순위와 산술 방식은 공통적입니다여러분들의 코드에서 표현식을 확실히 하기 위해선 아래 내용을 숙지하고 있어야 합니다..

signed int 자료형의 합의 결과는 signed int이며, int long의 합은 long으로 표현됨을 알야합니다. int double의 합은 double로 표현되며 합 연산 전에 int double형으로 변환됩니다만약 피연산자 중 하나가 unsigned이고 또 다른 피연산자는 signed int 일때 이 표현식은 unsigned 입니다.

 

 

Assignments

64-bit 시스템에서 포인터, int, long 형의 크기가 더 같지 않기 때문에 32-bit 시스템에서 작성된 코드가 문제가 될 수 있습니다.

int long 자료형을 서로 교환적으로 사용하지 마세요. 64-bit 시스템에서 long 자료형은 64-bit의 크기를 가지므로 유효자리수가 짤리고 할당 될 수 있습니다. 아래 예시처럼 코드를 작성하시면 안됩니다.

 

int i;

long l;

i = l;

 

또한, int 자료형에 pointer를 저장하는 방식의 코드를 작성하지 마세요. 32-bit 시스템에서는 정상적으로 동작할 지 모르지만 64-bit 시스템에서는 오류가 발생할 수 있습니다. 64-bit에서 pointer 64-bit의 크기를 가지기 때문에 32-bit 크기의 int 자료형에 할당할 수 없습니다. 예를 들어 아래와 같이 2가지 예시대로 코드를 작성하시면 문제가 발생 할 수 있습니다.

 

unsigned int i, *ptr;

i = (unsigned) ptr;

 

int *ptr;

int i;

ptr = (int *) i;

unsigned signed 32-bit 정수형을 함께 사용한 표현식을 signed long 자료형에 할당하는 경우에는 둘 중 하나를 long 자료형으로 타입캐스팅을 하던지 전체 표현식을 타입 캐스팅 한 후 할당해 줘야 합니다.

 

아래와 같은 코드는 문제를 일으킬 수 있습니다.

long n;

int i = -2;

unsigned k = 1;

n = i + k;

 

그럴 땐 아래와 같이 타입 캐스팅을 해주시면 됩니다.

 

n = (long) i + k;

n = (int) (i + k);

 

 

Numeric Constants

16진수 상수값은 mask나 특별한 비트 값으로 자주 사용됩니다. 16진수 상수에 접미사를 붙이지 않는다면 unsigned int 형으로써 정의됩니다. 예를들어 0xFFFFFFFFL signed long 형입니다. 32-bit 시스템에서 앞의 예시는 모든 bit들이 set됩니다. 하지만, 64-bit 시스템에서는 오직 아래 32-bit만이 set됩니다. 결과적으로 64-bit 시스템에서는 0x00000000FFFFFFFF으로 표현이 되는 것입니다.

 

만약 여러분들이 64-bit 시스템에서 모든 bit set 되길 원한다면 signed long형의 -1을 설정함으로써 모든 비트를 set 할수 있습니다.

 

long x = -1L;

 

또 다른 문제로 MSB(Most Significant Bit) set할 때 발생할 수 있습니다. 32-bit 시스템에서 여러분들이 만약 16진수 상수로 0x80000000을 사용하여 MSB set하게 되면, 64-bit 시스템에서는 적용되지 않습니다. 그 땐 아래와 같이 해주는 방식이 좋습니다.

 

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

 

 

Endianism

Endianism data를 저장하고 어떻게 byte들을 접근할 지에 대한 방법입니다

 

2가지 Endianism이 있습니다. 많이 들어보셨듯이 Little-Endian Big-Endian 방식입니다. Little-Endian LSB(Least Significant Byte)가 가장 낮은 메모리 주소 쪽에 저장되며, MSG(Most Significant Byte)가 가장 높은 메모리 주소쪽에 저장됩니다. Big-Endian Little-Endian과 정 반대입니다. LSB(Least Significant Byte)가 가장 높은 메모리 주소 쪽에 저장되며, MSG(Most Significant Byte)가 가장 낮은 메모리 주소쪽에 저장됩니다.

 

아래 Table을 보시면, 64-bit long integer의 예제를 보여줍니다.

Table . Layout of a 64-bit long int

 

Low address

 

 

 

 

 

 

High address

Little endian

Byte 0

Byte 1

Byte 2

Byte 3

Byte 4

Byte 5

Byte 6

Byte 7

Big endian

Byte 7

Byte 6

Byte 5

Byte 4

Byte 3

Byte 2

Byte 1

Byte 0

예를들어, 32-bit word 0x12345678 big-endian 기반에 표현되면 아래와 같습니다.

Table . 0x12345678 on a big-endian system

Memory offset

0

1

2

3

Memory content

0x12

0x34

0x56

0x78

만약 0x12345678이 두개의 word 타입으로 보여준다면 0x1234 0x5678로 나타날 수 있으며, 아래와 같이 보여집니다.

Table . 0x12345678 as two half words on a big-endian system

Memory offset

0

2

Memory content

0x1234

0x5678

그러나 Little-Endian 기반에서는 아래와 같이 표현됩니다.

Table . 0x12345678 on a little-endian system

Memory offset

0

1

2

3

Memory content

0x78

0x56

0x34

0x12

유사하게, 두 개의 word 타입으로 나눠도 아래와 같이 표현이 됩니다.

Table . 0x12345678 as two half words on a little-endian system

Memory offset

0

2

Memory content

0x5678

0x1234

위의 테이블을 보면 Big-Endian Little-Endian의 차이를 알 수 있습니다.

Endianism Bit mask로 사용되어 지거나, 객체의 간접 포인터 주로의 부분으로 사용될 때 중요합니다

C / C++에서 우리는 bit field를 사용함으로써 endian 문제를 해결 할 수 있습니다. mask field 16진수 상수를 사용하는 것보다 bit field를 사용하는 걸 추천합니다

 

 

Type definitions

여러분들의 C / C++ code에서 32-bit 시스템과 64-bit 시스템 사이에서 크기가 변하는 자료형을 사용하지 않는 것을 추천합니다. 사용한다면 명확하게 size가 정의되어 있는 type또는 macro를 사용하면 훨씬 좋을 것입니다.

 

ptrdiff_t

 : 두 포인터의 뺄셈의 결과로 signed int.

size_t

 : unsigned int형으로써 sizeof 연산의 결과. size_t malloc(3)과 같은 함수의 parameter fred(2)와 같은 함수들의 return 값으로 사용.

int32_t, uint32_t 

 : 미리 int형의 크기를 정의.

intptr_t , uintptr_t

 : int형 타입의 포인터를 저장할 때 사용. void형 포인터를 int형의 유효한 포인터로 표현할 때 사용.

 

Example 1.

아래 조건에서 sizeof  return 값이 64-bit 시스템에서는 32-bit가 잘려서 표현되게 됩니다.

 

int buffersize = (int) sizeof (something);

 

이 문제의 해결방법은 return 값을 size_t로 캐스팅하고 buffersize 변수 또한 size_t 타입으로 선언하는 것입니다.

 

size_t buffersize = (size_t) sizeof (something);  

 

Example 2.

32-bit 시스템에서 int long 자료형의 크기는 같습니다. 이 것 때문에 몇몇의 개발자들은 두개의 자료형을 교환하여 사용하는 경우가 있습니다. 하지만 이 것은 문제를 일으킬 수 있습니다. 가끔 pointer int 자료형 데이터를 할당하는 경우가 있는데 64-bit의 경우 pointer 54-bit 이기에 32-bit가 잘려 나가는 문제가 발생합니다. 이러한 경우 intptr_t, uintptr_t와 같은 특별한 type을 사용하여야 합니다.

 

 

Bit shifting

type을 정의하지 않은 정수형 상수는 unsigned int type입니다. 이렇게 type을 정의하지 않고 정수형 상수를 사용하는 것은 Bit shifting 과정 중에 문제가 발생할 수 있습니다.

 

예를 들어, 아래 코드에서 a의 최대 값은 31이 될 것 입니다. '1 << a' 코드가 int형 타입이기 때문입니다.

 

long t = 1 << a;

 

64-bit 시스템에서 shift를 제대로 하기 위해선 정수형 상수뒤에 접미사를 붙여 표현해 주는 것이 좋습니다.

 

long t = 1L << a;

 

 

Formatting String

printf(3) 와 이와 관련된 함수들은 64-bit 시스템으로 porting하는데 많은 문제의 원인이 됩니다. 예를들어 32-bit 시스템에서 '%d'를 이용한 출력은 int long 자료형 모두 정상적으로 동작하지만, 64-bit 시스템에서는 long 자료형에서 32bit가 잘리게 됩니다. 적절한 방법은 long 자료형에서 출력은 '%ld'를 사용하는 것 입니다.

 

비슷하게 char, short, int 잘형과 같은 작은 크기의 정수형을 printf(3)로 전달할 때 64-bit 크기로 커지게 되고, sign 또한 확장될 것입니다. 아례 예제를 보시면, printf(3) pointer 32 비트로 간주하게 됩니다.

 

char *ptr = &something;

printf ("%x\n", ptr);

 

위의 코드는 64-bit 시스템에서 에러가 발생할 것이고 오직 하위 4바이트만을 표시하게 됩니다. 위 문제를 해결하기 위해서 %x가 아닌, '%p'를 이용하여 출력하면 32-bit, 64-bit 시스템에서 모두 정상적으로 동작하게 될 것 입니다. 따라서 아래와 같이 코드를 수정하는 게 맞습니다.

 

char *ptr = &something;

printf ("%p\n", ptr);


 

 

Conclusion

최근에 주요 하드웨어 공급업체들은 다양한 성능, 가치등을 위해 64-bit로 확장을 하고 있습니다. 32-bit 시스템의 제약, 특히 4GB의 가상 메모리 등은 64-bit 시스템 개발에 더욱 주력하게 만들고 있습니다. 64-bit 아키텍쳐를 준수하고 개발하는 방법을 아는 것은 여러분들이 더욱 효율적이고 나은 코드를 작성하는데 도움을 줄 것 입니다.

 

 

Forums: 

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.