반응형

new 실패에 따른 프로세스 종료원인을 찾다가 좋은 정보를 찾았습니다.

블로깅...


출처 : http://kuaaan.tistory.com/123




동적 메모리를 다음과 같이 사용하였습니다. 어디가 틀렸을까요?

  1. LPINT   lpInt = NULL;  
  2.   
  3. lpInt = (LPINT)malloc(sizeof(INT));  
  4. *lpInt = 4;  
  5.   
  6. printf("Value : %d\r\n", *lpInt);  
  7.   
  8. free(lpInt);  

동적 메모리를 할당한 후 정상적으로 할당되지 않은 경우에 대한 예외처리가 되지 않았군요. ^^
좀더 꼼꼼한 분이라면 다음과 같이 코딩하시겠지요.
  1. LPINT   lpInt = NULL;  
  2.   
  3. lpInt = (LPINT)malloc(sizeof(INT));  
  4.   
  5. if (lpInt == NULL)  
  6. {  
  7.     printf("Fail to alloc memory!!\r\n");  
  8.     return FALSE;  
  9. }  
  10.   
  11. *lpInt = 4;  
  12.   
  13. printf("Value : %d\r\n", *lpInt);  
  14.   
  15. free(lpInt);  
위와 같이 코딩하면 메모리 할당에 실패한 경우 "Fail to alloc memory!!"라는 에러메시지를 표시하고 프로그램은 종료하게 됩니다.


그렇다면 malloc 대신 new를 사용하는 다음의 코드는 어떨까요?
많은 분들이 사용하시는 코드입니다만... 이렇게 하면 동적 메모리 할당이 실패한 경우에 대해 올바르게 Exception Handling이 가능할까요?
  1. LPINT   lpInt = NULL;  
  2. INT nTotalBytes = 0, nAllocByte = 10000000;  
  3.   
  4. while(TRUE)  
  5. {         
  6.     lpInt = NULL;  
  7.   
  8.     lpInt = new INT[nAllocByte];  
  9.     nTotalBytes += nAllocByte * sizeof(INT);  
  10.   
  11.     if (lpInt == NULL)  
  12.     {  
  13.         printf("Fail to alloc memory!!\r\n");  
  14.         break;  
  15.     }  
  16.   
  17.     printf("Alloc Success!! (%d)\r\n", nTotalBytes);  
  18. }  
  19.   
  20. // blah~ blah~  

결론부터 얘기하자면 위와 같이 포인터가 NULL인지 체크하는 방식으로는 new 연산자에 대한 예외처리를 할 수 없습니다.


위와 같이 프로그램은 에러창을 내뱉고 죽어버립니다. 죽는 부분에도 보면 "Fail to alloc memory!!" 라는 메시지는 보이지 않습니다. 말하자면, 예외 처리 루틴을 타지 않은 것이지요.

new 연산자 관련된 MSDN 을 검색해보면 다음과 같은 내용이 있습니다.

말하자면, new 연산자가 메모리 할당에 실패했을 경우, C++ 스탠다드에 따라 std::bad_alloc Exception을 throw한다는 의미입니다. exception이 throw되었기 때문에 넘겨받은 포인터가 NULL인지 체크하는 부분까지 진행되지도 않고 프로그램이 종료된 것입니다.



음. 그렇다면 다음과 같이 코딩하면 메모리 할당 예외처리가 가능하겠군요.
  1. try  
  2. {  
  3.     LPINT   lpInt = NULL;  
  4.     INT nTotalBytes = 0, nAllocByte = 10000000;  
  5.   
  6.     while(TRUE)  
  7.     {         
  8.         lpInt = NULL;  
  9.   
  10.         lpInt = new INT[nAllocByte];  
  11.         nTotalBytes += nAllocByte * sizeof(INT);  
  12.   
  13.         printf("Alloc Success!! (%d)\r\n", nTotalBytes);  
  14.     }  
  15. }  
  16. catch ( exception e )  
  17. {  
  18.     printf("%s\r\n", e.what( ));  
  19. }  

코드를 위와 같이 수정하고 실행시키면 다음과 같이 동작하게 됩니다.

종료되기 직전 부분을 보면 제대로 Exception Handling이 되었다는 것을 알 수 있습니다.

그런데, 동적메모리 할당할 때마다 이와 같이 try ~ catch를 해주려면 너무 귀찮죠. 성능 문제로 try ~ catch를 사용하시지 않는 경우도 있구요. 더 좋은 방법은 "new handler"를 등록해주는 것입니다.


