주식회사 누리아이티

정보자산의 보안강화를 위한 2차인증 보안SW 및 지문인식 OTP/출입/보안카드 전문기업

▶ Tuxedo/기술자료

Tuxedo 서비스 개발시 유의사항(AIX)

누리아이티 2010. 11. 16. 12:49

1.32bit/64bit 개발환경

 

   AIX에서는 2가지의 프로그래밍 모델을 제공한다.

    - ILP32

    - LP64

 

ILP32 integer/long/pointer 32bit임을 의미하며 AIX 32bit 프로그래밍 환경을 사용하게 된다. 이는 32bit 주소 공간을 사용하는 것을 의미하며 이론적으로 4GB 까지 메모리 제한이 있다.

 

LP64 long/pointer 64bit임을 의미하며 AIX 64bit 프로그래밍 환경을 사용하게 된다. 64bit 주소공간을 사용하게 되므로 참조할 수 있는 메모리는 4GB를 넘어가게 된다. 일반적으로 데이터 타입 크기와 얼라인먼트 차이를 제외하고, LP64 ILP32모델 프로그래밍 모델을 지원하며 널리 사용되는 int 데이터 타입에 대해서는 소급하여 호환성을 유지하게 된다.

 

C/C++ 표준에 의하면 int, short는 적어도 16bit여야 하며 long은 적어도 int와 같거나 그 이상의 크기여야 하고 32bit보다는 작아야 한다. 이 표준은 LP64모델에도 적용된다.

 

sizeof(char) sizeof(short) sizeof(int) sizeof(long)

 

LP64 데이터 모델은 대부분의 주요 벤더의 UNIX기반 시스템에서 지원되는 사실상의 표준이다. 따라서 LP64 모델을 따르는 어플리케이션은 다른 벤더로 포팅하는 작업이 매우 쉽다.

 

다음 테이블은 AIX ILP32, LP64 모델에서 기본 C/C++ 데이터 타입에 해당하는 크기를 보여주고 있다.

데이터 타입

ILP32

LP64

비고

char

1 byte

1 byte

 

short

2 byte

2 byte

 

int

4 byte

4 byte

 

long

4 byte

8 byte

 

long long

8 byte

8 byte

 

pointer

4 byte

8 byte

 

float

4 byte

4 byte

 

double

8 byte

8 byte

 

long double*

8/16 byte

8/16 byte

 

* long double의 크기는 -qlongdouble옵션이나 cc128, cc128_r 등 컴파일러 드라이버 이름에 128이 붙었는지의 여부로 결정된다.

 

 

1.1 64bit의 장점

 

64bit로 개발하는 주요한 원인은 더 새롭고 빠른 64bit H/W OS의 성능을 이용할 수 있으며, 복잡하고 메모리를 많이 이용하는 어플리케이션(데이터베이스, 과학계산 용 어플리케이션)에는 64bit가 유리하기 때문이다.

다음은 64bit환경에서 제공할 수 있는 장점이다.

- 64bit 주소를 이용하므로 어플리케이션이 사용할 수 있는 주소공간은 4GB 이상이 된다.

- 가상주소공간이 커지므로 프로세스 데이터 공간도 커진다.

- 데이터 구조와 실행파일이 더 커진다.

- 표준 시스템 라이브러리 함수를 이용하여 더 큰 파일을 지원할 수 있다.

- 물리적 메모리양도 늘어나므로 시스템의 파일 캐시 크기도 늘어난다.

- 기계어 상에서 64bit 데이터를 사용할 수 있기 때문에 수학연산을 좀더 효과적으로 할 수 있으며 레지스터를 전체에 걸쳐 효과적으로 사용할 수 있다.

- time_t, dev_t 등 시스템에서 사용하는 데이터 타입의 크기가 더 커진다.

 

이외에도 메모리 공간이 커지므로 데이터를 디스크보다 메모리에 둘 수 있는 가능성이 높아져서 I/O중심의 어플리케이션의 경우에는 성능이 급격히 개선될 수 있다.

 

 

1.2 컴파일러 지원

 

컴파일러 드라이버는 디폴트 32bit 모드로 컴파일러와 링커를 호출한다. 64bit 개발환경을 사용하려면 다음 사항을 이용해야 한다.

 

- 64bit 컴파일을 수행하기 전에 __64BIT__ 매크로를 정의한다.

- OBJECT_MODE 환경변수

- -q64 컴파일러 옵션

- -qarch 64bit 서브옵션을 지원한다.

 

환경변수를 설정하는 여부에 따라 64bit 혹은 32bit로 컴파일러를 수행할 수 있고, 명령행에서 바로 지정할 수 있다. 만약 일관성 없이 모드를 지정했다면 다음 순서로 우선순위가 결정된다.

 

1. OBJECT_MODE 환경변수

2. 환경설정 파일

3. 명령행 옵션

 

 

1.2.1 __64BIT__ 전처리기 매크로

 

컴파일러에서 지원되는 다른 특성처럼, 64bit로 컴파일할 때는 전처리기 매크로 __64bit__가 정의된다. 이 매크로를 정의하면 코드 내에서 32bit 64bit에 해당하는 부분으로 코드가 나누어질 때 코드 부분을 선택할 수 있다.

매크로는 조건 디렉티브를 사용하여 테스트할 수 있으며 다음은 사용 예제이다.

 

#if defined(__64BIT__)

/* 64-bit specific data structures or code */

#else

/* 32-bit mode */

#endif

 

최종 실행파일의 모드를 컴파일 시 __64bit__ 매크로를 사용하여 정의할 수 있기 때문에 실시간에 실행모드를 테스트할 필요가 없다. 그리고 공통으로 사용되는 헤더파일의 경우 실행모드에 따라 파일을 따로 관리할 필요가 없으며, 이미 라이브러리에서 사용하는 헤더파일은 이러한 방식으로 만들어져 있으므로 실행모드에 따라 헤더파일을 따로 사용하지 않아도 된다.

 

 

1.2.2 명령행 옵션

 

C/C++ 컴파일러에서 공통으로 제공하는 컴파일러 옵션인 -q64 -q32를 사용하여 64bit/32bit 모드로 컴파일 할 수 있다.

 

주의! 컴파일러외의 다른 지원명령어에서 사용하는 -X 옵션과는 달리 -q64, -q32 -q 다음에 빈칸을 두면 안 된다. -q64로 써야지 -q 64 로 사용하면 안 된다.

 

-qarch 옵션과 함께 사용할 때 이 옵션은 목표하는 아키텍쳐 용으로 기계어와 모드를 결정하게 되는데, -q32, -q64옵션은 -qarch 옵션보다 우선순위가 높다. 그리고 한 줄에 -q32 -q64 옵션을 모두 써주면 최종적으로 써준 옵션에 따라 모드가 결정된다. -qarch=com 으로 하면 앞으로의 호환성을 위해 좀더 일반적인 기계어를 사용하고 이외에는 특정 아키텍쳐에 많이 의존하게 된다.

 

주의! 64bit 모드에서 -qarch=com -qarch=ppc 와 동일하게 취급된다.

 

 

1.2.3 OBJECT_MODE 환경변수

 

컴파일할 때마다 매번 모드를 일일이 지정해줘야 한다면 매우 귀찮은 작업이 될 것이다.(예를 들어 링킹 단계에서 일일이 32bit 64bit를 구분해줘야 한다고 해보자) 만약 개발 중 오직 한가지 모드로만 컴파일한다면 OBJECT_MODE 환경변수를 디폴트 모드로 바꿔주면 된다. OBJECT_MODE 환경변수에 대입할 수 있는 값은 다음과 같다.

 

환경변수값

설명

비고

(unset)

32bit 오브젝트를 생성/사용한다.

 

32

32bit 오브젝트를 생성/사용한다.

 

64

64bit 오브젝트를 생성/사용한다.

 

32_64

32bit/64bit 오브젝트를 모두 허용한다.

 

 

주의! 컴파일러, 링커에서는 OBJECT_MODE=32_64 를 허용하지 않는다. 환경변수를 이와 같이 설정하면 다음과 같은 에러가 발생한다.

1501-254 OBJECT_MODE=32_64 is not a valid setting for the compiler.

 

OBJECT_MODE 환경변수를 사용하면 컴파일러나 링커뿐만 아니라 개발과정 중 필요한 다른 어플리케이션도 이 변수를 참고하는 경우가 있으므로 편리하게 사용할 수 있다.

 

주의! 디폴트 모드를 결정하기 위해 OBJECT_MODE를 사용하고 있는 것을 일반 사용자가 모르고 있다면 심각한 문제가 발생할 수 있다. 예를 들어 OBJECT_MODE 64bit로 설정되어 있을 때 사용자가 64bit로 설계되지 않은 프로그램을 컴파일한다면 자동으로 64bit 코드가 생긴다. 따라서 컴파일하기 전 사용자는 반드시 OBJECT_MODE를 검사해야 하고 자기가 의도한 모드로 설정되어 있는지 확인해야 한다.

 

 

1.2.4 링커 명령행 옵션

 

링커 ld는 디폴트로 -b32 옵션을 사용하며 32bit 링킹을 지원하고, -b64 옵션을 사용하면 64bit 링킹을 지원할 수 있다. 컴파일러 드라이버는 기본적으로 -q32 -q64 옵션을 보고 링킹 옵션을 결정하므로 특별히 링커 옵션을 따로 정해줄 필요는 없다.

 

 

1.2.5 그 외 명령어 지원

 

다음 명령어는 오브젝트 파일을 다룰 때 사용할 수 있으며 디폴트로 오브젝트 파일을 32bit XCOFF3로 가정한다.

 

명령어

설명

비고

ar

링커에서 사용하는 인덱스 처리된 라이브러리를 관리한다.

 

dump

오브젝트 파일에서 선택한 영역을 덤프한다.

 

lorder

오브젝트 라이브러리에서 멤버 파일의 가장 적당한 순서를 찾는다.

 

nm

오브젝트 파일, 실행 파일, 오브젝트 파일 라이브러리에서 심볼에 대한 정보를 보여준다.

 

ranlib

아카이브 라이브러리를 랜덤 라이브러리로 변환한다.

 

size

XCOFF 오브젝트 파일의 섹션 크기를 보여준다.

 

strip

바인더와 심볼릭 디버그 프로그램에서 사용하는 정보를 제거해서 XCOFF 오브젝트 파일의 크기를 줄인다.

 

 

 

64bit XCOFF 오브젝트 포맷을 지원하기 위해 위 명령어에는 -X 옵션이 추가되었다. -X 옵션은 명령어가 검사할 오브젝트 파일의 타입을 지정하며 다음 값을 지정할 수 있다.

 

설명

비고

32

32bit 오브젝트 파일만을 처리한다.

 

64

64bit 오브젝트 파일만을 처리한다.

 

32_64

32bit / 64bit 오브젝트 파일을 모두 처리한다.

 

 

주의! 위 명령어는 모두 OBJECT_MODE 환경변수를 참고한다. 그러나 -X 옵션으로 지정한 값이 더 우선순위가 높다.

 

64bit 어플리케이션을 개발하기 전에 우선 그 어플리케이션에서 사용할 라이브러리도 역시 64bit인지 살펴보아야 한다. AIX에서 제공하는 대부분의 C/C++ 라이브러리는 하이브리드 모드 아카이브(32bit, 64bit 오브젝트가 모두 들어있다)지만 써드파티 제품인 경우에는 해당되지 않을 수 있다.

 


 

2.프로그래밍 힌트 &

 

C 혹은 C++ 로 어플리케이션을 개발할 때 컴파일, 링킹 혹은 처음으로 프로그램을 수행할 때 여러 가지 문제가 발생할 수 있다.

 

 

2.1 프로그래밍시 추천사항

 

C/C++은 매우 강력한 고수준 프로그래밍 언어이다. 각각 2개의 언어로 작성한 어플리케이션은 매우 다른 방식으로 설계, 코딩할 수 있지만 결국 똑같은 기능을 수행하도록 만들 수 있다. 프로그래밍 스타일은 개인에 따라 매우 다르지만 그래도 알아두면 좋은 프로그래밍 가이드라인은 있으며, 이런 가이드라인을 잘 따를 경우 컴파일러에서 최적화된 코드를 생성할 수 있다. 이 섹션에서는 프로그램을 작성할 때 사용할 수 있는 팁과 가이드라인을 보여주며 이 팁은 C for AIX VisualAge C++ for AIX 컴파일러에 매우 유용하게 적용될 수 있다.

 

 

2.1.1 변수와 데이터 구조

 

C,C++에서 변수가 메모리에 머무는 기간, 스코프, 링킹 그리고 관련 있는 오브젝트 등등은 모두 변수를 어디에서 선언했는지에 달려있다. 그러나 메모리에 머무는 기간은 나중에 storage class specifier로 다시 선언하면 바꿀 수 있다. 가능하면 automatic storage class인 지역 변수(local variable)를 사용하는 편이 좋다.

 

컴파일러에서 수행하는 몇몇 최적화는 데이터 플로우 분석에 달려있다. 예를 들어 어떤 변수에 반복해서 값을 저장하면 앞쪽의 저장하는 명령은 지워버릴 수 있다. 어차피 변수를 참조하지 않는다면 가장 나중에 저장한 값만이 유효하기 때문이다.

 

int func1()

{

    int x;

    x = 1;

    func();

    x = 2;

    return x;

}

 

이 예에서, 첫번째 대입인 x = 1 은 아무 문제없이 삭제할 수 있다. 어차피 다음의 함수 호출인 func()는 변수 x 를 사용하지 않으며 나중에 다시 x 2를 대입하기 때문이다. 만약 변수 x 를 전역 변수로 선언했다면 func() 에서 전역 변수를 참조할 가능성도 있으므로 컴파일러가 이렇게 쉽게 첫번째 대입을 지워버릴 수 없다.

 

데이터 플로우 분석은 분기문을 삭제할 것인지 놓아둘 것인지 결정을 해야 할 때도 유용하다. 예를 들어

 

void func2()

{

    int x;

    x = 1;

    func();

    if (x == 1)

        printf("true\n");

    else

        printf("false\n");

}

 

func() 함수 호출은 변수 x 값을 바꿀 가능성이 없으므로 컴파일러는 분기문에서 else 뒤의 실행문을 삭제할 수 있으며 코드 생성은 TRUE값에 대한 부분만 하면 된다.

 

그러나 지역변수를 사용할 수 없는 경우도 있다. 같은 파일내의 서로 다른 두 함수가 데이터를 공유하려면 정적 변수(static variable)같이 정적으로 저장되는 종류의 변수를 사용해야 한다. 이러한 변수나 오브젝트는 내부적으로 링킹이 완료되어야 하는데, 즉 컴파일 단위 안에서만 접근이 가능해야 하고 외부에서 이 변수를 접근할 수 있으면 안 된다. 이러한 경우 컴파일 단위 안에서 정적 변수는 지역 변수처럼 취급할 수 있으므로 컴파일러가 좀더 코드를 최적화 시킬 수 있다.

 

하나 이상의 컴파일 단위끼리 정보를 공유하려면 외부 변수(external variable)를 사용해야 하는데 이 경우 불필요하게 메모리에 접근하는 것을 피하기 위해 필요한 변수를 구조체로 모아놓는 편이 좋다.

 

 

2.1.2 함수

 

현재 컴파일하는 코드 안에 함수가 정의되어 있지 않다면 컴파일러는 최악의 상황을 가정하게 된다. 즉 그 함수를 호출하면 역작용(side effect)이 발생할 수도 있으며 이러한 역작용으로 인해 실행환경이 바뀔 수 있다고 가정한다. 다음과 같은 몇 가지 수행으로 역작용이 발생할 수 있다.

 

- 휘발성 변수 및 오브젝트(volatile object)를 참조할 때

- 외부 변수 및 오브젝트(external object)를 참조할 때

- 정적 변수 및 오브젝트(static object)를 참조할 때

- 파일을 수정할 때

- 위의 동작을 수행하는 함수를 호출했을 때

 

만약 함수에 입력을 해야 한다면, 함수가 직접 전역 변수를 참조해서 값을 읽어가는 것보다 입력값을 인자 (argument)로 넘겨주는 편이 좋다. 만약 함수에 아무 역작용도 없다면 - 즉 위의 사항에 해당되지 않는다면 - #pragma isolated_call 디렉티브를 사용해서 컴파일러가 좀더 최적화를 잘 할 수 있도록 도와줄 수 있다. 이 경우 실행시간이 줄어드는 성능 향상을 보장할 수 있다. #pragma isolated_call 디렉티브로 표시한 함수는 포인터 인자(참조인자)에서 지정한 메모리 위치를 수정할 수 있다.

 

만약 현재 컴파일하는 단위 안에서만 함수가 필요한 경우라면, 이 함수를 static 으로 선언하면 속도가 좀더 빨라진다.

 

C++에서 반드시 필요한 경우가 아니라면 가상 함수(virtual function)를 사용하지 않는 편이 좋다. 실제로 수행되기 이전에는 가상함수의 위치가 정해지지 않기 때문이다. 컴파일러는 가상함수를 호출하기 위해 가상함수 테이블의 엔트리에 대해 간접적인 호출을 하도록 코드를 생성하고 이 경우 가상함수의 실제 위치는 로드할 때가 되어야 할 수 있다. 코드 분량을 줄이려면, 가상함수를 인라인으로 선언하면 안 된다. 가상함수를 인라인으로 선언하면 가상함수를 사용하는 클래스 때문에 생성해야 하는 가상함수 테이블이 훨씬 커지기 때문이다.

 

 

2.1.3 포인터

 

일단 포인터를 사용하게 되면 데이터 흐름상 불확실성이 생길 수 밖에 없으며, 컴파일러가 마음 놓고 최적화를 수행할 수 없다. 변수의 주소에 대해 포인터를 할당하게 되면, 포인터와 변수 두개의 경로로 같은 메모리 주소를 참조할 수 있게 된다. 다음 예제를 보자.

 

void func()

{

    int i = 55, a, b;

    int *p = &i;

    a = *p;

    i = 66;

    b = *p;

}

 

그냥 훑어보면, *p 는 두번째인 b = *p 에는 다시 계산이 안 되는 것으로 생각할 수도 있다. 따라서 그 결과로 변수 a b가 동일한 값을 가진다고 생각할 수 있다. 그러나 변수 p i의 주소는 결국 동일한 메모리 공간을 가리키기 때문에 i=66 부분을 수행하고 나면 *p 의 값이 바뀌고 따라서 b=*p 에서 p의 값은 바뀐 값으로 대입된다.

 

포인터가 어느 부분을 가리키는지 컴파일러가 전혀 알 수 없다면, 컴파일러는 여기에 대해 가장 보수적으로 가정을 해서 포인터가 어떤 변수든 가리킬 수 있는 것으로 가정한다. 다음 예제를 보자.

 

int rc;

 

void foo(int *p)

{

    rc = 55;

    *p = *p + 1;

    rc = 66;

}

 

이 경우 컴파일러는 함수 foo를 호출했을 때 포인터 p 가 변수 rc를 가리킬 가능성이 있다고 가정한다. 첫번째 대입문인 rc = 55 코드 다음의 *p 가 변수 rc를 참조할 수 있기 때문에 rc=55 코드가 실제로는 별 소용이 없어서 지워도 되는 코드임에도 불구하고 함부로 삭제하지 못한다.

 

만약 변수 p rc를 가리킬 가능성이 없다는 것이 보장되면, #pragma disjoint 디렉티브를 사용해서 이러한 사항을 컴파일러에게 알려줄 수 있다.

 

#pragma disjoint (*p, rc)

 

#pragma disjoint 디렉티브로 변수의 물리적 메모리 공간이 다른 변수와 공유되지 않는다는 사항을 컴파일러에게 알려줌으로써 좀더 강력한 최적화를 수행할 수 있다. 그러나 실제로는 물리적 메모리 공간을 다른 변수와 공유하는 변수에 대해 이 디렉티브를 사용하게 된다면 프로그램의 결과값이 제대로 나오지 않을 수 있으므로 주의해야 한다.

 

 

2.1.4 숫자 연산

 

일반적으로 나눗셈보다 곱셈이 빠르다. 똑같은 숫자로 여러번 나누기를 한다면 나누는 수를 역수로 만들어서 곱셈 연산을 하는 편이 좀더 빠르다. 다음 예제를 보자.

 

double preTax(double total)

{

    return total / 1.0825;

}

 

다음 코드가 더 빨리 수행된다.

 

double preTax(double total)

{

    return total * (1.0 / 1.0825);

}

 

나눗셈 연산인 (1.0 / 1.0825)는 컴파일시 단 한번 수행하여 저장되므로 속도가 빨라진다.

 

 

