이 글은Porting Linux applications to 64-bit systems을 참조하여 작성한 글입니다.
더욱 명확한 내용 확인을 원하시는 분들은 위URL참고하여 주시기 바랍니다.
지금은64-bit시스템이 서버나 데스크탑에서 흔하게 사용되지만예전엔 아니였습니다. Linux는64-bit프로세서들을 사용하기 위한 첫 번째cross-platform운영체제들 중의 하나였습니다. 많은 소프트웨어 개발자들은 예전에 개발했던32-bit기반 어플리케이션들을64-bit환경으로 포팅하기 위한 필요성을 느끼고 있습니다. 64-bit프로세서의 보급화가 빠르게 이뤄지면서 이 필요성은 지속적으로 중요하게되었죠.
UNIX나 다른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와 같이 규모가 큰application개발자들에게 어려움을 주는수많은 제약이 있었습니다. 컴퓨터 하드웨어의 향상된 성능을 충분히 이용하고 싶은 개발자들에게 말이죠.
과학분야에서 필요한 연산이 부동소수점 연산(floating-point mathematics)에 의존하는 것에 비해, 융분야에서 필요한 연산은 좁은 범위의 수를 필요로하기 때문에 고정소수점이 어울려 보이지만, 해당 연산들은 부동소수점보다는 높은 정확도를 가지는 연산을 필요로 하기때문에 상대적으로 정확도가 떨어지는 고정소수점을 사용하기엔 무리가 있어 보일 수 있습니다.
컴퓨터의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가 가지고 있는 이점은 아래와 같습니다.
64-bit Application은4 exabyte크기의 가상 메모리를직접 접근할 수 있습니다.
64-bit Linux는 4 exabyte까지 파일의 크기를 허용할 수 있습니다.큰Database에 접근하는 서버에겐 매우 중요한 이점입니다.
The Linux 64-bit architecture
안타깝게도, C언어는 새로운 기본자료형(fundamental data type)을 추가하는 메카니즘이 없습니다.
C언어에서64bit addressing과64bit연산을 지원하는 것은 바인딩을 변경하거나 기존에 존재하는 자료형에mapping또는 새로운 자료형(기본 자료형이 아닙니다. C언어에서는 기본 자료형을 추가할 방법이 없습니다.)을 추가하는 것입니다.
아래 표에64-bit 표준에 대해 나열하였습니다. 64-bit 모델들(LP64, LLP64, ILP64)의 차이는 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
: 어떤 유효한 포인터(valid pointer)를 무효한 포인터(void pointer)로 변환시킬 수 있는 정수형 포인터를 정의할 때 사용. 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는 64-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아키텍쳐를 준수하고 개발하는 방법을 아는 것은 여러분들이 더욱 효율적이고 나은 코드를 작성하는데 도움을 줄 것 입니다.
- 본문 검토해주신 yenarue 님께 감사드립니다.
Congratulations @linuxias! You received a personal award!
Click here to view your Board
Do not miss the last post from @steemitboard:
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit
Congratulations @linuxias! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Vote for @Steemitboard as a witness to get one more award and increased upvotes!
Downvoting a post can decrease pending rewards and make it less visible. Common reasons:
Submit