MSDN을 좀 더 검색해보면 "new handler"에 관한 내용이 나와 있습니다.

If the attempt is successful, the function returns a pointer to the allocated storage. Otherwise, the function calls the designated new handler. If the called function returns, the loop repeats. The loop terminates when an attempt to allocate the requested storage is successful or when a called function does not return.

The required behavior of a new handler is to perform one of the following operations:

  • Make more storage available for allocation and then return.

  • Call either abort or exit(int).

  • Throw an object of type bad_alloc.

The default behavior of a new handler is to throw an object of type bad_alloc. A null pointer designates the default new handler.

The order and contiguity of storage allocated by successive calls to operator new(size_t) is unspecified, as are the initial values stored there.


위의 내용을 요약해보면 다음과 같습니다.

1. new 연산자가 메모리 할당에 실패한 경우 "new handler"로 지정된 함수가 호출된다. 이 함수가 리턴한 다음에 메모리 할당이 실패한 시점부터 계속 실행된다.

2. "new handler" 는 다음의 세가지 작업 중 한가지를 수행해야 합니다.
1) 메모리를 확보한 후 return한다.
2) 프로세스를 종료시킨다.
3) bad_alloc 을 throw한다.

3. Default "New Handler"는 bad_alloc 을 throw 한다.

음, new 연산자가 메모리 할당에 실패했을 때 bad_alloc이라는 exception이 throw 되는데, 이것은 사실은 Default로 등록된 "new handler"에서 하는 일이라는 것을 알 수 있네요.

그렇다면, 이 내가 원하는 함수를 "new handler"로 등록하려면 어떻게 해야 할까요??
이런 용도로 사용되는 set_new_handler 라는 API가 있습니다.

set_new_handler 를 사용해 다음과 같이 new handler를 지정할 수 있습니다.
  1. void __cdecl MyNewHandler( )  
  2. {  
  3.    printf("Fail to Alloc Memory \r\n");  
  4. //   throw bad_alloc();  
  5.    ExitProcess(FALSE);  
  6.    return;  
  7. }  
  8.   
  9. int _tmain()  
  10. {  
  11.     set_new_handler (MyNewHandler);  
  12.   
  13.     LPINT   lpInt = NULL;  
  14.     INT nTotalBytes = 0, nAllocByte = 10000000;  
  15.   
  16.     while(TRUE)  
  17.     {         
  18.         lpInt = NULL;  
  19.       
  20.         lpInt = new INT[nAllocByte];  
  21.         nTotalBytes += nAllocByte * sizeof(INT);  
  22.   
  23.         printf("Alloc Success!! (%d)\r\n", nTotalBytes);  
  24.     }  
  25.   
  26.     // blah~ blah~  
  27. }  

결과는 다음과 같습니다.


제대로 Handler 가 호출되었다는 것을 알 수 있습니다.

그렇다면 이 Handler에서는 어떤 작업을 해주어야 할까요?

이 Handler가 return되면 실패했던 new 작업을 재시도합니다. 이때 만약 다시 실패한다면 new handler가 다시 호출되지요. 따라서 new handler에서 메모리부족을 해결하지 않고 return해버린다면 무한루프에 빠지게 됩니다.

그렇다면 new handler에서 메모리를 확보하는 것이 가능한가? 사실은 불가능합니다. 메모리가 부족하다는 것은 실제로 시스템 가상메모리가 부족하거나 아니면 Application에서 Memory Leak이 발생하고 있다는 의미가 되는데, 일개 Exception Handler에서 맘대로 시스템 가상메모리를 확보할 수도 없고, Leak된 메모리를 쫓아가서 메모리해제를 해 줄 수도 없는 노릇이죠. (만약 그게 가능하다면 처음부터 Leak이 발생하지도 않았겠죠. ^^)

제 생각에는 이 예외처리자는 그냥 로깅을 남기고 예외창 없이 조용히 죽게 만드는 정도의 작업 외에는 실제로 할 수 있는 일은 없을 것 같습니다.
혹시 다른 생각이 있으신 분은 알려주세요. :)

※ 이 글에서 쓰인 샘플코드는 MSDN의 코드를 참고하였습니다. :)


-----------------------------------------------------------------------------------------------------------------
출처 : http://cafe.naver.com/totalprogramming/34

보통 C++ 개발자중에 상당수가 C언어를 접했을 것이다.

간혹가다보면^^ C++개발자들이 실수를 하는데,

바로 new연산자를 사용함에 있다.

 

char* buf;

buf = new char[100];

if(NULL == buf){

      return FALSE;

}

 