2.1.5 선택구문과 반복구문

 

if문을 쓸 때 if의 조건문에서 될 수 있는 대로 앞쪽에 결정이 쉬운 조건을 집어넣어야 한다. 다음 예제를 보자.

 

struct {

    char *name;

    short len;

    _Bool active;

} rec;

...

if (rec.active == true && rec.len == 9 && !memcmp(rec.name, Elizabeth, 9))

...

 

case문이나 if-else 문을 쓸 때 될 수 있는 대로 발생 가능성이 높은 경우를 조건의 앞쪽에 놓아야 한다. 다음 예제를 보자.

 

typedef enum { VOWEL, CONSONANT, DIGIT, PUNCTUATION } _Type;

struct {

    char ch;

    _Type type;

} word;

...

switch (word.type) {

    case VOWEL:

         ...

         break;

    case CONSONANT:

         ...

         break;

    case PUNCTUATION:

         ...

         break;

    case DIGIT:

         ...

         break;

}

 

그리고

 

if (!error) {

    /* most likely condition first */

} else {

    /* error condition that does not happen too often */

}

 

for 루프, do 루프, while 루프 등을 사용할 때는 루프문 바깥으로 invariant expression (루프문 내에서 따로 변경이 일어나지 않거나 루프 첨자와 관계없는 표현식)을 빼야 한다.

 

 

2.1.6 표현식

 

C/C++ 컴파일러는 다음과 같은 경우 공통된 부표현식(sub-expression)을 찾아낼 수 있다.

 

 - 표현식의 왼쪽 끝에 표현식이 있을 때

 - 괄호 안에 있을 때

 

예를 들어, 컴파일러는 다음 두 대입문에서 a + b 부표현식을 찾아낼 수 있다.

 

x = a + b + c;

y = d * (a + b);

 

다음 찾아낸 부표현식을 단 한번 계산해서 속도를 높이게 된다. 결국 위의 코드는 논리적으로 다음과 같이 변경된다.

 

temp = a + b;

x = temp + c;

y = d * temp;

 

컴파일러는 부표현식을 계산하여 그 결과를 temp의 역할을 하는 레지스터에 저장한 다음 필요할 때 그 값을 가져다 사용한다.

 

 

2.1.7 메모리 사용

 

malloc()이나 calloc()과 같은 메모리를 할당하는 함수를 가지고 작은 오브젝트나 임시로 사용할 오브젝트에 대해 메모리를 너무 자주 할당하게 되면 힙영역에 메모리 단편화 현상(fragmentation)이 심해진다.

 

while (list) {

    char *temp = (char*)malloc(list->len+1);

    strcpy(temp, list->element);

    PrettyPrint(temp);

    free(temp);

    list = list->next;

}

 

위 예제에서 만약 temp의 크기를 미리 알 수 있다면 malloc()을 쓰지 않고, 스택에서 필요한 만큼 영역을 잡을 수 있다.

 

char temp[MAX_ELEMENT_SIZE];

 

만약 미리 temp의 크기를 알 수 없다면, C99의 특징인 가변 배열(Variable length arrays)을 사용해서 배열의 크기를 동적으로 할당하도록 할 수 있다.

 

char temp[list->len+1];

 

두 경우 모두 영역이 힙이 아닌 스택에 잡히므로, 변수 스코프를 벗어나면 자동으로 스택에서 해제된다.

 

 

 

2.1.8 빌트인함수 (built-in function)

 

성능상의 이유로 많은 라이브러리 함수가 컴파일러 빌트인으로 제공된다. 컴파일러 빌트인은 함수를 호출하는 곳에 컴파일러 빌트인의 함수본체를 확장해서 직접 코드를 끼워넣기 때문에 함수호출 시 오버헤드(파라미터를 넘기거나 스택을 할당하는 등)가 없다. 프로그래머가 직접 기계어 명령에 접근할 수 있도록 여러 가지 하드웨어 명령어 빌트인도 제공한다.

 

라이브러리 버전의 빌트인 함수를 사용하려면 적절한 라이브러리 헤더 파일을 사용해야 한다. 적절한 라이브러리 헤더파일을 포함해야 파라미터 간에 타입이 맞지 않는 경우를 방지할 수 있고 최적의 성능도 보장할 수 있다.

 

 

2.1.9 가상 함수 (virtual function)

 

일반적으로 C++ 코드를 작성할 때 왠만하면 가상 함수(virtual function)를 사용하지 않는 편이 좋다. 보통 가상함수는 간접적인 함수호출로 인코드되기 때문에 직접적 함수호출보다 훨씬 속도가 느리다.

 

그리고 가상함수를 인라인으로 선언하지 않는 편이 성능에 훨씬 도움이 된다. 다음 코드를 살펴보자.

 

class Base {

public:

    virtual void foo() { /* do something. */ }

};

 

class Derived: public Base {

public:

    virtual void foo() { /* do something else. */ }

};

 

int main(int argc, char *argv[])

{

    Base* b = new Derived();

    b->foo(); // not inlined.

}

 

이 예제에서 실시간에 foo()가 호출이 되기 이전에는 이 함수가 존재한다는 게 알려지지 않기 때문에 b->foo() 는 인라인이 안 된다. 이 경우는 일반적인 경우가 아니다.

 

그러나 가상함수를 인라인시키는 편이 좋은 경우도 있다. 예를 들어 Base::foo() 가 자주 사용되는 함수라면 다음 코드에서 인라인되는 편이 좋다.

 

int main(int argc, char *argv[])

{

    Base b;

    b.foo();

}

 

만약 클래스 안에 인라인된 가상함수가 없다면, 컴파일러는 첫번째 파일에 가상함수 테이블을 만들어서 가상함수 구현에 대한 정보를 제공하게 된다. 그러나 클래스내의 모든 가상함수가 인라인되어 있다면, 클래스를 사용하는 컴파일 단위마다 가상테이블과 가상함수 본체를 계속 복사해 두게 되고 이 경우 각각의 컴파일 단위에서 가상함수 테이블과 함수본체가 내부적으로 링킹이 된다. 이렇게 되면 컴파일시 -qfuncsect 옵션을 사용해도 링커가 실행파일에서 중복된 테이블과 함수본체를 제거할 수 없기 때문에 실행파일의 크기가 커진다.

 

 

 

2.2 컴파일시 에러 점검

 

컴파일시 컴파일러는 프로그래밍 언어의 문법이 틀렸거나 잘못 사용했을 때 표준에러 디바이스 파일(디폴트로 터미널 화면)로 에러 메시지를 내보낸다. 컴파일러 에러를 보고 프로그램에서 잘못된 곳을 알아낼 수 있는 여러 가지 방법이 있다.

 

 

2.2.1 메시지 분석

 

컴파일러 에러 메시지의 포맷은 다음과 같다. 에러메시지에서 에러의 원인과 위치에 대한 충분한 정보를 얻을 수 있다.

 

"file", line line.column: 15cc-nnn (sev) msg

 

file   : 에러가 발생한 소스 파일 이름

line   : 에러가 발생한 파일 내에서 해당되는 라인

column : 에러가 발생한 라인에서 해당하는 컬럼 (왼쪽에서 문자를 센 값)

cc     : 에러메시지를 발생시킨 컴파일러 구성요소. 2자리 코드값으로 보인다.

         00 : 옵티마이저/코드 생성기

         01 : 컴파일러 서비스

         05 : C 컴파일러

         06 : C 컴파일러

         40 : C++ 컴파일러

         47 : Munch 유틸리티

         86 : 프로시쥬어 간 분석기 (IPA)

nnn    : 메시지 넘버

sev    : 에러의 심각성 수준

         I  : Informational

         W  : Warning

         E  : Error

         S  : Severe error

         U  : Unrecoverable error

msg    : 에러 요약

 

 

2.2.2 유용한 옵션과 컴파일러 외의 프로그램

 

C/C++ 컴파일러에서는 프로그램 내에 프로그래밍 에러를 쉽게 찾아내서 수정할 수 있도록 몇 가지 옵션을 제공한다.

 

 

2.2.2.1 -qsrcmsg 옵션

 

컴파일러 에러의 형식은 디폴트로 "2.2.1 메시지 분석" 에서 나온 포맷으로 출력된다. 그러나 경우에 따라서 메시지만 보고 무엇이 문제인지 알 수 없는 경우도 있다. 컴파일러에서 -qsrcmsg 옵션을 사용하면 컴파일러 에러가 난 위치와 컴파일러가 에러의 원인이라고 생각한 위치, 그리고 원인까지 보여준다. 다음은 예제이다.

 

7 | char new[n];

                    ...........a..

a - 1506-195 (S) Integral constant expression with a value greater than zero is required.

 

소스코드는 매크로 치환이 전부 일어난 후의 코드를 보여준다.

 

 

2.2.2.2 컴파일러 리스팅

 

-qsource 옵션을 사용하여 컴파일러 리스팅을 뽑아볼 수 있다. 컴파일러 리스팅에는 컴파일 과정 중 어떤 부분에서 에러가 발생했는지 찾아낼 수 있는 유용한 섹션이 포함되어 있다. 진단 메시지(diagnostic message)가 있다면 컴파일러 리스팅에 포함된다.

 

SOURCE SECTION 라인넘버와 함께 소스코드를 보여준다. 매크로를 포함하고 있는 부분은 매크로 확장이 일어난 다음을 보여주므로 분량이 더 많아진다. 디폴트로 이 부분은 main 소스코드만 보여준다. -qshowinc 옵션을 쓰면 헤더파일까지 모두 확장해서 보여준다.

 

1) OPTIONS SECTION

컴파일 과정 중 영향을 주는 옵션 중 디폴트 옵션이 아닌 것만 보여준다. 컴파일 과정에 영향을 주는 모든 옵션을 보려면 -qlistopt 옵션을 사용해야 한다.

 

2) FILE TABLE SECTION

컴파일 과정 중 사용한 모든 파일을 보여준다. 각각의 파일에는 번호가 붙어있으며 main코드가 있는 소스 파일은 0번이 된다. 그리고 각각의 파일의 어떤 라인에서 다른 파일을 포함했는지 보여준다. -qshowinc 옵션을 사용하면 SOURCE SECTION의 소스코드 라인이 어느 파일에서 온 것인지도 보여준다.

 

3) COMPILATION EPILOGUE SECTION

에러의 심각성 수준으로 분류한 진단 메시지, 어떤 파일을 어느 줄까지 컴파일했는지, 그리고 컴파일이 성공했는지 실패했는지 등을 요약해서 보여준다.

 

4) ATTRIBUTE AND CROSS REFERENCE SECTION

-qattr -qxref 옵션을 사용하면 이 섹션이 만들어진다. 각각의 옵션은 컴파일 과정 중 사용한 모든 식별자에 관한 정보를 보여준다. 변수의 데이터 타입에 대한 정보, 메모리에 변수가 위치하는 기간, 스코프, 변수가 정의된 위치 및 참조 위치에 대한 정보를 제공한다.

 

5) OBJECT SECTION

-qlist 옵션을 사용하면 이 섹션이 만들어지며, 컴파일러에서 생성한 가상 어셈블리 코드(pseudo assembly code)를 보여준다. 만약 코드 생성에 문제가 있어서 프로그램이 잘못 작동한다는 의심이 들면 이 섹션을 잘 살펴보아야 한다.

 

 

2.2.2.3 -qinfo 옵션

 

-qinfo 옵션을 사용해서 컴파일러에게 프로그래밍 에러에 대한 좀더 자세한 정보를 보여주도록 할 수 있다. 추가의 진단 메시지를 사용하여 프로그램을 좀더 쉽게 디버깅할 수 있다.

 

동일한 에러 부분과 관련된 메시지는 그룹으로 처리되고, 각각의 그룹을 처리하려면 서브옵션을 사용해야 한다. 예를 들어 -qinfo=ini 서브옵션을 사용하면 반드시 초기화해야 하는데도 불구하고 초기화하지 않은 변수와 관련된 발생가능성 있는 에러를 보여준다.

 

C 컴파일러 Version 6 에서는 -qinfo=c99 라는 서브옵션이 새로 생겼다. 이 옵션은 C 코드를 검사하여 -qlanglvl=stdc89 -qlanglvl=stdc89 옵션을 사용했을 때 다르게 동작할 수 있는 사항에 대해 알려준다. 다음 예제를 보자.

 

$ cat test.c

#include <stdio.h>

 

int main()

{

    printf("sizeof(2147483648) = %d\n", sizeof(2147483648));

    return 0;

}

 

$ cc -qinfo=c99 -c test.c

"test.c", line 4.48: 1506-786 (I) Integral constant "2147483648" has an implied type of unsigned long int under the C89 language level. It has an implied type of long long int under C99.

 

 

2.2.2.4 -qsuppress 옵션

 

-qinfo 옵션을 사용했을 때 경우에 따라서 필요 없는 메시지가 너무 많이 출력되어 터미널 화면이나 리스팅 파일을 쓸데없이 길게 만들 수 있다. -qsuppress 옵션을 사용하면 컴파일러에서 생성하는 메시지를 보여주지 않게 할 수 있다. 콜론(:)으로 분리한 리스트에 메시지 넘버를 적어서 여러 개의 메시지가 보이지 않도록 할 수 있다.

 

 

2.2.2.5 -qflag 옵션

 

일정수준 이하의 심각한 오류는 보이지 않도록 할 수 있다. 예를 들어 어플리케이션을 개발하는 단계에서 경고 메시지는 그냥 무시하고 진행하려 할 수 있다. 이 경우 -qflag 옵션을 사용해서 터미널 화면이나 리스팅 파일에 진단 메시지가 보이지 않도록 할 수 있다. 만약 경고메시지를 보여주지 않으려면 -w 컴파일러 플랙을 사용하거나 -qflag=e:e 옵션을 사용해야 한다. 그리고 "2.2.1 메시지 분석" 의 에러의 심각성 수준에 나오는 영문자 한 개짜리 코드를 -qflag 옵션에 사용할 수 있다.

 

 

2.2.2.6 -qhaltonmsg 옵션

 

C++ 컴파일러에서 -qhaltonmsg 옵션을 사용하면 특정한 에러 메시지가 나왔을 때 컴파일을 멈추도록 할 수 있다. 지정한 수준 이상의 에러가 발생하면 컴파일을 멈추도록 할 수 있다.

 

 

2.2.2.7 -qhalt 옵션과 -qmaxerr 옵션

 

-qhalt 옵션을 사용하여 지정한 수준 이상의 심각한 에러가 발생하면 컴파일을 중지시킬 수 있다. "2.2.1 메시지 분석" "에러의 심각성 수준" 에 나오는 영문자 한글자 짜리 코드와 함께 -qhalt 옵션을 사용할 수 있다.

 

-qmaxerr 옵션을 사용하면 지정한 수준 이상의 메시지가 몇 개 이상 발생하면 컴파일을 중지하도록 할 수 있다. 이 옵션은 -qhalt 옵션보다 우선순위가 높다.

 

 

 

2.3 32bit에서 64bit로 마이그레이션

 

32bit 프로그램을 64bit로 마이그레이션 할 때, 데이터 모델이 달라지게 되고 실시간에 예상치 못한 결과가 발생할 가능성도 있다. 64bit 모드에서 포인터와 long 데이터 타입은 8바이트가 되고, 변환문제나 데이터 끝부분이 잘리는 손실(truncation)현상이 발생할 수 있다. -qwarn64 옵션을 사용하여 가능 발생한 문제들을 찾아낼 수 있다.

 

 

2.3.1 int long 타입

 

32bit 모드에서는 int long이 모두 4byte 크기이다. 따라서 32bit모드에서는 이 두 데이터 타입을 그다지 구분하지 않고 사용했다. 그러나 "1.32bit/64bit 개발환경" 의 테이블에서 64bit 모드의 long8byte 길이가 된다. 일반적으로는 소스 전체를 훑어보면서 long의 크기가 2배가 되어도 괜찮은지 보는 수 밖에 없다. 만약 변수의 값이 [-231...231-1] [0...232-1] 안에 들어온다면 int unsigned int 를 사용하는 편이 안전하다. 그 외에도 서브루틴에서 주로 사용하는 size_t 데이터 타입을 typedef unsigned long 으로 정의한 경우가 많으므로 주의해야 한다.

 

 

2.3.2 long에서 int로 변환

 

64bit 32bit 데이터 오브젝트끼리 변환할 때 데이터 손실 현상이 발생할 수 있다. 32bit 모드에서 int long은 둘 다 4byte이며, 이 두 데이터 타입을 섞어서 사용해도 아무 문제가 없다. 그러나 64bit모드에서 long int보다 길이가 2배가 되므로, long에서 int로 데이터를 변환하거나 캐스팅을 하면 데이터 손실 현상이 발생하게 된다.

 

void foo(long l)

{

    int i = l;

}

 

명시적으로 캐스팅을 해주지 않으면, 컴파일러는 대입문에서 값을 줄여서 대입해야 할지 말지를 결정할 수 없다. 만약 변수 l 의 값이 int의 범위 안에 들어가거나 데이터가 잘리는 게 원래의 의도라면 억지로 캐스팅을 시켜줘야 -qwarn64 옵션을 사용했을 때 경고메시지가 안 나올 것이다.

 

 

2.3.3 long으로 변환할 때 발생할 수 있는 예기치 못한 현상

 

64bit 모드에서 long int의 크기가 달라지기 때문에, 데이터 경계문제와 관련하여 int에서 long 데이터로 변환할 때 32bit모드와는 다르게 동작할 수 있다.

 

64bit모드에서 signed char, signed short, signed int unsigned long으로 변환하면 부호 확장 (sign extension) 현상이 발생하여 unsigned 값이 다르게 나올 수 있다. 다음 예제를 보자.

 

#include <stdio.h>

 

void foo(int i)

{

    unsigned long l = i;

    printf("%lu (0x%lx)\n", l, l);

}

 

void main()

{

    foo(-1);

}

 

32bit모드에서 프로그램 결과는 4294967295 (0xffffffff) 가 되지만 64bit 모드에서는 부호확장이 일어나서 결과가 18446744073709551615 (0xffffffffffffffff) 이 된다.

 

INT_MAX보다 더 큰 값을 대입한 unsigned int 변수를 signed long 으로 변환하면, 32bit 64bit의 경우가 값이 다르게 나온다. 다음 예제를 보자.

 

#include <stdio.h>

#include <limits.h>

 

void foo(unsigned int i)

{

    long l = i;

    printf("%ld (0x%lx)\n", l, l);

}

 

void main()

{

    foo(INT_MAX + 1);

}

 

32bit모드에서 INT_MAX+1 값은 반올림되어 -2147483648(0x80000000)이 된다. 그러나 64bit 모드에서는 8바이트 signed long 값이 되어 2147483648 (0x80000000) 이 된다.

 

UINT_MAX보다 크거나 0보다 작은 signed long long 변수는 unsigned long으로 변환되고, 64에서는 데이터 손실 현상이 발생하지 않는다. 다음 예제를 보자.

 

#include <stdio.h>

#include <limits.h>

 

void foo(signed long long ll)

{

    unsigned long l = ll;

    printf("%lu (0x%lx)\n", l, l);

}

 

void main()

{

    foo(-1);

    foo(UINT_MAX+ 1ll);

}

 

이 프로그램의 결과는 다음과 같다.

 

4294967295 (0xffffffff)

0 (0x0)

 

32bit 모드와 64bit 모드에서는

 

18446744073709551615 (0xffffffffffffffff)

4294967296 (0x100000000)

 

UINT_MAX보다 큰 unsigned long long 타입 변수는 unsigned long으로 변환할 수 있고 64bit 모드에서는 데이터 손실현상이 일어나지 않는다.

 

#include <stdio.h>

#include <limits.h>

 

void foo(unsigned long long ll)

{

    unsigned long l = ll;

    printf("%ld (0x%lx)\n", l, l);

}

 

void main()

{

    foo(UINT_MAX + 1ull);

}

 

32bit 모드에서는 워드의 위쪽 부분이 잘려서 0(0x0)이 되지만, 64bit모드에서는 데이터 손실현상이 일어나지 않기 때문에 4294967296 (0x100000000)로 된다.

 

INT_MIN보다 작거나 INT_MAX보다 큰 signed long long 변수는 signed long으로 변환되고, 64bit에서 데이터 손실현상은 발생하지 않는다. 다음 예제를 보자.

 

#include <stdio.h>

#include <limits.h>

 

void foo(signed long long ll)

{

    signed long l = ll;

    printf("%ld (0x%lx)\n", l, l);

}

 

void main()

