학습자료(~2017)/리눅스

[리눅스] 32bit 에서 64bit 로 소스 호환을 위한 고려사항

단세포소년 2012. 3. 13. 10:12
반응형
원문 : http://wiki.kldp.org/wiki.php/32bitCodeTo64bit


32bit 에서 64bit로 소스호환을 위한 고려사항 정리

  • 작성자
    조재혁 (Mminzkn@minzkn.com)

  • 고친과정
    2007년 6월 17일 : 처음씀

개요

이 내용은 제가 기존에 만들었던 여러 소스(mzapi) 들을 64bit 에서 동작하도록 포팅하면서 발생했던 고려사항들을 정리합니다.

변수형의 크기는 어떻게 달라지는가? 32bit 환경에서 64bit 환경으로 옮겨가면서 달라진 변수형은 다음과 같습니다.

  1. int 형은 32bit 를 유지합니다.
  2. short 형은 16bit 를 유지합니다.
  3. long 형은 32bit에서 64bit로 확장됩니다.
  4. long long 형은 64bit 를 유지합니다.
  5. pointer 형은 32bit 에서 64bit로 확장딥니다.
  6. long double 형이 12bytes 에서 16bytes로 확장됩니다.
  7. size_t과 ssize_t 형이 32bit 에서 64bit 로 확장됩니다.
  8. 이와 관련된 typedef 문 역시 확장됩니다.

64bit linux platforms

 Architecture    | uname -m | Size | Endian | Libpath | Miscellaneous
 ----------------+----------+------+--------+---------+--------------------------------
 Alpha           | alpha    | LP64 | little | lib     |
 AMD64           | x86_64   | LP64 | little | lib64   | executes x86 code natively
 IPF             | ia64     | LP64 | little | lib     | executes x86 code via emulation
 MIPS64          | mips64   | LP64 | both   | lib64   | executes MIPS code natively
 PowerPC64       | ppc64    | LP64 | big    | lib64   | executes PowerPC code natively
 Sparc64         | sparc64  | LP64 | big    | lib64   | executes Sparc code natively
 PA-RISC64       | parisc64 | LP64 | big    | -       | only kernel support, no 64-bit
                 |          |      |        |         | executes 32-bit PA-RISC code
 zSeries (s390x) | s390x    | LP64 | big    | lib64   | executes s390 code natively


문제가 되는 코드들

크기가 다른 변수형에 대한 포인터를 사용한 경우 (스택붕괴가 우려되는 경우)

getsockopt의 size인자가 socklen_t 로 정의되는 경우 아래의 코드는 s_socklen 변수를 int로 정의한것에서 문제가 발생할수 있습니다.
int s_socklen;
s_result = getsockopt(s_socket, s_level, s_optname, &s_optval, &s_socklen);


이것은 다음과 같이 변경되어야 합니다. 이것은 int와 socklen_t 는 크기가 다르기 때문입니다.
socklen_t s_socklen;
s_result = getsockopt(s_socket, s_level, s_optname, &s_optval, &s_socklen);


조건식을 int 로 받는 함수에 포인터를 넘겨줄경우 (컴파일러 경고) 일반적으로 다음과 같은 함수가 있을때
void my_assert(int s_expression)
{
 if(!s_expression)return;
 (void)fprintf(stderr, "assert....\n");
}


위와 같은 함수에 조건문을 사용할때
void *s_ptr = NULL;
my_assert(s_ptr);


다음과 같이 변경하여야 합니다. 이것은 포인터가 int 형과 다른 크기이므로 포인터는 int로 casting 되면서 잘리게 되고 NULL포인터가 아님에도 하위 32bit가 0인경우 my_assert 는 의도하지 않은 동작을 할수 있기 때문입니다.
void *s_ptr = NULL;
my_assert(s_ptr != NULL);


dword 정의 (만약 이렇게 써왔다면..)

다음과 같이 my_DWORD를 정의하여 사용했다면
#define my_DWORD unsigned long int