이런식으로 사용하고 예외 처리를 하는데, 여기에는 문제점이 있다.

new연산자는 heap 메모리가 부족할 경우에는 NULL을 리턴하지 않는다.

할당을 실패할 경우에는 예외가 발생되어, if(NULL == buf)이 구문이 수행되지 않는다.

NULL체크 방식은 과거의 malloc을 사용했을 경우의 처리인 것이다.

C++에서는 이런 예외처리를 하기 위해 아래와 같이 #include <new>에 있는 bad_alloc객체를 이용해서

예외처리를 하고, NULL체크를 할수 있는 방식도 제공해준다.

 

 

#include <new>
#include <iostream>
using namespace std;

class BigClass {
public:
   BigClass() {};
   ~BigClass(){}

#ifdef _WIN64
      double BigArray[0x0fffffff];
#else
      double BigArray[0x0fffffff];
#endif
};

int main() {
   try {
      BigClass * p = new BigClass;
   }

   catch( bad_alloc a) {
      const char * temp = a.what();
      cout << temp << endl;
      cout << "Threw a bad_alloc exception" << endl;
   }

   BigClass * q = new(nothrow) BigClass;

   if ( q == NULL )
      cout << "Returned a NULL pointer" << endl;

   try {
      BigClass * r[3] = {new BigClass, new BigClass, new BigClass};
   }

   catch( bad_alloc a) {
      const char * temp = a.what();
      cout << temp << endl;
      cout << "Threw a bad_alloc exception" << endl;
   }
}



반응형
반응형
출처 : http://blog.daum.net/hogi2271/10

AIX 5에서 공유 라이브러리 메모리 크기


   

난이도 : 초급

George Cross, 선임 소프트웨어 개발자, Business Objects Americas

옮긴이: 박재호 이해영 dwkorea@kr.ibm.com

2008 년 9 월 16 일

IBM® AIX®에서 공유 라이브러리 메커니즘과 메모리 크기에 대해 배워봅시다. 이 기사는 서버 코드를 작성하는 개발자와 AIX 시스템을 실제 운영하는 관리자에게 필요한 지식을 핵심만 간추려 설명합니다. 이 기사는 개발자와 관리자에게 명령어와 기법을 설명하고 AIX에서 서버 프로세스의 메모리 요구 사항을 분석하는 데 필요한 지식을 제공합니다. 이 기사는 또한 개발자와 관리자가 ps나 topas와 같은 표준 실시간 분석 도구로 파악하기 어려운 자원 부족을 회피하는 방법을 설명합니다. 이 기사는 시스템 관리자와 AIX용 응용 프로그램 개발자를 대상으로 작성했습니다.

소개

이 기사는 다음 명령어를 시연하면서 32비트 AIX 5L™(5.3)에서 공유 라이브러리가 메모리를 차지하는 방법을 살펴본다.

  • ps
  • svmon
  • slibclean
  • procldd
  • procmap
  • genkld
  • genld

이 기사는 커널 공유 라이브러리 세그먼트는 물론이고 프로세스의 가상 메모리 공간을 설명하며, 가상 메모리를 살펴보는 방법, 위에서 언급한 다양한 진단 도구가 제공하는 출력 결과 해석 방법도 다룬다. 이 기사는 또한 커널 공유 세그먼트가 꽉 찬 상황을 진단하며 이런 상황을 해결하기 위해 가능한 접근 방법도 설명한다.

이 기사에서 사용하는 예제로 소프트웨어 제품인 Business Objects Enterprise Xir2®에서 따온 프로세스를 사용한다. 이는 임의로 든 예며, 여기서 소개하는 개념은 AIX 5L에서 동작하는 모든 프로세스에 적용할 수 있다.




위로


검토

이제 무엇을 할지 공감했으니, 32비트 아키텍처를 조금 검토해보자. 검토 과정에서 아주 유용한 'bc' 명령행 계산기를 사용하겠다.

32비트 프로세서에서 레지스터는 2^32개의 가능한 값을 담을 수 있다.

	$ bc
	2^32
	4294967296
	obase=16
	2^32
	100000000


이 범위는 4기가바이트다. 이는 시스템에서 동작하는 프로그램이 0에서 2^32 - 1 범위 내에서 함수나 자료 주소에 접근할 수 있음을 의미한다.

	$ bc
 	2^32 - 1 
	FFFFFFFF
	obase=10
	2^32 - 1 
	4294967295