{

    foo(INT_MIN - 1ll);

    foo(INT_MAX + 1ll);

}

 

이 프로그램의 결과는 다음과 같다.(32bit)

 

2147483647 (0x7fffffff)

-2147483648 (0x80000000)

 

64bit에서 결과는 다음과 같다.

 

-2147483649 (0xffffffff7fffffff)

2147483648 (0x80000000)

 

INT_MAX보다 큰 unsigned long long 변수는 signed long으로 변환되며 64bit에서 데이터 손실 현상은 발생하지 않는다. 다음 예제를 보자.

 

#include <stdio.h>

#include <limits.h>

 

void foo(unsigned long long ll)

{

    signed long l = ll;

    printf("%ld (0x%lx)\n", l, l);

}

 

void main()

{

    foo(INT_MAX + 1ull);

}

 

32bit 모드에서 INT_MAX+1 값은 반올림되어 -2147483648(0x80000000)이 되지만, 64bit모드에서는 8바이트 부호확장이 되어 2147483648 (0x80000000) 이 된다.

 

 

2.3.4 포인터 대입과 연산

 

32bit환경에서 64bit 환경으로 프로그램을 마이그레이션 할 때, 포인터 변환이 매우 중요하다. 발생할 수 있는 문제들을 보자.

 

1) int (32 bit) 32-bit 16진수 상수를 포인터 타입 변수(64 bits)에 대입하거나 포인터를 int로 캐스팅하면 잘못된 주소값을 얻어오거나 잘못된 주소를 가리킬 수 있다. 그리고 포인터와 int를 비교할 때도 잘못된 결과를 야기할 가능성이 있다.

 

2) 포인터 값을 int unsigned int 로 변환하거나 캐스팅을 시키면 데이터가 보존되지 않고 손실현상이 일어날 가능성이 높다.

 

3) 함수 프로토타입을 제대로 정의하지 않으면 포인터 값을 반환하는 함수의 경우 반환값이 잘려서 돌아올 수 있다. 특히 암묵적으로 32bit int를 반환할 것으로 예상하고 64bit포인터를 반환하면 데이터 손실이 일어난다.

 

4) 계산상 포인터와 int가 동일한 크기라고 가정한 코드나 포인터끼리 연산을 하는 경우 마이그레이션 상 문제가 발생할 수 있다. ISO C 표준에 의하면 포인터 값을 증가시키면 포인터가 가리키는 데이터 타입의 크기만큼 증가하도록 되어 있다. 예를 들어 변수 p long을 가리키고 있을 때, 32bit 모드에서 p+1 4바이트를 증가시키지만 64bit에서는 8바이트를 증가시킨다. 따라서 long* int* 을 서로 캐스팅시킬 경우 포인터 오브젝트의 크기가 다르기 때문에 문제가 될 수 있다.(32bit 64bit)

 

 

2.3.5 포인터와 int간 잘못된 변환

 

포인터를 억지로 캐스팅해서 int로 변환하면, 데이터 워드의 위쪽 부분에서 데이터 손실이 일어난다. int를 포인터로 변환하면 이 포인터 값은 올바른 값이 아닐 뿐더러 메모리의 잘못된 위치를 참조할 수도 있다. 다음 예제를 보자.

 

#include <stdio.h>

#include <stdlib.h>

 

void main()

{

    int i, *p, *q;

    p = (int*)malloc(sizeof(int));

    i = (int)p;

    q = (int*)i;

    p[0] = 55;

    printf("p = %p q = %p\n", p, q);

    printf("p[0] = %d q[0] = %d\n", p[0], q[0]);

}

 

32bit 모드에서 포인터 p q는 동일한 메모리 위치를 가리킨다. 그러나 64bit 모드에서 포인터 q는 잘못된 위치를 가리킬 수 있고 q로 값을 참조할 때 세그먼테이션 에러 (segmentation fault)가 발생한다.

 

 

2.3.6 integer 상수

 

정밀도(precision)를 고려하지 않으면 상수로 연산을 할 때 데이터 손실이 발생할 수 있다. 이러한 문제는 매우 찾아내기 어려우며 잘 알려져 있지도 않다. 따라서 상수 계산을 할 때 숫자 뒤에 데이터 타입을 나타내는 문자{u, U, l, L, ll, LL}를 붙일 때 매우 주의해야 한다. 상수연산을 할 때 캐스트를 사용할 수도 있다.

 

특히 64bit로 마이그레이션 할 때, 64bit 모드에서 정수 상수값이 다른 데이터 타입이 될 수 있으므로 주의하자. ISO C 표준에 의하면 정수 상수값의 데이터 타입은 형식과 숫자 뒤에 붙는 문자에 의해 결정되며 데이터 타입이 여러 가지로 해석될 가능성이 있을 때는 가장 작은 데이터 타입을 따른다. 자리를 맞추기 위해 숫자의 앞쪽에 0을 채우는 것은 데이터 타입 결정에 영향을 주지 않는다.

 

첨자

십진수 상수

8진수/16진수 상수

비고

첨자가 없는 경우

int

long

long long

int

unsigned int

long

unsigned long

long long

unsigned long long

 

u or U

unsigned int

unsigned long

unsigned long long

unsigned int

unsigned long

unsigned long long

 

l or L

long

long long

long

unsigned long

long long

unsigned long long

 

u or U 그리고 l or L

(동시에 사용하는 경우)

unsigned long

unsigned long long

unsigned long

unsigned long long

 

ll or LL

long long

long long

unsigned long long

 

u or U 그리고 ll or LL

(동시에 사용하는 경우)

unsigned long long

unsigned long long

 

 

예를 들어 16진수 상수는 32bit에서는 unsigned long으로만 표현되지만, 64bit에서는 long으로 해도 된다. 상수의 데이터 타입이 바뀌면 예기치 않았던 결과가 발생할 수 있다.

 

 

2.3.7 데이터 얼라인먼트

 

현재의 프로세서 디자인에서는 가능한 최대의 성능을 낼 수 있도록 메모리에 데이터를 놓을 때 원래의 자연스러운 얼라인먼트 바운더리를 따르도록 하고 있다. 컴파일러는 데이터 오브젝트가 제대로 얼라인되도록 바운더리가 맞지 않을 경우 패딩 바이트를 끼워 넣는다. 패딩 바이트는 실제 데이터에는 영향을 주지 않지만 struct union 데이터 타입의 경우 기대했던 것과 크기가 달라질 수 있다.

 

64비트 모드에서는 포인터와 long데이터 타입이 2배가 되므로, 이 두 데이터 타입을 멤버로 가지는 struct union 32bit 모드 때와는 크기가 달라지게 된다.

 

#include <stdio.h>

#include <stddef.h>

 

void main()

{

    struct T {

        char  c;

        int  *p;

        short s;

    } t;

    printf("sizeof(t) = %d\n", sizeof(t));

    printf("offsetof(t, c) = %d sizeof(c) = %d\n", offsetof(struct T, c), sizeof(t.c));

    printf("offsetof(t, p) = %d sizeof(p) = %d\n", offsetof(struct T, p), sizeof(t.p));

    printf("offsetof(t, s) = %d sizeof(s) = %d\n", offsetof(struct T, s), sizeof(t.s));

}

 

32bit 모드에서 위 예제를 컴파일해서 수행하면, 다음 결과에서 보듯 p 앞과 s 뒤에 패딩을 채우게 된다.

 

sizeof(t) = 12

offsetof(t, c) = 0 sizeof(c) = 1

offsetof(t, p) = 4 sizeof(p) = 4

offsetof(t, s) = 8 sizeof(s) = 2

 

멤버 p 앞에 세 개의 패딩 바이트를 채우는데 이렇게 하면 p는 자연스럽게 4byte바운더리에 맞춰진다. struct 구조체 자체는 멤버 자체의 가장 엄격한 얼라인먼트 규칙을 따른다. 이 예제에서는 p때문에 4byte 얼라인먼트가 된다. 따라서 구조체 제일 끝에 2개의 패딩 바이트를 끼워넣어서 전체 구조체 크기는 4byte의 배수가 된다. 구조체 안에 배열을 선언했을 때 배열 안의 각 원소도 얼라인먼트 규칙에 따른다.

 

 

그러나 위 예제를 64bit 모드에서 컴파일해서 수행하면 구조체의 크기가 2배가 되는데 p 8바이트 natural alignment boundary를 맞추기 위해 좀더 많은 패딩이 들어가기 때문이다.

 

sizeof(t) = 24

offsetof(t, c) = 0 sizeof(c) = 1

offsetof(t, p) = 8 sizeof(p) = 8

offsetof(t, s) = 16 sizeof(s) = 2

 

 

구조체가 공유되는 상황이거나 32bit/64bit 프로세스 모두에서 사용하는 경우를 생각해보자. 각각의 환경에서 데이터 필드와 패딩은 서로에게 맞지 않는다.

 

이러한 차이점을 극복하고 구조체를 서로 공유하려면, 32bit/64bit 환경 각각의 얼라인먼트에 맞도록 구조체필드의 순서를 바꿀 수 있도록 하면 된다. 그러나 모든 경우에 이렇게 할 수 있는 것은 아니다. 구조체 내의 데이터 타입이나 구조체가 쓰이는 방법에 따라 다르다.(예를 들어 구조체가 다른 구조체나 배열의 멤버로 사용되는 경우)

 

구조체의 멤버 데이터의 순서를 바꿀 수 없거나 데이터 순서를 바꾸면 얼라인먼트가 맞지 않게 된다면, 구조체의 멤버가 자연스럽게 바운더리에 맞출 수 있도록 사용자 정의 패딩(user-defined padding)을 사용하면 된다. 데이터 타입에 따라 조건부 컴파일 섹션이 필요한 경우가 있다. 구조체가 32bit/64bit 환경에 따라 크기가 달라지는 데이터 멤버를 가지고 있을 때 조건부 컴파일 섹션(conditional compile section)을 사용해야 한다.

 

위 예제의 구조체를 다음처럼 바꾸는 경우를 보자.

 

struct T {

    char  c;

    short s;

#if !defined(__64BIT__)

    char  pad1[4];

#endif

    int  *p;

#if !defined(__64BIT__)

    char  pad2[4];

#endif

} t;

 

구조체는 32bit/64bit 모드 모두에서 크기가 같고 멤버 레이아웃이 동일하게 된다.

 

sizeof(t) = 16

offsetof(t, c) = 0 sizeof(c) = 1

offsetof(t, s) = 2 sizeof(s) = 2

offsetof(t, p) = 8 sizeof(p) = 4

 

위는 32bit 환경에서 수행한 결과이다. p의 크기(sizeof(p)) 64bit에서 8이 된다. 다음 그림은 사용자 정의 패딩을 사용했을 때 멤버의 레이아웃을 보여준다.

 

 

주의! 이 예제는 오직 보여주기 위한 예제일 뿐이다. 32bit 프로세스와 64bit 프로세스간 포인터를 공유하는 방법은 잘못된 결과를 낼 수 있기 때문에 추천하지 않는다.

 

구조체에 패딩을 끼워 넣을 때, char 배열을 사용하여 패딩 때문에 또 얼라인먼트 문제가 일어나는 것을 막는다. char의 얼라인먼트는 1byte이므로 메모리의 아무 위치에나 둘 수 있다.

 

 

2.3.8 Character 타입 bitfield

 

다음 코드는 Solaris, HP/UX, Linux에서는 에러 메시지 없이 컴파일되는데 AIX에서 다음과 같은 에러 메시지를 내보내며 xlc로 컴파일이 안 된다.

 

#include <stdio.h>

 

struct test_jskim {

    char test1:2;

    char test2:2;

    char test3:4;

};

 

main() {

    struct test_jskim test={1,2,3};

    unsigned int a, b, c;

 

    a = test.test1;

    b = test.test2;

    c = test.test3;

    printf(" %d %d %d\n", a, b, c);

}

 

$ xlc test.c

"test.c", line 3.5: 1506-009 (S) Bit-field test1 must be of type signed int, unsigned int or int.

"test.c", line 4.5: 1506-009 (S) Bit-field test2 must be of type signed int, unsigned int or int.

"test.c", line 5.5: 1506-009 (S) Bit-field test3 must be of type signed int, unsigned int or int.

 

그러나 이 코드에서 bit field char이 아닌 int로 바꾸면 컴파일이 된다.

 

#include <stdio.h>

 

struct test_jskim {

    int test1:2;

    int test2:2;

    int test3:4;

};

 

main() {

    struct test_jskim test={1,2,3};

    unsigned int a, b, c;

 

    a = test.test1;

    b = test.test2;

    c = test.test3;

    printf(" %d %d %d\n", a, b, c);

}

 

xlc 컴파일러로는 비트필드로 'unsigned char'를 사용할 수 없기 때문에 대신 'unsigned int'를 사용해야 한다. xlc ANSI가 아닌 extended mode 상태에서 'unsigned char' 'unsigned int'로 자동으로 변환한다.

 

cc로 컴파일하면 역시 에러메시지를 내보내기는 하지만 컴파일은 완수하고, xlc의 경우에는 심각한 에러라고 메시지를 내보내며 컴파일을 중지한다.

 

 


 

3.링크 타임시 에러 점검

 

C/C++ 컴파일러 드라이버는 오브젝트 파일을 링크할 때 시스템 링크 에디터인 ld를 사용한다. 여러분의 C/C++ 오브젝트 파일을 링크하기 위해 ld를 직접적으로 사용할 필요는 없다. 여기에서는 링크시 발생할 수 있는 에러에 대해 다루고 있다.

 

 

3.1 해석되지 않은 심볼

 

여러 개의 라이브러리를 사용하여 어플리케이션을 링크할 때, 특히 데이터베이스 라이브러리처럼 써드파티 제품을 사용하는 경우 개발 단계에서 심볼을 해석할 수 없다는 에러를 흔하게 볼 수 있다.

 

링커는 오브젝트 파일, 공유 오브젝트 파일, 아카이브 오브젝트 파일, 라이브러리, 반입 (import)/반출 (export) 파일을 모두 받아서 외부 심볼 참조를 해석하고 실행 가능한 파일을 만든다. 외부 심볼을 해석할 수 없는 경우, 링킹 과정은 실패하고 실행파일을 만들 수 없다.

 

심볼을 해석하지 못한 에러는 다양한 경우에 발생할 수 있지만 대부분의 이유는 필요한 파일을 제대로 주지 않은 경우이다. 예를 들어 디폴트 C 라이브러리 libc.a 에 들어있지 않은 라이브러리 함수를 호출하는 경우 해당하는 함수가 들어있는 라이브러리를 지정해주어야 한다.

 

$ cat test.c

#include <stdio.h>

#include <math.h>

 

void main()

{

    printf("%f\n", pow(2.0,3.0));

}

 

$ cc test.c

ld: 0711-317 ERROR: Undefined symbol: .pow

ld: 0711-345 Use the -bloadmap or -bnoquiet option to obtain more information.

 

$ cc test.c -lm

 

특히 써드파티 제품인 라이브러리를 사용할 때는 반드시 라이브러리를 지정해줘야 한다. 일일이 제품 문서를 뒤지고 어느 심볼이 어느 라이브러리에 정의되어 있는지 찾는 일은 매우 힘든 일이다. 차라리 제공된 라이브러리를 모두 link 명령으로 포함해서 링커가 필요한 심볼은 자기가 찾아가도록 하던가 nm 명령으로 라이브러리를 일일이 찾아야 한다.

 

링커는 링커 로그 파일을 생성할 수 있는 옵션을 제공한다. 이러한 로그파일을 사용하여 해석되지 않은 심볼을 찾기 위해 라이브러리나 오브젝트 파일을 분석할 수 있다. 그리고 에러가 발생한 라이브러리나 상관관계가 있는 라이브러리를 분석하는데도 편리하게 사용할 수 있다.

 

-bnoquiet 옵션은 각 바인더 서브 명령어와 그 결과를 표준출력으로 내보낸다. 해석되지 않은 심볼 참조의 구조와 지정한 라이브러리 모듈에서 반입된 심볼 목록을 보여준다.

 

-bmap:[filename] 옵션을 사용하면 주소맵(address map)을 만들게 된다. 파일의 제일 윗부분에는 해석되지 않은 심볼이 나오고 그 다음에는 반입된 심볼 들이 나온다.

 

-bloadmap:[filename] 옵션을 사용하면 링커 로그 파일을 만들게 된다. 이 파일에는 링커에게 넘겨준 인자, 읽어 들인 공유 오브젝트, 반입한 심볼의 개수 등을 기록한다. 만약 해석되지 않은 심볼이 나오면 -bloadmap 옵션을 사용했을 때 만들어지는 로그파일에는 그 심볼을 참조하는 오브젝트 파일이나 공유 오브젝트 이름이 나온다. 써드파티 제품인 라이브러리를 사용했다면 해석되지 않는 심볼이 나왔을 때 그 라이브러리도 찾아봐야 한다. 특히 어플리케이션에서 데이터베이스 관련 라이브러리를 사용할 때는 수십 개의 라이브러리를 사용하므로 더 주의해야 한다.

 

버전 관리 프로그램으로 소스를 관리하는 어플리케이션이라면 링킹/로드시 에러가 발생하면 우선 버전 관리 시스템부터 살펴봐야 한다. 보통 때는 아무 문제가 없는데 버전 관리 시스템을 사용하는 경우에만 에러가 발생한다면 버전 관리 시스템을 공급한 업체에게 링킹/로딩과 관련된 문제가 있는지 문의해야 한다.

 

 

3.2 중복된 심볼

 

동일한 심볼을 중복해서 정의할 경우 프로그래밍 에러가 발생한다. 같은 이름으로 여러 개의 함수를 외부에 정의하면 안 된다. 이 경우 링커는 첫번째 심볼 만을 인정하기 때문에 의도하지 않은 결과가 발생할 수 있다. 함수의 이름을 바꾸던가 정적(static) 함수를 사용해야 한다.

 

C++의 템플리트 함수를 여러 소스 파일에서 묵시적으로 인스턴스화 했을 때 링크시 함수이름을 중복해서 사용했다는 에러가 발생할 수 있다. 다음 예제를 보자.

 

// t.h

template <class T> class A {

public:

    int f();

};

template <class T> int A<T>::f()

{

    return 55;

}

 

// file1.C

#include "t.h"

 

int func();

int main()

{

    A<int> a;

    int obj1 = a.f() + func();

    return obj1;

}

 

#include "t.h"

 

int func()

{

    A<int> a;

    int obj2 = a.f();

    return obj2;

}

 

$ xlC -c file1.C file2.C

file1.C:

file2.C:

 

$ xlC file1.o file2.o

ld: 0711-224 WARNING: Duplicate symbol:

.A<int>::f()

ld: 0711-345 Use the -bloadmap or -bnoquiet option to obtain more information.

 

nm으로 보면, 두 오브젝트에 A<int>::f()가 모두 정의되어 있는 것을 볼 수 있다.

 

$ nm -g file1.o

.A<int>::f()     T     100

.func()          U     -

.main            T     0

A<int>::f()      D     180     12

main             D     168     12

 

$ nm -g file2.o

.A<int>::f()     T     68

.func()          T     0

A<int>::f()      D     148     12

func()           D     136     12

 

이 경우 A<int>::f() 심볼이 중복되어 똑같이 정의되어 있으므로 -bhalt:5 옵션을사용해서 링커에게 메시지를 보여주지 않도록 해야 한다.

 

$ xlC -bhalt:5 file1.C file2.C

file1.C:

file2.C:

 

-bhalt 링커 옵션은 링킹 과정 중 지정한 숫자보다 적은 에러메시지가 발생하면 이 메시지를 보여주지 않도록 한다. 이러한 에러를 수정하려면 -qtemplateregistry 옵션을 사용하고 VisualAge C++ for AIX 버전 6 컴파일러에서 제공하는 향상된 템플리트 처리 기능을 이용해야 한다.

 

 

3.3 링킹과정 중 메모리가 충분하지 않을 때

 

매우 큰 파일을 링크할 때 링커가 메모리를 전부 다 사용해버릴 가능성이 있다. 이런 경우가 발생하면 링커에서 다음과 같은 에러메시지를 보여준다.

 

ld: 0711-101 fatal error, allocation of bytes fail in routine initsymtab_info.

There is not enough memory.

 

대부분 원인은 페이징 공간이 너무 크기가 작거나, 링커명령을 사용한 사용자의 자원제한이 너무 낮아서 발생한다. AIX의 링커는 다른 UNIX 링커보다 좀더 강력한 기능을 제공하는 대신 가상메모리 공간을 좀더 많이 사용하는 경향이 있으며 특히 여러개 라이브러리로 작성하는 커다란 어플리케이션의 경우에는 이러한 현상이 좀더 심각하다.

 