다음과 같이 변경하여야 합니다.
#define my_DWORD unsigned int


x86계열 어셈블리 호환성 (안바꿔도 동작한다.)

예를 들어서 atomic exchange 를 다음과 같이 C함수로 구현했다면 전혀 수정할 필요없이 그대로 컴파일 가능합니다. 하지만 int형이 아닌 long 형을 사용했다면 register 는 eax, ebx, .. 가 아닌 rax, rbx, ... 로 변경을 고려해야 합니다.
int my_atomic_exchange(int * volatile s_to, int s_value)
{
 __asm volatile ("xchgl (%2), %0\n\t" : "=r"(s_value) : "0"(s_value), "r"(s_to) : "memory");
 return(s_value);
}


implementation 함수의 잘못된 고려 (size_t 등의 의미를 전혀 활용하지 못한경우 포팅이 쉽지 않을수 있음.)

예를 들어서 memcpy 를 한번 감싸서 다음과 같이 자신만의 함수를 만들었다면
void *my_memcpy(void *s_to, const void *s_from, int s_size)
{
 return(memcpy(s_to, s_from, s_size));
}


당연히 s_size 변수는 64bit를 담을수 없는 그릇으로 전달되기 때문에 원하지 않은 결과를 발생할수 있습니다. 때문에 다음과 같이 변경되어야 합니다.
void *my_memcpy(void *s_to, const void *s_from, size_t s_size)
{
 return(memcpy(s_to, s_from, s_size));
}


size_t, ssize_t 의 용도를 전혀 중요하게 생각지 않은 개발자라면 이 문제로 64bit 포팅에 좌절을 겪을수도 있습니다. 고려하지 않은 개발자분들은 위의 예제처럼 memcpy 에서보다는 memory offset 연산에서 후회할수 있습니다.

정렬

기존에는 구조체의 sizeof() 에 의한 정렬된 값이 보통 4byte align로 사용되었으나 이것은 이제 기본값으로 믿을수 없습니다. 이제는 보통 8byte align 이 기본값으로 사용됩니다. 때문에 반드시 의도적으로 align을 해야 되는 경우라면 다음과 같이 작성되어야 합니다. (이것은 뻔한 내용이지만 그것을 이야기 하려고 한것이 아니고 신중한 library 개발자라면 모든 구조체는 명시적으로 align 을 지정해줘야 될수 있다는 것을 말합니다.)
#pragma pack(push,8)
struct ts_my_struct
{
 unsigned char b;
 unsigned short w;
 unsigned int d;
 unsigned long d;
 unsigned long long q;
 long double paragraph;
}
#pragma pack(pop)

predefine 으로 64bit 를 검출

각 architecture 별 predefine의 종류가 많아서 간단하게 검출할수 있는 내용은 아니지만 제가 지금까지 포팅한 architecture는 다음과 같이 하여 검출하였습니다. 사실 이것이 모두 완벽하게 검출한다고 할수는 없습니다. 들어보지도 못한 architecture도 많으니까..
#if defined(__x86_64__) || defined(__ia64__) || defined(_M_AMD64) || defined(_M_IA64) || defined(_WIN64) || defined(__alpha__) || defined(__s390__) /* ... */
 /* 오~ 나 64bit 에서 컴파일 되네.. 어허 요렇게 처리하자~ */
#endif


캐쉬(cache) 조정

gcc 사용자라면 64bit 에서 보다 높은 성능을 위하여 __builtin_expect((long)(m_expression),(long)(m_value)) 내장함수도 한번 검토해보시면 좋을것 같습니다. 이것은 조건식이 거의 실행되지 않을 확률 또는 실행될 확률을 조정함으로써 컴파일러로 하여금 Cache 의 최적사용을 위한 최적화가 가능해지도록 유도될수 있습니다. 64bit 기계어 코드를 보시면 이러한 Cache 를 효과적으로 사용할수 있도록 하는 방법이 제공되기 때문에 잘만 사용하면 성능이 극대화 될것으로 생각됩니다.
반응형