이미 알고 있듯이, 운영체제는 잠재적으로 수백 개에 이르는 프로그램을 동시에 돌릴 수 있다. 응용 프로그램 각각이 4GB 메모리 범위에 접근이 가능할지라도, 개별 프로그램마다 물리 RAM을 4GB만큼 할당 받는다는 뜻은 아니다. 이렇게 하기란 비현실적이다. 그 대신 운영체제는 적당한 물리 RAM과 스왑(또는 페이징) 영역으로 지정된 파일 시스템 사이에서 코드와 자료를 스와핑하는 복잡한 정책을 구현했다. 또한 각 프로세서가 4GB라는 메모리 영역에 접근이 가능할지라도, 대다수 프로세서는 이 영역을 완전히 사용하지 않는다. 따라서 운영체제는 특정 프로세스마다 요구하는 코드와 자료를 올리고 스왑하기만 하면 된다.


그림 1. 가상 메모리를 개념으로 설명하는 도식
가상 메모리 관리

이런 방법은 종종 가상 메모리나 가상 주소 공간으로 부른다.

실행 파일이 동작할 때, 운영체제에 들어있는 가상 메모리 관리자는 파일을 구성하는 코드와 자료를 살펴서 어느 부분을 램으로 올리고 어느 부분을 스왑으로 올리고 어느 부분을 파일 시스템에서 참조할지 결정한다. 동시에, 운영체제는 몇몇 구조체를 만들어 4GB 범위 내에서 물리 영역을 가상 영역으로 사상한다. 이 4GB 범위는 (종종 VMM 구조와 함께) 프로세스의 이론적인 최대 범위를 표현하며, 프로세스의 가상 주소 공간으로 알려져 있다.

AIX에서 4GB 가상 공간은 256메가바이트짜리 세그먼트 16개로 나뉜다. 세그먼트에는 미리 정해진 기능이 있다. 몇 가지를 정리해보았다.

  • 세그먼트 0: 커널 관련 자료
  • 세그먼트 1: 코드
  • 세그먼트 2: 스택과 동적 메모리 할당
  • 세그먼트 3: 사상된 파일을 위한 메모리, mmap으로 설정한 메모리
  • 세그먼트 d: 공유 라이브러리 코드
  • 세그먼트 f: 공유 라이브러리 자료

반면에 HP-UX®에서 주소 공간은 4분면으로 나뉜다. 3사분면과 4사분면은 +q3p enable과 +q4p enable 옵션을 켜서 chatr 명령을 내릴 경우 공유 라이브러리 사상 목적으로 사용이 가능하다.




위로


공유 라이브러리가 메모리에 올라오는 위치

당연한 이야기지만, 공유 라이브러리는 공유할 목적으로 만들어졌다. 좀 더 구체적으로 말하자면, 코드("텍스트"로 알려진)와 읽기 전용 자료(상수 자료, 기록 시점에서 복사 가능한 자료)를 포함한 이진 파일 이미지에서 읽기 전용 영역을 물리적인 메모리에 올리고 나면 이를 요구하는 프로세스에 여러 번 사상할 수 있다.

이를 확인하기 위해, AIX가 동작하는 기계를 구해 현재 메모리에 올라온 공유 라이브러리를 살펴보자.

> su 
# genkld
Text address     Size File

    d1539fe0    1a011 /usr/lib/libcurses.a[shr.o]
    d122f100    36732 /usr/lib/libptools.a[shr.o]
    d1266080    297de /usr/lib/libtrace.a[shr.o]
    d020c000     5f43 /usr/lib/nls/loc/iconv/ISO8859-1_UCS-2
    d7545000    161ff /usr/java14/jre/bin/libnet.a
    d7531000    135e2 /usr/java14/jre/bin/libzip.a
.... [ lots more libs ] ....
d1297108 3a99 /opt/rational/clearcase/shlib/libatriastats_svr.a
[atriastats_svr-shr.o]
    d1bfa100    2bcdf /opt/rational/clearcase/shlib/libatriacm.a[atriacm-shr.o]
    d1bbf100    2cf3c /opt/rational/clearcase/shlib/libatriaadm.a[atriaadm-shr.o]
.... [ lots more libs ] ....
    d01ca0f8     17b6 /usr/lib/libpthreads_compat.a[shr.o]
    d10ff000    30b78 /usr/lib/libpthreads.a[shr.o]
    d00f0100    1fd2f /usr/lib/libC.a[shr.o]
    d01293e0    25570 /usr/lib/libC.a[shrcore.o]
    d01108a0    18448 /usr/lib/libC.a[ansicore_32.o]