위와 같은 에러가 발생하면 다음 사항을 체크해보고 가능하다면 페이징 공간을 늘리는 편이 좋다.

 - 시스템 내의 가용한 페이징 공간

 - 링커를 사용한 사용자가 사용할 수 있는 자원의 제한. 이 상황은 ulimit 명령으로 알아볼 수 있다.

 

이런 방법으로 문제가 해결되지 않으면, 링커를 32bit 대형 메모리 모델로 수행해야 한다.

 


 

4.런타임 에러 점검

 

프로그램을 성공적으로 컴파일하고 링크한 다음, 실행시간 중 예기치 않은 에러가 발생할 수도 있다. 프로그래밍 언어의 문법적인 오류가 없었다고 하여 컴파일러에서 제대로 동작하는 것까지 보장해주지는 않기 때문이다. 여기에서는 일반적인 에러와 에러를 찾아내는 방법, 그리고 에러를 수정하는 방법은 다음과 같다.

 

 

4.1 초기화하지 않은 변수

 

C/C++ 표준에 따르면, 정적인 영역에 잡지 않은 변수는 자동 저장 영역(스택 영역에 변수를 할당한다)에 잡히게 되고 이 변수는 명시적으로 초기화하지 않는 한 초기값이 결정되지 않는 것으로 되어 있다. 즉 자동저장 영역에 잡은 변수인 자동 변수(auto variable)를 초기화하지 않고 사용하면 실행할 때마다 다른 값을 결과로 보여주게 된다. 예를 들어 다음 프로그램을 처음 수행하면 제대로 동작하는 것처럼 보인다.

 

$ cat initauto.c

#include <stdio.h>

 

void func(int *p)

{

    if (p == NULL)

        printf("NULL pointer.\n");

    else

       *p = 0xdeadbeef;

}

 

void main()

{

    int *ptr;

    func(ptr);

}

 

$ cc initauto.c

 

$ a.out

NULL pointer.

 

그러나 또 실행했을 때 결과가 계속 동일하게 나온다는 보장은 없다.

 

C/C++ 컴파일러에서 -qinitauto 옵션을 사용하여 자동변수도 모두 지정한 값으로 초기화하는 코드를 끼워 넣도록 할 수 있다. 이 옵션이 추가로 만들어낸 코드 때문에 실시간 성능이 떨어질 수 있으므로 이 옵션은 디버깅 용도로만 사용하는 편이 좋다. 예를 들어 위 코드를 -qinitauto=FE 옵션으로 컴파일하면 문제가 발생할 수 있다는 것을 알려주는 결과를 보여준다.

 

$ cc -qinitauto=FE initauto.c

 

$ a.out

Segmentation fault(coredump)

 

자동변수를 초기화하기 이전에 사용한 곳이 어디인지 찾아내려면 -qinfo=gen 옵션을 사용하면 된다.

 

$ cc -qinfo=gen initauto.c

"initauto.c", line 13.10: 1506-438 (I) The value of the variable "ptr" may be used before being set.

 

 

4.2 실시간 에러체크

 

-qcheck 옵션을 사용하면 프로그램 실행파일에 실시간 검사 코드를 끼워 넣는다. 이때 다음과 같은 검사를 수행할 수 있다.

 

NULL pointer   : 포인터 참조에 사용한 포인터가 512보다 더 큰 주소값을 사용하는지 검사한다.

Array bounds   : 배열의 크기를 컴파일시 미리 알 수 있다면 실시간에 배열의 첨자가 배열 범위 안에

 들어있는지 검사한다.

Divide by zero : 정수값을 0으로 나누는 경우가 있는지 검사한다

 

서브옵션을 지정하면 지정한 에러사항이 발생했을 때 실시간으로 SIGTRAP 예외가 발생한다. 프로그램의 시그널 핸들러에서 이 시그널을 처리하게 할 수도 있다.

 

$ cat check.c

#include <stdio.h>

 

#ifdef DEBUG

#include <signal.h>

#define SIGNAL(sig, handler) signal(sig, handler)

 

void trap_handler(int sig)

{

    printf("SIGTRAP handled\n");

    exit(-1);

}

#else

#define SIGNAL(sig, handler) ((void)0)

#endif

 

void func(int *p)

{

    printf("p has address %p\n", p);

   *p = 0xdeadbeef;

}

 

void main()

{

    SIGNAL(SIGTRAP, trap_handler);

    func(NULL);

}

 

$ cc -DDEBUG -qcheck check.c

 

$ a.out

p has address 0

SIGTRAP handled

 

-qinitauto 옵션의 경우처럼 -qcheck옵션을 사용하면 실시간 성능이 떨어질 수 있으므로 이 옵션은 디버깅할 때만 사용하는 편이 좋다.

 

 

4.3 C에서 부호 없는 상태 유지

 

cc 컴파일러 드라이버를 사용하거나, -qlanglvl=extended 옵션을 사용하거나 혹은 -qupconv 옵션을 사용하면 C 컴파일러는 데이터 타입을 한단계 위로 올리며 부호 없는 상태 유지(unsignedness preservation)를 사용하게 된다. 이는 c89 표준 이전의 semantic이며 K&R C 에서는 자주 언급되었다.

 

부호 없는 상태유지는 int보다 작은 부호 없는 데이터 타입을 한단계 위로 데이터 타입을 바꾼다. 따라서 unsigned char unsigned short unsigned int로 바꾼다. 이와 반대로 c89 c99 에서는 int로 바꾸는 경우라고 하더라도 값 자체는 유지하도록 한다. 부호 없는 상태 유지는 프로그램 실행 중 예기치 않은 상태를 발생시킬 수 있다. 다음 예제를 보자.

 

$ cat upconv.c

#include <stdio.h>

 

void main()

{

    unsigned char zero = 0;

    if (-1 < zero)

        printf("NOUPCONV: Value-preserving rules in effect \n");

    else

        printf( "UPCONV: Unsignedness-preserving rules in effect \n");

}

 

$ cc upconv.c

 

$ a.out

UPCONV: Unsignedness-preserving rules in effect

 

$ cc -qnoupconv upconv.c

 

$ a.out

NOUPCONV: Value-preserving rules in effect

 

 

4.4 ANSI 알리아싱

 

C/C++ 컴파일러에서 지원하는 표준 프로그래밍 언어 레벨은 최적화중 타입기반 알리아싱 규칙을 지키도록 되어 있다. 타입 기반 알리아싱(type-based aliasing) ANSI 알리아싱(ANSI-aliasing)이라고도 하며 데이터 오브젝트를 안전하게 참조할 수 있는 lvalue 값을 제한한다. 결국 포인터는 자신과 동일한 데이터 타입만을 가리켜야 하며, 참조한 다음 포인터 값을 타입 캐스팅하면 안 된다. 여기에 대한 예외의 경우도 있다.

 

- 부호 한정자(sign qualifier)와 타입 한정자(type qualifier)는 타입 기반 알리아싱에 해당하지

않는다.

  - 캐릭터 포인터는 아무 타입이나 가리킬 수 있다.

 

예를 들어 다음 프로그램은 최적화 여부에 따라 다른 결과값을 보여준다.

 

$ cat ansialias.c

#include <stdio.h>

 

unsigned int rc;

 

unsigned int function( float *ptr )

{

    rc = 0;

   *ptr = 1;

    return rc;

}

 

void main()

{

    unsigned int x = function((float *)&rc);

    printf("x = %x rc = %x\n", x, rc);

}

 

$ c89 ansialias.c

 

$ a.out

x = 3f800000 rc = 3f800000

 

$ c89 -O ansialias.c

 

$ a.out

x = 0 rc = 3f800000

 

-O 옵션으로 최적화기능이 수행되면, 타입 기반 알리아싱 때문에 컴파일러는 포인터 ptr이 외부변수 rc를 가리킬 수 없다고 생각한다. 따라서 프로그램은 rc = 0 을 수행한 다음 호출한 함수에게 0을 반환해준다.

 

만약 cc 컴파일러 드라이버를 사용하거나 -qalias=noansi 옵션을 사용하면 컴파일러는 알리아싱 가정을 가장 보수적으로 하게 된다. 따라서 특정 데이터 타입의 포인터라고 하더라도 외부 오브젝트를 포함하여 데이터 타입에 상관없이 주소값이 알려진 어떤 오브젝트라도 포인트할 수 있다고 가정한다. 이렇게 하면 위 예제의 오류를 수정할 수 있지만 최적화 가능성을 줄여서 성능이 떨어질 수 있다. 따라서 타입기반 알리아싱 규칙을 제대로 지키도록 프로그램 코드를 수정하는 편이 좋다.

 

 

4.5 #pragma option_override

 

복잡한 어플리케이션의 경우 사실 모든 프로그래밍 에러를 잡아내기는 거의 불가능하다. 특히 최적화된 코드를 사용한 경우라면 직접 에러가 발생하기 전에는 에러가 있는지 알 수 조차 없다. 이 경우 프로그래밍 에러가 있는 함수에 대해서는 최적화를 일단 꺼 놓고, 에러가 없는 나머지 부분의 코드에 대해서는 최적화된 빠른 코드를 이용할 수 있도록 하면 편리할 것이다.

 

#pragma option_override 디렉티브를 사용해서 특정 함수에 대해 최적화 옵션을 지정하지 않을 수 있다. 4.4 ANSI 알리아싱 의 예제에 다음 코드를 추가해보자.

 

#pragma option_override (function, opt(level,0))

 

다음 결과에서 프로그래밍 에러를 잡아낼 수 있다.

 

$ c89 -O ansialias.c

 

$ a.out

x = 3f800000 rc = 3f800000

 

#pragma option_override 디렉티브를 사용하면 에러의 원인이 된 함수를 찾아낼 때도 함수를 직접 지우지 않아도 되기 때문에 매우 편리하다. 디렉티브를 사용하여 각각의 함수에 대해 최적화 기능을 차례로 꺼보면서 문제가 없어질 때까지 반복해서, 어떤 함수에 프로그래밍 에러가 있었는지 쉽게 찾아낼 수 있다.

 


 

5.디버깅

 

5.1 코어파일

 

어플리케이션이나 프로세스에서 다양한 원인에 의해 에러가 발생했을 때 코어파일이 만들어진다. 코어 파일이 만들어지는 원인은 메모리 참조를 잘못했거나 잘못된 명령어, 버스 에러(bus error), 사용자가 quit 시그널을 발생시킨 경우 등 여러 가지가 있다. 코어파일은 중단된 프로세스의 메모리 이미지를 포함한다.

 

어플리케이션이 중단된 경우가 아니더라도 특정한 시간의 메모리 이미지를 얻기 위해 코어 파일을 생성할 수도 있다. 코어파일은 디버깅하는데 사용할 수도 있고 로컬 혹은 원격에서 시스템에 생긴 문제를 지원하는데 사용할 수도 있다. 이런 경우 코어파일을 제대로 전달하기 위해서 여러 가지 필요한 정보를 함께 모으는 특별한 방법도 따로 있다.

 

 

5.1.1 코어파일 이름

 

AIX5L 버전 5.1이전에는 코어 파일은 항상 core 라는 이름 외에 다른 이름으로 만들 수 없었다. 따라서 동일한 어플리케이션 혹은 다른 어플리케이션이 그 위치에서 수행되다가 다시 코어파일을 생성하게 되면 미처 이름을 바꾸지 않았던 이전 코어파일을 덮어쓰게 되어 있었다.

 

AIX5.1부터는 코어파일의 디폴트 이름은 여전히 core 지만 코어파일마다 고유의 이름을 붙일 수 있게 되었다. 이 과정은 CORE_NAMING 환경변수에 NULL이 아닌 값을 대입해주면 된다.

 

CORE_NAMING=yes

 

CORE_NAMING 환경변수를 다시 이용하지 않으려면 다시 NULL값을 대입한다.

 

export CORE_NAMING=

 

CORE_NAMING 환경변수 값을 yes로 만들면 이제부터 생성되는 코어파일은 core.pid.ddhhmmss 와 같은 형식의 이름으로 만들어진다.

 

pid : 프로세스 ID

dd  : 날짜

hh  : 시간

mm  :

ss  :

 

다음 예제는 PID 30480 인 프로세스가 각각 다른 시간에 생성한 코어파일을 보여준다.

 

$ ls -l core*

-rw-r--r-- 1 ausres01 itsores 8179 Jan 28 2003 core.30480.28232347

-rw-r--r-- 1 ausres01 itsores 8179 Jan 28 2003 core.30482.28232349

 

여기에서 사용된 시간은 GMT기준이며 로컬에서 사용하는 타임존을 사용하지 않는다.

 

 

5.1.2 코어파일에 공유 메모리 정보 포함

 

AIX에서 코어파일을 생성할 때 디폴트로 공유메모리 세그먼트나 쓰레드 스택에 대한 정보는 포함되지 않는다. 만약 상세한 정보를 포함하는 코어파일(full-core)을 생성하려면 다음과 같은 2가지 방법 중 선택할 수 있다.

 

 

5.1.2.1 시스템 전체 full-core 설정

 

시스템 전체의 full-core 설정을 바꾸려면 chdev 명령어를 사용해야 한다. 디폴트 값은 시스템전체 full-core를 사용하지 않는다.

 

# lsattr -El sys0 -a fullcore

fullcore false Enable full CORE dump True

 

설정값을 바꾸려면 root 사용자 권한으로 chdev -l sys0 -a fullcore=true 명령을 사용해야 한다.

 

 

5.1.2.2 SA_FULLDUMP 플랙으로 시그널 핸들러 추가

 

어플리케이션 소스 코드 내에 시그널 핸들러를 붙여서 SA_FULLDUMP 플랙을 처리하도록 하면 코어파일을 생성하도록 할 수 있다. 시그널 핸들러를 등록하려면 sigaction()을 사용한다.

 

주의! 멀티쓰레드 어플리케이션을 디버깅하려면 full-core 설정을 사용해야 한다.

 

 

5.1.3 코어파일 모으기

 

코어 파일과 관련된 모든 정보는 pax 파일에 함께 압축하여 외부 시스템에 점검용도로 사용하도록 파일을 보낼 수 있다. 만약 분산 디버거(distributed debugger)를 사용하면 코어 파일을 생성한 기계에서만 코어 파일을 디버깅할 수 있다.

 

 

5.1.3.1 snapcore 명령

 

코어파일, 프로그램 실행파일, 관련된 라이브러리를 모두 모으려면 snapcore 명령을 사용한다. 명령어 형식은 다음과 같다.

 

snapcore core_filename [program_filename]

 

코어파일의 절대경로 전체와 프로그램 이름을 지정해야 한다. 프로그램 이름을 지정하지 않으면 snapcore는 코어파일에서 직접 프로그램이름을 읽어서 PATH 환경변수에서 지정한 경로 내에서 프로그램을 찾는다.

 

이 명령은 디폴트로 압축한 pax 파일을 /tmp/snapcore 디렉토리에 만든다. 다른 디렉토리를 지정하려면 -d 옵션을 사용해서 디렉토리를 지정한다. 다음 예제에서는 prog1에서 생성한 코어 파일과 관련된 라이브러리 파일을 /tmp/snapcore/core.31442.pax.Z pax파일에 압축하는 과정을 보여주고 있다.

 

$ snapcore core.31374.29164438 prog1

Core file "core.31374.29164438" created by "prog1"

pass1() in progress ....

              Calculating space required .

              Total space required is 6578 kbytes ..

              Checking for available space ...

              Available space is 119148 kbytes

pass1 complete.

pass2() in progress ....

              Collecting fileset information .

              Collecting error report of CORE_DUMP errors ..

Creating readme file ..

              Creating archive file ...

              Compressing archive file ....

pass2 completed.

Snapcore completed successfully. Archive created in /tmp/snapcore.

 

$ ls -l /tmp/snapcore

total 5960

-rw-r--r-- 1 ausres01 itsores 3049565 Jan 29 10:52 snapcore_31442.pax.Z

 

이제 아카이브 파일에 무슨 파일이 들어갔는지 검사해보자.

 

$ uncompress -c /tmp/snapcore/snapcore_31442.pax.Z | pax

core.31374.29164438

README

lslpp.out

errpt.out

prog1

./usr/lib/libc.a

./usr/lib/libcrypt.a

./usr/ccs/lib/libc.a

 

 

5.1.3.2 check_core 명령

 

코어파일만 있고 이 코어파일을 생성한 프로그램과 관련 라이브러리를 모를 때 check_core 명령을 사용한다. 다음 예제는 core.31374.29164438 코어파일을 가지고 어떤 프로그램이 이 코어 파일을 만들었고(여기서는 prog1) 이와 관련된 라이브러리는 무엇인지 보여주고 있다.

 

$ /usr/lib/ras/check_core core.31374.29164438

/usr/lib/libc.a

/usr/lib/libcrypt.a

prog1

 

 

5.1.3.3 AIX 에러 로그 엔트리

 

코어 덤프가 한번 발생할 때마다 AIX 에러 로그에 엔트리가 하나씩 추가된다. 덤프를 발생시킨 어플리케이션을 찾아내는데 매우 유리하다. 다음 명령을 사용해서 코어 덤프와 관련된 엔트리를 찾을 수 있다.

 

errpt -aJ CORE_DUMP

 

-s mmddhhmmyy 옵션을 사용해서 특정시간 이후의 에러 엔트리만을 찾아보자. (mm = , dd = 날짜, hh =시간, mm = , yy =연도)

 

-a 대신 -A 옵션을 사용하면 좀더 간추린 정보를 볼 수 있다. 예제 4-2 PID 31242 prog1 프로그램이 11번 시그널 (SIGSEGV)을 받고 코어파일을 생성한 과정을 보여주고 있다.

 

$ errpt -A -J CORE_DUMP -s 0129130003

-----------------------------------------------------------------------

LABEL:         CORE_DUMP

Date/Time:     Wed Jan 29 13:20:35 CST

Type:          PERM

Resource Name: SYSPROC

Description

SOFTWARE PROGRAM ABNORMALLY TERMINATED

Detail Data

SIGNAL NUMBER

       11

USER'S PROCESS ID:

       31242

FILE SYSTEM SERIAL NUMBER

       13

INODE NUMBER

       2048

PROGRAM NAME

prog1

ADDITIONAL INFORMATION

main 10C

main F8

__start 8C

 

 

 

5.2 printf() 디버깅 기법

 

가장 일반적이고 널리 사용되는 디버깅 기법은 아마 printf() 디버깅일 것이다. 이 기법을 사용하려면 우선 코드 안에 다음 매크로를 정의해야 한다.

 

#if !defined(DEBUG)

#define DBGMSG (MSGSTR) ((void)0)

#else

#define DBGMSG (MSGSTR) {\

        fprintf(stderr,"In %s() at line %d in file %s: %s"\

        , __FUNCTION__, __LINE__, __FILE__, (MSGSTR));\

}

#endif

 

다음 메시지를 출력하고 싶은 곳에서 다음처럼 매크로를 사용하면 된다.

 

int main(int argc, char *argv[])

{

    DBGMSG ("Hi there!\n");

    exit(0);

}

 

-DDEBUG 컴파일 옵션을 사용해서 컴파일하면 다음과 비슷한 디버그 메시지를 보여주게 된다.

 

In main() at line 16 in file printf-debug.c: Hi there!

 

이 방법은 개발의 초기 단계에서 아주 일반적으로 사용되는 방법이지만 다음과 같은 단점이 있다.

 

- printf()디버그 라인을 코드에 추가하고 나면 일일이 소스코드를 다시 컴파일해야 한다.

 - 멀티 쓰레드 어플리케이션을 printf()로 디버깅하려면 매우 복잡한 작업이 된다.

 - printf()를 많이 호출하므로 어플리케이션의 성능이 저하된다.

 - DBGMSG 매크로를 너무 많이 사용하면 디버그 메시지 자체가 너무 많아져서 실제 에러를 추적하기가

 매우 어렵다.

 

printf()디버깅에서는 C99에서 도입한 함수와 유사한 매크로가 매우 유용하다.

 

 

5.3 디버깅 용도로 어플리케이션 준비

 

어떤 디버거를 사용하더라도, 우선 어플리케이션을 -g 옵션을 사용해서 컴파일하고 최적화 옵션은 사용하지 말아야 한다. 이렇게 하지 않으면 디버거가 어플리케이션 내의 심볼을 해석할 수 없기 때문에, 디버깅과정 중에 변수나 함수를 참조할 수 없다.

 

strip 명령을 사용해도 이 명령은 어플리케이션 프로그램에서 심볼 정보를 제거하기 때문에 디버거가 심볼을 해석할 수 없어서 디버깅이 안 된다.

 