.... [ lots more libs ] ....
    d04a2100    fdb4b /usr/lib/libX11.a[shr4.o]
    d0049000    365c4 /usr/lib/libpthreads.a[shr_xpg5.o]
    d0045000     3c52 /usr/lib/libpthreads.a[shr_comm.o]
    d05bb100     5058 /usr/lib/libIM.a[shr.o]
    d05a7100    139c1 /usr/lib/libiconv.a[shr4.o]
    d0094100    114a2 /usr/lib/libcfg.a[shr.o]
    d0081100    125ea /usr/lib/libodm.a[shr.o]
    d00800f8      846 /usr/lib/libcrypt.a[shr.o]
    d022d660   25152d /usr/lib/libc.a[shr.o]

관찰 결과에 따르면, 현재 Clearcase와 자바(Java™)가 동작하고 있다. 여기서 libpthreads.a라는 공통 라이브러리 중 하나를 찍어보자. 라이브러리를 탐색해서 구현 함수 내역을 살핀다.

# dump -Tv /usr/lib/libpthreads.a | grep EXP
[278]   0x00002808    .data      EXP     RW SECdef        [noIMid] pthread_attr_default
[279] 0x00002a68 .data EXP RW SECdef [noIMid]
 pthread_mutexattr_default
[280]   0x00002fcc    .data      EXP     DS SECdef        [noIMid] pthread_create
[281]   0x0000308c    .data      EXP     DS SECdef        [noIMid] pthread_cond_init
[282]   0x000030a4    .data      EXP     DS SECdef        [noIMid] pthread_cond_destroy
[283]   0x000030b0    .data      EXP     DS SECdef        [noIMid] pthread_cond_wait
[284]   0x000030bc    .data      EXP     DS SECdef        [noIMid] pthread_cond_broadcast
[285]   0x000030c8    .data      EXP     DS SECdef        [noIMid] pthread_cond_signal
[286]   0x000030d4    .data      EXP     DS SECdef        [noIMid] pthread_setcancelstate
[287]   0x000030e0    .data      EXP     DS SECdef        [noIMid] pthread_join
.... [ lots more stuff ] ....

음, 흥미롭다. 이제 시스템에서 현재 메모리에 올라와 있는 동작 중인 프로세스를 살펴보자.

# for i in $(ps -o pid -e | grep ^[0-9] ) ; do j=$(procldd $i | grep libpthreads.a); \
	if [ -n "$j" ] ; then ps -p $i -o comm | grep -v COMMAND; fi  ; done
portmap
rpc.statd
automountd
rpc.mountd
rpc.ttdbserver
dtexec
dtlogin
radiusd
radiusd
radiusd
dtexec
dtterm
procldd : no such process : 24622
dtterm
xmwlm
dtwm
dtterm
dtgreet
dtexec
ttsession
dtterm
dtexec
rdesktop
procldd : no such process : 34176
java
dtsession
dtterm
dtexec
dtexec

멋지다! 이제 똑같은 작업을 하되, 중복을 없애보자.

# cat prev.command.out.txt | sort | uniq 
       
automountd
dtexec
dtgreet
dtlogin
dtsession
dtterm
dtwm
java
portmap
radiusd
rdesktop
rpc.mountd
rpc.statd
rpc.ttdbserver
ttsession
xmwlm

현재 동작 중이면서 libpthreads.a를 메모리에 올린 이진 파일 목록을 깔끔하게 분리해서 정리해보자. 이 시점에서 시스템에 더 많은 프로세스가 떠 있음에 주의하자.

# ps -e | wc -l 	
      85

이제 각 프로세스가 libpthreads.a를 어디에 올렸는지 살펴보자.

# ps -e | grep java
 34648      -  4:13 java
#
# procmap 34648 | grep libpthreads.a
d0049000         217K  read/exec      /usr/lib/libpthreads.a[shr_xpg5.o]
f03e6000          16K  read/write     /usr/lib/libpthreads.a[shr_xpg5.o]
d0045000          15K  read/exec      /usr/lib/libpthreads.a[shr_comm.o]
f03a3000         265K  read/write     /usr/lib/libpthreads.a[shr_comm.o]
#
# ps -e | grep automountd
 15222      -  1:00 automountd
 25844      -  0:00 automountd
#
# procmap 15222 | grep libpthreads.a
d0049000         217K  read/exec      /usr/lib/libpthreads.a[shr_xpg5.o]
f03e6000          16K  read/write     /usr/lib/libpthreads.a[shr_xpg5.o]
d0045000          15K  read/exec      /usr/lib/libpthreads.a[shr_comm.o]
f03a3000         265K  read/write     /usr/lib/libpthreads.a[shr_comm.o]
d10ff000         194K  read/exec         /usr/lib/libpthreads.a[shr.o]
f0154000          20K  read/write        /usr/lib/libpthreads.a[shr.o]
#
# ps -e | grep portmap              
 12696      -  0:06 portmap
 34446      -  0:00 portmap