AIX C/C++ 컴파일러 최신버전에서 제공하는 유용한 옵션은 다음과 같다.

 

옵션

설명

비고

-qfullpath

디버깅 정보에 소스 파일 전체 경로를 포함한다. 소스 파일 구조가 매우 복잡하고 큰 경우 디버거가 일일이 소스 파일위치를 물어보지 않아도 되므로 유용하다.

 

-qlinedebug

디버깅 정보에서 변수 설명을 제거한다. 최적화한 코드라면 변수설명부분을 제거하는 게 유리한데, 어차피 최적화된 코드라면 변수를 모니터링해도 잘못된 정보만을 보여주기 때문이다. 이 옵션을 사용하면 디버깅 정보부분의 크기를 많이 줄여주므로 특히 C++ 프로그램의 경우에 유리하다.

 

-qdbxextra

프로그램내의 모든 심볼의 참조여부에 관계없이 디버깅 정보를 모두 포함한다. 보통의 경우에는 참조하는 심볼에 대한 디버깅 정보만 포함한다. 이 옵션을 사용하면 경과로 생기는 실행파일의 크기가 매우 커진다.

 

 

어플리케이션을 컴파일할 때 디버깅 정보를 포함하도록 하면 실행파일의 크기가 매우 커질 수 있다. 특히 C++ 같은 경우는 거의 10배 이상으로 커진다. -qlinedebug를 잘 사용하고 필요 없는 부분에서 디버깅 정보를 제거하면 크기를 많이 줄일 수 있다.

 

 

5.4 dbx 사용법

 

심볼릭 디버거 dbx AIX를 포함하여 많은 UNIX에서 가장 널리 사용되는 디버거이다. 어플리케이션에 발생한 문제를 디버깅하기 위해 dbx는 다음과 같은 기능을 제공한다.

 

 - 오브젝트와 코어 파일을 검사한다.

 - 프로그램을 수행하는 환경을 커스터마이징할 수 있다.

 - 브레이크 포인트를 설정한다.

 - 프로그램의 로직을 따라가볼 수 있다.

 - 심볼릭 변수를 관리할 수 있다.

 

주의! dbx는 가벼운 코어 파일을 지원하지 않는다.

 

 

5.4.1 dbx 세션 시작

 

어플리케이션이 비정상으로 종료되고 코어 파일을 생성하면 다음과 같이 dbx를 사용한다.

 

dbx ObjectFile CoreFile

 

코어 파일이름을 지정하지 않으면 자동으로 현재 디렉토리의 core파일을 읽는다. dbx 세션을 시작하면 서브 명령어 프롬프트인 (dbx)를 보여준다. 다음 예제는 prog1 prog1이 만든 코어파일 core.30330.30144523 을 이용하여 디버깅하는 예제를 보여주고 있다. 예제에서는 main()함수의 13번째 줄에서 에러가 발생했으며 원인은 "segmentation fault"이다.

 

dbx prog1 core.30330.30144523

Type 'help' for help.

reading symbolic information ...

[using memory image in core.30330.30144523]

 

Segmentation fault in main at line 13

13 s[j] = j;

 

dbx 세션을 종료하려면 dbx 서브 명령어인 quit 을 사용한다.

 

(dbx) quit

 

 

5.4.2 수행되는 프로세스 붙이기

 

이미 수행되고 있는 프로세스가 무한루프를 돌고 있는지 알아보기 위해 수행되고 있는 도중에 프로세스를 디버깅해야 할 경우가 있다. 이 경우에는 -a 옵션을 사용하여 프로세스의 PID를 지정하여 이를 dbx 세션에 붙이는 방법을 사용한다.

 

$ dbx -a 25952

Waiting to attach to process 25952 ...

Successfully attached to prog6.

Type 'help' for help.

reading symbolic information ...

stopped in main at line 15

        15       while (i >= j) {

 

(dbx) quit

[2] + Killed prog6 &

 

주의! 서브 명령어 프롬프트에서 quit을 입력하면 dbx 세션뿐만 아니라 프로세스도 같이 종료된다. 프로세스는 종료하지 않고 dbx세션만 끝내려면 detach 서브 명령어를 사용한다.

 

 

5.4.3 dbx 세션 커스터마이징

 

dbx 명령은 다음과 같은 순서로 .dbxinit 초기 환경설정 파일을 읽는다.

 

 - 현재 디렉토리

 - 사용자 홈 디렉토리

 

만약 현재 디렉토리에 .dbxinit 파일이 있으면 사용자 홈 디렉토리까지 읽지 않는다. 물론 -c 옵션을 사용하여 환경설정파일을 지정해 줄 수 있다.

 

자주 사용하는 서브명령어나 매크로를 알리아스로 정의하면 dbx 세션을 편리하게 커스터마이즈할 수 있다.

 

dbx 세션을 이미 시작하고 난 뒤라면 다음 서브 명령어를 사용하여 환경설정 파일을 읽어올 수 있다.

 

(dbx) source /home/ausres01/.dbxinit

 

다음에 소개하는 방법을 이용하여 dbx 세션을 좀더 편리하게 커스터마이징할 수 있다.

 

 

5.4.3.1 구조체를 좀더 읽기 쉽게 보기

 

dbx 세션에서 print 서브 명령어를 사용하면 변수의 값을 찍어볼 수 있다. 그러나 C/C++의 복잡한 구조체나 union을 볼 때는 변수 값을 읽어보기 매우 어렵다. 이 경우에는 다음 2가지 방법을 사용할 수 있다.

 

(dbx) set $pretty="off"

(dbx) print names[0]

(effort = (low = (0, 0), name1 = "Fabian", name2 = "Julian", name3 = "Manuel",

high = (

(a = (0), b = 0)

(a = (0), b = 0)

)), range = (0))

(dbx) set $pretty=on"

(dbx) print names[0]

{

    effort = {

        low[0] = 0

        low[1] = 0

        name1 = "Fabian"

        name2 = "Julian"

        name3 = "Manuel"

        high[0] = {

            a[0] = 0

            b = 0

        }

        high[1] = {

            a[0] = 0

            b = 0

        }

    }

    range[0] = 0

}

 

 

5.4.3.2 $pretty="verbose" 설정

 

또 다른 스타일로 보려면 $pretty="verbose" 서브 명령어를 사용한다. 이 경우 각각의 값과 구분되는 이름이 함께 나온다.

 

(dbx) set $pretty="verbose"

(dbx) print names[0]

effort.low[0] = 0

effort.low[1] = 0

effort.name1 = "Fabian"

effort.name2 = "Julian"

effort.name3 = "Manuel"

effort.high[0].a[0] = 0

effort.high[0].b = 0

effort.high[1].a[0] = 0

effort.high[1].b = 0

range[0] = 0

(dbx)

 

 

5.4.3.3 명령행 라인 편집 모드

 

서브 명령어 set edit [vi/emacs] set -o [vi/emacs] dbx의 서브 명령어 라인 편집기를 vi emacs로 지정할 수 있다.

 

 

5.4.4 브레이크 포인트로 작업하기 - stop 서브 명령어

 

stop 서브 명령어는 특정한 조건 하에 어플리케이션의 수행을 중지한다. 프로그램이 중지하는 경우는 다음과 같다.

 - if 조건 플랙을 사용했을 때 조건이 true가 되면

 - 프로시저 플랙을 사용할 때 프로시저를 호출하면

 - 변수 파라미터를 지정했을 때 변수 값이 바뀌면

 - 소스라인 플랙을 사용했을 때 지정한 소스의 라인에 도달하면

 

 

5.4.5 -p 플랙으로 오브젝트 파일의 라이브러리 위치를 리다이렉션하기

 

코어 파일을 가지고 작업하면, dbx는 코어 파일을 생성할 당시 어플리케이션에서 사용한 라이브러리와 공유 오브젝트를 참조하는 변수를 모두 해석해야 한다. 이 정보는 코어 파일의 로더 섹션에 저장된다. main 실행 모듈을 제외하고 모든 파일이름은 절대경로로 해석한다. dbx명령은 LIBPATH 환경변수에 설정된 값 대신 테이블에 저장된 값을 사용한다.

 

코어파일을 검사하기 위해 이 파일을 다른 시스템으로 가져가면, 사용하던 라이브러리의 위치가 바뀌거나 없을 가능성이 있다. AIX5L 버전 5.2부터는 dbx -p 플랙을 사용하면 옛날 라이브러리 이름을 새 라이브러리로 대체할 수 있게 되었으므로 코어파일을 수정하지 않아도 된다.

 

다음 예제에서는 현재 디렉토리의 ./libone.so 라이브러리(Entry 5 에 나와있음)를 사용했다.

 

(dbx) map

Entry 1:

    Object name: prog8

    Text origin: 0x10000000

...

Entry 5:

    Object name: ./libone.so

    Text origin: 0xd00d7000

    Text length: 0x2fd

    Data origin: 0xf0323210

    Data length: 0x34

    File descriptor: 0x8

 

(dbx)

 

바이너리 파일이 더 이상 원래의 위치에 있지 않다면 dbx 세션을 시작하면 에러가 발생하게 된다.

 

$ dbx prog8 core

Type 'help' for help.

reading symbolic information ...dbx: fatal error: 1283-012 cannot open ./libone.so

 

이러한 문제를 해결하기 위해 이 경우 -p 옵션을 사용하여 이전에 사용하던 라이브러리의 위치를 새로 지정해줘야 한다.

 

$ dbx -p ./libone.so=./lib/libone.so prog8 core

Type 'help' for help.

[using memory image in core]

reading symbolic information ...

 

Segmentation fault in func8 at 0xd00d7158

0xd00d7158   (func8+0x30) 98640048    stb    r3,0x48(r4)

(dbx) map

Entry 1:

      Object name: prog8

...

Entry 5:

      Object name: ./lib/libone.so

      Text origin: 0xd00d7000

      Text length: 0x2fd

      Data origin: 0xf0323210

      Data length: 0x34

      File descriptor: 0x9

 

바뀐 정보가 많으면 매핑정보를 아예 파일로 작성해서 -p [filename]으로 한꺼번에 줄 수 있다.

 

 

5.5 truss로 디버깅

 

truss30명령은 라이브러리를 조사하여 프로세스에서 사용한 시스템 콜과 시그널 활동을 검사한다. truss 명령을 사용하여 프로세스에서 수행한 모든 시스템 콜과 시스템 콜로 넘긴 파라미터, 그리고 시스템 콜에서 반환한 데이터나 에러 값을 조사할 수 있다. 따라서 어플리케이션이 AIX OS에 요청한 사항과 결과를 바로 볼 수 있다.

 

truss가 어떻게 동작하는지 알아보기 위해 우선 4개 소스 파일을 예로 들었다.

 

/* main.c */

int main(int argc, char *argv[])

{

    func9_1(1);

    func9_3(2);

    exit(0);

}

 

/* sample9a.c */

void func9_1(int i)

{

    int offset;

    offset = 10;

    func9_2(i);

    func9_3(i + offset);

}

 

/* sample9b.c */

void func9_2(int i)

{

    func9_3(i);

}

 

/* sample9c.c */

#include <stdio.h>

#include <stdlib.h>

 

void func9_3(int i)

{

    printf("I am here in func9_3 with %d !!\n", i);

}

 

다음 prog_to_truss 라는 이름으로 어플리케이션을 만든다.

 

$ cc -G -o libs91.so sample9a.c

$ cc -G -o libs92.so sample9b.c

$ cc -G -o libs93.so sample9c.c

$ cc -brtl prog9.c -L. libs91.so libs92.so libs93.so -o prog_to_truss

 

prog_to_truss 어플리케이션은 main.o 라는 오브젝트 모듈과 실시간 링킹 공유 오브젝트에 대한 참조데이터를 포함한다.

 

디폴트로 truss는 라이브러리 호출을 탐색할 수 없다. 다음 예제에서 -t!__loadx 옵션을 지정했는데, 이 옵션은 시스템 콜 __loadx 를 호출하는 모든 경우를 결과물에서 제거하게 된다. 프로그램이 실시간 링킹 공유 오브젝트에서 3개의 함수를 호출하므로 이 옵션을 따로 지정하지 않으면 결과물을 읽기가 매우 어려울 것이다.

 

$ truss -t!__loadx prog_to_truss

execve("./prog_to_truss", 0x2FF229FC, 0x2FF22A04) argc: 1

sbrk(0x00000000)                          = 0x2000050C

sbrk(0x00000004)                          = 0x2000050C

sbrk(0x00010010)                          = 0x20000510

kioctl (1, 22528, 0x00000000, 0x00000000) = 0

I am here in func9_3 with 1 !!

kwrite(1, 0xF01B5168, 24)                 = 24

I am here in func9_3 with 11 !!

kwrite(1, 0xF01B5168, 25)                 = 25

I am here in func9_3 with 2 !!

kwrite(1, 0xF01B5168, 24)                 = 24

kfcntl(1, F_GETFL, 0x00000000)            = 67110914

kfcntl(2, F_GETFL, 0xF01B5168)            = 67110914

_exit(0)

 

다음 예제에서는 -u'*' 옵션을 사용하여 라이브러리 함수를 호출하는 경우만 보여주도록 했다.

 

$ truss -t!__loadx -u'*' prog_to_truss

execve("./prog_to_truss", 0x2FF229FC, 0x2FF22A04) argc: 1

sbrk(0x00000000)                          = 0x2000050C

sbrk(0x00000004)                          = 0x2000050C

sbrk(0x00010010)                          = 0x20000510

->librtl.a:func9_1(0x1)

->librtl.a:func9_3(0x1)

kioctl (1, 22528, 0x00000000, 0x00000000) = 0

I am here in func9_3 with 1 !!

kwrite(1, 0xF01B5168, 24)                 = 24

<-librtl.a:func9_3()                      = 18 0.000000

->librtl.a:func9_3(0xb)

I am here in func9_3 with 11 !!

kwrite(1, 0xF01B5168, 25)                 = 25

<-librtl.a:func9_3()                      = 19 0.000000

<-librtl.a:func9_1()                      = 19 0.000000

->librtl.a:func9_3(0x2)

I am here in func9_3 with 2 !!

kwrite(1, 0xF01B5168, 24)                 = 24

<-librtl.a:func9_3()                      = 18 0.000000

->librtl.a:exit(0x0)

kfcntl(1, F_GETFL, 0x00000000)            = 67110914

kfcntl(2, F_GETFL, 0xF01B5168)            = 67110914

_exit(0)

 

여기에서 func9_3()을 세 번 호출했는데 각각 integer파라미터 1(0x1), 11(0xb), 2(0x2)를 넘겨주었다. 물론 이 값은 func9_2()에서 호출하면서 넘겨준다.

 

 

참고) Tuxedo 서비스 프로그램에서 코어(Core)가 발생하는 주된 원인

 

1.변수 선언 후 초기화 하지 않는 변수(구조체 포함)를 사용할 경우.

  (사용하기 전에 반드시 초기화하고 사용해야 함)

 

2.varchar로 선언된 변수를 printf STRLOOK 함수를 사용할 때 변수명.arr로 하지 않고 변수명만

사용한 경우.

 

3.varchar 변수를 if문에서 비교할 때 변수명.arr로 하지 않고 변수명만 사용한 경우.

 

4.varchar 변수에 문자를 복사한 경우 반드시 다음과 같이 변수 길이를 설정해야 함.

  변수명.len = strlen(변수명.arr);

 

5.선언한 변수보다 큰 값을 설정한 경우.

 

6.숫자 변수에 문자값을 설정한 경우.

 

7.선언한 변수보다 큰 값을 설정한 경우 다음 선언한 변수로 값이 넘어가는 경우도 있슴.

 

8.FML Buffer Field 값이 존재하지 않은 Field에서 값을 가져올 경우.

 

9.함수에서 인수로 varchar 변수를 사용한 경우 반드시 다음과 같이 변수 길이를 설정해야 함.

  변수명.len = strlen(변수명.arr);

  변수명[변수명.len].arr = 0x00;

 


 

6. C & C++ 컴파일러

 

C for AIX Version 6.0 컴파일러는 AIX에서 사용할 수 있는 IBM C 컴파일러 중 가장 최근 제품이다. 이전 버전에 비해 좀더 향상된 기능을 가지며 특히 최적화 기능이 강화되었고 새로운 PowerPC® 아키텍쳐를 지원한다. 이 컴파일러는 AIX 4.3.3 과 그 이후 버전부터 사용할 수 있다.

 

IBM C for AIX 버전 4 5로 만든 C 프로그램 소스는 IBM C for AIX Version 6.0에서 사용할 수 있다. 이외에도 IBM Set ++ for AIX 버전 2 3 그리고 XL C compiler component of AIX Version 3.2 로 작성한 소스도 IBM C for AIX Version 6.0에서 사용할 수 있지만, 이 경우 결과를 정의할 수 없는 쓸데없는 프로그램이나 그러한 프로그램 코드 부분을 찾아낼 수 없다. 그러나 소스가 호환된다고 해서 프로그램이 이전과 완전히 동일하게 수행된다고 보장할 수는 없다. 새로운 옵션이 프로그램의 동작에 영향을 줄 수 있다.

 

컴파일러를 설치하면, 디폴트로 /usr/vac 디렉토리에 설치되고 /etc/vac.cfg 환경설정 파일을 이용한다.

 

 

6.1 새로운 최적화 기능과 개선된 사항

 

C for AIX Version 6 에서 여러 가지 최적화 관련 기능을 추가했고 기존 기능도 개선되었다.

 

 

6.1.1 프로시쥬어간 분석

 

프로시쥬어 간 분석(IPA)은 함수간에 이루어지는 최적화 과정이다. 전통적인 컴파일러 기법에서는 한 개의 컴파일 단위 안의 한 개의 함수 안에서만 최적화 분석을 했었지만, IPA 는 전체 어플리케이션 내의 모든 함수를 분석하여 최적화를 하게 된다.

 

최적화 수행기(optimizer)가 하는 일반적인 최적화 외에도, IPA는 다음과 같은 프로시쥬어간 최적화를 수행한다.

 

 - 컴파일 단위간의 인라인

 - 프로그램 파티셔닝

 - 전역변수 병합(Global variables coalescing)

 - 사용하지 않는 코드 제거(Dead code elimination)

 - Code straightening

 - Constant propagation

 - Copy propagation

 

-qipa 옵션을 사용하면 처리하는데 좀더 많은 시간이 들어가므로 컴파일 시간이 오래 걸린다. 그러나 C for AIX Version 6.0 에서는 새 옵션 -qipa=threads 가 생겼는데 이 옵션을 사용하면 멀티 쓰레드로 프로시쥬어간 분석을 수행하게 되므로 시간이 좀더 줄어들 수 있다. -qipa=threads=N 옵션에서 N 을 컴파일러가 IPA 분석 및 코드 생성에 사용할 쓰레드 개수로 지정해줄 수 있다.

 

 

6.1.2 프로파일 기반 피드백(Profile-directed feedback)

 

프로파일 기반 피드백(PDF)은 실행파일에 특수한 코드를 끼워 넣어 프로그램의 실행패턴에 대한 정보를 모을 수 있도록 한다. 두번째 컴파일할 때 프로그램 수행 패턴에 대한 정보를 가지고 코드 수행 패턴이나 조건 분기에 대한 패턴에 따라 최적화를 수행할 수 있다.

 

이 기능을 좀더 효과적으로 이용하려면 처음 프로그램을 수행할 때 의도한 실제환경에 가까운 환경에서 테스트해야 한다. 즉 입력값이나 데이터를 실제 환경에서 의미 있는 값으로 주어야 한다.

 

PDF는 개발단계 중 거의 최종적인 단계에서 사용해야만 완전한 최적화 버전을 얻을 수 있다.

 

 

6.1.3 새로운 옵션과 pragma

 

C for AIX Version 6.0 에서는 새로운 성능관련 옵션과 pragma 가 추가되었다.

 -qarch=pwr4

 -qtune=pwr4

 -qhot

 -qlargepage

 -qsmallstack

 -qunwind

 -qtocmerge

 -qreport

 -qipa=threads=N

 #pragma execution_frequency

 #pragma pack

 #pragma snapshot

 

주의! -qarch=pwr4 -qtune=pwr4 옵션으로 POWER4 프로세서에 가장 최적화된 실행파일을 만들 수 있다.

 

 

6.1.4 새로운 빌트인함수(built-in function)

 

프로그래머는 컴파일러 빌트인함수를 사용하여 하드웨어 아키텍쳐 상 머신 코드에 직접 접근할 수 있다. 이 코드는 하드웨어 명령코드에 직접 대응되므로 함수를 호출하는데 들어가는 오버헤드(예를 들어, 스택 할당이나 스택 조정, 인자 넘기기 등등)가 전혀 없다.

 

 

6.2 ISO C 표준 적합성

 

IBM C for AIX Version 6.0 compiler는 보통 C99라고 하는 최신 ISO/IEC 9899:1999 국제표준을 지원한다. C99는 이전의 기준 ISO/IEC 9899:1990 국제표준 (C89)보다 기능이 향상되었다. 새 표준에 적용되는 유용한 기능은 다음과 같다.

 

 

6.2.1 _Bool 타입

 

C99는 이전 C89 표준의 타입 지정자 외에도 C++ bool 타입과 유사한 _Bool 타입을 지원한다. stdbool.h 에서 true false 에 대한 매크로를 지정하고 있으므로 더 이상 어플리케이션에서 사용할 매크로를 정의하지 않아도 된다.

 

 

6.2.2 long long 데이터 타입

 

이전 C for AIX 컴파일러의 -qlonglong 옵션과 달리, C99에 추가된 long long 데이터 타입은 정수 상수에 대한 프로그램상 의미(semantic)를 바꿀 수 있다. C89에서 타입을 나타내는 suffix를 붙이지 않은 정수 상수의 경우 그 상수값을 나타내기에 충분한 int, long int, unsigned long int 데이터 타입이 된다. 그러나 C99에서 타입을 나타내는 suffix를 붙이지 않은 정수 상수의 경우는 int, long int, long long int 가 된다.

 

#include <stdio.h>

#include <stdlib.h>

 

int main(int argc, char *argv[])

{

    printf("sizeof(2147483648) = %d\n", sizeof(2147483648));

    exit(0);

}

 

위의 예제에서 정수 2147483648 LONG_MAX 보다 큰 값이다. -qlanglvl=stdc89 옵션으로 컴파일하면 이 상수는 unsigned long int 타입이기 때문에 결과로 4를 찍는다. 그러나 -qlanglvl=stdc99 옵션으로 컴파일하면 이 상수는 long long int 타입이 되므로 8을 찍는다.

 

 

6.2.3 Complex 데이터 타입

 

C99 C 언어에 complex(복소수) 데이터 타입을 지원한다. 세개의 complex 데이터 타입 float _Complex, double _Complex, long double _Complex 와 세개의 imaginary(허수) 타입 float _Imaginary, double _Imaginary, long double _Imaginary 을 지원한다. 이들은 모두 합쳐서 complex floating 타입이라고 한다. complex 타입은 논리적으로 원소가 2개 있는 배열과 같으며 각각은 실수 부분의 부동소수점과 허수부분의 부동소수점을 의미한다. 따라서 complex 타입의 크기는 해당 부동소수점 타입의 2배가 된다.

 

다음은 복소수 변수 c 와 이 값을 {1.0, 2.0i}로 초기화하는 코드이다.

 

double _Complex c = 1.0 + 2.0 * __I;

 

C 언어에서 기본적인 숫자연산자를 지원하고, 시스템 헤더 파일 complex.h 를 인클루드하면 런타임 라이브러리에서 수학관련 함수를 지원한다.

 

 

6.2.4 인라인 함수 지정자(inline function specifier)

 

함수 인라인은 함수를 호출하는 오버헤드를 줄여줄 뿐만 아니라 최적화 수행기가 최적화를 좀더 잘 수행할 수 있도록 해준다. -O 옵션을 사용할 경우 컴파일러는 이미 인라인 최적화를 수행하므로 이 기능을 사용해서 더 이상 얻을 수 있는 이점은 없다.

 

 

6.2.5 제한된 한정자(restrict qualifier)

 

오브젝트를 제한된 한정 포인터(restrict qualified pointer)로 수정한 경우, 이 오브젝트를 접근하는 모든 경우는 직접 접근이던 간접 접근이던 동일한 포인터로 접근해야 한다. 다른 포인터는 이 오브젝트에 접근할 수 없다.

 

 

6.2.6 배열 선언 시 static 키워드

 

함수를 선언할 때, 파라미터가 배열인 경우 해당 배열 원소의 타입에 맞는 포인터로 파라미터를 지정한다.

 

void func(int arr[])

{

    ...

}

void func(int *arr)

{

    ...

}

 

의 두 경우는 동일한 선언이다. C99에서는 배열 파라미터 선언 시 storage class specifier static 을 사용하도록 한다. 이러한 방법으로 함수를 호출할 때 NULL이 아닌 인자값을 넘겨야 하며 지정한 숫자만큼의 원소를 넘겨야 함을 컴파일러가 보장하게 한다. 예를 들어

 

void vector_add(int a[static 10], const int b[static 10])

{

    int i;

    for(i = 0; i < 10; i++)

        a[i] += b[i];

}

 

 

6.2.7 Universal character name

 

C for AIX Version 5 이후부터 -qlanglvl=ucs 옵션을 사용하면 Universal character 를 사용할 수 있었지만, C99부터는 이 사항이 표준이 되었다. 기본 character set이 아닌 character를 사용할 때 이 기능을 사용한다. 식별자, 문자열 상수, 주석문에 universal character를 사용할 수 있다.

 

 

6.2.8 __func__

 

컴파일러가 생성한 내부 변수인 __func__ C for AIX 컴파일러에서 정의한 매크로인 __FUNCTION__ 과 유사하며 다음과 같은 형태를 가진다.

 

static const char __func__[] = "function_name";

 

function_name __func__ 가 현재 참조하는 함수의 이름이다. 이 기능은 디버그 코드를 작성할 때 유용하게 사용할 수 있다.

 

 

6.2.9 16진수 부동소수점 상수(Hexadecimal floating point constant)

 

16진수 정수 상수를 사용하여 정수의 정확한 이진 포맷을 나타낼 수 있는 것처럼, C99에서는 16진수로 부동소수점 상수를 나타낼 수 있다.

 

double d = 0x123.abcp+10;

 

 

6.2.10 가변 길이 배열(Variable length arrays)

 

지역변수로 선언하고 자동으로 크기가 할당되는 오브젝트는 컴파일할 때 크기가 정해지고, 오브젝트가 유지되는 기간과 범위는 그 오브젝트를 선언한 함수에서 나가는 순간 종료된다. 그러나 컴파일 시 원소의 개수를 알 수 없는 배열의 경우 크기를 미리 알 수 없으므로, 프로그래머가 일일이 동적으로 실시간에 메모리를 할당해주고 범위를 벗어날 때 메모리를 해제해줘야 한다. C99는 가변 길이 배열을 추가했으며, 프로그래머가 동적으로 메모리를 할당하고 해제하는 수고를 덜어준다.

 

다음 예제에서 지역변수로 선언한 배열 new는 가변길이 배열이다.

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

void reverse(const char *str, int n)

{

    int i;

    char new[n];

    for(i = 0; i < n; i++)

        new[i] = str[n-i-1];

    printf("%.*s\n", n, new);

}

 

int main(int argc, char *argv[])

{

    reverse("Hello World", strlen("Hello World"));

    exit(0);

}

 

가변길이 배열이 지원되지 않는다면, 지역변수로 선언된 배열 new는 동적으로 영역을 할당해야 한다.

 

char *new = (char *)malloc(n);

 

그리고 함수 reverse 제일 끝에서 메모리를 해제해야 한다.

 

free(new);

 

 

6.2.11 Compound literals

 

괄호 안에 타입을 지정한 식별자를 넣어 compound literal 오브젝트를 만들어준다. 오브젝트의 값은 { } 를 붙인 리스트로 초기화시킬 수 있다. Compound literal은 일시적으로 오브젝트가 필요할 때 사용한다. 다음 예제에서 볼드체인 부분이 compound literal을 사용하는 곳이다.

 

#include <stdio.h>

 

typedef struct {

    short serial;

    char *name;

} Record;

 

void show(Record rec)

{

    printf("Employee serial: %d\n", rec.serial);

    printf("Employee name: %s\n", rec.name);

}

 

int main(int argc, char *argv[])

{

    show((Record){ 12345, "Elizabeth" });

    exit(0);

}

 

Compound literal을 사용하지 않으면, 함수를 호출할 때 Record 타입의 오브젝트를 임시로 만들어야 한다.

 

Record tmp_rec = { 12345, Elizabeth };

show(tmp_rec);

 

 

6.2.13 의도적 초기화(Designated initialization)

 

C89 에서는 초기화할 때 멤버의 순서대로 초기화해야 한다. 정적인 storage duration을 가지기 때문에 미리 0으로 초기화되는 경우라고 하더라도, 특정 멤버나 오브젝트의 특정 원소만 초기화하는 경우 여러 초기화 코드를 사용해야 한다. 다음 구조체 선언을 보자.

 

typedef struct {

    short serial;

    char *name;

    int   salary;

    char  addr[40];

    char  city[20];

    char  state[2];

    char  zip[5];

    short location;

} Record;

 

만약 모든 멤버를 디폴트 값 0으로 두고 제일 마지막 멤버 location 만 다른 값을 주려고 해도, 값을 바꾸지 않는 멤버의 값도 초기화하는 것으로 코드를 작성해야 한다.

 

Record emp1 = { 0, 0, 0, "", "", "", "", 649 };

 

의도적 초기화 기능을 사용하면 초기화할 멤버만 적어주면 된다.

 

Record emp2 = { .location = 649 };

 

이러한 방법으로 디버그하기 어려운 오류를 줄일 수 있다.

 

 

6.2.14 automatic aggregate에 대한 비상수 초기화

 

C99에서 비상수 초기화 코드로 automatic storage duration 을 가지는 aggregate member를 초기화할 수 있다.

 

#include <stdlib.h>

 

void func()

{

    struct {

        short serial;

        char *name;

    } rec = { .name = (char*)malloc(30) };

}

 

 

6.2.15 변수 인자를 가지며 함수와 유사한 매크로

 

가변 인자를 가지는 함수로 다음과 같은 함수가 있다.

 

extern int printf(const char *, ...);

 

인자 갯수가 달라져도 함수에서 받아들일 수 있게 함으로써 동일한 함수에 대해 여러가지 버전을 유지하지 않아도 된다. C99는 이러한 개념을 좀더 발전시켜 인자의 개수를 바꿀 수 있는 함수와 유사한 매크로를 도입하고 있다. 다음 예제에서 보듯 debug 코드를 좀더 세련되게 사용할 수 있다.

 

#include <stdio.h>

#if !defined(DEBUG)

#define DBGMSG(fmt, ...) ((void)0)

#else

#define DBGMSG(fmt, ...) ( \

        fprintf(stderr, "In %s: ", __func__), \

        fprintf(stderr, fmt, __VA_ARGS__) )

#endif

 

int main(int argc, char *argv[])

{

    int rc = 55;

    DBGMSG("return code = %d\n", rc);

    return rc;

}

 

 

6.2.16 Pragma 연산자

 

_Pragma 연산자는 매크로 안에 pragma 디렉티브를 사용할 수 있도록 한다. 다음 예제에서 구조체 S 를 선언하고 인스턴스 s를 정의하는 과정은 두개의 매크로 PACK_1 PACK_POP 사이에 들어간다.

 

#define PACK_1   _Pragma("pack(1)")

#define PACK_POP _Pragma("pack(pop)")

 

PACK_1 struct S {

    char ch;

    int  i;

} s; PACK_POP

 

 

6.2.17 코드와 선언을 섞어서 사용

 

C99 에서는 C++과 유사하게 선언부와 코드부분을 섞어서 사용할 있다.

 

void func()

{

    int i;

    i = 10;

    int j;

    j = 20;

    ...

}

 


 

7.Tuxedo 서비스 프로그램

 

7.1 프로그램 작성시 주의사항

 

1) 항상 기동되어 있는 Server 프로그램이기 때문에, 프로그램 내에 malloc() 함수를 사용할 경우에는 반드시 free() 함수를 사용해 할당된 메모리를 반납해야 함.

  - 가급적 malloc() 함수를 사용하지 말 것.

- C Library 함수 중에 ascftime()과 같은 함수는 내부적으로 malloc() 함수를 사용하고free() 함수

를 사용하지 않으므로 사용하지 말 것.

 

2) tpalloc() 혹은 tprealloc() 함수를 사용할 경우에 반드시 tpfree() 함수를 사용해 할당된 메모리를 반납해야 함.

-> tpreturn() 함수 사용시에는 내부적으로 tpfree() 함수를 사용 함.

 

3) 서비스 프로그램 내에서 필요시 fopen() 함수를 사용할 경우에는 반드시 fclose() 함수를 사용해 열려 있는 파일을 닫아 주어야 함.

 

4) Client에서 N Byte 만큼 FML 버퍼를 tpalloc() 하였을 경우, Server에서는 FML 버퍼에 대하여 반드시 동일한 Size 만큼 할당되지 않음.

- Client에서 FML 버퍼 Size 10 Kbyte 할당하고, FML 버퍼에 실 데이터를 500 Byte 저장하고

  Server로 보내면, Server에서는 4 Kbyte 만큼 FML 버퍼 Size가 할당 됨.

-> FML 버퍼에 저장된 데이터의 Size(Fsizeof32() 함수 이용) 4 Kbyte 미만이면 내부적으로

4 Kbyte 만큼 할당되고, 그 이상일 경우에는 반드시 tprealloc() 해야 함.

- FML 버퍼 사이즈는 최소 단위가 4 Kbyte !!

- FML 버퍼 구조

Header

Data

Index

    Fused/Fused32() 함수     : Index 부분을 제외한 Header Data의 사이즈

Fsuzeof/Fsizeof32() 함수 : Header, Data Index의 사이즈

- 만약, 64 Kbyte FML Buffer를 선언했다면 field-id 혹은 size 등으로 인하여 실제 데이터는 40

Kbyte 까지만 전송된다.

(실제 데이터는 할당된 FML Buffer size 2/3 정도만 전송됨.)

 

5) Unix 머신의 32bit 64bit의 경우에 따라 타입의 사이즈가 다르므로 확인하여 코딩해야 함.

데이터형

32 bit

64bit

비고

char

1 byte

1 byte

 

short

2 byte

2 byte

 

int

4 byte

4 byte

 

long

4 byte

8 byte

 

float

4 byte

4 byte

 

double

8 byte

8 byte

 

서비스 프로그램에서 FML Buffer와 관련된 변수(GET, GETVAR, PUT, PUTVAR) 중에 Host variable int type의 변수를 사용하지 말고 long type의 변수를 사용해야 함.

(FML field-id type에는 int type이 없슴)

 

6) 서비스 프로그램 내에서 Varchar 형의 Host Variable 선언 시 Size는 반드시 Table Column Size + 1로 설정해야 한다. Varchar 형의 가변길이 문자형이므로 실데이터 끝에 NULL을 포함하기 때문임.

char    col_2[8];

varchar col_1[10+1], col_3[20+1];

 

7) 프로그램 내에서 에러 처리는 반드시 해야 함.

   - tpalloc() 혹은 tprealloc() 사용시 "NULL" Check하여 에러 처리.

   - SQL 사용시 "SQLCODE != SQL_OK" Check하여 에러 처리.

   - tpcommit(), tpcall() TUXEDO ATMI 사용시 "-1 : FAIL" Check하여 에러 처리.

 

8) GET()/GETVAR(), PUT()/PUTVAR()등의 매크로 함수를 사용할 경우 Occurrence를 변수로 선언하여 반복시킬 때, 반드시 Occurrence에 대한 변수는 초기화 되어 있어야 함.

 

9) 서비스 프로그램 내에서 에러 처리를 하기 위하여 fprintf(stderr, ...) 함수를 사용하지 말아야 함.

         --> "stderr" 화일에 결과가 쓰여지기 때문에 "txrpt" 명령어를 사용하여 서비스의 수행건수, 수행

시간 등 을 정확히 파악할 수 없음.

 

10) 서비스 프로그램 내에서 디버깅하기 위해 printf 함수를 사용하지 말고, 디버깅 함수(debug.h)

 사용해야 함.

(컴파일시 옵션(CFLAG -D DEBUG로 내정)의 지정에 따라 디버그 코드의 추가 및 삭제가 가능하게 하여 차후 프로그램 디버그 및 유지보수를 원활히 하기 위해 사용.)

    ) 디버깅 함수(debug.h)   

/*#define DEBUG 1*/

 

#ifdef DEBUG

 

#define FMLLOOK(func)            { printf("%s FML buffer-----\n",func);\

                                   Fprint32(transf);\

                                   printf("------------------------\n"); \

                                   fflush(stdout); }

#define STRLOOK(func,var,val)    { printf("%s : %s === %s\n",func,var,val); \

                                   fflush(stdout); }

#define INTLOOK(func,var,val)    { printf("%s : %s === %d\n",func,var,val); \

                                   fflush(stdout); }

#define LONGLOOK(func,var,val)   { printf("%s : %s === %ld\n",func,var,val); \

                                   fflush(stdout); }

#define FLOATLOOK(func,var,val)  { printf("%s : %s === %f\n",func,var,val); \

                                   fflush(stdout); }

#define DOUBLELOOK(func,var,val) { printf("%s : %s === %lf\n",func,var,val); \

                                   fflush(stdout); }

#define LINELOOK(func,val)       { printf("%s : %s \n",func,val); \

                                   fflush(stdout); }

 

#else

 

#define FMLLOOK(func)

#define INTLOOK(func,var,val)

#define STRLOOK(func,var,val)

#define LONGLOOK(func,var,val)

#define FLOATLOOK(func,var,val)

#define DOUBLELOOK(func,var,val)

#define LINELOOK(func,val)

 

#endif

 

11) 전역변수(Global variable) 사용은 자제하고, 지역변수(Local variable)를 사용해야 함.

    변수는 선언 뒤 사용하기 전에 반드시 매크로에 정의된 함수를 사용하여 초기화해야 함.

 

12) 서비스 프로그램 내에서 업무 로직의 추가 및 삭제가 가능하게 하여 차후 프로그램의 유지보수를

원활히 하기 위하여 SQL 한 문장 당 하나의 local function으로 나누어 개발하고 업무 로직이 있는

메인에서 해당 Function을 호출하여 사용할 것.

 

13) 서비스 프로그램에서 서비스를 호출하는 경우 tpforward() 함수를 사용을 고려해야 함.

 

14) Tuxedo 8.1 부터 성능 향상을 위해 tpreturn시 포함된 FML Buffer를 해제하는 하는 방식이 바뀜.

Tuxedo 8.1 이하 버전에서는 메모리 해제를 시켰는데, 이후에는 데이터만 초기화 시키고, 다음

서비스가 호출되면 기존 FML Buffer의 주소를 가지고 있어 FML Buffer를 재활용함.

 

15) FML Buffer의 데이터를 GET, GETVAR 등 매크로 함수를 이용해 모두 꺼낸 후 또는 tpreturn FAIL

Return하는 경우 오류 메시지 설정 전에 반드시 FML Buffer를 초기화하여 불필요한 데이터

전송을 하지 말아야 함.

 


 

7.2 개발시 사용하면 편리한 프로그램

 

7.2.1 Tuxedo Admin 계정이 아닌 계정에서 서버 프로세스의 기동/종료

 

Tuxedo Admin 계정이 아닌 계정에서 tmboot/tmshutdown의 기능 제한을 위한 Wrapping 프로그램(tmstart.c)을 작성한다.

 

Wrapping 프로그램은 C 언어을 이용 tmboot/tmshutdown command "tmboot -s servername or tmboot -i server-id or tmshutdown -s servername or tmshutdown -i server-id" 만 실행되도록 별도의 실행파일을 만든다.

 

 

7.2.1.1 Wrapping 프로그램을 이용한 "tmboot, tmshutdown"을 실행

 

- 프로그램명 : tmstart, tmstop

  - Wrapping 프로그램 source 파일(tmstart.c)

#include <stdio.h>

#include <stdlib.h>

 

int main(int argc, char *argv[]) {

    char *tux_dir, cmdstr[1024];

    int  i = 0;

 

    memset(cmdstr, 0x00, sizeof(cmdstr));

 

    if (argc != 3) {

        printf("Usage : %s -s Servername or %s -i Server-id\n\n", argv[0], argv[0]);

        exit(-1);

    }

    if ((tux_dir = getenv("TUXDIR")) == (char *)0) {

        printf("env variable TUXDIR Not Set !!\n");

        exit(-1);

    }

    sprintf(cmdstr, "%s/bin/", tux_dir);

    if (strcmp(argv[0], "tmstart") == 0) {

        strcat(cmdstr, "tmboot ");

    } else if (strcmp(argv[0], "tmstop") == 0) {

        strcat(cmdstr, "tmshutdown ");

    } else {

        printf("Usage : %s -s Servername or %s -i Server-id\n\n", argv[0], argv[0]);

        return(-1);

    }

    if ((strncmp(argv[1], "-i", 2) == 0) || (strncmp(argv[1], "-s", 2) == 0)) {

        strcat(cmdstr, argv[1]);

        strcat(cmdstr, " "    );

        strcat(cmdstr, argv[2]);

    } else {

        printf("You can use -i or -s options only !\n\n");

        return(-1);

    }

    return(system(cmdstr));

}

 