#
# procmap 12696 | grep libpthreads.a
d0045000          15K  read/exec      /usr/lib/libpthreads.a[shr_comm.o]
f03a3000         265K  read/write     /usr/lib/libpthreads.a[shr_comm.o]
d10ff000         194K  read/exec         /usr/lib/libpthreads.a[shr.o]
f0154000          20K  read/write        /usr/lib/libpthreads.a[shr.o]
#
# ps -e | grep dtlogin
  6208      -  0:00 dtlogin
  6478      -  2:07 dtlogin
 20428      -  0:00 dtlogin
#
# procmap 20428 | grep libpthreads.a
d0045000          15K  read/exec      /usr/lib/libpthreads.a[shr_comm.o]
f03a3000         265K  read/write     /usr/lib/libpthreads.a[shr_comm.o]
d0049000         217K  read/exec      /usr/lib/libpthreads.a[shr_xpg5.o]
f03e6000          16K  read/write     /usr/lib/libpthreads.a[shr_xpg5.o]

각 프로세스는 libpthreads.a를 매번 동일 주소에 올린다는 사실에 주목하자. 라이브러리를 구성하는 목록에 현혹되지 말자. AIX에서는 동적 공유 라이브러리(보통 .so 파일)는 물론이고 아카이브 라이브러리(보통 .a 파일)도 공유할 수 있다. 이런 공유 기능은 전통적인 링크와 마찬가지로 링크 시점에서 심볼을 결정하지만 최종 이진 파일로 구성 목적 파일(아카이브에서 .o 파일) 복사가 필요하지 않다. 그렇기 때문에 동적 공유 라이브러리(.so/.sl 파일)와는 달리 동적(실행 중) 심볼 결정을 수행하지 않는다.

또한 read/exec로 표시된 libpthreads.a 코드 영역은 세그먼트 0xd에 올라왔다는 사실에 주목하자. 이 세그먼트는 앞서 언급한 바와 같이 공유 라이브러리를 위한 세그먼트로 AIX에서 지정되어 있다. 다시 말해 커널은 공유 라이브러리의 공유 가능한 세그먼트를 동일한 커널에서 동작 중인 모든 프로세스가 공유하는 영역에 올린다.

자료 섹션 역시 동일한 세그먼트(공유 라이브러리 세그먼트 0xf)에 위치한다는 사실을 눈치챘을지도 모르겠다. 하지만 이는 각 프로세스가 libpthreads.a의 자료 섹션까지 공유함을 의미하지는 않는다. 조금 느슨하게 정의해 보자면, 이런 배치는 동작하지 않는다. 각 프로세스 별로 다른 이름으로 다른 자료 값을 유지할 필요가 있기 때문이다. 물론 가상 메모리 주소는 동일할지 몰라도 세그먼트 0xf는 libpthreads.a를 사용하는 각 프로세스마다 다르다.

svmon 명령어는 프로세스에 대한 Vsid(가상 메모리 관리자에서 세그먼트 ID)를 보여준다. 공유 라이브러리 코드 세그먼트는 Vsid가 같지만, 공유 라이브러리 자료 세그먼트는 Vsid가 제각각이다. 유효 세그먼트 ID인 Esid는 프로세스의 주소 공간 범위 내에서 세그먼트 ID를 의미한다(그냥 용어 설명이므로 혼동하지 말기 바란다).

# svmon -P 17314

-------------------------------------------------------------------------------
     Pid Command          Inuse      Pin     Pgsp  Virtual 64-bit Mthrd  16MB
   17314 dtexec           20245     9479       12    20292      N     N     N

    Vsid      Esid Type Description              PSize  Inuse   Pin Pgsp Virtual
       0         0 work kernel segment               s  14361  9477    0 14361 
   6c01b         d work shared library text          s   5739     0    9  5786 
   19be6         f work shared library data          s     83     0    1    87 
   21068         2 work process private              s     56     2    2    58 
   18726         1 pers code,/dev/hd2:65814          s      5     0    -     - 
    40c1         - pers /dev/hd4:2                   s      1     0    -     - 
#
# svmon -P 20428

-------------------------------------------------------------------------------
     Pid Command          Inuse      Pin     Pgsp  Virtual 64-bit Mthrd  16MB
   20428 dtlogin          20248     9479       23    20278      N     N     N

    Vsid      Esid Type Description              PSize  Inuse   Pin Pgsp Virtual
       0         0 work kernel segment               s  14361  9477    0 14361 
   6c01b         d work shared library text          s   5735     0    9  5782 
   7869e         2 work process private              s     84     2   10    94 
                   parent=786be
   590b6         f work shared library data          s     37     0    4    41 
                   parent=7531d
   6c19b         1 pers code,/dev/hd2:65670          s     29     0    -     - 
   381ae         - pers /dev/hd9var:4157             s      1     0    -     - 
    40c1         - pers /dev/hd4:2                   s      1     0    -     - 
   4c1b3         - pers /dev/hd9var:4158             s      0     0    -     - 




위로


산수 놀이

공유 세그먼트 0xd에서 얼마나 많은 메모리를 차지하는지 살펴보자. 다시 한번 bc 계산기를 써보자. 정신 바짝 차리고, 세그먼트 0xd 크기를 비교해보자.

# bc    
ibase=16
E0000000-D0000000
268435456
ibase=A
268435456/(1024^2)
256

여기까지는 좋아 보인다. 위에서 언급한 내용처럼 각 세그먼트는 256MB다. 좋다. 이제 현재 사용 중인 메모리 용량을 살펴보자.

$ echo "ibase=16; $(genkld | egrep ^\ \{8\} | awk '{print $2}' | tr '[a-f]' '[A-F]' \
	|  tr '\n' '+' ) 0" | bc
39798104
$
$ bc <<EOF
> 39798104/(1024^2)
> EOF
37

현재 사용 중인 메모리는 37MB라고 알려준다. XIr2를 시작한 다음에 비교해보자.

$ echo "ibase=16; $(genkld | egrep ^\ \{8\} | awk '{print $2}' | tr '[a-f]' '[A-F]' \
	|  tr '\n' '+' ) 0" | bc
266069692
$
$ bc <<EOF
> 266069692/(1024^2)
> EOF
253

이제 253MB를 사용 중이다. 이는 256MB 한계에 아주 근접한 값이다. WIReportServer와 같은 프로세스를 임의로 골라 공유 영역으로 얼마나 많은 공유 라이브러리를 밀어넣었으며 얼마나 많은 라이브러리를 내부적으로 사상했는지 살펴보자. 공유 세그먼트 시작 주소가 0xd000000라는 사실을 알고 있으므로, procmap 결과에서 필터링해보자. 단지 코드 섹션만 세그먼트 0xd에 사상된다는 사실을 기억하자. 따라서 read/exec 행만 살펴보면 된다.

$ procmap 35620 | grep read/exec | grep -v ^d
10000000       10907K  read/exec         boe_fcprocd
31ad3000       14511K  read/exec
/crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/libEnterpriseFramework.so
3167b000        3133K  read/exec
/crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/libcpi18nloc.so
3146c000        1848K  read/exec
/crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/libBOCP_1252.so
31345000         226K  read/exec
/crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/btlat300.so

위에 나타난 네 가지 라이브러리는 공유 세그먼트로 사상될 수 없는 듯이 보인다. 필연적으로 네 가지 라이브러리는 mmap() 루틴을 호출해 할당한 범용 메모리로 쓰이는 내부 세그먼트 0x3에 사상되었다.

공유 라이브러리를 32비트 AIX에서 내부적으로 강제로 사상하기 위해서는 몇 가지 조건이 필요하다.

  • (위에서 발생한 상황처럼) 공유 세그먼트 0xd 영역이 꽉 차 있다.
  • 그룹과 다른 사람에 대한 실행 권한이 공유 라이브러리에 없다. 이런 문제를 해결하려면 접근 허가를 rwxr-xr-x로 지정하면 된다. 하지만 개발자들은 자신에게만 접근 허가를 주기를 원하므로(예: rwx------), 테스트 목적으로 공유 라이브러리를 컴파일해 배포할 때마다 sibclean을 돌릴 필요가 없다.
  • 몇몇 문서는 nfs 위에서 공유 라이브러리를 메모리에 올리면 이렇게 된다고 말한다.

AIX 커널은 동일한 라이브러리라도 다른 위치에서 시작했다면 공유 메모리에 두 번 올릴 것이다.

sj2e652a-chloe:~/e652_r>genkld | grep libcplib.so
        d5180000    678c6 /space2/home/sj2e652a/e652_r/lib/libcplib.so
        d1cf5000    678c6 /home/sj1e652a/xir2_r/lib/libcplib.so