- Wrapping 프로그램 컴파일

$ cc -v -o tmstart tmstart.c

 

  - Wrapping  실행파일 복사(tmstop)

$ cp tmstart tmstop

 

- Wrapping  실행파일 권한부여(+s)

Tuxedo Admin 계정이 아닌 계정에서 서버 프로세스를 Start, Stop할 때 boot된 서버 프로세스가 Tuxedo Admin 계정으로 실행되도록 Effective User Tuxedo Admin 계정으로 설정한다.

$ chmod +s tmstart tmstop

   권한을 부여한 tmstart, tmstop 실행파일을 $TUXDIR/bin 디렉토리에 위치 시킨다.

 

2) 설정 절차

  - Tuxedo 관련 환경변수 설정

export TUXDIR=/tuxedo

export BASDIR=/home/tuxadmin

export TUXCONFIG=$BASDIR/env/tuxconfig

export LIBPATH=$TUXDIR/lib:$LIBPATH

export PATH=$TUXDIR/bin:$PATH

 

- Wrapping  프로그램 실행(서버 프로세스 기동)

$ tmstart -s 서버명 or tmstart -i 서버ID

 

- Wrapping  프로그램 실행(서버 프로세스 종료)

$ tmstop -s 서버명 or tmstop -i 서버ID

 

3) 주의 사항

- Tuxedo Admin 계정으로 작업(컴파일, 권한부여)해야 한다.

- tmstart, tmstop 실행 파일에 +s 권한 부여해야 한다. 

[tuxadmin@devapp01:/tuxedo/bin]ls -al tms*

-rwsrwsr-x    1 tuxadmin tux            8185 Apr 08 10:34 tmstart

-rw-rw-r--    1 tuxadmin tux            1066 Apr 08 10:34 tmstart.c

-rwsrwsr-x    1 tuxadmin tux            8185 Apr 08 10:34 tmstop

Wrapping  프로그램의 실행 파일에 권한을 부여하지 않으면 오류가 발생하며, 오류 메시지는 다음

과 같다.

tmboot: CMDTUX_CAT:1113: ERROR: Must be the administrator to execute this command

- TUXDIR, TUXCONFIG, PATH, LIBPATH 환경변수를 설정해야 하며, 환경변수를 설정하지 않으면 오류가 발생하며, 오류 메시지는 다음과 같다.

tmshutdown: internal error: CMDTUX_CAT:1360: ERROR: configuration file not found

 

 

7.2.1.2 Tuxedo 서비스를 이용한 "tmboot, tmshutdown"을 실행

 

- 프로그램명 : tuxcmd,tuxserv

  - 클라이언트 프로그램 Source 파일(tuxcmd.c)

#include <stdio.h>

#include <string.h>

#include <sys/proc.h>

#include "atmi.h"       /* TUXEDO  Header File */

 

int main(int argc, char *argv[]) {

    char *sendbuf, *rcvbuf;

    long  sendlen, rcvlen;

    int   ret, cnt, idx, offset;

 

    /* Attach to System/T as a Client Process */

    if (tpinit((TPINIT *) NULL) == -1) {

        printf("Tpinit failed\n");

        exit(1);

    }

    cnt     = argc;

    sendlen = 0, offset = 0;

 

    for(idx = 1; idx < cnt; idx++) {

        sendlen += strlen(argv[idx]);

    }

 

    /* Allocate STRING buffers for the request and the reply */

    if ((sendbuf = (char *) tpalloc("STRING", NULL, sendlen+1)) == NULL) {

        printf("Error allocating send buffer\n");

        tpterm();

        exit(1);

    }

 

    if ((rcvbuf = (char *) tpalloc("STRING", NULL, sendlen+1)) == NULL) {

        printf("Error allocating receive buffer\n");

        tpfree(sendbuf);

        tpterm();

        exit(1);

    }

 

    for(idx = 1; idx < cnt; idx++) {

        sprintf(sendbuf + offset, "%s ", argv[idx]);

        offset = strlen(sendbuf);

    }

    /* Redirect stderr to stdout all of the tuxedo adamin commands */

    strcat(sendbuf, " 2>&1");

 

    /* Request the service TOUPPER, waiting for a reply */

    ret = tpcall("tuxcmd", (char *)sendbuf, 0, (char **)&rcvbuf, &rcvlen, (long)0);

    if (ret == -1) {

        printf("Can't send request to service tuxcmd\n");

        printf("Tperrno = %d\n", tperrno);

        tpfree(sendbuf);

        tpfree(rcvbuf);

        tpterm();

        exit(1);

    }

    printf("%s\n", rcvbuf);

 

    /* Free Buffers & Detach from System/T */

    tpfree(sendbuf);

    tpfree(rcvbuf);

    tpterm();

    return(0);

}

 

- 클라이언트 프로그램 컴파일

$ buildclient -o tuxcmd -f tuxcmd.c

tuxcmd 실행파일을 $TUXDIR/bin 디렉토리에 위치 시킨다.

 

  - 서비스 프로그램 Source 파일(tuxserv.c)

#include <stdio.h>  

#include <ctype.h>

#include <stdlib.h>

#include <errno.h>

#include <atmi.h>      /* TUXEDO Header File */

#include <userlog.h>   /* TUXEDO Header File */

 

/* tpsvrinit is executed when a server is booted, before it begins

   processing requests.  It is not necessary to have this function.

   Also available is tpsvrdone (not used in this example), which is

   called at server shutdown time.

*/

 

extern int errno;

 

tpsvrinit(int argc, char *argv[]) {

        /* Some compilers warn if argc and argv aren't used. */

        argc = argc;

        argv = argv;

 

        /* userlog writes to the central TUXEDO message log */

        userlog("Welcome to the tuxserv");

        return(0);

}

 

void tpsvrdone() {

        /* userlog writes to the central TUXEDO message log */

        userlog("tuxserv terminate");

}

 

tuxcmd(TPSVCINFO *rqst) {

    int   i;

    char  cmd[256];

    char  errmsg[BUFSIZ*8];

    char  buf[BUFSIZ];

    FILE *p_result;

 

    memset(cmd    , 0x00, sizeof(cmd   ));

    memset(errmsg , 0x00, sizeof(errmsg));   

    memset(buf    , 0x00, sizeof(buf   ));   

 

    strcpy(cmd, rqst->data);

 

    if ((p_result = popen(cmd, "r")) != NULL) {

        while (fgets(buf, BUFSIZ, p_result) != NULL) {

            strcat( errmsg, buf );

        }

        pclose(p_result);

    }

    else {

        printf("error occurred %s : cause => %s\n", cmd, strerror(errno)); 

        sprintf( errmsg, "error occurred %s : cause => %s\n", cmd,  strerror(errno));       

    }

    strcpy( rqst->data, errmsg );

 

    /* Return the transformed buffer to the requestor. */

    tpreturn(TPSUCCESS, 0, rqst->data, 0L, 0);

}

 

- 서비스 프로그램 컴파일

$ buildserver -o tuxserv -f tuxserv.c  -s tuxcmd

tuxserv 실행파일을 $TUXDIR/bin 디렉토리에 위치 시킨다.

 

2) 설정 절차

  - Tuxedo 관련 환경변수 설정

export TUXDIR=/tuxedo

export BASDIR=/home/tuxadmin

export TUXCONFIG=$BASDIR/env/tuxconfig

export LIBPATH=$TUXDIR/lib:$LIBPATH

export PATH=$TUXDIR/bin:$PATH

 

- Tuxedo 구성파일(ubbconfig) tuxserv 서버 등록(SERVERS)

DEFAULT:  SRVGRP=NZZ1  MAXGEN=255 REPLYQ=Y CLOPT="-A -r"

 

tuxserv   SRVID=1001  MIN=10 MAX=10 RQADDR="NZZ1.1001"

          CLOPT="-A -o /home/tuxadmin/applog/tuxserv.out -e / home/tuxadmin/applog/ tuxserv.err"

 

- 프로그램 실행(서버 프로세스 기동)

$ tuxcmd tmboot -s 서버명 or tuxcmd tmboot -i 서버ID

 

- 프로그램 실행(서버 프로세스 종료)

$ tuxcmd tmshutdown -s 서버명 or tuxcmd tmshutdown -i 서버ID

 

3) 주의 사항

- Tuxedo Admin 계정으로 작업(컴파일, 권한부여)해야 한다.

- tuxcmd, tuxserv 실행 파일에 +x 권한 부여해야 한다. 

[tuxadmin@devapp01:/tuxedo/bin]ls -al tux*

-rwsrwsr-x    1 tuxadmin tux            8185 Apr 08 10:34 tuxcmd

-rwsrwsr-x    1 tuxadmin tux            8185 Apr 08 10:34 tuxserv

  실행 파일에 권한을 부여하지 않으면 오류가 발생하며, 오류 메시지는 다음과 같다.

tmboot: CMDTUX_CAT:1113: ERROR: Must be the administrator to execute this command

- TUXDIR, TUXCONFIG, PATH, LIBPATH 환경변수를 설정해야 하며, 환경변수를 설정하지 않으면 오류가 발생하며, 오류 메시지는 다음과 같다.

tmshutdown: internal error: CMDTUX_CAT:1360: ERROR: configuration file not found

 


 

7.3 운영시 사용하면 편리한 프로그램

 

Tuxedo 어플리케이션과 Web 어플리케이션을 연동하는 경우 대부분 WTC(WebLogic Tuxedo Connector) Jolt를 사용한다.

 

WTC(WebLogic Tuxedo Connector) Jolt를 사용하는 경우 호출해야 할 서비스가 많아지면 등록 중에 오류가 발생하는 경우가 종종 발생한다.

 

또한 급하게 서비스를 추가하는 경우도 발생하는데 이런 경우 효율적으로 Tuxedo 시스템을 운영하기 위해 대표로 호출하는 Tuxedo 서비스를 만들고 그 서비스에서 실제 호출해야 할 서비스로 다음과 같은 Forwarding 서비스를 만들어 사용하면 된다.

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <ctype.h>

#include <math.h>

#include <limits.h>

#include <time.h>

#include <errno.h>

 

#include <atmi.h> /* TUXEDO Header File */

#include <userlog.h> /* TUXEDO Header File */

 

#include <Usysflds.h>

#include "ssc.flds.h"

 

#define INITVAR(x) { \

        memset(x.arr,0x00,sizeof(x.arr));\

        x.len = 0; }

 

#define GETVAR(x,y,z) { \

        (z.arr)[0] = 0x00; \

        Fget((FBFR *)(transf), (x), (y), (char *)(z.arr), 0); \

        (z.len) = strlen((char *)(z.arr)); \

        (z.arr)[(z.len)]=0x00; }

 

wsltotux(TPSVCINFO *transb)

{

    /*------------------------------------------------------------------------*/

    /* 변수 선언.                                                             */

    /*------------------------------------------------------------------------*/

    int     i = 0, j = 0, k = 0;                           /* Index           */

    varchar lv_service[30+1];                              /* 서비스명        */

    /*------------------------------------------------------------------------*/

    /* FBFR 버퍼 포인터를 얻음.                                               */

    /*------------------------------------------------------------------------*/

    FBFR   *transf = (FBFR *)transb->data;

    /*------------------------------------------------------------------------*/

    /* 변수 초기화.                                                           */

    /*------------------------------------------------------------------------*/

    INITVAR(lv_service);                                   /* 서비스명        */

    /*------------------------------------------------------------------------*/

    /* Move Data From TUXEDO Buffer To Host Variables.                        */

    /*------------------------------------------------------------------------*/

    GETVAR(p_service, 0, lv_service);                      /* 서비스명        */

    /*------------------------------------------------------------------------*/

    /* Return the transformed buffer to the requestor.                        */

    /*------------------------------------------------------------------------*/

    tpforward(lv_service, (char*)transf, 0, 0);

}

 


 

7.4 Tuxedo 환경변수

 

7.4.1 BBWAIT_TIME

 

Tuxedo BBL에 부하가 많이 주어지는 경우, 부하 조절을 위해 Tuxedo 내부적으로 일정시간()동안 sleep을 하게 된다. 해당 변수는 BBWAIT_TIME이며 10초로 설정이 되어 있다.

 

그런데 해당 설정값이 현재로서는 비효율적으로 너무 크게 설정되어 있는 관계로, 외부 환경 변수릍 통해 사용자가 임의로 값을 변경할 수 있게 Enhancement가 이루어 졌다.

 

해당 환경변수명은 BBWAIT_TIME이며, 사용자가 1~10초사이의 임의의 값을 설정할 수 있다.

 

현재 설정되어 있는 5초는 순간적인 큐잉이 발생할 경우 너무 큰 시간이므로 순간적인 큐잉이 발생할 경우를 예상하는 최소 설정값인 1초로 설정을 변경해야 한다.

 

Tuxedo BBL에 부하가 많이 주어지는 경우 BBWAIT_TIME의 환경변수와 TM_TKTSPIN_YLDCNT, TM_TKTSPIN_YLDCNT_NAPTIME 환경변수를 같이 설정하는 것이 바람직하다.

 

export BBWAIT_TIME=1

 

 

7.4.2 TM_TKTSPIN_YLDCNT

 

Tuxedo BBLSpin lock에 대한 반복횟수를 설정한다. (BBWAIT_TIME 설정시 같이 설정해야 함)

 

export TM_TKTSPIN_YLDCNT=1000

 

 

7.4.3 TM_TKTSPIN_YLDCNT_NAPTIME

 

Tuxedo BBL Spin lock 에 대한 Sleep time(microsecond)을 설정한다.(BBWAIT_TIME 설정시 같이 설정해야 함)

 

export TM_TKTSPIN_YLDCNT_NAPTIME=10000

 

 

7.4.4 BBLRTESCANFIRST

 

내부 behavior와 관련된 환경변수로  내부 Timeout check Transaction table보다 Registry table을 먼저 Check한다.

 

TPETIME에 대한 timing issue를 최소화하기 위해 첫번째만 Registry table Check하기 위한 방법으로 해당 옵션을 아래와 같이 환경파일에 설정한다.

 

export BBLRTESCANFIRST=Y

 

 

7.4.5 TM_PREVENT_DEADLOCK

 

내부 Behavior와 관련된 환경변수로 Long time service call Issue하기 전에 BB에 대한 Lock Release하도록 해당 옵션을 아래와 같이 환경파일에 설정한다.

 

export TM_PREVENT_DEADLOCK=1

 

 

7.4.6 TMULOGUSINGSERVICENAME

 

WSNAT_CAT:1043 에러가 발생했을 때 클라이언트가 요청한 서비스 이름을 표시하는 방법으로 해당 옵션을 아래와 같이 환경파일에 설정한다.

 

export TMULOGUSINGSERVICENAME=Y

 

 

7.4.7 TM_KILL_WITH_BBLOCK

 

Tuxedo 프로세스가 BB에서 작업하고 있을 때 SIGKILL이 내포된 명령어를 사용하여 프로세스를 강제 종료하는 경우에 BB의 데이터가 훼손을 방지할 수 있는 방법으로 해당 옵션을 아래와 같이 환경파일에 설정한다.

 

export TM_KIL_WITH_BBLOCK=Y

 

 

7.4.8 TMNOTHREADS

 

Tuxedo 7.1부터 Multithreads 환경이 도입됨으로써, 컨텍스트를 보호하기 위해 ATMI 함수를 호출할 때는 내부적으로 항상 Mutexing 함수가 사용된다. 이로 인해 예전에 비해 부하가 증가한다.

Multithreads를 사용하지 않는 Application에 대해서는 이를 방치함으로써, 성능을 크게 향상시킬 수 있다.

 

환경변수 TMNOTHREADS가 선언되어 있으면 Multithreads processing이 방지된다.

 

export TMNOTHREADS=Y

 

이 기능은 Tuxedo 8.0에서부터 지원되며, 환경변수가 아니라 ENVFILE에 이 변수를 등록하면 프로세스별로 설정을 달리할 수 있다.

 

 

7.4.9 TM_TMSCOUNT_HIGH

 

트랜잭션 관리 서버의 갯수(TMSCOUNT)의 설정 범위는 '2 TMSCOUNT 10' 이내에서 설정한다.(기본 설정값 3)

 

그러나 10 이상의 값을 설정하고자 할 경우, 최대 256까지 트랜잭션 관리 서버 갯수의 최대값을 아래와 같이 환경파일에 설정한다.

 

export TM_TMSCOUNT_HIGH=256

 

 

7.4.10 SETTCPNODELAY

 

데이터가 소켓에 존재하면 TCP 패킷이 찰 때까지 기다리지 않고 바로 전송하는 방법으로 해당 옵션을 아래와 같이 환경파일에 설정한다.

 

export SETTCPNODELAY=1

 

 

7.4.11 TUXWA4ORACLE

 

오라클 데이터베이스와 XA Connection을 맺고 있는 서버의 경우에 해당 데이터베이스로의 접속이 끊어진 경우, ORA - 3113 에러가 발생하는 경우에 자동으로 재접속을 하기 위해서는 TUXWA4ORACLE 환경 변수가 있어야 한다.

 

"WorkAround For Oracle"이란 의미로 해당 환경 변수에 설정되어 값은 의미가 없고 단지 설정이 되어 있으면 동작한다. 따라서 설정은 아래와 같이

 

export TUXWA4ORACLE =1

 

설정을 하면 된다.

 

재접속은 해당 서버가 오라클에 접근하여 3113 에러가 발생하는 순간에 이루어진다.

 

 

7.4.12 FML_PRINT_NOHEX

 

디버깅이나 데이터 로깅을 위해서 Fprint(32) 함수를 이용하여 데이터를 출력하는 경우가 많은데, 한글 데이터의 경우 16진수 형태로 출력되어 문자를 확인하기 어렵다.

 

이것은 binary data가 그대로 출력되어 화면제어가 깨지는 것을 막기 위한 것이다.

 

하지만 한글 문자열인 경우는 16진수 형태로 출력되면 알아볼 수가 없다. 이를 위해서 기능이 추가되었는데, 아래의 환경변수를 설정하면 데이터를 있는 그대로 출력하여 한글이 제대로 보이도록 하였다.

 

export FML_PRINT_NOHEX=Y

 

 

7.4.13 ULOGMILLISEC

 

Tuxedo 9.0부터 ULOG time stamp 1/1000초까지 로깅할 수 있는데, 환경변수 ULOGMILLISEC Y로 설정하면 된다.

 

export ULOGMILLISEC=Y

 

 

7.4.14 ULOGRTNSIZE

 

Tuxedo에서 생성되는 ULOG size는 기본값이 2GByte이다. 이를 좀 더 작은 단위로 설정하여 디버깅시에 파일을 열기 편하게 하는 옵션이다.

 

export ULOGRTNSIZE=size가 환경변수에 설정되어 있으면, 해당 size까지 로그가 쌓이며 size 초과시 다음 파일로 rotation(ULOG.mmddyy.nn)된다.

 

export ULOGRTNSIZE=10000000

 

 

7.4.15 UMASKULOGPERM

 

Tuxedo에서 생성되는 ULOG 파일의 permission global permission으로 설정되어 권한 밖의 사용자들도 해당 로그파일에 접근이 가능하다. ULOG 에 대한 접근제한이 필요한 경우가 있을 때, UMASKULOGPERM 옵션을 통해서 해당 ULOG 파일에 대한 접근을 제한할 수 있다.

 

export UMASKULOGPERM=Y가 환경변수에 설정되어 있으면, 해당 user umask 값으로 permission이 생성되며, 설정이 되어 있지 않을 시에는 default 값인 0666(rw-rw-rw)로 생성된다.

 

export UMASKULOGPERM=Y

 

 

7.4.16 TM_GWT_READIPCQUEUE

 

일반적으로 Domain G/W 사용시 tmadmin 상으로 GWTDOMAIN 서버에 대해서 pq 정보가 ipcs 결과 값과 다르게 보이는 경우가 존재한다. 이 경우는 Tuxedo 내부적으로 Round robin 등과 같은 메커니즘을 처리하기 위해서 pq 정보에 관리되는 정보이기 때문에 근본적으로 bug는 아니다.

 