위로


뭔가 잘못되었을 때

다른 디렉터리에 설치된 XIr2 인스턴스를 다시 한번 돌린다면, 프로세스 메모리 크기에 상당한 차이가 난다.

$ ps -e -o pid,vsz,user,comm | grep WIReportServer
28166 58980   jbrown WIReportServer
46968 152408 sj1xir2a WIReportServer
48276 152716 sj1xir2a WIReportServer
49800 152788 sj1xir2a WIReportServer
50832 152708 sj1xir2a WIReportServer

'jbrown' 계정에서 돌리는 인스턴스가 첫 번째로 시작했으며, 'sj1xir2a' 계정에서 돌리는 인스턴스가 두 번째로 시작했다. 두 번째 인스턴스를 돌리기 앞서 bobje/setup/env.sh 파일에서 적절한 위치에 다음과 같은 항목을 설정해 뭔가 조금 이상한 작업을 했다면

    LIBPATH=~jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000:$LIBPATH

메모리 사용량이 정규화된 상태를 확인할 것이다(이 LIBPATH 테스트에서는 WIReportServer를 시동할 수 없기에 프로세스 boe_fcprocd를 사용했다).

$ ps -e -o pid,vsz,user,comm | grep boe_fcprocd   
29432 65036   jbrown boe_fcprocd
35910 67596   jbrown boe_fcprocd
39326 82488 sj1xir2a boe_fcprocd
53470 64964 sj1xir2a boe_fcprocd

그리고 기대한 바와 같이 procmap은 ~jbrown에서 올라온 파일을 보여준다.

53470 : /crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/boe_fcprocd
-name vanpg 
10000000       10907K  read/exec         boe_fcprocd
3000079c        1399K  read/write        boe_fcprocd
d42c9000        1098K  read/exec
/home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcrypto.so
33e34160         167K  read/write
/home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcrypto.so
33acc000        3133K  read/exec
/home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcpi18nloc.so
33ddc697         349K  read/write
/home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcpi18nloc.so




위로


정리

응용 프로그램이 종료되었다면, 공유 라이브러리는 여전히 공유 세그먼트 0xd에 남아있을지도 모른다. 이런 경우에는 'slibclean' 유틸리티를 사용해 더 이상 참조하지 않는 공유 라이브러리를 메모리에서 내린다. 이 유틸리티에는 인수가 필요없다.

slibclean

또한 -l 옵션을 추가하면 procmap과 비슷한 결과를 보여주는 genld라는 유틸리티는 현재 시스템에 존재하는 모든 프로세스를 보여준다.

genld -l

종종 slibclean을 돌린 다음에도 공유 라이브러리 복사가 여전히 불가능할 경우가 있다. 예를 들면 다음과 같다.

$ cp /build/dev/bin/release/libc3_calc.so   /runtime/app/lib/
cp: /runtime/app/lib/libc3_calc.so: Text file busy

이미 slibclean을 돌렸기 때문에 'genld -l'은 이 라이브러리가 메모리에 올라온 프로세스를 보여주지 않는다. 하지만 시스템은 여전히 이 파일을 보호하고 있다. 이런 문제점을 극복하려면 우선 목표 위치에 있는 공유 라이브러리를 지운 다음에 새로운 공유 라이브러리를 복사하면 된다.

$ rm /runtime/app/lib/libc3_calc.so
$ cp /build/dev/bin/release/libc3_calc.so   /runtime/app/lib/

공유 라이브러리 개발 과정 동안, 컴파일, 링크, 실행, 예제 실행을 반복한다면 단지 소유주(r_xr__r__)만 실행 가능한 공유 라이브러리를 만드는 방법으로 매 주기마다 slibclean 명령을 내리지 않아도 된다. 이렇게 하면 테스트 목적으로 사용하는 프로세스를 메모리에 올려 공유 라이브러리를 내부적으로 사상할 것이다. 하지만 궁극적으로는 모든 사람이 실행 가능하도록 주의해야 한다(즉, 제품 배포 시점에서 r_xr_xr_x이 되어야 한다).




위로


요약

공유 라이브러리가 메모리를 차지하는 방법과 이를 검사하기 위해 사용된 유틸리티에 대한 방법을 자세히 살펴봤으리라 믿는다. 이 기사를 통해, 응용 프로그램이 요구하는 메모리 크기 조건을 평가하고 AIX 시스템에서 돌아가는 프로세스에 대한 메모리 사용량 구성 요소를 분석할 수 있을 것이다.



반응형

+ Recent posts