그러나 이러한 pq 정보와 ipcs 정보와의 결과가 다르게 나옴으로 인해서 고객들이 오해하는 경우가 종종 발생한다.

 

해당 옵션을 아래와 같이 환경파일에 설정한다.

 

export TM_GWT_READIPCQUEUE=y

 

TM_GWT_READIPCQUEUE 옵션에 대해서 아래와 같이 동작을 한다.

1.Set TM_GWT_READIPCQUEUE to y:

  GWTDOMAIN pq 정보를 IPC queue로부터 읽어 들임.

2.Set TM_GWT_READIPCQUEUE to n or unset :

  기존 처리방식을 그대로 유지

 

결국 TM_GWT_READIPCQUEUE를 사용하여, tmadmin   GWTDOMAIN pq 정보에 큐잉 정보가 ipcs 정보를 읽어서 표현이 되기 때문에 실제 큐잉정보를 tmadmin을 통해서 확인할 수 있다.

 

 

7.4.17 TUX_GW_ACTMAX

 

"maximum action table size reach" 오류는 DMTLOGSIZE가 충분치 않은 경우 발생하므로 크기를 늘려주면 해결된다.

 

action table size는 내부적으로 10000으로 되어있는데 Tuxedo 7.1+ 부터는 환경변수 TUX_GW_ACTMAX를 통하여 이를 설정할 수 있도록 하였다.

 

export TUX_GW_ACTMAX=30000

 

 

7.4.18 RETRY_INCREMENT_INTERVAL

 

일반적으로 Tuxedo domain gateway 사용시 비정상적으로 종료가 된 상태에서 Connection policy on_STARTUP일 경우 retry interval 간격으로 재시도 하게 된다. 그런데 대부분안정적인 네트워크가 바로 복구가 되거나, 다른 선택적인 대안에 의해서 네트워크가 곧바로 정상화 되는 경우가 많다. 이럴 때 이 retry interval 시간은 불필요한 시간이 되기 때문에 이 값에 대한 제어가 필요하다.

 

환경파일에 아래와 같은 옵션을 설정한다.

 

export RETRY_INCREMENT_INTERVAL

 

- 0보다 큰 정수

- 단위 :

- default : disable

 

위와 같이 RETRY_INCREMENT_INTERVAL 옵션을 설정할 경우 Connection 재 연결에 대한 복구를 좀더 빠르게 할 수 있다.

 

이러한 재 연결 메커니즘은 아래와 같은 순서로 진행된다.

1.Connection이 끊어졌을 때, 혹은 부팅직후 1초안에 retry시도.

2.만일 1단계가 실패하면 RETRY_INCREMENT_INTERVAL 동안 기달렸다가 시도한다.

만일 RETRY_INCREMENT_INTERVAL 5라면 5초 후에 재 시도하게 된다.

3.만일 2단계가 실패하면 해당 RETRY_INCREMENT_INTERVAL 값의 두배 동안 wait 하다가 재시도하게 된다.

   여기서는 10초 후에 재 시도한다.

4.결국 이런 식으로 계속 double (5,10,20,40)을 가지고 재시도를 하다가 RETRY_INCREMENT_INTERVAL 값이 60초가 되었을 경우에는 그 이후에 60초의 값을 가지고 계속 시도하게 된다.

 

결국 일시적인 네트워크 문제나 네트워크 복구가 순간적으로 빨리 복구 되었을 경우에는 이 옵션을 통해서 Domain gateway의 가용성을 좀더 높일 수 있는 장점이 존재한다.

 

export RETRY_INCREMENT_INTERVAL=5

 

 

7.4.18 DMKEEPALIVE

 

일반적으로 Tuxedo에서 TCP-level keepalive DM_TDOMAIN Section에 설정함으로써 idle connection 에 대한 유지를 처리할 수 있다.

 

이러한 기능이 Tuxedo 8.1 이후 버전에서는 TCP-level keepalive 뿐만 아니라 application-level keepalive을 처리할 수 있는 DMKEEPALIVE, DMKEEPALIVEWAIT 옵션이 존재한다.

 

Local Access Point Remote Access Point에 대한 Application-level keepalive 설정하는 옵션

이다. 해당 값은 아래와 같은 범위 내에서 가능하다.

 

-1 : Remote domain access point 에서만 사용가능하며, 해당 Remote domain access point에 연결되는

local domain access point application-level keepalive 값을 따른다.

0 : application-level keepalive 가 해당 access point 에 대해서 disable 함을 의미한다.

 

1 <= Value <= 2147483647 : 해당 Connection 에 대한 idle time 주기이며, 단위는 milliseconds 이다.

 이 값에 도달하면 Gateway는 상대방 Gateway heart beat 메시지를 보내며, 만일 해당 connection 이 유효한 상태이면, 상대방 Gateway 에서 응답을 보내게 된다. 만일 connection이 비정상적인 상태라면, DMKEEPALIVEWAIT 옵션 값만큼 기다린 후 해당 Connection에 관련된 resource를 정리하게 된다.

 

export DMKEEPALIVE=5

 

 

7.4.19 DMKEEPALIVEWAIT

 

일반적으로 Tuxedo에서 TCP-level keepalive DM_TDOMAIN Section에 설정함으로써 idle connection 에 대한 유지를 처리할 수 있다.

 

이러한 기능이 Tuxedo 8.1 이후 버전에서는 TCP-level keepalive 뿐만 아니라 application-level keepalive을 처리할 수 있는 DMKEEPALIVE, DMKEEPALIVEWAIT 옵션이 존재한다.

 

Local Access Point 또는 Remote Access Point에서 keepalive 메시지에 대한 acknowledgement 받기 위해서 대기하는 시간을 말한다.

 

export DMKEEPALIVEWAIT=2

 

 

7.4.20 TM_GW_AUDITLOG_ENHANCE

 

Domain audit log 기능을 이용하여 Domain간 어떤 service들이 어느 정도 call이 되는지 확인하려고 한다.

 

audit log 파일에 많은 request들이 쌓여 request별로 구분이 필요한 경우 TM_GW_AUDITLOG_ENHANCE=y를 설정하면 unique ID가 포함된다.

 

export TM_GW_AUDITLOG_ENHANCE=y

 

 

7.4.21 LDR_CNTRL

 

AIX에서 공유 오브젝트가 미리 메모리에 로드되어 있지 않다면, 공유 오브젝트를 필요로 하는 프로그램을 처음 실행할 때 메모리에 로드하게 된다. 필요로 하는 페이지의 위치에 따라 VMM (virtual memory manager:가상 메모리 관리자)이 로딩과정을 수행하고, 이때 로드되는 페이지의 실제 크기는 현재 VMM의 옵션 설정에 따라 결정된다. VMM의 옵션은 AIX5L 버전 5.2 이상에서는 vmo 에서 설정하고, 그 외 버전의 AIX에서는 vmtune 명령으로 설정한다.

 

특히 공유 라이브러리를 C++ 로 작성한 경우에는 라이브러리끼리 서로 참조를 많이 하게 된다. 이런 경우는 페이지 요청이 있을 때마다 라이브러리를 로드하기보다는, 프로그램을 시작할 때 한꺼번에 필요한 라이브러리를 모두 메모리로 읽어 들이는 편이 더 빠르다.

 

이런 경우를 위해 환경변수를 다음처럼 설정한다.

 

export LDR_CNTRL=NAMEDSHLIB=TUXEDO_SHLIB

 


 

7.5 Oracle Pre-compiler 옵션

 

Pro*C PreCompiler SOURCE에 포함되어 있는 Embedded SQL문을 SQLLIB Function으로 SOURCE코드를 생성시켜 C SOURCE로 변환해 주는 프리 컴파일러이다.

 

프리컴파일 옵션은 명령행에서도 사용가능하고 또 SOURCE 에 포함할 수도 있다.

명령행에서의 사용은

 

[OPTION_NAME=value] [OPTION_NAME=value] ...

 

로 사용하고 각각의 옵션 사이는 공백으로 구분한다. 예를 들어

 

CODE=ANSI_C MODE=ANSI

 

Inline으로 SOURCE 내에 삽입하여 사용 가능한데, 이때는 "EXEC ORACLE" 이라는 문구를 사용하여 삽입한다.

 

EXEC ORACLE OPTION (OPTION_NAME=value);

 

예를 들어

 

EXEC ORACLE OPTION (RELEASE_CURSOR=yes);

 

SOURCE 파일에 포함되어진 옵션들도 명령행이나 CONFIGURATION 화일에 중복하여 사용할 수 있다.

"EXEC ORACLE" 구문은 특히 프리컴파일 도중에 옵션을 변경할 때 유용하다.

EXEC ORACLE 의 유효범위는 SOURCE 파일 내에 같은 옵션으로 EXEC ORACLE 이 사용된 이전까지 유효하다.

 

 

7.5.1 AUTO_CONNECT={YES | NO}

 

AUTO_CONNECT 옵션은 자동으로 OPS$ 계정으로 연결한다 .

YES 이면 어플리케이션 프로그램이 첫 SQL 문장을 실행할 시점에서 OPT$ 계정으로 연결을 시도한다.

NO 이면 자동 연결은 되지 않고 source 파일 내에 connect 문장이 존재해야 한다.(기본 설정값은 NO )

 

7.5.2 CHAR_MAP={VARCHAR2 | CHARZ | STRING | CHARF}

 

CHAR_MAP 옵션은 문자 배열과 문자열의 매핑을 지정한다. (기본 설정값은 CHARZ )

 

7.5.3 CLOSE_ON_COMMIT={YES | NO}

 

CLOSE_ON_COMMIT 옵션은 모든 커서를 받을 때 COMMIT할지를 지정한다. (기본 설정값은 NO )

 

7.5.4 CODE={ANSI_C | KR_C | CPP}

 

CODE 옵션은 C 함수의 어떤 프로토타입을 생성시키는 지정한다.

ANSI_C X3.159-1989 에서 제공한 표준을 제공한다. ANSI_C 표준으로 생성한다.

extern void(sqlora(long *, void *);

CODE=KR_C 이면

extern void sqlora(/*_ long *, void * */);

CODE=CPP 이면 C++ 에 맞는 source code를 생성시킨다 . (기본 설정값은  KR_C )

 

7.5.5 COMMON_PARSER={YES | NO}

 

COMMON_PARSER 옵션은 DECLARE CURSOR 성명에서 선택하면 SELECT, INSERT, UPDATE, DELETE 또는 DECLARE CURSOR 문에서 SQL99 구문의 지원를 지정한다. (기본 설정값은 NO )

 

7.5.6 COMP_CHARSET={MULTI_BYTE | SINGLE_BYTE}

 

COMP_CHARSET 옵션은 C/C + + 컴파일러가 지원하는 문자 셋을 지정한다. (기본 설정값은 MULTI_BYTE )

 

7.5.7 CONFIG=<filename>

 

CONFIG 옵션은 사용자 지정 CONFIGURATION 파일을 지정한다.

각 라인마다 한 개의 옵션이 들어가게 파일을 생성해 이름을 지정한다. (기본 설정값은 없슴)

 

7.5.8 CPP_SUFFIX=< extension >

 

CPP_SUFFIX 옵션은 C++ 옵션으로 source 파일의 확장자를 지정한다. (기본 설정값은 없슴)

 

7.5.9 DBMS={V7 | NATIVE | V8}

 

DBMS 옵션은 데이타베이스 버젼간의 호환성 정의한다. (기본 설정값은  NATIVE )

NATIVE : 접속되는 데이타 베이스에 설정된 테이블에 따른다.

 

7.5.10 DEF_SQLCODE={YES | NO}

 

DEF_SQLCODE 옵션은 #define SQLCODE 매크로를 생성을 지정한다. (기본 설정값은 NO )

 

7.5.11 DEFINE=<name>

 

DEFINE 옵션은 Pro*C 프리프로세서에서 사용될 이름 정의한다. (기본 설정값은 없슴)

프리프로세서의 매크로를 컴파일 시점에서 선언한다.

SOURCE 파일에 아래와 같이 정의 되어 있다면

#ifdef XYZZY

 ....

#else

 ...

#endif

 

proc my_prog DEFINE=XYZZY

로 프리컴파일하면

#define XYZZY SOURCE 에 포함시킨 것과 같다.

EXEC ORACLE IFDEF XYZZY;

 ...

EXEC ORACLE ELSE;

 ...

EXEC ORACLE ENDIF;

도 같은 효과를 나타낸다.

 

7.5.12 DURATION={TRANSACTION | SESSION}

 

DURATION 옵션은 캐시에 object에 대한 pin 기간을 지정한다. (기본 설정값은 TRANSACTION )

 

7.5.13 DYNAMIC={ANSI | ORACLE}

 

DYNAMIC 옵션은 ORACLE 또는 ANSI SQL semantics를 지정한다. (기본 설정값은 ORACLE )

 

7.5.14 ERROR={YES | NO}

 

ERROR 옵션은 에러메세지 출력할 위치를 지정한다. (기본 설정값은  YES )

 YES: stdout, NO: list 화일

 

7.5.15 ERRTYPE=<filename>

 

ERRTYPE 옵션은 INTYPE에 대한 파일명을 지정한다. (기본 설정값은 없슴)

 

7.5.16 FIPS={NO | SQL89 | SQL2 | YES}

 

FIPS 옵션은 ANSI/ISO  이외의 플래그를 지정한다. (기본 설정값은 없슴)

 

7.5.15 HEADER=<extension>

 

HEADER 옵션은 미리 컴파일된 헤더파일에 대한 파일 확장명을 지정한다. (기본 설정값은 없슴)

 

7.5.16 HOLD_CURSOR={YES | NO}

 

HOLD_CURSOR 옵션은 SQL 문이나 PL/SQL 블럭을 커서 캐쉬 안에서 보관시킬 건지를 결정한다. (기본 설정값은 NO )

 

7.5.17 INAME=<filename>

 

INAME 옵션은 컴파일할 source 파일명을 명시한다. (기본 설정값은 없슴)

proc sample MODE=ANSI

proc INAME=sample1 MODE=ANSI

 

7.5.18 INCLUDE=<pathname>

 

INCLUDE 옵션은 컴파일시 포함되어져야 할 파일의 디렉토리명을 명시한다. (기본 설정값은 없슴)

#include EXEC SQL INCLUDE 문에서 명시된 파일들이 위치한 곳을 명시한다.

 

컴파일시 헤더 파일을 찾는 순서는 다음과 같다.

1.SYS_INCLUDE 로 지정되어진 디렉토리

2.현재 디렉토리

3.standard 헤더 파일의 디렉토리

4.INCLUDE 옵션으로 지정된 디렉토리

 

7.5.19 INTYPE=<filename>

 

INTYPE 옵션은 type 정보에 대한 입력 파일명을 지정한다. (기본 설정값은 없슴)

 

7.5.20 IRECLEN

 

IRECLEN 옵션은 source 파일의 한 라인의 길이를 정한다. (기본 설정값은  80 )

 

7.5.21 LINES={YES | NO}

 

LINES 옵션은 #line 지시어 생성 생성여부를 지정한다. (기본 설정값은 NO )

 

7.5.22 LNAME=<filename>

 

LNAME 옵션은 리스트 파일의 이름을 명시한다. (기본 설정값은  업슴)

 

7.5.23 LTYPE={NONE | SHORT | LONG}

 

LTYPE 옵션은 생성될 리스트 파일의 type을 설정한다.(기본 설정값은 NONE )

 

7.5.24 MAXLITERAL=10..1024

 

MAXLITERAL 옵션은 문자 LITERAL 의 최대길이를 설정한다 (기본 설정값은 1024 )

 

7.5.25 MAXOPENCURSORS=5..255

 

MAXOPENCURSORS 옵션은 동시에 OPEN 할 수 있는 최대 커서의 갯수를 설정한다. (기본 설정값은 10 )

 

7.5.26 MODE={ANSI | ISO | ORACLE}

 

MODE 옵션은 ANSI/ISO 또는 ORACLE 모드를 지정한다. (기본 설정값은 ORACLE )

 

7.5.27 NLS_CHAR=(var1, ..., varn)

 

NLS_CHAR 옵션은 multi-byte 문자변수를 지정한다. (기본 설정값은 없슴)

 

7.5.28 NLS_LOCAL={YES | NO}

 

NLS_LOCAL 옵션은 multi-byte 문자 의미 제어여부를 지정한다. (기본 설정값은 NO )

 

7.5.29 OBJECTS={YES | NO}

 

OBJECTS 옵션은 object type의 지원여부를 지정한다. (기본 설정값은 YES )

 

7.5.30 onAME=<filename>

 

ONAME 옵션은 OUTPUT 화일명을 명시한다. (기본 설정값은 iname.c )

 

기본적으로 INAME 의 뒤에 CPP_SUFFIX 를 붙여 출력한다.

proc iname=my_test

proc iname=my_test oname=my_test_1.c

 

7.5.31 ORACA={YES | NO}

 

ORACA 옵션은 ORACA 의 사용여부를 설정한다. (기본 설정값은 NO )

ORACA=YES SOURCE 프로그램 내에 EXEC SQL INCLUDE ORACA.H #include oraca.h 를 포함해야만 한다.

 

7.5.32 PAGELEN=30..256

 

PAGELEN 옵션은 리스트 파일의 PAGE 길이를 설정한다. (기본 설정값은 80 )

 

7.5.33 PARSE={NONE | PARTIAL | FULL}

 

PARSE 옵션은 .pc의 소스는 C/C++ 파서의 구문분석 여부를 지정한다. (기본 설정값은 FULL )

 

7.5.34 PREFETCH=0..65535

 

PREFETCH 옵션은 쿼리 속도를 향상시키기 위해 사전에 Fetch Row 수를 지정한다. (기본 설정값은 1 )

 

7.5.35 RELEASE_CURSOR={YES | NO}

 

RELEASE_CURSOR 옵션은 커서의 보관을 취소여부를 지정한다. (기본 설정값은 NO )

 

7.5.36 SELECT_ERROR={YES | NO}

 

SELECT_ERROR 옵션은 SELECT 오류 플래그를 지정한다. (기본 설정값은 YES )

 

7.5.37 SQLCHECK={SEMANTICS | FULL | SYNTAX | SYMANTICS}

 

SQLCHECK 옵션은 SQL 문의 검사를 설정한다. (기본 설정값은 SYNTAX )

- SQLCHECK=SEMANTICS

  데이타조작문 , PL/SQL 블럭 , 호스트변수의 데이타 타입을 검사

- SQLCHECK=FULL

  SQL 문장 및 데이타베이스에 접속해 현재의 SQL 문장의 유효성을 검사한다.

  USERID 가 정의되어 있어야 한다.

- SQLCHECK=SYNTAX

  SQL 문장의 문법적인 오류만 검사

- SQLCHECK=SYMANTICS

  PL/SQL 블럭이 SOURCE 내에 포함되어 있으면 USERID 와 함께 이 옵션을 써야 한다.

 

7.5.38 SYS_INCLUDE=<pathname>

 

SYS_INCLUDE 옵션은 iostream.h 같은 시스템 헤더 파일의 디렉토리를 지정한다. (기본 설정값은 없슴)

 

7.5.39 THREADS={YES | NO}

 

THREADS 옵션은 공유서버 어플리케이션을 지정한다. (기본 설정값은 NO )

 

7.5.40 TYPE_CODE={ORACLE | ANSI}

 

TYPE_CODE 옵션은 동적 SQL에 대해 Oracle 또는 ANSI type 코드를 지정한다. (기본 설정값은 ORACLE )

 

7.5.41 UNSAFE_NULL={YES | NO}

 

UNSAFE_NULL 옵션은 ORA-01405 메시지를 비활성화 여부를 지정한다. (기본 설정값은 NO )

 

7.5.42 USERID=username/password[@dbname]

 

USERID 옵션은 접속할 ORACLE USERID PASSWORD 를 명시한다. (기본 설정값은 없슴)

 

7.5.43 UTF16_CHARSET={NCHAR_CHARSET | DB_CHARSET}

 

UTF16_CHARSET 옵션은 UNICODE(UTF16)에 사용되는 문자셋을 지정한다. (기본 설정값은 NCHAR_CHARSET )

 

7.5.44 VARCHAR={YES | NO}

 

VARCHAR 옵션은 암시적으로 VARCHAR 구조의 사용여부를 지정한다. (기본 설정값은 NO )

 

7.5.45 VERSION={ANY | LATEST | RECENT}

 

VERSION 옵션은 어떤 버전의 개체를 반환하는 것을 지정한다. (기본 설정값은 RECENT )

 


 

7.6 Tuxedo 환경변수

 

환경변수