반응형


출처 : http://sthyun.tistory.com/entry/EWOULDBLOCKEAGAIN

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


넌블럭킹 소켓

socket() 으로 생성되는 소켓은 기본값으로 Blocking 소켓이다. 하지만 이미 생성된 소켓을 fcntl() 함수를 사용하여 nonblocking socket으로 변경 가능하다.

※ Blocking Socket(B)/Nonblocking Socket(N)

(여기서 errno는 errno.h를 인클루드해야 이용할수 있다.)

- read

  • B : read 버퍼가 비어있을때 block
  • N : read 버퍼가 비어있을때 -1 return, errno==EWOULDBLOCK/EAGAIN

* Blocking socket의 경우에 read 버퍼에 존재하는 데이터의 크기가 read시 요청한 데이터의 크기보다 작은 경우라도 read 버퍼에 존재하는 데이터만큼 리턴되며 block 되지 않음.

- write

  • B : write 버퍼가 꽉 차있을때 block
  • N : write 버퍼가 꽉 차있을때 -1 return, errno==EWOULDBLOCK/EAGAIN

- accept

  • B : backlog( 현재의 connection 요청 큐 )가 비어있을때 block
  • N : backlog( 현재의 connection 요청 큐 )가 비어있을때 -1 return, errno==EWOULDBLOCK/EAGAIN

- connect

  • B : connection이 완전히 이루어질때까지 block
  • N : connection이 완전히 이루어지 않더라도 곧바로 return. 나중에 getsockopt로 connection이 완전히 이루어졌는지 확인가능.

※ Nonblocking 소켓의 장점/단점

  • 장점 : 멀티스레드를 사용하지 않고도 다른 작업을 할 수 있다.
  • 단점 : 프로그램이 복잡해지며, CPU 사용량이 증가한다.
  • 멀티쓰레드 기반에서 nonblock socket 을 사용하면 cpu 사용량이 엄청나게 증가한다.
    이건 성능상의 차이는 확연한데 gprof 를 돌려도 안나온다. 멀티쓰레드 환경에서는 절대 사용하지말것.

※ Nonblocking 소켓으로 만드는 방법 : fcntl()함수를 이용한다.

int flag;

flag = fcntl( sock_fd, F_GETFL, 0 );

fcntl( sock_fd, F_SETFL, flag | O_NONBLOCK );

파일 입력과 출력
이 절은 파일 기술자상의 기본 입력과 출력 명령을 수행하기 위한 함수들을 설명하고 있다:

read, write, 그리고 lseek. 이들 함수들은 헤더파일 'unistd. h'에 선언되어 있다.

read함수는 기술자 filedes의 파일로부터 size 바이트를 읽고, 그 결과를 버퍼에 저장한다. (이것은 문자 스트링이 필요하지 않고 그곳에는 부가된 널 종료문자가 없다)


ssize_t read (int filedes, void *buffer, size_t size)


반환 값은 실제로 읽은 바이트의 수이다.

이것은 size보다 적을수도 있다


예를 들어, 만일 파일에 남겨진 바이트의 수가 적거나 즉시 유용한 바이트의 수가 적은 경우 등이 있다.

정확한 동작은 파일의 종류가 무엇인지에 따라 의존한다.

size 바이트보다 덜 읽는 것은 에러가 아님을 기억하라.

0의 값은 파일의 끝을 지적한다. ( 만일 size 인수의 값이 0인 경우를 제외하고. . ) 이것은 에러로 간주하지 않는다.


만일 당신이 파일의 끝인 상태에서 read를 호출하면, 그것은 0을 반환하는 것 외에 아무 일도 하지 않는다.

만일 read가 적어도 한 문자를 반환한다면, 당신이 파일의 끝에 도달했는지를 알 수 있는 아무런 방법이 없다.

그러나 만일 당신이 끝에 도달해 있었다면 다음 read의 호출은 0을 반환해서 파일의 끝임을 지적해줄 것이다.

에러가 발생한 경우에, read는 -1을 반환한다.


다음의 errno는 이 함수에서 정의된 에러의 상황이다.


EAGAIN 일반적으로, 즉시 유용한 입력이 없을 때, read는 입력을 기다린다.

그러나 만일 그 파일에서 O_NONBLOCK가 설정되면 read는 아무런 데이터도 기다리지 않고 즉시 반환하고, 이 에러를 보고한다.

호환성 노트 : BSD Unix의 대부분의 버전은 이것을 위한 다른 에러코드를 사용한다:

EWOULDBLOCK. GNU 라이브러리에서는, EWOULDBLOCK은 EAGAIN의 다른 이름이다. 그래서 당신이 어떤 이름을 사용해도 문제가 발생되지 않는다.


어떤 시스템들은, 특별한 문자 파일로부터 데이터의 큰 덩어리를 읽으려 할 때, 만일 커널(kernal)이 당신의 것을 담을 수 있는(to lock down the user's pages), 충분한 물리적 메모리를 얻을 수 없는 경우에 EAGAIN의 에러를 내고 실패했음을 지적한다.

디바이스가 사용자의 메모리 영역을 직접적으로 억세스 하는 것이 제한되어 있는 것은 그들은 커널내부의 분리된 버퍼를 사용하기 때문이다. 그것에 터미널들은 포함되지 않는다,


EBADF 

filedes 인수에 주어진 것이 유용한 파일 기술자가 아니다.


EINTR 

read가 입력을 기다리고 있는 동안 시그널에 의해 인터럽트 되어졌다.


EIO 

많은 디바이스들, 그리고 디스크 파일들을 위하여, 이 에러는 하드웨어 에러를 지적한다.

EIO는 또한 제어 중인 터미널로부터 배경 프로세스가 읽기를 시도하고, SIGTTIN의 신호가 아무런 동작도 하지 않고 보내짐에 의해 멈춘 프로세스의 일반적 동작에 대해 발생한다.


이것은 만약 신호가 블록되어지거나 무시되거나, 프로세스 그룹이 부모 프로세스를 잃어 버렸다면 발생되어질 것이다.


ssize_t write (int filedes, const void *buffer, size_t size)


write함수는 기술자 filedes 파일에 버퍼에 있는 size 바이트의 데이터를 쓰는 함수이다. 버퍼에 있는 데이터는 문자 스트링과 널 문자가 필요하지 않다. 반환 값은 실제로 쓰여진 바이트들의 개수이다.이것은 보통은 size와 같지만, 더 적을수도 있다 ( 예를 들어, 만일 물리적 매체가 채워져 있는 경우 ). 에러가 발생하면 write는 -1을 반환한다.


다음의 errno는 이 함수에서 정의한 에러상황이다.


EAGAIN 

일반적으로 write 명령하에서 블록 쓰기 동작은 완벽하다.

그러나 만일 그 파일에서 O_NONBLOCK 플래그가 설정되어 있다면, 그것은 어떤 데이터도 쓰지 않고 곧바로 반환하고, 에러를 발생한다.

그 상황에 대한 하나의 예는 프로세스가 출력하려는 블록을 STOP 문자를 받아들임으로 인해 출력이 일시 중단되고, 흐름제어를 지원하는 터미널 디바이스에 쓰기를 시도할 때 발생한다.


EWOULDBLOCK. 

GNU 라이브러리에서는, EWOULDBLOCK은 EAGAIN의 다른 이름이다.

그래서 당신이 어떤 이름을 사용해도 문제가 발생되지 않는다.

어떤 시스템들은, 특별한 문자 파일로부터 데이터의 큰 덩어리를 쓰려 할 때, 만일 커널(kernal)이 당신의 것을 담을 수 있는( to lock down the user's pages ), 충분한 물리적 메모리를 얻을 수 없는 경우에 EAGAIN의 에러를 내고 실패했음을 지적한다.

디바이스가 사용자의 메모리 영역을 직접적으로 억세스 하는 것이 제한되어 있는 것은 그들은 커널내부의 분리된 버퍼를 사용하기 때문이다. 그것에 터미널들은 포함되지 않는다,


EBADF 

filedes 인수는 유용한 파일 기술자가 아니다.


EFBIG 

파일의 크기가 그 실행에서 지원할 수 있는 것보다 크다.


EINTR 

write 오퍼레이션은 명령이 완전히 수행될 때까지 기다리는 동안 신호에 의해 인터럽트 되어졌다.


EIO 많은 디바이스들, 그리고 디스크 파일들을 위하여, 이 에러는 하드웨어 에러를 지적한다.

EIO는 또한 제어 중인 터미널로부터 배경 프로세스가 읽기를 시도하고, SIGTTIN의 신호가 아무런 동작도 하지 않고 보내짐에 의해 멈춘 프로세스의 일반적 동작에 대해 발생한다.

이것은 만약 신호가 블록되어지거나 무시되거나, 프로세스 그룹이 부모 프로세스를 잃어 버렸다면 발생되어질 것이다. ENOSPC 디바이스가 차 있다.


EPIPE

이 에러는 어느 프로세스에 의해서 읽기 위해 개방되지 않는 파이프나 FIFO에 쓰려 시도할 때 반환된다.

이것이 발생될 때, SIGPIPE 신호를 프로세스에 보낸다.


당신이 EINTR 실패를 방지하기 위해 조정하지 않았다면, 당신은 실패한 write의 호출에 대해서 errno를 체크해야할 것이다. 그리고 만일 errno가 EINTR 이라면, 그냥 간단하게 다시 호출해주면 된다.

이것을 하는 쉬운 방법으로 매크로 TEMP_FAILURE_RETRY 가 있다. 다음처럼:

nbytes = TEMP_FAILURE_RETRY (write (desc, buffer, ount));

write 함수는 fputc처럼 스트림에 쓰는 모든 함수들에 기본적으로 포함되어 있다. 

반응형
반응형

출처 : http://kuaaan.tistory.com/trackback/142



delete와 delete[], new와 new[]를 구분해서 사용해야 하는 이유 :

delete를 사용하든 delete[]를 사용하든 메모리는 정상적으로 해제된다. 하지만 4바이트 INT 타입의 10개짜리 Array를 메모리해제할 경우 delete[]를 사용하면 4바이트씩 10번 해제하지만 delete를 사용하면 40바이트짜리 메모리를 한번 해제한다. 

내부적으로 얘기하자면 new[] 를 사용할 경우 할당하는 메모리 앞에 4바이트 메모리를 더 할당하여 배열의 Size를 저장해놓는다. 이 메모리를 해제할 때 delete[]를 사용하면 이 값(배열의 Size)을 확인하지만 delete를 사용하면 확인하지 않는다. 따라서 delete[]를 사용해야 Array가 Class Type Array인 경우 각각의 Entry에 대해 배열의 Size만큼의 생성자/소멸자를 호출해 줄 수 있다.
http://minjang.egloos.com/1414494
요약하면, class array를 메모리 해제할때 delete[]를 사용하지 않고 그냥 delete를 사용할 경우 소멸자가 한번만 호출되어버리는 문제가 발생한다.

실제로 테스트를 해보니 맞다. ^^
  1. class CTest  
  2. {  
  3. public:  
  4.     CTest()  
  5.     {  
  6.         printf("Constructer(0x%08x)\r\n"this);  
  7.     }  
  8.     ~CTest()  
  9.     {  
  10.         printf("Destructor(0x%08x)\r\n"this);  
  11.     }  
  12. };  
  13.   
  14. int _tmain(int argc, _TCHAR* argv[])  
  15. {  
  16.     CTest* pTest = new CTest[10];  
  17.     delete pTest; // 여기!!  
  18. //  delete[] pTest;   
  19.     return 0;  
  20. }  


위와 같은 소스코드를 실행시키면 다음과 같이 된다.
Constructer(0x003e3c1c)
Constructer(0x003e3c1d)
Constructer(0x003e3c1e)
Constructer(0x003e3c1f)
Constructer(0x003e3c20)
Constructer(0x003e3c21)
Constructer(0x003e3c22)
Constructer(0x003e3c23)
Constructer(0x003e3c24)
Constructer(0x003e3c25)
Destructor(0x003e3c1c)
계속하려면 아무 키나 누르십시오 . . .

10개의 인스턴스가 해제되었는데, 소멸자는 한번만 호출되었음을 알 수 있다.
만약 소멸자에서 해제해 주는 메모리나 리소스가 있었다면 9개씩의 Leak이 발생했을 것이다.

delete를 delete[]로 수정해주고 나니 아래와 같이 정상적으로 소멸자가 호출된다.
Constructer(0x003e3c1c)
Constructer(0x003e3c1d)
Constructer(0x003e3c1e)
Constructer(0x003e3c1f)
Constructer(0x003e3c20)
Constructer(0x003e3c21)
Constructer(0x003e3c22)
Constructer(0x003e3c23)
Constructer(0x003e3c24)
Constructer(0x003e3c25)
Destructor(0x003e3c25)
Destructor(0x003e3c24)
Destructor(0x003e3c23)
Destructor(0x003e3c22)
Destructor(0x003e3c21)
Destructor(0x003e3c20)
Destructor(0x003e3c1f)
Destructor(0x003e3c1e)
Destructor(0x003e3c1d)
Destructor(0x003e3c1c)
계속하려면 아무 키나 누르십시오 . . .

아 그렇군. ^^


반응형
반응형

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://no1rogue.blog.me/30097158983



* 메모리 맵 - mmap() <파일 혹은 디바이스를 메모리와 대응>

; 파일(리눅스에서는 디바이스도 파일로 처리하므로 디바이스도 메모리 맵으로 연결 가능)을 처리하기 위해서는 보통 저수준으로는 파일 디스크립터를 이용하고, 고수준으로 접근하기 위해서는 파일 구조체 포인터를 이용하여 접근하게 된다. 하지만 이런 방식을 이용하면 버퍼를 거쳐서 실제 입출력을 하게된다. 하지만 mmap()을 이용하여 메모리 맵 방식으로 파일을 연결하게 되면 버퍼를 이용하는 것이 아니라 '페이지(page)'를 이용하여 데이터 처리가 가능해진다. 상대적으로 크기가 작은 버퍼에 비해 보통 4KB의 크기를 가지는 페이지를 이용하면 처리 가능한 크기와 처리 속도가 향상된다. 그렇기 때문에 데이터 크기가 크거나 빠른 처리를 해야하는 경우 메모리 맵 방식을 종종 사용하게된다. 메모리 맵을 사용하면 처리 속도가 빨라지는 예가 바로 처리하는 데이터가 큰 동영상을 볼 때이다. 그리고 실제 실행 파일도 윈도우든 리눅스든 실행 할 때 페이지로 분할되므로 메모리 맵 방식으로 매핑된다. 대표적으로 코드 영역은 데이터의 변경이 없기 때문에 메모리 맵을 이용해 맵핑하면 처리 속도는 물론이고 메모리 낭비도 없다. 

 

 

* 특징

1. 생성된 메모리 맵을 포인터를 이용하여 쉽게 사용 가능하다.

2. 파일로 연결하여 사용시 메모리-파일 사이의 동기화가 편하다.

3. IPC(프로세스간 통신)로 활용 가능하다.

4. 대용량의 데이터를 사용할 시 성능이 향상된다.

 

* 주의점

- 메모리 맵은 바로 파일의 처리하는게 아니라 가상 메모리로 활용되는 페이지에 맵핑하는 방식이다. 그러므로 파일과 해당 메모리 맵이 된 페이지가 다른 공간이다. 그러므로 커널에 의해 여유 시간에 동기화(둘의 데이터가 같아지는..)가 될 때까지 서로 다른 데이터를 가질 수 있다. 그러므로 동기화에 대한 주의를 할 필요가 있다. <개발자가 직접 커널에 동기화를 명령 할 수 있는 함수도 있다. - fsync()>

 IPC로 사용 할 때에도 프로세스간 동기화에 대한 주의도 필요하다.


* 2가지 방식

1. 공유 메모리 맵 방식 (Shred Memory-Map)

; 메모리 맵 변경 시 원본 파일과 데이터가 동기화가 되는 방식

2. 복사 메모리 맵 방식 (Private Memory-Map)

; 처음 메모리 맵에 매핑 될때 파일의 내용을 읽어와서 복사하고 그 이후 동기화 하지 않는 방식.

 

 

* 메모리 맵 생성 함수 - mmap()

 

void* mmap(void *state, size_t length, int prot, int flags, int fd, ott_t offset);

 

- void *state

; 할당받기 원하는 메모리 주소. 보통 0을 써서 커널이 적합한 공간을 임의로 할당해 주소를 받을 수 있고, 직접 입력하여 사용해도된다. 하지만 직접 입력하는 경우 해당 시스템의 페이지 (배수)단위로 주소 값을 설정해줘야 한다.

 

- size_t length

; 메모리 맵을 할 크기. 바이트 단위로 설정한다.

 

- int prot

; 메모리 보호 메커니즘. 플래그 형식이므로 비트 연산으로 복수 속성으로 지정 가능하다.

 + PROT_EXEC; 해당 페이지 실행 가능.

 + PROT_READ; 해당 페이지 읽기 가능.

 + PROT_WRITE; 해당 페이지 쓰기 가능.

 + PROT_NONE; 해당 페이지 접근 불가.

 => 매핑할 파일 디스크립터와 속성이 같아야한다.

 

- int flags

 + MAP_SHARED; 공유 메모리 맵 방식.

 + MAP_PRIVATE; 복사 메모리 맵 방식.

 + MAP_FIXED ; 메모리 시작 번지 지정시 사용.

 =>MAP_SHARED/MAP_PRIVATE 둘 중에 한개만 지정해야된다.

 

- int fd

 ; 메모리 맵 방식을 사용할 파일 디스크립터.(파일 혹은 디바이스)

 

- ott_t offset

 ; 해당 파일 디스크립터에서 메모리 맵을 시작할 오프셋 값.



p style="margin-right: 0px; margin-left: 0px; padding: 0px; line-height: 20px; color: rgb(72, 72, 72); text-align: justify; ">+ return value

; 메모리 맵핑이 이루어진 가상 메모리 주소. 실패시 MAP_FAILED가 발생하고 errno에 해당 상황에 대한 값이 설정된다.

 

 

* 메모리 맵 해제 - munmap()

; 메모리 맵을 사용하고 자원을 해제 할 때 사용한다.

 

int munmap(void *start, size_t length);

 

- void *start

; 메모리 맵핑이 시작된 주소. mmap()의 반환 값을 넣으면된다.

 

- size_t length

; 메모리 맵을 한 길이. mmap() 사용시 size_t length 인자와 크기를 주면된다.

 

+ return value

;성공 0, 실패 -1

 

 

* 메모리 맵과 파일의 동기화 - fsync()

; 메모리 맵에 데이터를 갱신해도 바로 파일과 동기화가 이루어지는 것은 아니다. 커널이 여유 있을 때 동기화를 수행한다. 그러므로 개발자가 직접 동기화를 보장하고 싶을 때 사용한다. 그리고 munmap()을 하여 메모리 맵을 해제 할때에도 동기화를 해주면 데이터가 보장된다.

 

int msync(void *start, size_t length, int flags);

 

- void *start

; mmap()를 통해 리턴 받은 메모리 맵의 시작 주소.

 

-  size_t length

; 동기화를 할 길이. 시작 주소로 부터 길이를 지정하면 된다.

 

- int flags

 + MS_ASYNC

 ; 비동기 방식. 동기화(Memory->File)하라는 명령만 내리고 결과에 관계 없이 바로 리턴.

   그렇기 때문에 동기화가 된 건지 알수 없다.

 + MS_SYNC

 ; 동기 방식. 동기화(Memory->File)가 될때까지 블럭 상태로 대기한다.

 + MS_INVALIDATE

 ; 현재 메모리 맵을 무효화하고 파일의 데이터로 갱신. 즉 File->Memory

 

 

===================================================================================

       ...

#define MMAP_FULENAME "test_mmap"

#define MMAP_SIZE 64

       ...

int main()

{

     int fd;

     char *p_mmap;      //메모리 맵으로 사용할 데이터 타입

       ...

     fd = open(MMAP_FILENAME, O_RDWR|O_CREAT, 0664);

       ...

     p_mmap = (char*)mmap((void*)0, MMAP_SIZE, PROT_READ|PROT_WRITE,

                                                                     MAP_SHARED, fd, 0);

     //공유 메모리 방식을 사용한다. 읽기/쓰기 가능

       ...

     //memcpy(); 등으로 메모리 맵 데이터 갱신.

       ...

     msync(p_mmap, MAP_SIZE, MS_SYNC);

       ...

     munmap(p_mmap);

     return 0;

}

  

=> 메모리 맵 할 파일 이름만 같으면 IPC도 된다.


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

출처 : http://hsnote.tistory.com/141


윈도우의 경우는 아래의 자료를 참고.




[메모리 매핑파일]


HANDLE CreateFileMapping(HANDLE hFile,    // (1)
   LPSECURITY_ATTRIBUTES lpAttributes, //(2)
   DWORD flProtect,  // (3)
   DWORD dwMaximumSizeHigh,  // (4)
   DWORD dwMaxImumSizeLow,  // (5)
   LPCTSTR lpName   //(6)
   );


위의 함수가 매모리매핑 파일을 만드는 함수이다..

위의 함수 사용시 매모리맵파일이 생성은 되지만 실제로 매모리에 연결되어 있진않다. 

연결을 하려면 MapViewOfFile 이라는 함수를 써야지 그 때부터 메모리에 연결이 된다.

일단위 함수의 설명을 들어보자

 

목적 및 하는 일 : MMF라는 커널 객체를 만들고 그 핸들을 리턴한다.
                         메모리매핑파일을 만들수있는 기본정보들을 갖고 있는 커널객체를 만들고 그 핸들을 리턴한다.


인자설명 : 
 (1) hFile  : CreateFile 함수로 해당파일을 열고 리턴받은 값.. (CreaqteFile 의 리턴값  파일핸들) 파일핸들을 인자로 넘겨준다.. , (어떤파일을 파일매핑할건지 정하는거다.)
 
 (2) lpAttributes : 솔직히 잘 모르겠는데 .. 보통 이값은 NULL로 줬다. (아시는 분은 좀 알려주세여^^)

 (3) flProtect : 읽기 전용으로 할건지 읽기 쓰기를 할건지 설정하는 옵션이다. ex) PAGE_READONLY, PAGE_READWRITE 등등

 (3) dwMaximumSizeHigh : 매핑할 범위를 지정하는 상위 4바이트 (용량이 작은 범위라면 이곳에 0을 주고 그 다음 인자에 파일크기 및 원하는 범위를 지정한다.)
 
 (4) dwMaximumSizeLow : 매핑할 범위 지정하는 하위 4바이트

 (5) lpName : 파일매핑 객체이기 때문에 객체에 이름을 부여할때 쓴다. 보통   NULL을 주고 이름을 붙이도 다른곳에서 참조할때는 이름을 문자열로 이름을 지정한다.


사용예제 ) CreateFileMapping(hSource, NULL, PAGE_READONLY, 0, dwFileSize, NULL);
 
 CreateFileMapping(hTarget, NULL, PAGE_READWRITE, 0, dwFileSize, NULL);


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


LPVOID MapViewOfFile(HANDLE hFileMappingObject, // (1)
   DWORD dwDesiredAccess,  // (2)
   DWORD dwFileOffsetHigh,  //(3)
   DWORD dwFileOffsetLow,   // (4)
   SIZE_T dwNumberOfBytesToMap   // (5)
  );

목적 및 하는 일 : CreateFileMapping 함수로 만든 MMF 객체를 대상으로 실제 메모리번지에 연결을 하여 메모리 매핑파일을 만든다.
  메모리 매핑파일 만들고 그 메모리의 시작번지를 리턴한다.


인자설명 :
 (1) hFileMappingObject : CreateFileMapping 함수로 리턴받은 핸들값을 인자로 준다.

 (2) dwDesireAccess : 접근권한을 설정하는 것으로 FILE_MAP_READ, FILE_MAP,WRITE 등으로 옵션을 준다.

 (3) dwFileOffsetHigh :  메모리 주소가 연결될 크기 상위 4바이트

 (4) dwFileOffsetLow : 메모리 주소가 연결될 크기 하위 4바이트

 (5) dwNumberOfBytesToMap : 오프셋으로 부터 원하는 크기를 정한다.

사용예제 ) MapViewOfFile(hMapSource, FILE_MAP_READ, 0, dwAs, dwLen);

 MapViewOfFile(hMapTarget, FILE_MAP_WRITE, 0, dwAs, dwLen);


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

해당함수들을 사용후에는 해제 해주어야 하는데

CreateFileMapping 함수 같은 경우 파일 핸들을 리턴함으로

CloseHandle(리턴값); 으로 해제하고

MapViewOfFile 함수 같은 경우는 메모리 주소를 리턴함으로

UnmapViewOfFile(리턴값); 으로 해제한다.

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

반응형
반응형

===================================================================================================


[gcc 컴파일]


1) gcc 파일명(*.c) : Default로 out 파일이 생성된다. (ex a.out)

2) gcc -c 파일명(*.c) : 오브젝트 파일을 생성한다.

3) gcc -c 오브젝트_파일명(*.o) 파일명(*.c)

   gcc -o 실행파일명(*.out) 오브젝트_파일명(*.o)


4) gcc -o 실행파일 파일명(*.c) : 실행 파일을 만든다. (3번을 한줄로...)


===================================================================================================


[gcc 옵션]


1. -Wall 옵션 : 모든 모호한 코딩에 대해서 경고를 보내는 옵션

2. -W 옵션 : 합법적이지만 모호한 코딩에 대해서 경고를 보내는 옵션

3. -W -Wall 옵션 : 아주 사소한 모호성에 대해서도 경고가 발생

4. O2 옵션 : 최적화 레벨 2로 설정. (대부분의 최적화를 시도)

5. -E 옵션 : 전처리 과정의 결과를 화면에 보이는 옵션 

             (전처리과정 중 발생한 오류를 검증)

    ※ enhanced Tip: --save-temps 옵션 


6. -S 옵션 : cc1으로 전처리된 파일을 어셈블리 파일로 

             컴파일까지만 수행하고 멈춘다. (*.s)


7. -c 옵션 : as에 의한 어셈블까지만 수행하고 링크는 수행하지 않는다.

8. -v 옵션 : gcc가 컴파일을 어떤 식으로 수행하는지를 화면에 출력한다.

9. --save-temps 옵션 : 컴파일 과정에서 생성되는 중간 파일인 

                       전처리 파일(*.i)과 어셈블리 파일(*.s)을 지우지 않고,

                       현재 디렉토리에 저장한다. (오류 분석에 사용)


===================================================================================================


[cpp0 옵션]

: 소스내에서 사용된 헤더 파일과 define 매크로와 관련된 옵션들이다.

  전처리 과정에서 오류가 발생한다면 cpp0 옵션들을 점검해야 한다.


1) -l 옵션 : 전처리 과정에서 헤더 파일을 탐색하는 기본 디렉토리를 추가할 때 사용하는 옵션

2) -include 옵션 : 헤더 파일을 소스내에 추가할 때 사용한다.

3) -D[매크로] 옵션 : 매크로를 외부에서 define 할 때 사용한다.

4) -D[매크로]=[매크로 값] 옵션 : 소스 내에 #define [매크로] [매크로 값] 옵션을 추가한 것과 

                                 동일하다.


5) -U[매크로] 옵션 : -D와 반대로 소스 파일 내에 #undef[매크로] 옵션을 추가한 것과 

                     동일하다.


6) -M / -MM 옵션 : -M 옵션 - make를 위한 소스 파일의 모든 종속 항목을 출력

                   -MM 옵션 - 기본 include 디렉토리에 있는 헤더 파일은 빼고 종속 항목을

                              출력한다.


7) -nostdinc 옵션 : 디폴트 include 디렉토리(usr/include)에서 헤더 파일을 탐색하지 않고,

                    -l 옵션으로 추가한 디렉토리에서만 헤더 파일을 찾는다.


8) -C 옵션 : -E 옵션과 함께 사용하며, 전처리 과정에서 주석을 제거하지 않는다.

9) -Wp,[옵션들] 옵션 : 만약 cpp0와 gcc의 옵션이 같은 것으로 중복되면 gcc 옵션으로 

                       해석되므로... gcc의 해석을 거치지 않고 바로 cpp0 옵션으로 전달하고 

                       싶을 때 사용한다.         


===================================================================================================


[cc1 옵션]

: "C언어 옵션, 경고 옵션, 최적화 옵션, 디버깅 옵션"의 4가지 종류

  "경고 수위 조절 or 최적화 수위 조절"을 하고 싶을 때 사용한다.


1. C언어 옵션 : C언어 종류와 표준에 관련된 옵션

   1) -ansi 옵션 : ANSI C 표준에 부합하는 소스를 작성하고자 할 때 사용하는 옵션

   2) -std=[C 표준들] 옵션 : 기타 다른 표준들을 지정하고자 할 때 사용한다.

   3) -traditional 옵션 : 오래된 Traditional C 문법으로 문법을 검사한다.

   4) -fno -asm 옵션 : gnu89 문법을 바탕으로 asm, inline, typeof 키워드를 사용하지 않기를

                       원할 때 사용한다.

     


2. 경고 옵션 : cc1의 옵션을 조정하여 경고 수위를 조절할 수 있다.

   1) -W / -Wall 옵션 (gcc 옵션 참고)

   2) -w(소문자) 옵션 : 모든 경고 메시지를 제거한다.

   3) -Werror 옵션 : 모든 경고를 컴파일을 중단하는 오류로 취급한다.

                     (경고가 하나만 나와도 컴파일이 중단된다.)


   4) -pedantic 옵션 : ANSI C89 표준에서 요구하는 모든 경고 메시지를 표시한다.

   5) -pedantic-errors 옵션 : ANSI C89 표준에서 요구하는 모든 오류 메시지를 표시한다.

   6) -Wtraditional 옵션 : 소스가 ANSI C와 K&R C 간에 서로 다른 결과를 가져올 수 있는

                           부분이 있다면 경고한다.


3. 최적화 옵션 : ⓐ 실행 파일의 크기를 줄여 메모리와 하드디스크의 사이즈를 절약 (큰 의미 X)

                 ⓑ 실행 파일의 크기를 줄여 실행 속도를 향상시키는 것.

   1) -O0 옵션 : 최적화를 수행하지 않는다.

   2) -O1 옵션 : -O0보다는 조금 낫다. 

   3) -O2 옵션 : 가장 많이 사용하는 옵션. 일반 응용 프로그램이나 커널을 컴파일 할 때 사용

                 (거의 대부분의 최적화를 수행한다.)

   4) -O3 옵션 : 가장 높은 레벨의 최적화. 모든 함수를 인라인 함수와 같이 취급한다.               

                 (Call 인스트럭션은 사용 X. but, 되도록이면 사용하지 않는 것이 좋다. 

                   → 너무나 많은 소스의 변경이 가해지기 때문에 왜곡이 발생할 위험이 있다.)

   5) -O5 옵션 : 사이즈 최적화를 실행한다. (공간이 협소한 곳에서 사용 - 임베디드 시스템)


4. 디버깅 옵션

   1) -g 옵션 : gdb에게 제공하는 정보를 바이너리에 삽입한다.

                (-g 옵션을 사용하지 않고 gdb로 디버깅하면, 역어셈 → 어셈블리 코드로만 디버깅 가능)


   2) -pg 옵션 : 프로파일을 위한 코드를 삽입한다. 

                 (-pg 옵션으로 컴파일 → gmon.out(프로파일 정보) → gprof로 gmon.out 파일 분석)



===================================================================================================


[as의 옵션]

: gcc는 as의 옵션에 대해서는 알지 못한다. -Wa,[as 옵션들] 형식으로 gcc를 거치지 않고 

  바로 전달해야 한다. -Wa, -al, -as와 같은 형식으로 사용하면 as에게 -al -as 옵션이 같이 전해진다.


-Wa,[as 옵션들]

1) -al 옵션 : 어셈블된 인스트럭션을 보인다.

2) -as 옵션 : 정의된 심볼을 보인다.

3) -l[패스] 옵션 : include 디렉토리를 지정한다. 어셈블리 소스 내에서 사용된 include 지정자가 

                   지정하는 헤더파일을 찾고자 할 때 사용한다.

4) -W / --no-warn : 경고 메시지를 출력하지 않는다.

5) -march=[아키텍처 문자열] : 해당 어셈블리


===================================================================================================


[collect2 / ld 옵션]

: 링크 옵션


1) -L[라이브러리 디렉토리] 옵션 : 라이브러리를 찾을 디렉토리를 지정한다.

2) -l 옵션 : 같이 링크할 라이브러리를 지정한다.

3) -shared 옵션 : 공유 라이브러리와 정적 라이브러리가 같이 있을 경우, 공유 라이브러리를 우선하여

                  링크한다. (아무 옵션을 주지 않아도 공유 라이브러리를 우선으로 링크한다.)


4) -static 옵션 : 정적 라이브러리와 공유 라이브러리가 같이 있다면, 정적 라이브러리를 우선하여

                  링크한다. (속도는 빠르지만 파일 사이즈가 커진다는 점 고려할 것!)


5) -nostdlib 옵션 : 링크시에 표준 C 라이브러리를 사용하지 않는다. 

                    (OS, 부트로더와 같은 프로그램을 컴파일 할 때 사용)


6) -nostartfiles 옵션 : crt1.o 등과 같은 start up 파일을 링크하지 않는다.

                    (OS, 부트로더와 같은 프로그램을 컴파일 할 때 사용)


7) -Wl,[링크 옵션들] 옵션 : gcc를 거치지 않고 바로 링크에게 옵션을 정해주고자 할 때 사용한다.

                            (사용법은 -Wa와 동일한다.)

   

   < 유용한 링크 옵션들 >

   ① -s 옵션 : 실행 파일에서 심볼 테이블을 제거

   ② -x 옵션 : 출력 파일에서 로컬 심볼 제거

   ③ -n 옵션 : 텍스트 영역을 읽기 전용으로 만듬

   ④ -r 옵션 : 추후 링크가 가능하게 오브젝트를 만듬

   ⑤ -e [name] 옵션 :  시작 심볼을 name 심볼로 사용 (default 시작심볼 : _start 심볼)

   ⑥ -M 옵션 : 심볼들의 정보를 자세하게 출력

   ⑦ oformat [format] 옵션 : 주어진 형식의 오브젝트 파일을 생성


===================================================================================================




출처 : http://lvzuufx.blogspot.kr/2014/09/gnu-make-gcc-auto-dependency-makefile.html





Autotools, CMake, qmake 같은 빌드 도구나 Open Watcom 의 wmake 같은 경우에는 자체적으로 auto-dependency 를 지원하거나 이를 위한 지시자를 제공한다. 하지만 GNU Make 의 경우에는 이러한 기능을 자체적으로 지원하지도 않고, 이를 위한 지시자를 제공하지도 않는다.

하지만, 다행히도 gcc 를 이용해서 이 기능을 구현할 수 있다. 우선 이에 필요한 gcc 의 기능부터 살펴보자. 

-M 옵션 : 해당 소스 파일의 의존성을 GNU Make 의존성 규칙에 맞추어 출력한다. 시스템 헤더 파일들도 포함된다.

예를 들어, 다음과 같은 hello.c 를 생각해보자.

1
2
3
4
5
6
7
8
9
// hello.c
#include <stdio.h>

int main( void )
{
    printf("Hello, world\n");

    return 0;
}



  • gcc -M hello.c


                    이를 실행한 결과는 이렇다.

                    • hello.o: hello.c f:/lang/gcc/usr/include/stdio.h \
                    •   f:/lang/gcc/usr/include/sys/cdefs.h \
                    •   f:/lang/gcc/usr/include/sys/gnu/cdefs.h \
                    •   f:/lang/gcc/usr/include/features.h f:/lang/gcc/usr/include/sys/_types.h \
                    •   f:/lang/gcc/usr/include/machine/_types.h \
                    •   f:/lang/gcc/usr/include/386/_types.h


                    -MM 옵션 : -M 옵션과 비슷한데, 시스템 헤더 파일은 제외한다.

                    • gcc -MM hello.c 


                    다음은 실행 결과이다.

                    •  hello.o: hello.c


                     -MP 옵션 : -M 또는 -MM 옵션과 함께 쓰일 때, 헤더 파일들에 대한 phony 타겟을 만든다. 이는 헤더 파일이 지워졌을 때, 오류가 발생하는 것을 방지한다.

                    •  gcc -M -MP hello.c


                    다음은 실행 결과이다.

                    •  hello.o: hello.c f:/lang/gcc/usr/include/stdio.h \
                    •   f:/lang/gcc/usr/include/sys/cdefs.h \
                    •   f:/lang/gcc/usr/include/sys/gnu/cdefs.h \
                    •   f:/lang/gcc/usr/include/features.h f:/lang/gcc/usr/include/sys/_types.h \
                    •   f:/lang/gcc/usr/include/machine/_types.h \
                    •   f:/lang/gcc/usr/include/386/_types.h
                    • f:/lang/gcc/usr/include/stdio.h:
                    • f:/lang/gcc/usr/include/sys/cdefs.h:
                    • f:/lang/gcc/usr/include/sys/gnu/cdefs.h:
                    • f:/lang/gcc/usr/include/features.h:
                    • f:/lang/gcc/usr/include/sys/_types.h:
                    • f:/lang/gcc/usr/include/machine/_types.h:
                    • f:/lang/gcc/usr/include/386/_types.h: 


                    -MT 옵션 : -M 또는 -MM 옵션과 함께 쓰일 때, 타겟의 이름을 바꾼다.

                    • gcc -M -MT hello_MT.o hello.c


                    다음은 실행 결과이다.

                    • hello_MT.o: hello.c f:/lang/gcc/usr/include/stdio.h \
                    •   f:/lang/gcc/usr/include/sys/cdefs.h \
                    •   f:/lang/gcc/usr/include/sys/gnu/cdefs.h \
                    •   f:/lang/gcc/usr/include/features.h f:/lang/gcc/usr/include/sys/_types.h \
                    •   f:/lang/gcc/usr/include/machine/_types.h \
                    •   f:/lang/gcc/usr/include/386/_types.h


                    -MF 옵션 : -M 또는 -MM 옵션과 함께 쓰일 때, 출력 결과를 해당 장치 또는 파일로 보낸다.

                    • gcc -M -MF hello.d hello.c


                    이 때 실행 결과는 표준 출력 장치에 나타나는 것이 아니라 hello.d 라는 파일에 저장된다.

                    바로 이 옵션들을 통해서 auto-dependecy Makefile 파일을 만들 수 있다. 문제는 이 내용을 Makefile 파일에서 어떻게 활용할 것인가이다.

                    의존성 파일부터 만들어 보자. 의존성 파일을 만들기 위한 기본적인 형태는 다음과 같다.

                    • hello.d : hello.c
                    •     gcc -M -MP -MF $@ $<


                    이제는 이렇게 만들어진 의존성 파일을 포함시켜야 한다. GNU Make는 이를 위해 include 지시자를 지원한다.

                    • include hello.d


                    이때 hello.d 가 없다면 주어진 생성 규칙을 통해서 새로이 만들거나 갱신한다. 그럼에도 불구하고 hello.d 를 만들 수 없다면, GNU Make 는 오류를 보고하고, 중지한다.

                    마지막으로 실행 파일의 의존성 규칙을 추가해주면 된다.

                    •  hello.exe : hello.o


                    물론 다음과 같은 기본적인 내용이 Makefile 앞 부분에 더 추가되어야 한다.

                    • .SUFFIXES : .c .o .exe
                    •  
                    • .PHONY : all
                    •  
                    • all : hello.exe


                    완성된 Makefile 의 모습은 이렇다.

                    1
                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    .SUFFIXES : .c .o .exe

                    .PHONY : all

                    all : hello.exe

                    hello.d : hello.c
                        gcc -M -MP -MF $@ $<

                    include hello.d

                    hello.exe : hello.o
                        gcc -o $@ $<



                    이제 make 를 실행하면, hello.c 자체가 수정되거나 hello.c 가 포함하는 헤더 파일이 수정되면 새롭게 빌드된다.

                    그런데 문제는 hello.d 파일 자체는 한 번 만들어지면 hello.c 가 수정되지 않는 이상, 다시 갱신되지 않는다. 왜냐하면 hello.d 파일에 대한 의존성은 hello.c 밖에 없기 때문이다. 이 문제는 hello.d 의 의존성을 hello.o 의 의존성과 동일하게 만들어주면 된다. 위 8번째 줄을 아래와 같이 바꾸자.

                    • gcc -M -MP -MT "$(@:.d=.o) $@" -MF $@ $<


                    GNU Make 의 다중 대상 기능을 이용한 것으로. 타겟이 "hello.o" 에서 "hello.o hello.d" 로 된다.

                    GNU Make 를 실행하다보면 hello.d 가 지워진 경우에,  다음과 같은 오류 메세지가 출력된다.

                    • Makefile:10: hello.d: No such file or directory


                    이 오류 메세지가 나오더라도 중단되지는 않지만, 뭔가 꺼림칙하다. 어떻게 없앨 수 없을까 ? 바로 -include 를 쓰면 된다.

                    이 예제의 경우에는 소스 파일이 하나에 불과해서 이 정도이지만, 소스 파일이 여러 개일 경우에는 해당 소스 파일마다 일일이 추가해줘야 한다. 그리고 다른 프로젝트에 쓰기에도 수정해야 할 것이 많다. 이 얼마나 불편한가 ? 이를 개선해 보자.

                    우리가 고려할 것은 실행 파일과, 소스 파일이다. 다른 모든 것들은 이들로부터 변형될 수 있다. 이들을 위한 변수를 도입하자. PROGNAME 과 SRCS 에 실행파일과 소스 파일을 담자.

                    • PROGNAME := hello.exe
                    • SRCS := hello.c


                    이제 의존성 파일 변수와 실행 파일을 만들기 위한 목적 파일 변수를 도입하자.

                    • PROGNAME_DEPS := $(SRCS:.c=.o)
                    • DEPS := $(SRCS:.c=.d)


                    이 내용들을 반영하면 위 Makefile 은 다음과 같이 쓰여질 수 있다.

                    1
                    2
                    3
                    4
                    5
                    6
                    7
                    8
                    9
                    10
                    11
                    12
                    13
                    14
                    15
                    16
                    17
                    18
                    19
                    PROGRAM := hello.exe
                    SRCS := hello.c

                    PROGRAM_DEPS := $(SRCS:.c=.o)
                    DEPS := $(SRCS:.c=.d)

                    .SUFFIXES : .c .o .exe

                    .PHONY : all

                    all : $(PROGRAM)

                    $(PROGRAM) : $(PROGRAM_DEPS)
                         gcc -o $@ $^

                    %.d : %.c
                         gcc -M -MP -MT "$(@:.d=.o) $@" -MF $@ $<

                    -include $(DEPS)



                    위 내용을 보면 특정 이름 대신에 변수를 사용한 것 외에도 몇 가지 달라진 점이 있는데, 하나는 16번째 줄에 있는 암묵적 규칙이고, 14번째 줄에 있는 $^ 이다. 암묵적 규칙은 여러개의 파일에 대해 동일한 규칙을  적용하기 위한 것이고, $^ 은 모든 의존 파일들을 뜻한다. 반면에 $< 은 첫번째 의존 파일을 뜻한다.
                    이렇게 해 두면, 다른 프로젝트에 적용을 한다든지 또는 소스가 변경되었다든지 할 때, PROGRAM 과 SRCS 만 바꾸어주면 된다. 소스 파일을 더 추가하고 싶다면 빈 칸을 사이에 두고 파일 이름을 적어주면 된다. 예를 들어 hello.c 외에 world.c 가 추가되었다면 2번째 줄을 이렇게 바꾸면 된다.

                    • SRCS := hello.c world.c


                    이외에도 여러가지 컴파일 플래그나 링커 플래그, 컴파일러나 링커를 위한 여러 변수들을 도입하면 훨씬 더 유연한 Makefile 이 될 것이다.

                    그리고 기회가 된다면 추후에 여러 개의 실행 파일과 DLL 등을 한꺼번에 빌드할 수 있는 Makefile 에 대해서도 글을 쓰도록 하겠다. 



                    반응형
                    반응형

                    출처 : http://ospace.tistory.com/189


                    소켓 관련 자세한 내용 joinc를 참고!!

                    : http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/Network_Programing/AdvancedComm/SocketOption


                    들어가기

                    소켓 프로그래밍에서 비동기 방식으로 처리를 많이 사용하고 있다. 장점도 있고 단점도 있다. 그러나 최근 고성능 서버 프로그램 작성할 때에는 거의 대부분이 비동기 방식으로 처리한다.
                    이런 부분의 장점과 단점은 인터넷에 잘 나와있으니 알아서 찾아보시고, 여기에서는 서버 보다는 클라이언트에 집중해보려고 한다. 즉 접속를 하는 시스템을 집중하겠다.

                    사실 서버에서도 접속을 요청할 수 있다. 이는 push방식 인가 pull 방식 인가에 따라서 서버에서도 사용할 가능성 있다. 이런 push와 pull도 인터넷에서 검색하시길 바란다.

                    이제 본론으로 들어가보자.


                    Connect 함수

                    기본적으로 소켓 생성이 끝나고 연결만 남은 상태라고 보자. 그리고, 대부분 소켓 프로그래밍에 대해 기초적인 부분을 알고 있다고 생각해서 진행하겠다.

                    int connect(int, const struct sockaddr*, socklen_t); // for linux
                    int connect(SOCKET, const struct sockaddr*, int); // for windows

                    많이 보던 함수라서 익숙 할 것이다. 각 인자마다 들어가는 타입이 틀리지는 모르지만, 결국 같은 값을 사용한다. 일반적인 동기 접속이 이뤄지는 경우 반환 값은 다름과 같다.

                    동기접속 Return Value
                    성공: 0 반환
                    실패: -1 (Linux), SOCKET_ERROR(Windows)


                    비동기 인경우는 조금 다르게 처리한다. 리턴 값이 바로 접속이 성공하면 0 이지만, -1 값이 실패로 처리되지 않는다. -1은 비동기 처리에서는 기본적이며, Linux는 ierrno값을 이용하여 현재 처리되는 상태를 확인하고 Windows는 WSAGetLastError()를 사용해서 에러를 확인한다.

                    비동기접속 Return Value
                    성공: 0 반환
                    진행중: -1 반환. errno값을 비교하여 진행 상태 확인
                    errno == EINPROGRESS(linux), EAGAIN
                    WsaGetLastError() == WSAEWOULDBLOCK

                    한 가지 주의할 것은 linux에서는 처리 중인 상태를 errno값에서 EINPROGRESS를 사용한다. 간혹 EAGIN를 사용하는 프로그램이 있다. 이부분을 좀더 검증이 필요하다.



                    연결 상태 확인

                    connect를 호출해서 연결을 한다고 해도, errno 값이 EINPROGRESS라고 해서 연결이 성공적으로 완료되었다고는 말하기 힘들다. 비동기이기 때문에 그 결과를 반환 값에 넣을 수 없다. 그 때 필요한 것이 getsockopt()를 이용한 것이다.

                    int getsockopt(int, int, int, void*, socklen_t*); // for linux
                    int getsockopt(SOCKET, int, int char*, int*);   // for windows
                    그럼 비동인 경우 연결 결과를 가져오는 코드는 다음과 같다.
                    int error = 0;
                    socklen_t len = sizeof( error );
                    if( getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0 ) {
                        // 값을 가져오는데 에러 발생
                        // errno을 가지고 에러 값을 출력
                        // 연결 오류로 처리
                    }

                    getsockopt()인 경우는 BSD와 Solaris에서 결과가 매우 다르다. Solaris는 확인하기 어렵고 일단 Linux와 Widows을 중심으로 살펴보았다.

                    Linux Return Value
                    성공: 0
                    실패: -1, 에러 종류는 errno에 저장됨

                    Windows Return Value
                    성공: 0
                    실패: SOCKET_ERROR, 에러 종류는 WSAGetLastError()로 가져옴


                    둘다 SO_ERROR 값 획득에 성공하면, error를 통해 연결 상태에 대한 결과를 확인해 볼 수 있다.

                    Linux
                    ECONNREFUSED: 연결 거부
                    ETIMEDOUT: 연결 대기 시간 초과
                    Windows
                    WSAECONNREFUSED: : 연결 거부
                    WSATIMEDOUT: : 연결 대기 시간 초과


                    error 값을 가지고 위의 값과 비교해보면 알 수 있다.
                    당연히 error 값이 0이면 에러가 없으므로 성공적으로 접속했다는 의미이다.

                    최종코드

                    다음은 위의 결과를 정리한 코드이다. 물론 직접 테스트해본 코드가 아니기 때문에 컴파일시 에러가 발생할 수 있다. 그리고 기본 플랫폼은 Linux로 정했다.

                    int fd = socket(AF_INET, SOCK_STREAM, 0); // tcp socket
                    if( fcntl( fd, F_SETFL, O_NONBLOCK) == -1 ) {
                        return -1; // error
                    }
                    // Windows인 경우
                    // unsigned long nonblock = 1; 
                    // nonblock 설정
                    // ioctlsocket(fd, FIONBIO, &nonblock);
                    // struct sockaddr_in 형의 peer를 초기화함
                    int result = connect (fd, (struct sockaddr*)&peer, sizeof(peer));
                    if( 0 == result ) {
                        // 연결 성공
                    } else if ( EINPROGRESS == errno ) {
                        // 비동기 연결 이벤트로 등록
                    } else {
                        // 연결 실패
                    }

                    비동기 연결 이벤트에 의해서 해당 이벤트가 활성화되어 실행하는 경우, getsockopt()를 이용해서 결과를 확인한다.

                    // 연결 이벤트 헨들러
                    int error = 0;
                    socklen_t err_len = sizeof(error);
                    if( getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*) &error, &len) < 0 ) {
                        // 결과값 갖고 오는데 에러 발생
                        // 연결중 에러 발생으로 errno 값으로 결과 확인
                        // 에러 리턴
                    }
                    if( 0 != error ) {
                        if( ECONNREFUSED == error ) {
                            // 연결 거부로 연결 실패
                            // 에러 처리
                        } else if( ETIMEOUT == error ) {
                            // 연결 대기 시간 초과로 연결 실패
                            // 에러 처리
                        }
                        // 원인 모를 에러?
                        // 알아서 처리 ㅡ.ㅡ;
                    }
                    // error가 0이기 때문에 연결 성공 ^^


                    이상입니다. 안에 코드는 두서없이 작성한 것이라서 나름대로 적당히 수정하면 되겠죠. error값은 switch문을 사용하는 식으로 말입니다.



                    결론

                    비동기 연결에 대해서 다룬 경우는 거의 없다. Stevnes 아저씨의 책을 많이 참고 했다. 이를 이용해서 다중 연결 요청을 만드는 프로그램을 작성할 수 있었다.

                    마지막으로 위에서 사용한 헤더 파일에 대해서 정리해보았다. 물론 다 알고 계신분은 상관없지만, 그 때마다 찾아쓰는 나와 같은 사람은 헤더파일 알아내느라 참 힘들다. ㅡ.ㅡ;

                    Windows
                    WinSock2.h: connect(), getsockopt(), WSAGetlastError(), WSAECONNREFUSED, WSAETIMEOUT

                    Linux
                    sys/socket.h: connect(), getsockopt()
                    errno.h: ECONNREFUSED, ETIMEOUT 정의

                    이상입니다. 모드 즐프하세요. ospace.

                    참조

                    [1] Stevens, Unix Network Programming Volumn1 2ed
                    [2] man, connect, getsockopt
                    [3] MSDN, connect, getsockopt


                    -------------------------------------------------------------------------------------------------------
                    안녕하세요.

                    저 같은경우 비동기 커넥트시

                    connect 후 커넥트가 진행중이면 read 셋,write 셋, except 셋 에다
                    소켓을 셋한후 select 후 write 셋을 검사하여 write 셋에 소켓이
                    셋되있으면 접속된걸로 간주하고 except 셋에 소켓이 셋 되잇으면
                    실패한걸로 간주합니다.

                    여기서 궁금한거는 최초의 select 에서 아무런 이벤트(접속성공,실패)를
                    감지하지 못했을경우 다시 감지를 하기 위해서 write 셋과 except 셋에
                    다시 소켓을 셋해야 하는지 궁금합니다. 당연히 그래야 할것 같은데
                    제가 테스트(윈도우에서)할때는 최초의 select 에서 접속성공 또는 실패
                    를 알수 있어서 그런가보다 하고(최초의 select에서 접속성공실패여부 확인)
                    알고 있었습니다.

                    리눅스(SuSE) 에서 상황을 접속실패로 해놓고(접속할서버를닫아놓고) 테스트
                    해보면 최초의 select 호출후 read,write,except 의 각 fd_set 의 변화를
                    살펴봤더니 read 셋과 write 셋에 소켓이 셋 되있었습니다
                    (connect 호출후 read,write,except 에 소켓을 셋해두었습니다)

                    상황이 접속 실패다 보니 except 에 소켓이 셋 되길 바라고 있었지만
                    황당하게도 write 셋 말고도 read 셋까지 소켓이 셋되있어서 머리에서
                    쥐가날 지경입니다. 혹시 이런 현상을 겪으신분 계시는지 궁금합니다.

                    man 페이지를 살펴보면

                    ------------------
                    The socket is non-blocking and the connection cannot be completed
                    immediately. It is possible to select(2)
                    or poll(2) for completion by selecting the socket for
                    writing. After select indicates writability, use get?
                    sockopt(2) to read the SO_ERROR option at level
                    SOL_SOCKET to determine whether connect completed success?
                    fully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is
                    one of the usual error codes listed here, explaining
                    the reason for the failure).

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

                    위와같이 소켓이 write able( 셀렉트 호출이후 write set 에 셋되었을경우 )
                    되었을경우
                    getsockopt 로 옵션을 SO_ERROR 로 줘서 결과를 얻어 접속성공이냐 실패냐를
                    알아보라고 했는데 성공해야 할경우나 실패햐애 할 경우에나 항상 결과는
                    성공으로 나옵니다.

                    코드는
                    int ret = getsockopt( sock, SOL_SOCKET, SO_ERROR, &error, &error_size );
                    이며 error 값과 ret 값이 0 이 아닐경우를 접속실패로 간주했습니다.

                    사실상 getsockopt 를 이용한 비동기 커넥션 성공여부 판단은 포기하고 있는
                    실정이지만 혹시 이에대해 좀 아시는분 참고말씀 부탁 드립니다.

                    아래글에도 이와비슷한 글을 올렸는데 어느분께서
                    접속성공 실패 여부를 떠나서 일단 무조건 select 이후 write 셋에는
                    소켓이 셋되있다고 하시더군요(UNP 에 나온다고말씀)
                    만약 접속이 실패든 성공이든 무조건 write 셋에 걸린다면 select 이후
                    이벤트체크에서 걸린 소켓(즉 write 셋에 걸려버린소켓)은 어떻게
                    접속이 성공했는지 실패했는지 알수가 있는지 궁금합니다.

                    말이 길었는데 요약하자면
                    보통 select 를 이용한 비동기 소켓 커넥션은 어떤식으로 이루어지는지와
                    최초의 select 호출이후 아무런 이벤트를 감지하지 못햇을경우
                    다시 read,write,except 셋에 접속성공 여부를 감지할 소켓을 셋하고
                    감지할때까지 이와같은일을 반복하는지..

                    또 저같은경우 접속실패 상황에서 왜 read 와 write 셋이 셋되서
                    나오는지...

                    조그만 참고말씀이라도 귀담아 듣겠습니다.
                    감기조심하시고 읽어주셔서 감사합니다.


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

                    UNIX에서는 Winsock과 다르다고 하네요
                    UNIX에서는 연결이 성공했는지의 여부를 알리기 위해서
                    읽기와 쓰기가 모두 가능하게 만듭니다.
                    커널 구현이 그런걸 ... 왜 그렇냐고 따지기 보다 ㅎㅎ 그냥 쓰셔야 겠죠

                    일단 select() 리턴후

                    readable도 아니고 writable도 아니라면
                    소켓은 연결이 되어 있는 상태로 판단가능하구요

                    그다음엔
                    getpeername()을 호출해서 정상리턴이면 연결된 상태입니다.

                    만약 getpeername()이 ENOTCONN을 리턴하면 연결안된상태이구요.
                    getsockopt() 호출해줘서 아직 가지고 오지 않은 에러코드를 가지고 오신후에 error코드를 살펴보시면 됩니다.
                    만약 getsockopt() 자체가 실패하면
                    errno가 getsockopt()호출에 대한 실패를 저장하겠죠?(맞나 ㅡ.ㅡ, 불확실..)

                    제가 말씀 드린 부분을 isconnected() 함수로 만들어서

                    사용하시면 될것 같네요


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

                    일단 저의 상황에 대해 말씀드리자면

                    전 커넥트 타임 아웃 함수를 만드는것이 아닙니다.

                    UNP 책이 없어 게시판뒤져서 다른분들이 예제올리신거 봤는데

                    비동기 커넥트 타임아웃 함수더군요.

                    저는 반응시간에 민감한 (중간에 거의 멈춤없이 계속돌아가는)

                    서버프로그램을 짜고있고 다른 서버와의 연동을 위해 타 서버로

                    접속하는 클라이언트 모듈을 짜고 있습니다. 여기서 타 서버로 접속시

                    블럭 또는 시간지연이 안되게 하기 위해 비동기 connect 를 이용하며

                    이 클라이언트모듈을 모든 서버 소켓및 차일드소켓과 같이 관리 합니다.

                    일단 비동기 connect 에 대한 방법은 많이 파악하고 있었는데 계속 이상한

                    오동작을 해 많이 혼란스러웠던것 같습니다. 지금 오동작을 하는 상황을 판단

                    했는데 다음과 같습니다.

                    접속하는 서버가 꺼져있는상황에서.
                    (이 서버프로그램은 타서버로 접속하는 클라이언트 소켓만 관리 하는
                    것이 아니라 차일드 소켓들도 관리합니다. 그래서 따로 접속용 select 를
                    만들어서 돌리지 않으며 테스트 할때는 클라이언트 소켓만 관리해서
                    테스트 했습니다)

                    1. 목적 서버가 로칼호스트일경우(즉 같은 컴퓨터) ex)127.0.0.1

                    select 를 통과하면 read 셋과 write 셋이 동시에 셋 됩니다.(select 리턴값 2 )

                    2. 목적 서버가 타 호스트일경우(remote) ex)192.168.1.1

                    select 를 통과하면 아무 fd_set 도 셋 되지 않습니다.

                    그래서 접속실패 경우 어떤 상황이 정상인것으로 판단 해야 하는것인가
                    에 대한 고민을 많이 하다 위 1번상황(목적 서버 꺼짐, 목적서버 로칼호스트)
                    을 고려하여 다음과 같이 했습니다

                    일단 read set 체크 루틴은 데이타가 오거나 접속이 끊긴것을 처리해야
                    하는 루틴이므로 비동기 접속에 대한 실패여부를 판단하는 루틴을
                    넣는것은 안좋은것 같아 어차피 접속이 안됬으면 recv 에서 접속 끊긴것
                    으로 판단하여 소켓을 닫는 루틴이 호출될것이므로 그냥 둠.

                    read set 이 셋됨과 동시에 write set 도 셋 되므로 별도의 처리가 필요하
                    다고 판단.

                    write set 이벤트 발생처리 루틴은
                    write set 될때 접속요청중인 소켓이였으면(따로 소켓클래스에 셋팅해둠)
                    접속성공처리를 하는 루틴임.
                    하지만 접속 실패의 경우도 write set 이 발생하므로 어떻게 할까 고민하다
                    위 write set 이벤트를 처리하기전 read set 에서 소켓을 닫으므로
                    소켓이 닫혀 있으면 continue 처리.

                    이렇게 하였습니다. 일단 이렇게 하니 문제없이 잘 동작은 하는데

                    마지마으로 궁금한점!

                    왜 접속 실패하엿을경우 exception set 은 발생하지 않을까??

                    exception set 은 실제로 거의 쓰이지 않는 set 일까 하는점입니다..

                    긴글 읽어주셔서 감사합니다. ^^ 참고 말씀 기달리겠습니다.

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


                    UNP 에서는 넌블러킹 소켓으로 connect 를 했을 때 생기는 문제점과 connect 후에 처리해야 할 일 등을 기술하고 있습니다. 그 예제 코드만을 가지고서 말씀하시지 마시고, 먼저 읽어보시는 것이 도움이 됩니다.

                    UNP 구현과 달리 select 를 중앙집중식으로 하시는가 봅니다. UNP 구현의 select 부터를 작성하시는 코드에 적절히 배치하면 될 것이구요.

                    그리고, 1. 번의 경우에 어떻게 처리해야 하는지 UNP에 얘기되어 있고, 2. 번의 경우 select 에 시간을 주어서 대기했다면 어떻게든 (접속되든 안되든) 해당 fd_set 을 (최소한 writable) 세팅해줍니다. 만약 기다리지 않게 하고 조사하거나 시간이 만료되었다면, 접속성공/실패 여부를 모르고 시간만료에 해당하는 0을 리턴하고 아무런 fd_set 을 설정하지않고 리턴될 것입니다. 중앙에서 select 한다면 판단이 애매해질 수 있겠습니다. 다른 fd와 섞여서 select의 리턴값만으로는 넌블러팅 connect의 시간만료인지를 알 수 없을테니, 해당 fd 의 타임아웃 시간인지를 판단하는 코드가 추가되고 시간만료이면 fd_set 세팅이 없을 때라는 조건이 있어야 실패로 확인되겠습니다.

                    정 책이 없다면... 서점에서라도 보실 수 있지 않습니까. 해당하는 페이지가 몇페이지 안되니까요. 보기만해도 좋은 결론에 도달할 수 있다고 생각합니다.

                    UNP 를 먼저 꼭 구해서 읽어보시면 좋겠습니다. 먼저 경험한 사람의 충실한 경험담이 있습니다. 혼자서 고생하시지 마시고, 쉽게 정보를 구할 수 있다면 이용하는게 맞다고 봅니다.

                    UNP 는 내용을 본다면 책값이 너무 싼 편에 속한다고 생각합니다.


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

                    예 그렇습니다. 저같은경우 timeval 안에 값을 0,0 으로 셋팅합니다.
                    그래서 read 나 write 이 셋 되지 않고 셀렉트가 0을 리턴하는군요.
                    궁금한점은 select 가 0을 리턴했어도 다음번,또 다음번 select 호출시에는
                    read 나 write 가 왜 셋되지 않을까 입니다. timeval 값을 0,0 으로 하면
                    select 가 read 나 write 을 셋하기에는 너무 짧은 시간인지요?

                    UNP 번역판을 전 회사에서 본적이 있는데 번역기를 돌린건지
                    번역이 이상하더군요.. 살려고 했는데 번역본에 대한 실망때문에
                    망설이고 있었습니다. 이참에 맘먹고 장만해야겠네요..^^


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

                    마지막 글에 질문이 있었군요... 처제네 집들이를 하는 와중에 쓴글이라 경황이... -_-;

                    넌블러킹 connect 를 호출 후에 성공이냐 실패냐를 정확하게 판단할 수 있는 시점은 select 에서 넘어오는 순간입니다. 만약 select 를 대기없이 조사만하도록 했다면 (만료시간 0) 이것은 connect 시도의 상태만을 알아보는 것이므로 성공/실패 여부를 알아보기에는 조금 힘든 방법 같습니다.

                    일반적으로 상대편 서버가 다운되어 있을 때, connect 를 시도하면 보통 60초정도 (환경마다 다르겠죠) 있다가 성공/실패 여부를 돌려준다고 합니다. 여러번 select 를 호출했다고 해도 이 시간이 되기전에는 어떻게 된 것인지 알 수가 없는 것이고, rasungboy 님처럼 만료시간 0으로 한다면 그 시간이 될 때까지는 계속 0만을 리턴하는 상황이겠습니다. (같은 호스트라면 즉시 성공/실패를 판단)

                    결국 넌블러킹 connect 후에 select 로 일정 시간을 대기해야 한다는 결론입니다. 하지만...

                    지금 하시고자 하는 것이 여러 다른 일반 fd 들과 함께 connect 시도도 병행하고자 하는 것으로 보입니다. 이럴 경우 문제가 있는데, select 에 주어지는 시간 만료는 여러 fd 들에게 독립적인 것이 아니라 select 자체에 대한 시간 만료이기 때문에 시간에 대한 처리를 따로 하셔야 한다는 것입니다.

                    각 fd 별로 타임 아웃 시간을 각각 적용하기 위해서는 각 fd 별 타임 아웃을 우선 순위 큐 등을 이용해서 가장 짧은 시간을 select 의 시간 만료값으로 주고 처리해야 합니다. 이것을 제대로 구현하기가 좀 까다롭고 테스트하기도 귀찮고... 좀 그렇습니다...

                    select 로 분기하는 구조로 간다면 이래저래 처리해야 할 것도 많아지고 상태 머신이 아주 복잡해지는 경향이 있습니다. (간단한 처리 서버라면 상관없겠지만요...) 그래서 대부분 그냥 일반 쓰레드를 사용하거나 특수하게 서버용 사용자-공간 쓰레드를 사용하기도 합니다.

                    쓰레드라는 것이 막 쓰기에는 그렇지만 좀 신경을 쓴다면 그리 효율을 떨어뜨리지 않고서도 로직의 흐름대로 프로그래밍할 수 있어서 좋다고 생각합니다. (물론 남용은 금물...)


                    반응형
                    반응형

                    출처 : http://www.joinc.co.kr/modules/moniwiki/wiki.php/man/4200/WSAGetLastError



                    int WSAGetLastError(void);
                    


                    매개변수

                    이 함수는 매개변수가 없다.

                    반환 값

                    가장 최근 발생한 소켓에러에 대응되는 에러코드를 반환한다.

                    설명

                    다중 쓰레드 프로그램일 경우 모든 쓰레드의 소켓에 대해 에러값을 측정한다. 이 경우 어느 쓰레드의 소켓함수 호출에서 에러가 발생한 건지 명확하지 않을 수 있다. 특정한 윈도 소켓에 에러가 발생하면, 그에 적절한 에러코드를 확인해서 반환한다. getsockopt()함수를 SO_ERROR 매개변수로 호출한 에러코드와는 다를 수 있다.

                    특정 소켓함수가 성공했다고 해서, 가장 최근의 소켓에러값이 리셋되지 않는다. 에러코드를 리셋하려면 WSAGetLastError 함수의 iError매개변수를 0으로 호출 해야 한다.

                    비동기 네트워크로 메시지로에서 IParam'매게변수로 전달되는 에러 값은 WSAGetLastError과 다를 수 있다.

                    에러코드 종류

                    이름 설명
                    WSA_INVALID_HANDLE 6 지정된 이벤트 객체 핸들이 잘못 되었다.
                    WSA_NOT_ENOUGH_MEMORY 8 메모리가 충분하지 않다.
                    WSA_INVALID_PARAMETER 87 하나 이상의 잘못된 인자가 사용됐다.
                    WSA_OPERATION_ABORTED 995 overlapped(:12) 연산이 중단 되었다.
                    WSA_IO_INCOMPLETE 996 Overlapped 입출력 이벤트가 신호 상태가 아니다.
                    WSA_IO_PENDING 997 Overlapped 연산은 나중에 완료될 것이다. 중첩 연산을 위한 준비가 되었으나, 즉시 완료되지 않았을 경우발생
                    WSAEINTR 10004 WSACancelBlockingCall()에 의해 블록화 호출이 취소: Interrupted system call
                    WSAEBADF 10009 잘못된 파일 기술자가 사용되었음
                    WSAEACCES 10013 요청한 주소가 브로드캐스트 주소인데 setsockopt()로 SO_BROADCAST 가 설정되지 않았다.
                    WSAEFAULT 10014 잘못된 주소를 사용했음
                    WSAEINVAL 10022 바인딩 실패. 이미 bind된 소켓에 바인드하거나 주소체계가 일관적이지 않을 때
                    WSAEMFILE 10024 너무 많은 파일이 열려있음
                    WSAEWOULDBLOCK 10035 non overlapped 소켓 : 비 봉쇄 소켓에 아직 읽을 데이터가 없음, overlapped(:12) 소켓 : 너무 많은 중첩 입출력 요구가 있음
                    WSAEINPROGRESS 10036 블록화 함수가 진행되는 동안 부적절한 윈속 API함수가 호출.
                    WSAEALREADY 10037 비봉쇄모드 소켓에서 아직 진행중인 작업임. (connect가 완료되지 않은 상태에서 connect 함수의 호출등..)
                    WSAENOTSOCK 10038 잘못된 소켓기술자를 사용했음
                    WSAEDESTADDRREQ 10039 목적지 주소를 명시하지 않았음
                    WSAEMSGSIZE 10040 송수신에 사용된 데이터가 버퍼의 크기를 초과해서 크기에 맞게 잘렸음
                    WSAEPROTOTYPE 10041 소켓에 맞지 않는 프로토콜을 사용했음
                    WSAENOPROTOOPT 10042 잘못된 옵션을 사용했음. 지원되지 않는 옵션으로 getsockopt함수를 호출하는 등.
                    WSAEPROTONOSUPPORT 10043 윈속에서 지원하지 않는 주소체계를 사용했음
                    WSAESOCKTNOSUPPORT 10044 소켓타입이 지원하지 않는 주소체계를 사용했음
                    WSAEOPNOTSUPP 10045 소켓이 지원하지 않는 명령을 사용했음. listen()함수를 데이터그램 통신 (SO_DGRAM)에서 호출
                    WSAEPFNOSUPPORT 10046 지원하지 않는 프로토콜을 사용했음
                    WSAEAFNOSUPPORT 10047 윈속에서 지원하지 않는 주소체계를 사용했음
                    WSAEADDRINUSE 10048 지정된 주소가 이미 사용 중임
                    WSAEADDRNOTAVAIL 10049 사용할 수 없는 주소임
                    WSAENETDOWN 10050 네트워크 서브 시스템에 문제가 있음. 네트워크 접속 끊김등.
                    WSAENETUNREACH 10051 네크워크에 접근할 수 없음
                    WSAENETRESET 10052 네트워크 재설정으로 연결이 끊어졌음.
                    WSAECONNABORTED 10053 타임아웃 혹은 상대방의 접속종료들과 같은 소프트웨어적인 문제로 연결이 끊겼음.
                    WSAECONNRESET 10054 연결이 원격 호스트에 의해 재설정되었음.
                    WSAENOBUFS 10055 남아있는 버퍼공간이 없어서 소켓을 사용할 수 없음
                    WSAEISCONN 10056 이미 연결이 완료된 소켓임. connect로 연결이 완료된 소켓에 다시 connect를 시도할 경우
                    WSAENOTCONN 10057 연결되지 않은 소켓임. 연결되지 않은 소켓에 읽고 쓰는 경우
                    WSAESHUTDOWN 10058 소켓이 종료되었음. 종료된 소켓에 데이터를 읽고 쓰려 경우
                    WSAETOOMANYREFS 10059 Too many references
                    WSAETIMEDOUT 10060 접속시도 제한 시간 초과
                    WSAECONNREFUSED 10061 서버가 연결시도를 거절함. 수신 대기열이 가득찬 상태에서 클라이언트의 connect 호출이 있을 경우
                    WSAELOOP 10062 너무 많은 심볼릭링크가 사용되었음
                    WSAENAMETOOLONG 10063 파일이름이 너무 김
                    WSAEHOSTDOWN 10064 호스트 다운
                    WSAEHOSTUNREACH 10065 호스트로의 경로를 설정할 수 없음
                    WSAENOTEMPTY 10066 네트워크 서브시스템이 아직 통신할 준비가 되어 있지 않음. WSAStartup에 의해 반환
                    WSAEPROCLIM 10067 너무 많은 프로세스가 생성되었음
                    WSAEUSERS 10068 사용자가 너무 많음
                    WSAEDQUOT 10069 디스크 허용 할당량 초과
                    WSAESTALE 10070 Stale NFS file handle
                    WSAEREMOTE 10071 Too many levels of remote in path
                    WSAEDISCON 10101 종료가 진행중임
                    WSASYSNOTREADY 10091 네트워크 서브시스템이 아직 통신할 준비가 되어 있지 않았음
                    WSAVERNOTSUPPORTED 10092 윈속이 지원하지 않는 프로토콜 버전을 사용했음. WSAStartUp()에서 반환
                    WSANOTINITIALISED 10093 WSAStartup() 함수가 성공적으로 실행되지 않은 상황에서 윈속 함수를 호출했을 때
                    WSAHOST_NOT_FOUND 11001 요청된 호스트를 찾을 수 없음
                    WSATRY_AGAIN 11002 요청된 호스트를 찾을 수 없음
                    WSANO_RECOVERY 11003 복구할 수 없는 에러가 발생하였음
                    WSANO_DATA 11004 요청한 이름이 유효하지만 정보를 찾지 못했음. gethostbyaddr()등에서 발생 


                    반응형
                    반응형



                    우선 가장 정답에 대해서 말하자면 포인터를 선언할때는 


                    1. 변수 선언시 NULL로 초기화 해줘야 한다.

                    2. new 로 생성한 객체를 delete 한 뒤에는 NULL로 다시 초기화 해준다.


                    위와 같은 사실 이전에 한가지 의문이 있는데

                    delete는 NULL pointer에 대해 safe 한가?


                    MEC++ (More Effective C++)에는 C++ 에서는 safe to null pointer 라고 명시되어 있다.

                    즉 NULL 포인터를 delete를 해도 null pointer exception error가 발생하지 않는다는 것.


                    즉 이러한 코드는 필요가 없는 것이다.

                    if(var) delete var;


                    일단 테스트 해본 결과 메모리 릭이 발생하는 것은 아니지만 일반적으로 매크로를 사용해서 NULL로 다시 초기화 해준다. 

                    (디버깅이 문제가 될 수 있어 template나 공용함수를 사용해도 무방)

                    #define SAFE_DELETE(var)    { delete var; var = NULL; }



                    반응형
                    반응형



                    출처 : http://sea1sun.egloos.com/8879051



                    이 두 용어는 여러 바이트로 이루어진 하나의 데이터를 메모리에 저장하는 순서를 나타낸다.

                    메모리에 저장된 바이트들의 순서
                    예 : 0x013F (십진수 : 319)
                    빅엔디언에서의 메모리 로드 (sun/모토롤라 MP 계열 CPU)
                    0000 0001 0011 1111
                    ^ ^
                    | |
                    1000번지 1001번지 (저장되는 메모리 번지) 0x01 0x3F
                    (unsigned int i = 8; 이라고 했다면 unsigned int가 4바이트)
                    (00000000 00000000 00000000 00001000)
                    리틀엔디언에서 메모리 로드 (인텔 호환 기반의 cpu)
                    0011 1111 0000 0001
                    ^ ^
                    | |
                    1000번지 1001번지 (저장되는 메모리 번지) 0x3F 0x01
                    (unsigned int i = 8; 이라고 했다면 unsigned int가 4바이트)
                    (00001000 00000000 00000000 00000000)
                    => 리틀엔디언이라도 한 바이트 내에서는 빅엔디언으로 정렬
                    * 데이터 통신에서는 비트 순서를 둘 중 어느 한 방식으로 하는 것이 가능하다.
                    따라서 주는 쪽과 받는 쪽이 이를 확인하고 데이터 처리해야 함.

                    * 조나단 스위프트의 걸리버 여행기에 나오는 소인국 사람들(Lilliputian)은
                    삶은 달걀을 넓은 면을 깨서 먹는 사람들(Big Endian)과 좁은 면을 깨서
                    먹는 사람들(Little Endian)로 나뉘어 정치적인 대립하게 된다.
                    컴퓨터 구조상에서 쓰이는 “빅 엔디언(Big Endian)”과
                    “리틀 엔디언(Little Endian)” 은 바로 여기서 유래되었다.

                    자바 기본 유형의 바이트 순서 변환

                    author : Yoon Kyung Koo(yoonforh@moon.daewoo.co.kr)
                    Copyright (c) 1999 Yoon Kyung Koo, All rights reserved.
                    LITTLE ENDIAN은 LSB가 처음에 나오고
                    BIG ENDIAN은 MSB가 처음에 나옵니다.
                    그리고 네트웍 바이트 순서는 BIG ENDIAN입니다.
                    만약 4바이트 (즉, 자바의 int, 32비트 기계에서 C의 int나 long)라면
                    Little Endian에서 1234 순서로 바이트가 배치되면
                    자바나 Big Endian은 4321 순서로 바이트를 해석해야 합니다.
                    
                    float나 double의 경우는 시스템에 따라 좀더 복잡합니다.
                    하지만 IEEE 754를 따르는 경우에는 단순히 바이트 순서만 바꾸면 해석 가능합니다.
                    C에서
                    float은 보통 32비트(4바이트)이고
                    double은 보통 64비트(8바이트)입니다.
                    자바도 이에 준할 것입니다.
                    따라서 리틀 엔디안에서 더블을 표현할 때
                    12345678 바이트 순서라면
                    빅 엔디안에서는
                    87654321 바이트 순서로 해석하면 됩니다.
                    
                    
                    다음은 간단하게 자바 int를 다른 엔디안 값으로 바꿔보는 것입니다.
                    
                    
                    // htonl.java
                    
                    class htonl {
                    
                            public static void main(String args[]) {
                    
                                    if (args.length<1) {
                    
                                            System.out.println("Usage:htonl number");
                    
                                            System.exit(0);
                    
                                    }
                    
                                    int value=Integer.parseInt(args[0]);
                    
                                    // all java integer is BIG-ENDIAN(network byte-order)
                    
                                    byte[] bytes=new byte[4];
                    
                                    bytes[0]=(byte)((value&0xFF000000)>>24);
                    
                                    bytes[1]=(byte)((value&0x00FF0000)>>16);
                    
                                    bytes[2]=(byte)((value&0x0000FF00)>>8);
                    
                                    bytes[3]=(byte) (value&0x000000FF);
                    
                                    int newValue = 0;
                    
                                    newValue |= (((int)bytes[3])<<24)&0xFF000000;
                    
                                    newValue |= (((int)bytes[2])<<16)&0xFF0000;
                    
                                    newValue |= (((int)bytes[1])<<8)&0xFF00;
                    
                                    newValue |= (((int)bytes[0]))&0xFF;
                    
                                    System.out.println("big endian value="+value+" ("+bytes[0]+","+bytes[1]+","+bytes[2]+","+bytes[3]+")");
                    
                                    System.out.println("little endian value="+newValue+" ("+bytes[3]+","+bytes[2]+","+bytes[1]+","+bytes[0]+")");
                    
                            }
                    
                    }
                    


                    반응형

                    'Coding Tip > JAVA' 카테고리의 다른 글

                    jdk 설치 후 환경변수 설정  (0) 2013.01.03
                    반응형


                     

                    출처 : http://www.linuxlab.co.kr/docs/98-11-2.htm

                     

                    공유 라이브러리 만들기

                      번역 : 이성주/ 고려대학교 컴퓨터학과
                      하이텔(linuxlee)

                      이 글에 대한 원문은 다음의 웹 페이지에서 보실 수 있습니다.
                      http://www.es.linuxfocus.org/English/November1997/article6.html

                     

                    1. 소개 : 프로그램을 제작하는 과정

                      근래의 프로그램 제작 과정은 오랜 시간동안 고통받으면서 쌓아올려진 프로그래머들과 디자이너들의 노력의 산물이다.

                      이러한 프로그램 제작 과정은 다음과 같은 순서를 따른다.

                      우선 텍스트 편집기로 고급 언어를 사용한 소스 코드를 만든다. 매우 큰 프로그램 같은 경우는 하나의 파일에 담기 매우 힘들기 때문에 소스 코드 자체가 기능적 모듈별로 그 기능을 수행하는데 적합한 프로그래밍 언어가 다를 수 있기 때문에 앞에서 나뉘어진 소스 코드들이 다 같은 프로그래밍 언어를 사용해야 하는 것은 아니다.

                      소스 코드들을 생성한 후, 각각의 코드들은 기계(컴퓨터)에 의해 실행가능한 코드로 변환되어야 한다. 이렇게 기계에 의해 실행 가능한 코드를 목적 코드(object code)라고 한다. 이 코드는 기계에 의해 실행 가능하다는 것만 빼고는 앞에서의 소스코드와 똑같은 기능을 수행하게 된다. 소스코드를 목적 코드로 변환하는 과정을 일반적으로 컴파일(compile)이라고 부른다. 컴파일은 대개의 경우 앞에서 나눈 각각의 소스 파일들에 대해 따로따로 적용된다. 컴파일된 목적 코드들은 프로그램이나 서브 루틴, 변수 등 다음 단계에서 필요한 대부분의 요소들을 포함하고 있다.

                      프로그램을 생성하는데 필요한 목적 코드들이 생성된 후, 링커(linker)라고 불리우는 프로그램에 의해 앞에서 생성된 목적 코드들을 한데 묶고 연결하는 과정이 진행된다. 이 과정에서는 각각의 모듈(목적 코드)들에 포함되어 있는 다른 모듈을 가리키는 메모리 주소들이 결정되게 된다(예를 들어 한 모듈에서 다른 모듈에 들어있는 변수를 가리키게 되는 경우 링크 과정에서 각 모듈들을 합한 후 최종적으로 결정되는 절대 주소를 변수 부분에 넣어 주게 된다.) 이렇게 해서 생성되는 제품이 일반적으로 메모리에 로딩되고 실행되는 실행 파일이다.

                      프로그램 실행은 운영 체제에 포함된 특별한 소프트웨어에 의해 이루어지며 예를 들자면 리눅스의 시스템 콜exec()등이 있다. 이 시스템 콜은 실행 파일을 찾은 후, 프로세스에게 메모리를 할당하고, 파일의 특별한 부분(실행코드와 변수들)을 메모리에 적재하고 실행파일의 '텍스트'라는 부분에서 제어권을 CPU에게 넘긴다.

                     

                    2. 프로그램 제작 과정의 간략한 역사

                      프로그램 제작 과정은 좀더 나은 실행 성능과 시스템 리소스 사용을 위해 지속적인 개혁을 해왔다.

                      초기에 개발자들은 직접적으로 기계어 코드를 이용하여 프로그램을 개발하였다. 이후에 프로그램을 고급 언어로 개발한 후 기계어로 번역하는 것이 자동화됨으로써 프로그램의 생산성은 비약적으로 증가하게 되었다.

                      프로그램 컴파일이 가능해진 후, 프로그램 제작 과정은 소스 파일들을 생성하고, 컴파일하고, 최종적으로 실행 파일을 실행하는 일련의 과정을 포함하게 되었다.

                      그러나 컴파일 과정에 CPU 시간을 포함한 상당히 많은 자원이 투자되고 컴파일을 통하여 생성된 많은 프로그램들이 계속 재 사용되는 부분을 포함하고 있다는 것을 차츰 깨닫게 되었다. 더욱이 누군가가 소스의 한 부분을 고치게 되면 그 소스 코드를 포함한 모든 소스코드를 다시 컴파일해야만 하는 불편함이 있다는 사실도 깨닫게 되었다.

                      위와 같은 이유 때문에 컴파일을 모듈별로 따로 하는 방식이 나오게 되었다. 이 방식은 메인 부분과 자주 재 사용되는 함수 부분을 따로 분리하여 재 사용되는 함수 부분을 특별한 장소에 미리 컴파일하여 놓아 두는 것이다. (후에 이를 라이브러리라고 부르게 된다.)

                      이로써 개발자는 재 사용되는 코드 부분을 다시 작성할 필요없이 위에 미리 컴파일된 목적 코드의 도움을 받으면서 프로그램을 제작할 수 있게 되었다. 그럼에도 불구하고, 이러한 과정 또한 프로그래머가 각각의 모듈들을 링크시킬 때 다 알고 있어야 한다는 점 때문에 복잡하기는 마찬가지였다.(또한 이러한 과정은 프로그래머가 알고 있는 함수만 쓸 수 있다는 제약점을 낳게 되었다.)

                     

                    3. 라이브러리란 무엇인가?

                      위에서 이야기한 여러 가지 불편함 때문에 사람들은 라이브러리를 만들게 되었다. 라이브러리란 링커가 이해할 수 있는 파일 포맷을 가지며 개발자가 라이브러리를 지정하면 링커가 알아서 프로그램에 필요한 모듈만 링크 시켜 주는 특수한 파일 형태일 뿐이다. 일 라이브러리를 통해 개발자는 거대한 라이브러리에 있는 함수들의 어떠한 종속성에도 신경쓰지 않고 라이브러리 내의 모든 함수를 마음껏 사용할 수 있게 되었다.

                      우리가 지금까지 이야기한 라이브러리는 현재에도 별로 변화하지 않고 있다. 다만 라이브러리 파일의 포맷이 조금 바뀌었는데 새로 바뀐 포맷에서는 파일 맨 앞에 그 라이브러리에 대한 정보와 링커가 모든 라이브러리 파일을 풀지 않고도 라이브러리 내부의 함수들을 알 수 있게 해주는 식별자를 가지고 있다. 리눅스에서는 ranlib(1)이라는 명령어가 이러한 기능(라이브러리 파일에 심볼 테이블을 더하는 작업)을 수행해 준다. 위에서 설명한 라이브러리를 정적 라이브러리(Static Library)라고 한다.

                      최초로 멀티 태스킹 시스템이 소개된 후 라이브러리에도 '코드의 공유'라는 변화가 몰려오게 된다. 만약 같은 시스템에서 같은 코드를 가진 두 개의 프로그램이 동시에 실행된다면, 두 프로그램의 코드는 공유될 수 있다.(프로그램실행 시, 프로그램이 코드를 전혀 바꾸지 않기 때문에 가능한 것이다.) 이러한 아이디어는 복수개의 중복된 코드를 메모리에 로드할 필요를 없애 주었고 거대한 다중 사용자 시스템에서 많은 양의 메모리를 절약할 수 있게 되었다.

                      위의 개혁에서 한 발짝 앞으로 더 나아가, 많은 프로그램들이 같은 라이브러리를 사용한다는 사실을 생각해보자. 비록 각각의 프로그램들이 같은 라이브러리를 사용한다고 해도, 그들이 라이브러리에서 사용하는 코드의 양은 각기 다를 것이다. 더욱이 메인 코드는 서로 마다 다 다를 것이다. 더욱이 메인 코드는 서로 마다 다 다를 것이고 따라서 그들의 텍스트 또한 공유할 수 없을 것이다. 여기서 만약 하나의 라이브러리를 사용하는 각기 다른 프로그램들이 라이브러리 코드를 공유할 수 있다고 가정한다면 상당한 메모리를 절약할 수 있을 것이다. 자 그럼 서로 다른 프로그램 텍스트를 가지며 동일한 라이브러리를 공유하는 서로 다른 프로그램이 있다고 가정해 보자.

                      그러나 프로세스의 입장에서 보면 일은 조금 복잡해 진다. 실행 가능한 프로그램은 완전히 링크된 것이 아니라, 라이브러리의 식별자들의 주소 값을 가지는데 그것도 프로그램의 프로세스로의 적재 때문에 지연된다. 링커는 자신이 공유 라이브러리를 취급하고 있다고 인지하고 프로그램에 공유된 코드를 포함시키지 않는다. 커널의 경우 exec() 함수를 통해 프로그램을 실행시킬 때 자신이 공유 라이브러리를 로딩하기 위한 특별한 프로그램을 실행시킨다.(이 프로그램은 그 자신의 텍스트에 대한 공유 메모리를 할당하고 라이브러리의 변수들을 위한 개인 메모리를 할당하는 등의 작업을 한다.) 실행 파일을 로딩할 때 이 프로세스는 실행되고 모든 프로시져는 더더욱 복잡하다.

                      물론 링커가 일반적인 라이브러리를 만났을 때에는 원래대로 행동한다.

                      공유 라이브러리는 목적 코드를 포함한 파일들의 집합이라기보다는 목적 코드를 포함한 파일 그 자체라고 볼 수 있다. 공유 라이브러리를 링킹하는 동안에 링커는 어떤 모듈이 프로그램에 첨가되고, 어떤 모듈은 빠져야 되는가에 대한 정보를 라이브러리로부터 얻어내지 않는다. 링커는 단지 할당되지 않은 주소 값이 제대로 할당되도록 보장만 하고 라이브러리를 포함함으로써 어떠한 것이 반드시 리스트에 추가되어야 하는가를 검사한다. 모든 공유 라이브러리들의 묶음을 만들 수도 있지만 대개의 경우 공유 라이브러리는 여러 다양한 모듈이 링크된 결과이고 따라서 라이브러리는 추후 실행시간에 필요하기 때문에 라이브러리들을 한 묶음으로 만드는 일은 드물다. 아마도 공유 라이브러리라는 말 보다도 공유 객체라는 말이 더 어울릴 것이다.(그럼에도 불구하고 공유 객체라는 용어를 사용하지 않는다.)

                       

                    4. 라이브러리의 형태

                      앞에서 미리 언급했지만, 리눅스에는 정적 라이브러리와 공유 라이브러리, 두  가지의 형태의 라이브러리가 존재한다. 정적 라이브러리들은 ar(1)이라는 유틸리티를 이용해 여러 모듈들을 하나의 파일에 모아놓고 ranlib(1) 이라는 유틸리티를 이용해 각 모듈들을 색인해 놓은 형태를 지니고 있다. 이러한 모듈들은 대개 파일의 이름이 .a로 끝나는 파일에 저장되어 있다. 링커는 파일의 이름이 .a로 끝나는 라이브러리를 만나게 되면 파일의 앞에 수록되어 있는 모듈들의 리스트를 뒤져서 라이브러리를 사용하는 프로그램 코드의 아직 정해지지 않은 주소 값들을 사용하는 모듈들의 주소 값으로 바꾸고 본 프로그램에 사용되는 모듈들을 더하게 된다.

                      반대로, 공유 라이브러리의 경우는 하나의 파일이라기 보다는 특수한 코드로 표시된 재 할당가능한 객체라고 볼 수 있다. 앞에서도 언급했듯이 링커 ld(1)는 프로그램 코드에 직접 모듈을 더하지 않고 라이브러리에서 제공하는 식별자를 골라서 프로그램 코드에 삽입하게 된다. 이대 모듈의 코드는 본 프로그램에 삽입되지 않으며 마치 본 프로그램에 이미 삽입되어 있는 척 하게 된다. 링커 ld(1)는 파일의 끝이 .so로 끝나는 것을 보면 공유 라이브러리로 인식한다.

                     

                    5. 리눅스에서의 링크 작업

                       모든 프로그램은 실행시키기 위해 연결된 목적 모듈들로 이루어져 있다. 이렇게 모듈들을 연결하는 역할을 수행하는 프로그램을 리눅스 링커 ld(1) 이라고 한다.

                      ld(1) 은 실행시 여러 가지 옵션을 제공하는데 본 기사에서는 편의를 위해 라이브러리 사용에 관한 옵션만을 다루도록 하겠다. ld(1) 은 사용자가 직접 실행시킬 수는 없으며 gcc(1) 와 같은 컴파일러가 컴파일의 마지막 단계에서 실행시키게 된다. ld(1)의 실행 방식을 이해함으로써 리눅스에서 라이브러리를 사용하는 방식을 좀 더 쉽게 이해할 수 있을 것이다.

                      ld(1)은 그 자체의 적합한 수행 조건으로 프로그램에 링크될 오브젝트파일의 리스트를 필요로 한다. 우리가 앞에서 이야기한  조건(공유 라이브러리는 .so로 끝나고 정적 라이브러리는 .a로 끝난다.)을 따르기만 한다면 ld(1)은 어떠한 순서로라도 리스트에 있는 오브젝트파일들을 읽어들일 수 있다.

                      그러나 실제로 '어떠한 순서로라도' 라는 용어가 꼭 맞지는 않는다. ld(1)은 라이브러리를 포함하는 순간에 주소 값을 결정하는데 필요한 모듈만을 포함하기 때문에 뒤에 포함되는 모듈에 의해 결정되어야 할 주소 값은 그대로 결정이 되지 않은 상태로 남게 된다. 따라서 실제로 어떠한 순서로라도 모듈을 읽어 들일 수 있는 것은 아니다.

                      반면에 ld(1)은 옵션 -I 과 -L을 이용하여 표준 라이브러리를 포함할 수도 있다.

                      그러나 표준 라이브러리라고 해서 다른 것은 별로 없다. 단지 표준 라이브러리의 경우는 미리 정해진 장소에서 모듈을 찾는 다는 것만이 다를 뿐이다.

                      라이브러리는 기본적으로 /lib 이나 /usr/lib 디렉토리에 위치한다. -L 옵션은 사용자가 기본 디렉토리 이외에 다른 디렉토리를 기본 디렉토리(기본적으로 라이브러리를 찾는 디렉토리) 목록에 추가시켜 준다. 사용법은 다음과 같다.

                      < -L 추가하고자 하는 디렉토리 이름 >

                      표준 라이브러리는

                      < -I 적재될 라이브러리의 이름 >

                      과 같은 방식으로 결정된다. 그러면 ld(1)은 순서대로 해당되는 디렉토리에서 libName.so 파일을 찾는다.(적재될 라이브러리의 이름이 libName 이라고 가정) 만약 찾지 못한다면 그 다음으로 libName.a를 찾게 된다.

                      만약 ld(1) 이 libName.so 파일을 찾게 되면, ld(1)은 지정된 라이브러리를 링크 시키게 된다. 그러나 libName.so는 찾지 못하고 libName.a를 찾았을 경우는 여기서 얻은 모듈로 주소 값을 결정하게 된다.

                       

                    6. 공유 라이브러리의 동적 링킹과 로딩

                      동적 링킹은 실행 파일을 메모리에 적재하는 순간에 /lib/ld-linux.so 라는 특수한 모듈에 의해 수행된다.(사실 이 자체도 공유 라이브러리이다.)

                      실제로 동적 라이브러리와 링크하기 위해서 두 개의 모듈이 있다. /lib/ld.so(예전 a.out 형식의 실행포맷에서 사용한다.) 와 /lib/ld-linux.so(이것은 EFL 실행포맷에서 사용한다.)이다.

                      이러한 모듈들은 프로그램이 동적으로 링크될 시기에는 반드시 로딩되어 있어야 한다. 이것들의 이름은 표준적인 것이다. (그래서 이것들을 /lib 디렉토리에서 옮기면 안되고 이것들의 이름도 변경되면 안된다.) 만약 /lib/ld-linux.so 라는 이름을 변경한다면 이 공유 라이브러리는 사용하는 어떠한 프로그램도 자동적으로 실행이 멈추게 된다. 왜냐하면 이 모듈은 실행시에 아직 해석되지 않은 모든 참조 위치를 해석하는 일을 맡고 있기 때문이다.

                      /lib/ld.so 모듈은 /etc/ld.so.cache 파일을 사용한다. 이 파일은 해당 라이브러리를 포함하는 대부분의 실행파일을 가리키고 있다. 이것에 대해서는 나중에 다시 말할 것이다.

                     

                    7. soname, 공유 라이브러리의 버전, 호환성

                      이제부터 공유 라이브러리에 대한 이해하기 어려운 주제에 대해서 이야기하려고 한다. 버전이 바로 그것이다. 이 기사를 읽은 분들은 종종 'library libX11.so.3 not found' 라는 에러메세지를 발견하곤 했을 것이다. 그러나 현재 자신의 라이브러리 버전을 libX.so.6 인 것을 보고 매우 당혹했을 것이고 이러한 에러 메시지에 대해서 어떻게 처리해야 할지 난감해 하는 경우가 있을 것이다. 어째서 ld.so(8)이 libpepe.so.45.0.1과 libpepe.so.45.22.3 은 서로 교환할 수 있는 버전으로 이해하지만 libpepe.so.46.22.3 은 그렇지 못하다고 인식하는 것일까?

                      리눅스에서(물론 다른 모든 ELF 형식을 가지는 운영체제에서도 마찬가지이지만) 라이브러리들은 이것들을 구별하는데 연속된 문자를 사용한다. 이것이 soname 이다.

                      soname은 라이브러리 내부에 포함되어 있고 연속된 문자들은 라이브러리들을 구성하는 대상이 링크될 때 결정된다. 공유 라이브러리가 만들어질 때 우리는 ld(1) 에 -soname 옵션을 넘겨주어야 한다. 물론 -soname 다음에는 이 문자열을 값으로 주어야 한다.

                      동적 로더는 어떠한 실행파일이 실행될 때 로드 되어지고 인식되어야 하는 공유 라이브러리를 인식하기 위해서 이 문자열을 사용한다. 이 과정은 다음과 같다. 먼저 ld-linux.so 는 프로그램이 어떠한 라이브러리가 필요하고 해당 soname 이 뭔지 결정한다. 그런 다음 해당 이름을 가지고 /etc/ld.so.cache에서 그 라이브러리를 포함하고 있는 파일의 이름을 얻어온다. 그리고 라이브러리 안에 존재하는 이름과 요청된 soname을 비교한다. 만약 서로 동일하다면 그것으로 끝이다. 그러나 만약 그렇지 않다면 발견될 때까지 검색을 하고 그래도 발견되지 않는다면 에러를 낸다.

                      만약 라이브러리가 로드되기에 적절한 것이라면 soname을 발견할 수 있을 것이다. 왜냐하면 ld-linux.so는 요청되어진 soname이 요구되어질 파일과 일치한다고 확신하기 때문이다. 만약 일치하지 않는 경우라면 보통 눈에 익은 'library libXXX.so.Y not found'가 나타날 것이다. 찾아야 하는 것이 soname이고 해당 soname을 참조할 때 주어지는 에러메세지인 것이다.

                      여기에서 우리는 라이브러리 이름이 변경되거나 지속성에 문제가 발생할 대에는 혼돈을 일으킬 소지를 가지고 있다. 그러나 Linux 사회에는 soname을 할당하는데 관례가 있기 때문에 soname을 접근하거나 soname을 변경하는 것을 좋은 생각이 아니다.

                      관례상 라이브러리의 soname은 반드시 적절한 라이브러리와 동일해야 하고 그러한 라이브러리에 대한 인터페이스여야 한다. 만약 라이브러리가 변경되어진다면 그것은 단지 내부적으로 일어나는 변화에 그쳐야 하지 전체적인 인터페이스에 영향을 미쳐서는 안되는 것이다. 즉 그 라이브러리에 대한 함수나 변수 그리고 함수에 대한 인자의 수 등에 영향을 안된다는 것이다. 이럴 경우에 구버전과 신버전 사이에 상호교환적이 되어야 한다. 즉 전체적으로는 변함이 없고 변경된 것은 적은 부분에서 이루어짐으로써 서로간에 교환 즉 호환성이 보장된다는 것이다. 적은 부분의 변화로 minor 숫자의 변경이 이루어지는 경우가 많다. 즉 이것은 soname 자체적으로는 변화가 없다는 것이다. 그리고 해당 라이브러리는 어떠한 중대한 문제없이 교체할 수 있게 되는 것이다.

                      그러나 함수를 더하거나 제거하거나 하는 경우 등과 같은 인터페이스가 변경되는 경우에는 상호 교환이 불가능하다는 것을 쉽게 짐작할 수 있을 것이다. 예를 들면 libX11.so.3 과 libX11.so.6 사이를 보면 알 수 있다. X11R5 에서 X11R6 로 업그레이드 되면서 새로운 많은 함수들이 정의되어졌고 인터페이스에도 변경이 가해졌는데 이것들 사이에 상호교환이라는 것은 큰 문제를 유발할 수 있게 될 것이다. X11R6-v3.1.2에서 X11R6-v3.1.3과 같은 변화는 인터페이스에는 변화가 없고 그 라이브러리도 같은 soname을 가지고 있다. 이 경우도 구 버전을 유지하기 위해서는 다른 이름을 주어야 하는 것은 당연하다.(이러한 이유로 라이브러리의 이름에 soname 에 보여지는 주어진 번호가 있고 그 외에 전체적인 버전 숫자가 나타나는 것이다.)

                     

                    8. ldconfig(8)

                      /etc/ld.so.cache 라는 파일을 언급한 것이 있는데 이것은 라이브러리가 어떠한 파일에 포함되어져 있는지에 대한 정보를 가지고 있는 것이다. 이것은 표율성을 위해서 바이너리 파일로 만들어지고 ldconfig(8) 유틸리티에 의해서 만들어진다. ldconfig(8)은 /etc/ld.so.conf에 지정된 디렉토리를 찾아다니면서 발견된 각각의 동적 라이브러리들에 대해서 그 라이브러리의 soname 에 의해서 불리어지는 심볼릭 링크들을 만들어낸다. ld.so가 파일의 이름을 얻으려고 할 때 이것은 soname을 발견한 파일의 디렉토리를 선택하는 그러한 일을 한다. 그리고 이렇게 함으로서 우리가 라이브러리를 추가할 때마다 ldconfig(8)를 수행할 필요는 없게 되는 것이다. ldconfig는 리스트에 새로운 디렉토리를 만들어 널때만 실행시키면 된다.

                     

                    9. 동적 라이브러리 만들기

                      동적 라이브러리를 만들기 전에 고려할 사항이 있다. 그것은 바로 이렇게 동적 라이브러리로 만들었을 때 유용한가 하는 것이다. 동적 라이브러리는 여러 가지 이유로 시스템에 부하를 주게 된다. 프로그램의 로딩중에는 여러 가지 단계가 있다. 첫째는 메인 프로그램을 로딩하는 것이고 그 외에 프로그램에서 사용하는 각 동적 라이브러리를 로딩한다.

                      동적 라이브러리는 재할당이 가능한 코드로 만들어져야 한다. 이것은 당연한 것으로 프로세스를 위해서 가상 주소 공간 안에 할당된 주소가 해당 프로그램이 로드될 때까지 알 수 없기 때문이다. 컴파일러는 라이브러리의 로딩위치를 유지하기 위해서 레지스터를 예약해 놓아야 한다. 그리고 결과적으로 코드 최적화에서 이것 때문에 레지스터 하나를 사용할 수 없게 된다. 이러한 경우는 사소하다고 볼 수 있다. 왜냐하면 이러한 것 때문에 나타나는 시스템의 부하는 대부분의 경우 다른 부하들의 5%이하이기 때문이다.

                      동적 라이브러리가 적절한 것이 되려면 다른 여러 프로그램에서 자주 사용되어야 한다. 즉 이렇게 하면 시작한 프로세스가 없어진 이후에도 그 라이브러리에 대한 TEXT가 다시 로딩되는 것을 방지할 수 있다. 다시 로딩이 안되는 이유는 해당 라이브러리는 다른 프로그램에서 사용하고 있기 때문에 메모리에서 없애 버리면 안되기 때문에 항상 메모리가 남아있어서 다시 불러들일 필요가 없어진다.

                      동적 라이브러리의 좋은 예는 C 표준 라이브러리이다. (이 라이브러리들은 대부분의 C 프로그램을 작성하는데 사용 되어진다.)평균적으로 모든 함수들이 자주 사용되어진다.

                      정적 라이브러리의 경우에 좀처럼 드물게 사용하는 함수를 포함할 때 유리하다. 즉 해당 함수는 이것을 포함하고 있는 모듈에 있기 때문에 내가 필요하지 않으면 링크를 시키지 않으면 된다.

                     

                      9.1  소스코드의 컴파일

                      소스의 컴파일은 한 가지만 제외하고는 대부분의 소스를 컴파일하는 경우와 같은 형식으로 수행된다. 차이점이라면 프로세스의 가상 주소 공간 안의 다른 지역에 로드될 수 있도록 하는 코드를 만들기 위해서 '-f PIC' -PIC(Position Independent Code)- 라는 옵션을 사용해야 한다는 것이다.

                      동적 라이브러리에서 이 단계는 반드시 필요한 것이다. 왜냐하면  정적으로 링크된 프로그램에서는 라이브러리 오브젝트의 위치가 링크시에 결정되고 그래서 고정된 시간이 되어 버리기 때문이다. 예전의 a.out 실행형식에서는 이러한 단계가 불가능했다. 왜냐하면 각 공유 라이브러리가 가상주소의 공간 안에 고정된 위치를 가지게 만들어졌기 때문이다. 결과적으로 가상메모리의 오버래핑된 지역에 로드된 두 라이브러리는 사용하길 원하는 프로그램에서는 어떠한 경우에는 충돌이 발생하게 되었다. 이것은 라이브러리들의 리스트를 유지해야만 하는 결과를 만들었다. 즉 동적 라이브러리로 만들기를 원한다면 해당 라이브러리가 어디에서 어디까지 사용한다는 것을 선언하는 부분이 반드시 필요하게 되어진 것이고 이 영역을 다른 사람들이 사용하면 안되게 된 것이다.

                      그렇다면 우리가 이전에 언급한 것에 따르면 공식적인 리스트에 동적 라이브러리를 등록하는 것이 필요없게 된 것이다. 왜냐하면 라이브러리는 로딩이 되어질 때 로드될 위치를 결정하게 되기 때문이다. 이것은 코드를 위치조정이 가능하게 만들었기 때문에 가능한 일이다.

                       

                      9.2  라이브러리의 오브젝트를 링크하기

                      모든 객체를 컴파일 한 후에 동적인 로딩이 가능한 오브젝트파일로 만들기 위한 특별한 옵션이 필요하다.

                      gcc -shared -o libName.so.xxx.yyy.zzz -Wl, -soname, libName.so.xxx

                      짐작할 수 있듯이 공유 라이브러리를 만들어내는 옵션이 보이는 것을 제외하고는 보통의 링크과정처럼 보인다.
                      하나하나 확인해보자.

                      -shared

                      이것은 공유 라이브러리를 만들어라 하는 옵션이다. 그래서 라이브러리에 관련된 출력파일에 실행 가능한 형태가 되는 것이다.

                      -o libName.so.xxx.yyy.zzz

                      최종적인 출력파일이름이다. 이름을 짓는데는 보통의 관례를 따를 필요는 없지만 이것은 표준적으로 개발에 사용되기를 원한다면 관례를 따르는 것이 좋다.

                      Wl, -soname, libName.so.xxx

                      이 -W1옵션은 gcc(1)가 콤마로 부분된 다음 옵션들을 링커에게 알려주기 위한 것이다. 이것은 gcc(1)가 ld(1)에게 옵션을 넘겨주기 위한 방법이다. 이 예에서는 링커에게 다음과 같은 옵션을 넘겨준 것이다.

                      -soname libName.so.xxx

                      이 옵션은 라이브러리에 대한 soname을 고정시키는 것이다. 그리고 필요한 다른 프로그램에서는 이 soname을 사용해야만 한다.

                       

                      9.3 라이브러리를 설치하기

                      이미 관련된 실행파일을 가지고 있게 되었다. 이제는 이것을 적절한 장소에 설치하고 사용하기만 하면 된다. 새로운 라이브러리를 원하는 프로그램을 컴파일하기 위해서 다음과 같이 하면 된다.

                      gcc -o program libName.so.xxx.yyy.zzz

                      또는 만약 라이브러리가 /usr/lib에 설치되어 있다면 단순히 다음과 같이 하는 것만으로 충분하다.

                      gcc -o program -lName

                      (만약 /usr/local/lib에 있다면 '-L/usr/local/lib'라는 것을 추가 시켜주면 된다.)

                      라이브러리를 설치하기 위해서는 다음과 같이 한다.

                      라이브러리를 /lib 난 또는 /usr/lib 에 복사한다. 만약 다른 장소에 복사하기로 했다면(예를 들면 /usr/local/lib) 링커인 ld(1)가 자동적으로 링크할 수는 없을 것이다. libName.so.xxx.yyy.zzz로부터 libName.so.xxx 라는 심볼릭링크를 만들기 위해서 ldconfig(1)을 수행한다. 이 단계에서 만약 이전단계가 확실히 수행되어졌다면 이 라이브러리는 동적 라이브러리로 인식이 될 것이다. 링크된 프로그램들은 이 단계에서는 영향을 받지 않는다. 이것은 실행시에 라이브러리가 로딩될 때 영향을 받는 것이다.

                      libName.so.xxx.yyy.zzz(또는 soname 인 libName.so.xxx)로부터 libName.so 라는 심볼릭 링크를 만들어야 한다. 이렇게 해야만 하는 이유는 링커가 -1옵션으로 라이브러리를 발견할 수 있게 하기 위해서이다. libName.so 라는 형식에 맞추어진 라이브러리의 이름이 필요한 경우를 위해서 고안된 작동방법이다.

                       

                    10. 정적 라이브러리 만들기

                      만약에 위와는 반대로 정적 라이브러리를 만드려고 한다면(또는 정적으로 링크된 것과 동적으로 링크된 것 둘 다를 원한다면) 다음과 같은 과정을 따른다.
                      주의 : 라이브러리를 발견하는데 있어서 링커는 libName.so 라는 파일을 먼저 찾고 그 다음에 libName.a를 찾는다. 만약 두 개의 라이브러리(정적인 버전과 동적인 버전)를 같은 이름으로 부른다면 일반적으로 각각의 경우를 링크(동적인 것이 항상 먼저 링크되기 때문에)하는 것을 결정하는 것은 불가능하게 된다.

                      이러한 이유로 항상 만약에 두 개의 라이브러리가 필요한 버전이 있다면 정적인 것은 libName_s.a 라고 이름을 붙여주고 동적인 것은 그냥 libName.so 라고 이름 붙여줄 것을 권하고 싶다. 링크할 때에는 그래서 다음과 같이 하면 될 것이다.

                      gcc -o program -lName_s

                      이 경우는 정적인 경우이고 동적인 것을 만들 때면 다음과 같이 한다.

                      gcc -0 program -lName

                       

                      10.1  소스를 컴파일하기

                      소스를 컴파일하기 위해서는 특별한 방법은 없다. 위에서 말한 대로 링크단계에서 결정해야 한다. 필요하다면 -f PIC 옵션을 사용하는 것이다.

                       

                      10.2  라이브러리를 객체에 링크하기

                      정적 라이브러리의 경우에는 링크 단계가 필요없다. 모든 객체는 ar(1) 명령어의 해서 라이브러리 파일들을 만들 수 있다. 그런 다음 심볼들을 빠르게 해석해내기 위해서 라이브러리에 대한 ranlib(1) 명령을 수행하는 것이 좋다. 비록 반드시 필요한 것은 아니지만 이 명령을 실행하지 않으면 실행파일에 대한 모듈에 대한 링크가 지워질지도 모른다.

                       

                      10.3  라이브러리의 설치

                      정적 라이브러리는 만약 오로지 정적 라이브러리 형태로만 사용하려고 한다면 libName.a 형태로 되어있는 이름을 가지고 있을 것이다. 그러나 두 가지 형태의 라이브러리를 모두 가지고 있기를 바란다면 libName_s.a 라고 이름을 지어줄 것을 권하고 싶다. 이것은 정적이나 또는 동적으로 로딩하는 것을 쉽게 제어하기 위해서 이다.

                      링크단계에서 -static 옵션을 줄 수 있다. 이 옵션은 /lib/ld-linux.so 모듈의 로딩을 제어하는 것이다. 그러나 이것은 라이브러리의 검색 순서에 영향을 미치지는 않는다. 그래서 만약에 -static 으로 지정하고 ld(1)이 동적 라이브러리를 발견한다면 ld(1)는 발견된 동적 라이브러리를 가지고 작업을 할 것이다. 즉 -static 이라고 지정했다고 해서 계속적으로 정적으로 되어 있는 것을 찾지는 않는다는 것이다. 이것은 실행시에 라이브러리 루틴을 호출할 때 에러를 발생시킬 수 있다. 왜냐하면 동적 라이브러리는 실제 자신의 프로그램에는 해당 라이브러리가 있지 않기 때문이다. -자동적으로 동적인 로딩을 위한 모듈이 링크되어지지 않았고 그래서 이 단계가 수행될 수 없으므로 에러를 내는 것이다. 그래서 사용할 때 -static이 주어지면 링커가 정확한 라이브러리를 찾도록 해주어야 한다.

                       

                    11.정적 링크하기와 동적 링크하기

                      잘 알려진 라이브러리를 사용한 프로그램을 배포하는 경우를 생각해보자.(아마 모티프라이브러리를 사용한 것이 적절한 예가 될 것이다.)

                      이런 종류의 소프트웨어를 만들기 위해서는 세가지 선택 사항이 있다.
                      첫 째는 실행파일을 정적으로 링크시켜 만드는 것이다.(오직 .a 라이브러리만을 사용한다.) 이러한 종류의 프로그램은 오직 한번만 로드되어지고 시스템 안에 있는 어떠한 라이브러리도 가지고 있을 필요가 없다.(심지어 /lib/ld-linux.so 까지도) 그러나 이것은 바이너리 파일 안에 해당 소프트웨어에 필요한 모든 것들을 가지고 다녀야 하는 단점이 있다. 그리고 대부분 프로그램의 크기가 매우 크다.
                      두 번째는 동적으로 만드는 것이다. 이것은 즉 우리의 소프트웨어가 실행되기 위해서는 연관된 모든 동적 라이브러리들이 모두 제공되어져야 한다는 것을 의미한다. 실행파일은 매우 작지만 모든 라이브러리를 모두 가지고 다니는 것은 쉽지 않은 일일 것이다.(예를 들면 Motif를 가지고 있지 않은 사람도 있다.)
                      세 번째 선택 사항이 있다. 이것은 두 가지 형태를 서로 합치는 것이다. 즉 어떠한 라이브러리는 동적으로 또 어떤 라이브러리는 정적으로 해서 두 개의 장점을 모두 취하는 것이다. 이러한 형태의 소프트웨어가 배포에는 가장 편한 방법이 된다.

                      예를 들면, 세가지 다른 라이브러리 포함 형태는 다음과 같다.

                      gcc -static -o program.static program.o -lm_s -lXm_s -lXt_s -lX11_s -lXmu_s -lXpm_s

                      gcc -o program.dynamic program.o -lm -lXm -lXt -lX11 -lXmu -lXpm

                      gcc -o program.mixed program.o -lm -lXm_s -lXt -lX11 -lXmu -lXpm

                      세 번째 경우에 Motif 라이브러리인 Motif(-lXm_s)는 정적인 결과를 얻고 그리고 다른 것들은 동적인 결과를 얻게 된다. 프로그램이 실행되기 위해서는 적당한 버전을 위한 라이브러리들이 필요하게 된다. 여기에서는 libm.so.xx, libXt.so.xx, libX11.so.xx, libXmu.so.xx, libXpm.so.xx 등이 필요에 따라서 사용될 것이다.



                     

                    컴파일 참고

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

                    gcc -fPIC -m64 -c 파일명.c

                    gcc -fPIC -m64 -I{INCLUDE_PATH} -c 파일명.c

                    ex) gcc -fPIC -m64 -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -c 파일명.c

                     

                    gcc -shared -o 출력라이브러리명.so -WI,soname,라이브러리버전명.so.xx.xx.xx 목적파일1.o 목적파일2.o ....

                     

                    Makefile 생성시

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

                    INCLUDEPATH = -I$(JAVA_HOME)/include \

                    -I$(JAVA_HOME)/include/linux

                     

                    BITS = -m64

                    CC = gcc

                     

                    TARGET = 출력라이브러리명.so

                    OBJECTS = 목적파일1.o \

                    목적파일2.o ...

                     

                    $(TARGET) : $(OBJECTS)

                    $(CC) $(BITS) -g -shared -o $@ -WI,soname,$@ @^

                     

                    .c.o :

                    $(CC) $(BITS) -g -fPIC $(INCLUDEPATH) -c $<

                     

                    clean :

                    rm *.so *.o

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

                     

                     


                    반응형
                    반응형

                    출처 : http://cafe.naver.com/cafec/260475

                     

                     

                    1. 동기(
                    同期, synchronous)

                    동기의 사전적 의미는 "동시에 발생하는 것" 입니다.
                    클라이언트서버에 "저 지금 접속했습니다." 라고 요청했다면,
                    서버가 "아 그래? 너 접속했구나? 너 접속했다고 해줄께." 라고 대답할 때까지 클라이언트가 무릎 꿇고 가만히 기다려야합니다.
                    "이게 동시에 발생하는 것과 무슨 상관이 있지?"
                    라고 생각하시는 분들을 위해, 간단한 예를 들어 설명해드리겠습니다.

                    제가 고또님께 꼭 드릴 말씀이 있어 전화를 걸었습니다.
                    그런데 고또님이 전화를 받지 않으십니다.
                    저는 고또님이 전화를 받으실 때까지 계속 전화기를 붙잡고 기다립니다.

                    이 과정에서 저는 전화기를 붙잡고 기다리느라 아무런 행동도 할 수 없습니다.
                    (물론 코를 판다던가, 하품을 한다던가 하는 자잘한 행동들은 예외지만요.)

                    이를 적용하면, 클라이언트는 서버가 답하기 전까지 죽어버린다는 겁니다.

                    그러니 클라이언트가 언제 요청할지, 서버가 언제 답할지를 프로그램 설계시에 명확히 해야겠습니다.

                    이를 동시에 요청하고 응답한다고 말할 수도 있지 않을까요?
                    (물론 완벽히 동시는 아니지만, "거의" 동시니까요 ^^)




                    2. 비동기(非同期, asynchronous)

                    비동기의 사전적 의미는 "동시에 발생하지 않는 것" 입니다.
                    동기 앞에 아닐 비(非)가 붙었으니 당연하려나요? ^^;
                    이번에는 바로 예를 들어보겠습니다.

                    제가 고또님께 꼭 드릴 말씀이 있어 전화를 걸었습니다.
                    그런데 고또님이 전화를 받지 않으십니다.
                    저는 전화기를 스피커폰으로 전환하고 게임을 하면서 기다립니다.

                    동기와는 다르게 전화를 걸면서 게임을 하고 있습니다!

                    이를 프로그램에 적용해볼까요?

                    클라이언트는 서버에게 요청을 하고, 그 요청이 오기 전까진 다른 일을 할 수 있습니다.

                    즉, 서버가 응답하지 않는다고해서 프로그램이 죽어버리지는 않는다는 것이죠!
                    (물론 서버가 응답하지 않으면 프로그램을 종료시킨다던가의 루틴은 제외하구요..)




                    ※ "그럼 동기보다 비동기가 무조건 좋은 것이 아니냐?"

                    라고 하시는 분들이 계실 것 같습니다.

                    아닙니다. 동기와 비동기가 둘 다 존재하는 이유는 동기와 비동기가 각각 쓰임새가 다르기 때문입니다.




                    3. 블로킹(blocking)

                    블로킹"무언가를 막는다"는 뜻입니다.
                    무엇을 막을까요?
                    동기식으로 설계를 했을 때, 응답이 오기 전까지 다음 루틴으로 넘어가는 것을 막습니다.

                    즉, 고또님이 전화를 받으시기 전까지(응답이 오기 전까지)
                    다른 행동(다음 루틴)을 못하도록 막는 것이죠.

                    다음에 소켓 프로그래밍에 대한 예제에서 다시 설명드리겠지만,

                    socket 함수로 소켓을 생성하면 기본적으로 블로킹 모드로 소켓이 생성됩니다.

                    이 상태에서 send 함수나 recv 함수를 사용하게 돼면 응답이 있기 전까지 return을 하지 않습니다.

                    함수에서 계속 머뭅니다.




                    4. 논-블로킹(non-blocking)

                    앞에 "~이 아닌" 이라는 접두어 "non"이 붙었으니 "무언가를 막지 않는다."라는 뜻이겠죠?
                    블로킹과는 반대로 비동기식으로 설계했을 때, 응답이 왔든 안왔든 다음 루틴으로 넘어가는 것을 막지 않습니다.

                    고또님께 전화를 걸어 놓고(서버에 요청하고)
                    스피커 폰으로 전환한 뒤(서버에서 응답이 오든 안오든)
                    게임을 하면서(다음 루틴을 실행하면서)
                    고또님이 전화를 받으시면(서버에서 응답하면)
                    고또님과 대화하면 되는거죠.(서버와 통신)

                    이 또한 다음에 다시 설명드리겠지만,

                    fcntl 함수로 소켓을 논-블로킹 상태로 만들 수 있습니다.

                    이때에, send 함수와 recv 함수를 실행했을 때, 보내거나 받을 데이터가 있으면 보내고 받지만,

                    그렇지 않을 경우 errno.h 헤더파일에 존재하는 errno 변수에 상태를 넣어주므로

                    errno 변수의 값을 판단하여 어떤 행동을 할지 설계하면 됩니다.

                    꼭 이 방법 뿐만 아니라 다른 많은 방법으로도 비동기식으로 설계가 가능하다고 합니다.






                    시험이 끝난 후에 간단한 예제와 함께 돌아오겠습니다!

                    혹시 잘못된 점이 있으면 따끔히 지적해주세요!

                    읽어주셔서 감사합니다. m(_ _)m

                     

                    반응형
                    반응형

                    _open

                    _O_APPEND
                    모든 쓰기 작업 전에 파일 포인터를 파일 끝으로 이동한다.
                    _O_BINARY
                    모드로 파일을 연다. (fopen 참고)
                    _O_CREAT
                    쓰기위해 생성하고 새로 만든 파일을 연다. 만약 이미 존재한 파일이면 아무런 효과가 없다. _O_CREAT가 쓰일 때 pmode 인자를 필요로 한다.
                    _O_CREAT | _O_SHORT_LIVED
                    만약 디스크에서 flush가 가능 하지 않을때 임시로서 파일을 생성한다. _O_CREAT가 쓰일 때 pmode 인자를 필요로 한다.
                    _O_CREAT | _O_TEMPORARY
                    임시로 파일을 생성한다.; 마지막 파일 descriptor가 닫혀질때 파일은 삭제된다. O_CREAT가 쓰일 때 pmode 인자를 필요로 한다.
                    _O_CREAT | _O_EXCL
                    파일이 이미 존재한다면 에러를 리턴한다. 오직 _O_CREAT일때만 적.
                    _O_RANDOM
                    Specifies that caching is optimized for, but not restricted to, random access from disk.
                    _O_RDONLY
                    오직 읽기위해 연다.; O_RDWR or _O_WRONLY와 함께 쓰여질 수 없다.
                    _O_RDWR
                    읽고 쓰기위해 연다.; _O_RDONLY or _O_WRONLY와 함께 쓰여질 수 없다.
                    _O_SEQUENTIAL
                    Specifies that caching is optimized for, but not restricted to, sequential access from disk.
                    _O_TEXT
                    text모드로 변환되어 연다. (더 많은 정보는 Text and Binary Mode File I/O 과 fopen 참고.)
                    _O_TRUNC
                    Opens file and truncates it to zero length; file must have write permission. You cannot specify this flag with _O_RDONLY. _O_TRUNC used with _O_CREAT opens an existing file or creates a new file.
                    Note The _O_TRUNC flag destroys the contents of the specified file.
                    _O_WRONLY
                    오직 쓰기위해 연다.; _O_RDONLY 또는 _O_RDWR와 함께 쓰여질 수 없다.

                    밑에 있는 플래그는 SYS\STAT.H 에 정의 되어있다.

                    _S_IREAD

                    오직 읽기만 허락됨.

                    _S_IWRITE

                    쓰기가 허락됨(실제로 쓰기, 읽기가 허락됨).

                    _S_IREAD | _S_IWRITE

                    쓰기, 읽기가 허락됨.

                    _close

                    _read

                    _write

                    _lseek - FP를 특정한 위치로 이동한다.

                    SEEK_SET
                    Beginning of file.
                    SEEK_CUR
                    Current position of file pointer.
                    SEEK_END

                    _tell - FP위치를 얻는다.

                    _access - 파일 접근의 허가를 결정한다.

                    00 오직 존재한다.
                    02 쓰기 허가
                    04 읽기 허가
                    06 읽고쓰기 허가

                    remove - 파일을 삭재한다.

                    _unlink - 파일을 삭재한다.

                    rename - 파일이나 디렉토리의 이름을 바꾼다.

                    _chmod - 파일 허가 설정을 바꾼다.

                    _S_IWRITE
                    쓰기를 허락한다.
                    _S_IREAD
                    읽기를 허락한다.

                    _findfirst - 파일을 처음으로 검색한다.

                    _findnext - 다음으로 검색한다.

                    _findclose - 검색을 마친다.

                    struct _finddata_t {

                    unsigned attrib;

                    time_t time_create; /* -1 for FAT file systems */

                    time_t time_access; /* -1 for FAT file systems */

                    time_t time_write;

                    _fsize_t size;

                    char name[260];

                    };

                    _chdir - 지금 활동중인 디렉토리를 바꾼다

                    _mkdir - 새로운 디렉토리를 만든다.

                    _rmdir - 디렉토리를 삭제한다.

                    _getcwd - 지금 활동중인 디렉토리를 얻는다.

                    _splitpath - 파일 경로를 각 구성성분으로 나눈다.

                    _makepath - 구성성분으로부터 파일 경로를 만든다.

                    _getdrive - 현재의 드라이브를 얻는다.

                    _chdrive - 현재 활동중인 드라이브를 바꾼다.

                    _getdiskfree - 디스크 정보인 밑의 구조체를 얻는다.

                    struct _diskfree_t {

                    unsigned total_clusters;

                    unsigned avail_clusters;

                    unsigned sectors_per_cluster;

                    unsigned bytes_per_sector;

                    };

                    반응형
                    반응형

                     

                     

                     

                    1. JAVA_HOME

                    자바 설치 디렉토리

                    ex) C:\Program\Java\jdk.1.7.0_04

                     

                     

                    2. PATH - 전역으로 해도 되고 사용자로 해도 되지만 기본적으로 사용자 설정만

                    ex) %JAVA_HOME%\bin 추가.

                     

                    3. CLASS_PATH

                    ex) %JAVA_HOME%\lib;. 추가

                     

                    반응형
                    반응형

                    출처 : http://blog.naver.com/dalmagru?Redirect=Log&logNo=70068719762

                     

                    wait(), waitpid() : 부모 프로세스가 자식 프로세스가 종료했음을 확인하는 함수.

                    waitpid() : 인자로 프로세스ID를 받음으로써 특정 자식 프로세스의 종료를 기다릴수 있다.

                     

                    함수원형

                    #include <sys/types.h>
                    #include <sys/wait.h>

                    pid_t wait(int *status);
                    pid_t waitpid(pid_t pid, int *status, int options);

                     


                    ※ 자식 프로세스의 상태를 확인하는 매크로들

                    매크로

                    세부사항

                    WIFEXITED(status)

                    0이 아닌 값을 리턴하면 자식프로세스가 정상종료했다는 뜻이다.

                    WEXITSTATUS(status)

                    WIFEXITED(status)매크로를 통하여 자식 프로세스가 정상종료했음을 확인하면 이 매크로를 통하여 종료 코드를 확인할 수 있다. 이 종료 코드는 exit()나 _exit()에서 인자로 주는 값을 말한다. 즉 exit(0)으로 프로그램을 종료했다면 이 0 값이 WIFEXITED 매크로로 알수 있다. 단, 이 매크로는 하위 8비트 값만을 확인하므로 0부터 255까지의 값까지 확인할 수 있다.

                    WIFSIGNALED(status)

                    이 매크로가 참이라면 자식 프로세스가 비정상 종료했다는 뜻.

                    WTERMSIG(status)

                    SIFSIGNALED(status)매크로가 참일 경우 자식 프로세스를 종료시킨 시그널 번호를 얻는 매크로

                    WIFSTOPPED(status)

                    이 매크로가 참이면 자식 프로세스는 현재 멈춰있는(stopped) 상태이다. 다음에서 살펴볼 option인자에 WUNTRACED옵션이 설정되어 있는 경우 자식 프로세스의 멈춤 상태를 알아낼수 있다.

                    WSTOPSIG(status)

                    WIFSTOPPED(status)매크로가 참일 경우 자식 프로세스를 멈춤상태로 만든 시그널번호를 얻는다.

                    WCOREDUMP(status)

                    시스템에 따라서는 WIFSIGNALED(status)가 참일 경우 자식 프로세스가 core덤프 파일을 생성했는지를 확인하는 이 매크로를 제공해주기도 한다.

                     

                     

                    waitpid()에서 사용하는 pid인자값의 의미

                    pid의 값

                    세부사항

                    pid < -1

                    pid의 절대값과 동일한 프로세스 그룹ID의 모든 자식 프로세스의 종료를 기다린다.

                    pid == -1

                    모든 자식 프로세스의 종료를 기다린다.
                    만약 pid값이 -1이면 waitpid함수는 wait()함수와 동일하다.

                    pid == 0

                    현재 프로세스의 프로세스 그룹ID와 같은 프로세스 그룹ID를 가지는 모든 자식 프로세스의 종료를 기다린다.

                    pid > 0

                    pid값에 해당하는 프로세스 ID를 가진 자식 프로세스의 종료를 기다린다.

                     

                     

                    waitpid()에서 사용하는 option인자

                    인자

                    인자의 의미

                    WNOHANG

                    waitpid()를 실행했을 때, 자식 프로세스가 종료되어 있지 않으면 블록상태가 되지 않고 바로 리턴하게 해준다.

                    WUNTRACED

                    pid에 해당하는 자식 프로세스가 멈춤 상태일 경우 그 상태를 리턴한다.
                    즉 프로세스의 종료뿐 아니라 프로세스의 멈춤상태도 찾아낸다.

                     

                    좀비 프로세스 (zombie process) : 자식 프로세스가 종료되었지만, 부모 프로세스가 아직 그 종료를 확인하지 않는 프로세스

                    고아 프로세스 : 자식보다 먼저 부모프로세스가 죽었을 경우의 자식 프로세스.

                    [출처] wait(), waitpid()|작성자 달마

                     

                    반응형
                    반응형

                     

                     

                    Language C - Libxml

                     

                    libxml2을 설치하면 libxml 폴더에 각종 헤더 파일이 들어있다.

                    현재 내가 사용하는 header 파일은 아래 두개인데..

                    물론 더 많이 사용하는 법이 있겠지만 기본적으로 사용하는 법을 알아보자..

                     

                    libxml/xmlmemory.h

                    libxml/parser.h

                     

                    노드 타입은 아래와 같다

                    참고 할 것.

                    XML_ELEMENT_NODE= 1,
                    XML_ATTRIBUTE_NODE= 2,
                    XML_TEXT_NODE= 3,
                    XML_CDATA_SECTION_NODE= 4,
                    XML_ENTITY_REF_NODE= 5,
                    XML_ENTITY_NODE= 6,
                    XML_PI_NODE= 7,
                    XML_COMMENT_NODE= 8,
                    XML_DOCUMENT_NODE= 9,
                    XML_DOCUMENT_TYPE_NODE= 10,
                    XML_DOCUMENT_FRAG_NODE= 11,
                    XML_NOTATION_NODE= 12,
                    XML_HTML_DOCUMENT_NODE= 13,
                    XML_DTD_NODE= 14,
                    XML_ELEMENT_DECL= 15,
                    XML_ATTRIBUTE_DECL= 16,
                    XML_ENTITY_DECL= 17,
                    XML_NAMESPACE_DECL= 18,
                    XML_XINCLUDE_START= 19,
                    XML_XINCLUDE_END= 20

                     

                    참고 : http://blog.naver.com/kdepirate?Redirect=Log&logNo=50033294493

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

                    [C] XML Parser (libxml)



                    간단한 사용법 및 자주 쓰이는 함수


                    ○ 기본 데이타타입

                    ㅁxmlDoc, xmlDocPtr

                    ㅁxmlNode, xmlNodePtr

                    ㅁxmlAttr, xmlAttrPtr

                    ㅁxmlChar

                     


                    ○ XML문서 파싱할때

                    ㅁdoc = xmlParseDoc(string of docuement), xmlParseFile(filename) : XML문서 파싱

                    ㅁnode = xmlDocGetRootElement(doc) : 문서의 ROOT원소를 받음

                    ㅁnode = node->xmlChildrenNode : 그 밑의 노드로 감

                    ㅁnode->name : 노드의 이름

                    ㅁstr = xmlNodeGetContent(node) : 노드의 내용을 얻음

                    ㅁstr = xmlGetProp(node, property name) : 노드의 property내용을 얻어옴

                    -> xmlGetProp로 얻은 xmlChar * 내용은 xmlFree 또는 Free 해당 메모리영역을 해제 해줘야 한다.

                        단 xmlFree의 경우 버그가 있다고 알려져 있는 듯한데 ... 그렇다고 free로 해제하면 SF가 날때가 있다...

                        좀 더 여러가지로 사용해 봐야 할듯한데 우선 xmlFree로 하자.

                    ㅁnode = node->next : 다음 노드로 감

                     

                     

                     

                     

                    [출처] libxml을 사용하여 xml만들거 파싱하기 (Crazy For Computer) |작성자 coolpiece416

                     

                    <함수>

                    xmlNewDoc //xml전체를 의미하는 document를 생성.

                    xmlNewNode //노드를 하나 추가하낟.

                    xmlDocSetRootElement //해당 노드를 root노드로 지정한다.

                    xmlNodeSetContent //생성된 노드에다가 내용을 넣는다.

                    xmlAddChild //한 노드를 다른 노드의 child로 단다.

                    xmlDocDumpMemory //생성된 xml전체를 가져온다. -> <root><aaa>qqqq</aaa><bbb>zzz</bbb></root> 이렇게 라인으로.

                    xmlParseDoc //xmlChar* 의 형태로 저장된 xml 데이터를 파싱되어 xmlNewDoc형태로 구성한다.

                    xmlDocGetRootElement //root 를 가져온다.

                    xmlNodeGetContent //해당 노드의 내용을 가져온다.

                     

                     

                    <xml 만들기>

                    xmlDocPtr doc;

                    xmlNodePtr rootNode;

                    xmlNodePtr subNode;

                    xmlChar* xmlData;

                    int size;

                    doc = xmlNewDoc((xmlChar*)"1.0"); //1.0 버전으로 생성

                    rootNode = xmlNewNode(NULL,(xmlChar*)"root"); //root라는 이름의 새 노드 생성

                    xmlDocSetRootElement(doc,rootNode); //rootNode를 root노드로 지정

                    subNode = xmlNewNode(NULL,(xmlChar*)"Company"); //Company라는 이름의 새 노드 생성

                    xmlNodeSetContent(subNode,(xmlChar*)"한국회사"); //Company노드의 내용 등록

                    xmlAddChild(rootNode,subNode); //rootNode에 subNode를 단다.

                    subNode = xmlNewNode(NULL,(xmlChar*)"location");

                    xmlNodeSetContent(subNode,(xmlChar*)"korea");

                    xmlAddChild(rootNode,subNode);

                    xmlDocDumpMemory(doc,&xmlData,&size); //xmlData에는 <Document version=1.0><root><Company>한국회사</Company><location>korea</location></root> 가 들어가 있다.

                     

                     

                    <xml 파싱>

                    xmlDocPtr doc;

                    xmlNodePtr node;

                    doc = xmlParseDoc((xmlChar*)xmlData); //xmlData = 위에서 xmlData안에 있는 내용의 형태를 가진 것

                    node = xmlDocGetRootElement(doc);

                    node = node->xmlChildrentNode;

                    xmlChar* data1 = xmlNodeGetContent(node);

                    xmlChar* data2 = xmlNodeGetContent(node);

                    xmlChar* data3 = xmlNodeGetContent(node);

                    ...... 이런식으로 가져온다.

                     

                    ○ XML문서 생성할때 

                    ㅁdoc = xmlNewDoc("1.0") : 1.0버전의 xml문서객체 생성

                    rootnode = xmlNewNode(NULL, name of element) : 새로운 노드객체 생성

                    ㅁxmlDocSetRootElement(doc, rootnode) : 위에서 생성한 노드를 루트원소로 설정

                    ㅁnode = xmlNewNode(NULL, name of element) : 노드하나 더 생성

                    ㅁxmlNodeSetContent(node, 내용) : 노드에 내용 추가

                    xmlNewProp(node, name fo property, content of property) : 노드에 property추가

                    ㅁxmlAddChild(rootnode, node) : 루트노드 혹은 다른 노드의 하위노드로 추가

                    xmlDocPtr xmlParseDoc (const xmlChar * cur)

                    xmlNodePtr xmlDocGetRootElement (xmlDocPtr doc)

                    node = node->xmlChildrenNode : 그 밑의 노드로 감

                    node->name : 노드의 이름

                    str = xmlNodeGetContent(node) : 노드의 내용을 얻음

                    str = xmlGetProp(node, property name) : 노드의 property내용을 얻어옴

                    node = node->next : 다음 노드로 감

                    void xmlFreeDoc (xmlDocPtr cur)


                    Structure xmlNodestruct _xmlNode {
                    void * _private : application data
                    xmlElementType type : type number, must be second !
                    const xmlChar * name : the name of the node, or the entity
                    struct _xmlNode * children : parent->childs link
                    struct _xmlNode * last : last child link
                    struct _xmlNode * parent : child->parent link
                    struct _xmlNode * next : next sibling link
                    struct _xmlNode * prev : previous sibling link
                    struct _xmlDoc * doc : the containing document End of common p
                    xmlNs * ns : pointer to the associated namespace
                    xmlChar * content : the content
                    struct _xmlAttr * properties : properties list
                    xmlNs * nsDef : namespace definitions on this node
                    void * psvi : for type/PSVI informations
                    unsigned short line : line number
                    unsigned short extra : extra data for XPath/XSLT
                    }

                     

                     

                     

                    makefile

                    구성 : xml_parser.c

                    xml_parser : xml_parser.o
                    gcc -o xml_parser xml_parser.o -L/usr/local/lib -lxml2

                    xml_parser.o : xml_parser.c
                    gcc -c xml_parser.c -I/usr/local/include/libxml2
                    clean :
                    rm -f ./xml_parser ./xml_parser.o


                     

                    -c 옵션으로 .o 파일을 만들고

                    -o 옵션으로 바이너리 파일을 만든다.

                     

                    따라서 include 하는 헤더파일들의 위치는 -c 옵션을 줄때 하고

                    라이브러리들을 사용할 때에는 -L 로 해당 라이브러리의 위치를 지정해주고 바로 뒤에 이에 해당하는 라이브러리를 입력한다.

                    이때 -lxml2의 실제 이름은 libxml2.a 이다. libxml2.a 에서 ib와 .a를 뺀것으로 lib중 l만, lib뒤에 붙는 이름만 쓰고 .a는 입력하지 않는다.

                    라이브러리가 여러 디렉토리에 있다면

                     

                    -L라이브러리위치 -사용할 라이브러리 -L라이브러리위치 -사용할 라이브러리 ...

                    이렇게 해야 할 듯...

                    [출처] libxml 이것만 알면 될려나? 계속 업데이트하자...|작성자 kdepirate

                     

                     

                     

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

                    libxml tutorial 한글번역

                    Libxml tutorial 원문 : http://xmlsoft.org/tutorial/index.html

                    출처 : http://kylesoft.blogspot.kr/2012/06/libxml-tutorial.html#!/2012/06/libxml-tutorial.html 


                    Abstract
                    Libxml은 자유롭게 사용할 수 있는 라이센스로 제공되고, 다양한 플랫폼에서 사용할 수 있는 XML을 다루기 위한 C언어 라이브러리이다. 이 문서에서는 기본적인 기능들의 예제를 제공한다.

                    Introduction
                    Libxml은 XML 데이터를 읽고, 쓰고, 다루기 위한 함수들을 구현한 C언어 라이브러리이다. 이 문서에서는 예제 코드와 기본적인 기능들에 대해서 설명한다. 완전한 문서와 더욱 자세한 사항에 대해서는 프로젝트 홈페이지에서 확인할 수 있다.
                    이 문서에서는 아래에 나열된 간단한 XML 프로그램들을 사용하여 설명한다.
                    • XML 문서 해석
                    • 특정 엘리먼트로부터 문자열 추출하기
                    • 엘리먼트와 내용을 추가하기
                    • 속성을 엘리먼트에 추가하기
                    • 속성값 추출하기
                    예제 프로그램의 전체 소스코드는 Appendix에서 찾을 수 있다.

                    Data types
                    Libxml에서는 몇 가지 data type을 정의하고 있다.
                    • xmlChar : UTF-8로 인코딩된 character type이다. UTF-8이 아닐 경우 반드시 UTF-8로 변환하여 사용해야한다.
                    • xmlDoc : 해석된 XML 문서가 트리 구조로 저장될 수 있도록 마련된 구조체이다. xmlDocPtr은 xmlDoc 구조체의 포인터형이다.
                    • xmlNode : 하나의 노드를 저장하기 위한 구조체. xmlNodePtr은 xmlNode 구조체의 포인터형이다. xmlDoc의 트리구조를 탐색하는데 사용된다.

                    Parsing the file
                    xml 파일을 불러와서 해석하고, 에러 검사를 하는데는 파일이름과 하나의 function만 있으면된다.
                    xmlDocPtr doc; // (1)
                    xmlNodePtr cur; // (2)
                    doc = xmlParseFile(docname); // (3)
                    if(doc == NULL) { // (4)
                        fprintf(stderr, "Document not parsed successfully.\n");
                        return;
                    }
                    cur = xmlDocGetRootElement(doc); // (5)
                    if(cur == NULL) { // (6)
                        fprintf(stderr, "empty document\n");
                        xmlFreeDoc(doc);
                        return;
                    }
                    if(xmlStrcmp(cur->name, (const xmlChar *)"story")) { // (7)
                        fprintf(stderr, "document of the wrong type, root node != story");
                        xmlFreeDoc(doc);
                        return;
                    }
                    (1) 해석된 문서를 가리킬 포인터 선언
                    (2) 노드를 가리키기 위한 포인터 선언
                    (3) docname의 문서를 불러와 해석한다.
                    (4) 문서가 정상적으로 로드/해석 되었는지 확인한다.
                    (5) root 엘리먼트를 찾는다.
                    (6) 문서에 내용이 있는지 확인한다.
                    (7) root 엘리먼트의 이름이 story인지 확인한다.
                    Note : 이 예제에서 에러가 발생할 수 있는 것은 적절하지 않은 인코딩이다. XML 표준에서는 문서가 UTF-8 또는 UTF-16이 아닌 다른 인코딩으로 저장되어 있을 경우 명시적으로 해당 인코딩 타입을 기술하도록 되어 있다. 문서에 인코딩 타입이 기술되어 있다면, libxml은 자동적으로 해당 인코딩 타입에서 UTF-8로 변환한다. 자세한 XML 인코딩 requirement에 대해서는 XML 표준을 참조하라.

                    Retrieving element content
                    엘리먼트의 내용을 추출하기 위해서는 문서 tree에서 해당 엘리먼트를 찾아야한다. 이 예제에서는 "story" 엘리먼트로부터 "keyword"라는 엘리먼트를 찾는다. 원하는 것을 찾기 위해서 tree를 하나하나 검색해야한다. doc(xmlDocPtr), cur(xmlNodePtr)은 이미 가지고 있다고 가정하고 설명한다.
                    cur = cur->xmlChildrenNode; // (1)
                    while(cur != NULL) { // (2)
                        if((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))) {
                            parseStory(doc, cur);
                        }
                        cur = cur->next;
                    }
                    (1) cur의 첫번째 자식 노드를 가져온다. 여기서 cur은 문서의 root 엘리먼트인 "story"이다. 즉, "story"의 첫번째 자식 엘리먼트를 가져오는 것이다.
                    (2) 이 loop에서는 "story" 엘리먼트의 자식들 중에서 "storyinfo"인 엘리먼트를 찾는다. "storyinfo"가 아니면 다음 자식 엘리먼트로 이동하고, 찾으면 parseStory()를 호출한다.

                    void parseStory(xmlDocPtr doc, xmlNodePtr cur) {
                        xmlChar *key;
                        cur = cur->xmlChildrenNode; // (1)
                        while(cur != NULL) { // (2)
                            if((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) {
                                key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); // (3)
                                printf("keyword: %s\n", key);
                                xmlFree(key);
                            }
                            cur = cur->next;
                        }
                        return;
                    }
                    (1) 첫 번째 자식 노드를 가지고온다.
                    (2) 이전 코드의 loop 처럼 loop를 사용하여 자식 노드들 중에서 "keyword"라는 이름을 가진 노드를 찾는다.
                    (3) "keyword" 노드를 찾으면 내용을 xmlNodeListGetString()을 사용해서 가져와 출력한다.
                    xmlNodeListGetString()을 호출할때 cur->xmlChildrenNode를 인자로 넘겨주는데, XML에서는 내용이 엘리먼트의 자식 노드로 표현이 되기 때문에 "keyword"의 자식노드들 중에서 문자열 데이터를 가져오기 위해서 자식 노드들 중에서 검색하는 것이다.
                    Note. xmlNodeListGetString()은 메모리에 공간을 잡아서(할당하고) 문자열을 넣어 return하기 때문에 사용한 이후에 반드시 메모리를 해제해야만 한다.(xmlFree() 사용)

                    Using xpath to retrieve element content
                    Libxml2에서는 문서 tree에서 엘리먼트를 탐색하기 위한 추가적인 방법인 XPath라는 것을 포함하고 있다. XPath는 문서에서 특정 노드를 찾기위한 표준적인 검색 방법을 제공한다.
                    Note. XPath에 대해서 자세히 알고 싶다면 XPath 문서를 참고하자.

                    XPath를 사용하기 위해서는 xmlXPathContext를 설정하고, xmlXPathEvalExpression() 함수를 호출한다. 이 함수는 xmlXPathObjectPtr를 반환한다.
                    xmlXPathObjectPtr getnodeset (xmlDocPtr doc, xmlChar *xpath) {
                        xmlXPathContextPtr context; // (1)
                        xmlXPathObjectPtr result;
                        context = xmlXPathNewContext(doc); // (2)
                        result = xmlXPathEvalExpression(xpath, context); // (3)
                        if(xmlXPathNodeSetIsEmpty(result->nodesetval)) { // (4)
                            printf("No result\n");
                            return NULL;
                        }
                        xmlXPathFreeContext(context);
                        return result;
                    }
                    (1) 변수 선언
                    (2) context 변수 초기화
                    (3) XPath 표현식 적용
                    (4) 결과 확인 & 메모리 해제
                    위 함수에서 반환되는 xmlXPathObjectPtr은 노드들과 반복적인 동작을 위한 정보들의 집합을 포함하고 있다. 노드 집합은 엘리먼트 개수(nodeNr)와 노드들의 배열(nodeTab)을 가지고 있다.
                    for(i = 0; i < nodeset->nodeNr; i++) { // (1)
                        keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, 1); // (2)
                        printf("keyword: %s\n", keyword);
                        xmlFree(keyword);
                    }
                    (1) nodeset->nodeNr은 노드 집합이 가지고 있는 엘리먼트 개수이다.
                    (2) 각 노드의 내용을 추출하여 출력한다.

                    Writing element content
                    엘리먼트 내용을 추가하는 것은 이전에 이미 했던 과정들(문서를 해석하고, 노드들을 탐색)과 많은 부분이 동일하다. 문서를 불러와 해석하고, 원하는 노드를 찾고, 내용을 추가하면 된다. 여기서는 "storyinfo" 엘리먼트를 찾아서 keyword를 추가하고 파일로 저장한다.
                    void parseStory(mxlDocPtr doc, xmlNodePtr cur, char *keyword) {
                        xmlNewTextChild(cur, NULL, "keyword", keyword); // (1)
                        return;
                    }
                    (1) xmlNewTextChild 함수는 현재 노드에 새로운 자식 노드를 추가한다.
                    노드를 추가한 후에는 파일로 저장하기를 원할 것이다. 네임스페이스가 포함되어 저장되기를 원한다면 여기서 추가할 수 있다. 아래 예제에서는 네임스페이스가 NULL인 경우이다.
                    xmlSaveFormatFile(docname, doc, 1);
                    첫번째 인자는 파일 이름이다. 읽어들인 파일명과 동일한 파일명을 입력하면 덮어쓰게 된다. 두번째 인자는 xmlDoc 구조체의 포인터이다. 세번째 인자를 1로 설정하면 indenting하여 저장한다.

                    Writing attribute
                    속성을 추가하는 것은 새 엘리먼트를 추가하는 것과 비슷하다. 이 예제에서는 libxml tutorial 문서의 URI를 추가한다.
                    추가할 위치는 story 엘리먼트의 child이다. 따라서 새로운 엘리먼트와 속성을 추가할 위치를 찾는 것을 간단하다.
                    먼저 변수를 선언한다.
                    xmlAttrPtr newattr;
                    xmlNodePtr도 추가적으로 필요하다.
                    xmlNodePtr newnode;
                    root 엘리먼트가 story이면, 다른 노드를 탐색하기 전까지는 cur이 root 엘리먼트를 가리키고 있다. 따라서 cur에 새 엘리먼트와 속성을 추가하면 된다.
                    newnode = xmlNewTextChild(cur, NULL, "reference", NULL); // (1)
                    newattr = xmlNewProp(newnode, "uri", uri); // (2)
                    (1) 먼저 새로운 노드를 현재 노드의 child로 xmlNewTextChild 함수를 사용해서 생성한다.
                    (2) 생성된 노드에 새로운 속성을 추가한다.
                    앞의 예제와 같이 노드가 추가되면 파일에 저장한다.

                    Retrieving attributes
                    속성값을 추출하는 것은 노드에서 내용을 추출하는 이전의 예제와 거의 동일하다. 이 예제에서는 앞의 예제에서 추가했던 URI 속성의 값을 추출할 것이다.
                    void getReference(xmlDocPtr doc, xmlNodePtr cur) {
                        xmlChar *uri;
                        cur = cur->xmlChildrenNode;
                        while(cur != NULL) {
                            if((!xmlStrcmp(cur->name, (const xmlChar *)"reference"))) {
                                uri = xmlGetProp(cur, "uri"); // (1)
                                printf("uri: %s\n", uri);
                                xmlFree(uri);
                            }
                            cur = cur->next;
                        }
                        return;
                    }
                    (1) 핵심 함수는 xmlGetProp()이다. 이 함수는 속성의 값을 xmlChar 형식으로 반환한다.
                    Note. 만약 고정으로 선언된 DTD를 사용하거나 속성의 기본 값이 설정되어 있다면, 이 함수는 값을 추출할 것이다.

                    Encoding conversion
                    데이터 인코딩 호환성 문제는 XML을 다루는데 있어 가장 어려운 문제중 하나이다. 이 문제를 회피하고 싶다면 어플리케이션을 디자인할때 내부적으로, libxml로 저장/관리되는 데이터에 대해서 UTF-8 사용을 고려하라. 프로그램에서 사용되는 다른 포맷으로 된 데이터(ISO-88590-1과 같은 데이터)는 libxml 함수들로 전달되기 전에 반드시 UTF-8로 변환되어야한다. 출력 데이터가 UTF-8이 아닌 다른 포맷을 원할 경우 역시 반드시 변환을 거쳐야 한다.
                    Libxml은 데이터 변환이 가능한 경우 iconv를 사용한다. iconv가 없을 경우 UTF-8과 UTF-16, ISO-8859-1만 사용될 수 있다. iconv가 있을 경우 어떤 포맷이라도 변환을 거쳐 사용할 수 있다. 현재 iconv는 150여가지의 포맷을 지원한다.
                    Warning. 일반적으로 저지르기 쉬운 실수 중의 하나는, 하나의 코드 내, 다른 부분에서 사용되는 내부 데이터에 서로다른 포맷을 사용하는 것이다. 가장 흔한 경우가 libxml에서는 내부 데이터를 UTF-8로 가정하는데 libxml를 사용하는 어플리케이션에서는 내부 데이터를 ISO-8859-1로 가정하는 경우이다. 그 결과 어플리케이션 내부 데이터를 각 코드에서 다르게 실행하게 되므로 잘못 해석하는 경우가 발생할 수 있다.
                    이 예제는 간단한 문서를 구성하고, command line에서 입력되는 내용을 root 엘리먼트에 추가하고, 그 결과를 적절한 인코딩으로 stdout으로 내보낸다. 여기서는 ISO-8859-1 인코딩을 사용한다. command line에서 입력된 문자열은 ISO-8859-1에서 UTF-8로 변환된다. 예제에서 변환과 캡슐화를 위해 사용된 함수는 xmlFindCharEncodingHandler이다.
                    xmlCharEncodingHandlerPtr handler; // (1)
                    size = (int)strlen(in) + 1; // (2)
                    out_size = size * 2 - 1;
                    out = malloc((size_t)out_size);
                    ...
                    handler = xmlFindCharEncodingHandler(encoding); // (3)
                    ...
                    handler->input(out, &out_size, in, &temp); // (4)
                    ...
                    xmlSaveFormatFileEnc("-", doc, encoding, 1); // (5)
                    (1) xmlCharEncodingHandler 함수를 위한 handler 포인터 선언
                    (2) xmlCharEncodingHandler 함수는 입력과 출력의 크기를 필요로한다. 여기서 그 크기를 계산한다.
                    (3) xmlFindCharEncodingHandler 함수는 인자로 초기 데이터 인코딩 타입을 받아 built-in 변환 핸들러를 검색하여 있으면 핸들러를 반환하고, 찾지 못한 경우 NULL을 반환한다.
                    (4) 변환 함수는 인자로 입력과 출력 문자열에 대한 포인터와 각각의 크기를 필요로 한다. 크기정보는 반드시 이전에 계산 되어 있어야 한다.
                    (5) 출력으로 UTF-8이 아닌 특정 인코딩 타입으로 원할 경우 xmlSaveFormatFileEnc 함수를 사용한다.

                    Appendix
                    a. Compilation
                    Libxml에는 xml2-config 스크립트가 포함되어 있으며, 이것은 컴파일에 필요한 flag들을 생성해준다. pre-processor 및 컴파일 flags를 생성하기 위해서는 xml2-config --cflags를 사용하고, linking 작업에는 xml2-config --libs를 사용하라. 다른 옵션을 확인하고 싶으면 xml2-config --help를 입력하면 된다.

                    b. Sample document
                    <?xml version="1.0"?>
                    <story>
                        <storyinfo>
                            <author>John Fleck</author>
                            <datewritten>June 2, 2002</datewritten>
                            <keyword>example keyword</keyword>
                        </storyinfo>
                        <body>
                            <headline>This is the headline</headline>
                            <para>This is the body text.</para>
                        </body>
                    </story>

                    c. Code for keyword example
                    #include <stdio.h>
                    #include <string.h>
                    #include <stdlib.h>
                    #include <libxml/xmlmemory.h>
                    #include <libxml/parser.h>
                    
                    void parseStory(xmlDocPtr doc, xmlNodePtr cur) {
                        xmlChar *key;
                        cur = cur->xmlChildrenNode;
                        while(cur != NULL) {
                            if((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) {
                                key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                                printf("keyword: %s\n", key);
                                xmlFree(key);
                            }
                            cur = cur->next;
                        }
                        return;
                    }
                    
                    static void parseDoc(char *docname) {
                        xmlDocPtr doc;
                        xmlNodePtr cur;
                        doc = xmlParseFile(docname);
                        if(doc == NULL) {
                            fprintf(stderr, "Document not parsed successfully.\n");
                            return;
                        }
                        cur = xmlDocGetRootElement(doc);
                        if(cur == NULL) {
                            fprintf(stderr, "empty document\n");
                            xmlFreeDoc(doc);
                            return;
                        }
                        if(xmlStrcmp(cur->name, (const xmlChar *)"story")) {
                            fprintf(stderr, "document of the wrong type, root node != story\n");
                            xmlFreeDoc(doc);
                            return;
                        }
                        cur = cur->xmlChildrenNode;
                        while(cur != NULL) {
                            if((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))) {
                                parseStory(doc, cur);
                            }
                            cur = cur->next;
                        }
                        xmlFreeDoc(doc);
                        return;
                    }
                    
                    int main(int argc, char **argv) {
                        char *docname;
                        if(argc <= 1) {
                            printf("Usage: %s docname\n", argv[0]);
                            return(0);
                        }
                        docname = argv[1];
                        parseDoc(docname);
                        return(1);
                    }

                    d. Code for xpath example
                    #include <libxml/parser.h>
                    #include <libxml/xpath.h>
                    
                    xmlDocPtr getdoc (char *docname) {
                        xmlDocPtr doc;
                        doc = xmlParseFile(docname);
                        if (doc == NULL ) {
                             fprintf(stderr,"Document not parsed successfully. \n");
                            return NULL;
                        }
                        return doc;
                    }
                    
                    xmlXPathObjectPtr getnodeset (xmlDocPtr doc, xmlChar *xpath) {
                        xmlXPathContextPtr context;
                        xmlXPathObjectPtr result;
                        context = xmlXPathNewContext(doc);
                        result = xmlXPathEvalExpression(xpath, context);
                        if(xmlXPathNodeSetIsEmpty(result->nodesetval)) {
                            printf("No result\n");
                            return NULL;
                        }
                        xmlXPathFreeContext(context);
                        return result;
                    }
                    
                    int main(int argc, char **argv) {
                        char *docname;
                        xmlDocPtr doc;
                        xmlChar *xpath = (xmlChar *)"//keyword";
                        xmlNodeSetPtr nodeset;
                        xmlXPathObjectPtr result;
                        int i;
                        xmlChar *keyword;
                        if (argc <= 1) {
                            printf("Usage: %s docname\n", argv[0]);
                            return(0);
                        }
                        docname = argv[1];
                        doc = getdoc(docname);
                        result = getnodeset (doc, xpath);
                        if (result) {
                            nodeset = result->nodesetval;
                            for (i=0; i < nodeset->nodeNr; i++) {
                                keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, 1);
                                printf("keyword: %s\n", keyword);
                                xmlFree(keyword);
                            }
                            xmlXPathFreeObject (result);
                        }
                        xmlFreeDoc(doc);
                        xmlCleanupParser();
                        return (1);
                    }

                    e. Code for add keyword example
                    #include <stdio.h>
                    #include <string.h>
                    #include <stdlib.h>
                    #include <libxml/xmlmemory.h>
                    #include <libxml/parser.h>
                    
                    void parseStory (xmlDocPtr doc, xmlNodePtr cur, char *keyword) {
                        xmlNewTextChild (cur, NULL, "keyword", keyword);
                        return;
                    }
                    
                    xmlDocPtr parseDoc(char *docname, char *keyword) {
                        xmlDocPtr doc;
                        xmlNodePtr cur;
                        doc = xmlParseFile(docname);
                        if (doc == NULL ) {
                            fprintf(stderr,"Document not parsed successfully. \n");
                            return (NULL);
                        }
                        cur = xmlDocGetRootElement(doc);
                        if (cur == NULL) {
                            fprintf(stderr,"empty document\n");
                            xmlFreeDoc(doc);
                            return (NULL);
                        }
                        if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
                            fprintf(stderr,"document of the wrong type, root node != story");
                            xmlFreeDoc(doc);
                            return (NULL);
                        }
                        cur = cur->xmlChildrenNode;
                        while (cur != NULL) {
                            if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))) {
                                parseStory (doc, cur, keyword);
                            }
                            cur = cur->next;
                        }
                        return(doc);
                    }
                    
                    int main(int argc, char **argv) {
                        char *docname;
                        char *keyword;
                        xmlDocPtr doc;
                        if (argc <= 2) {
                            printf("Usage: %s docname, keyword\n", argv[0]);
                            return(0);
                        }
                        docname = argv[1];
                        keyword = argv[2];
                        doc = parseDoc (docname, keyword);
                        if (doc != NULL) {
                            xmlSaveFormatFile (docname, doc, 0);
                            xmlFreeDoc(doc);
                        }
                        return (1);
                    }

                    f. Code for add attribute example
                    #include <stdio.h>
                    #include <string.h>
                    #include <stdlib.h>
                    #include <libxml/xmlmemory.h>
                    #include <libxml/parser.h>
                    
                    xmlDocPtr parseDoc(char *docname, char *uri) {
                        xmlDocPtr doc;
                        xmlNodePtr cur;
                        xmlNodePtr newnode;
                        xmlAttrPtr newattr;
                        doc = xmlParseFile(docname);
                        if (doc == NULL ) {
                            fprintf(stderr,"Document not parsed successfully. \n");
                            return (NULL);
                        }
                        cur = xmlDocGetRootElement(doc);
                        if (cur == NULL) {
                            fprintf(stderr,"empty document\n");
                            xmlFreeDoc(doc);
                            return (NULL);
                        }
                        if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
                            fprintf(stderr,"document of the wrong type, root node != story");
                            xmlFreeDoc(doc);
                            return (NULL);
                        }
                        newnode = xmlNewTextChild (cur, NULL, "reference", NULL);
                        newattr = xmlNewProp (newnode, "uri", uri);
                        return(doc);
                    }
                    
                    int main(int argc, char **argv) {
                        char *docname;
                        char *uri;
                        xmlDocPtr doc;
                        if (argc <= 2) {
                            printf("Usage: %s docname, uri\n", argv[0]);
                            return(0);
                        }
                        docname = argv[1];
                        uri = argv[2];
                        doc = parseDoc (docname, uri);
                        if (doc != NULL) {
                            xmlSaveFormatFile (docname, doc, 1);
                            xmlFreeDoc(doc);
                        }
                        return (1);
                    }

                    g. Code for retrieving attribute value example
                    #include <stdio.h>
                    #include <string.h>
                    #include <stdlib.h>
                    #include <libxml/xmlmemory.h>
                    #include <libxml/parser.h>
                    
                    xmlDocPtr parseDoc(char *docname, char *uri) {
                        xmlDocPtr doc;
                        xmlNodePtr cur;
                        xmlNodePtr newnode;
                        xmlAttrPtr newattr;
                        doc = xmlParseFile(docname);
                        if (doc == NULL ) {
                            fprintf(stderr,"Document not parsed successfully. \n");
                            return (NULL);
                        }
                        cur = xmlDocGetRootElement(doc);
                        if (cur == NULL) {
                            fprintf(stderr,"empty document\n");
                            xmlFreeDoc(doc);
                            return (NULL);
                        }
                        if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
                            fprintf(stderr,"document of the wrong type, root node != story");
                            xmlFreeDoc(doc);
                            return (NULL);
                        }
                        newnode = xmlNewTextChild (cur, NULL, "reference", NULL);
                        newattr = xmlNewProp (newnode, "uri", uri);
                        return(doc);
                    }
                    
                    int main(int argc, char **argv) {
                        char *docname;
                        char *uri;
                        xmlDocPtr doc;
                        if (argc <= 2) {
                            printf("Usage: %s docname, uri\n", argv[0]);
                            return(0);
                        }
                        docname = argv[1];
                        uri = argv[2];
                        doc = parseDoc (docname, uri);
                        if (doc != NULL) {
                            xmlSaveFormatFile (docname, doc, 1);
                            xmlFreeDoc(doc);
                        }
                        return (1);
                    }

                    h. Code for encoding conversion example
                    #include <string.h>
                    #include <libxml/parser.h>
                    
                    unsigned char* convert (unsigned char *in, char *encoding) {
                        unsigned char *out;
                        int ret,size,out_size,temp;
                        xmlCharEncodingHandlerPtr handler;
                        size = (int)strlen(in)+1;
                        out_size = size*2-1;
                        out = malloc((size_t)out_size);
                        if (out) {
                            handler = xmlFindCharEncodingHandler(encoding);
                            if (!handler) {
                                free(out);
                                out = NULL;
                            }
                        }
                        if (out){
                            temp=size-1;
                            ret = handler->input(out, &out_size, in, &temp);
                            if (ret || temp-size+1) {
                                if (ret) {
                                    printf("conversion wasn't successful.\n");
                                }
                                else {
                                    printf("conversion wasn't successful. converted\n");
                                }
                                free(out);
                                out = NULL;
                            }
                            else {
                                out = realloc(out,out_size+1);
                                out[out_size]=0; /*null terminating out*/
                            }
                        }
                        else {
                            printf("no mem\n");
                        }
                        return (out);
                    }
                    
                    int main(int argc, char **argv) {
                        unsigned char *content, *out;
                        xmlDocPtr doc;
                        xmlNodePtr rootnode;
                        char *encoding = "ISO-8859-1";
                        if (argc <= 1) {
                            printf("Usage: %s content\n", argv[0]);
                            return(0);
                        }
                        content = argv[1];
                        out = convert(content, encoding);
                        doc = xmlNewDoc ("1.0");
                        rootnode = xmlNewDocNode(doc, NULL, (const xmlChar*)"root", out);
                        xmlDocSetRootElement(doc, rootnode);
                        xmlSaveFormatFileEnc("-", doc, encoding, 1);
                        return (1);
                    }
                    반응형
                    반응형



                    윈도운 버전!

                    void MakeDirectory(char *full_path)
                    {
                    char temp[256], *sp;
                    strcpy(temp, full_path); // 경로문자열을 복사
                    sp = temp; // 포인터를 문자열 처음으로

                    while((sp = strchr(sp, '\\'))) { // 디렉토리 구분자를 찾았으면
                    if(sp > temp && *(sp - 1) != ':') { // 루트디렉토리가 아니면
                    *sp = '\0'; // 잠시 문자열 끝으로 설정
                    //mkdir(temp, S_IFDIR);
                    CreateDirectory(temp, NULL);
                    // 디렉토리를 만들고 (존재하지 않을 때)
                    *sp = '\\'; // 문자열을 원래대로 복귀
                    }
                    sp++; // 포인터를 다음 문자로 이동
                    }

                    }

                    MakeDirectory("C:\\TEMP\\BIN\\ABC.txt");

                    처럼 호출하면

                    C:\TEMP 디렉토리 만들고

                    C:\TEMP\BIN 디렉토리 만든다.

                    유닉스 버전


                    void MakeDirectory(char *full_path)
                    {
                    char temp[256], *sp;

                    strcpy(temp, full_path); // 경로문자열을 복사
                    sp = temp; // 포인터를 문자열 처음으로

                    while((sp = strchr(sp, '/'))) { // 디렉토리 구분자를 찾았으면
                    if(sp > temp && *(sp - 1) != ':') { // 루트디렉토리가 아니면
                    *sp = '\0'; // 잠시 문자열 끝으로 설정
                    mkdir(temp, S_IFDIR | S_IRWXU | S_IRWXG | S_IXOTH | S_IROTH);
                    // 디렉토리를 만들고 (존재하지 않을 때)
                    *sp = '/'; // 문자열을 원래대로 복귀
                    }
                    sp++; // 포인터를 다음 문자로 이동
                    }
                    }

                    MakeDirectory("/TEMP/BIN/abc.txt");

                    처럼 호출하면

                    /TEMP 디렉토리 만들고

                    /TEMP/BIN 디렉토리 만든다.

                    생각보다 쉽기도 하고 생각보다 어렵기도 한 내용!! 고생해서 만들었음 으흐흐

                    반응형
                    반응형


                    출처 : http://bestheroz.blog.me/109841952


                    시간과 날짜와 관련된 라이브러리 함수

                    구분

                    함수 원형과 인자

                    함수 설명

                    시간

                    계산

                    time_t time(time_t *timeptr);

                    1970 1 1일 자정부터 경과된 현재 시간을 초단위로 계산

                    시간을

                    문자열로

                    변환

                    char *asctime(strcut tm *time);

                    구조체 tm형식의 시간을 문자열로 변환

                    char *ctime(time_t *time);

                    함수 time()로부터 계산된 현재 시간을 문자열로 변환

                    시간을

                    구조체로

                    변환

                    struct tm *localtime(time_t *time);

                    지역 시간(local time)을 구조체 tm의 형식으로 가져오는 함수

                    struct tm *gmtime(time_t *time);

                    Greenwich Meam Time(GMT)을 구조체 tm 형식으로 가져옴

                    시간

                    차이

                    계산

                    clock_t clock(void);

                    clock tick으로 경과된 시간

                    double difftime(time_t time2, time_t time1);

                    두 시간의 차이를 초단위로 계산

                    시간

                    지연

                    void Sleep(unsigned millisecond);

                    인자가 지정하는 만큼의 1/1000초 단위의 시간을 지연

                    void delay(unsigned millisecond);

                    예제

                    #include<stdio.h>

                    #include<time.h>

                    int main(void)

                    {

                    time_t now;

                    time(&now);

                    printf("현재날짜와시간: %s", asctime(localtime(&now)));

                    printf("현재날짜와시간: %s", ctime(&now));

                    return 0;

                    }

                    strcut tm은 <time.h>에 다음과 같이 정의되어 있다.

                    struct tm {

                    int tm_sec; /* (seconds) - [0,59] */

                    int tm_min; /* (minutes) - [0,59] */

                    int tm_hour; /* 시간(hour) - [0,23] */

                    int tm_mday; /* 날짜(day) - [1,31] */

                    int tm_mon; /* (month) - [0,11] */

                    int tm_year; /* 1900년이후의연도수*/

                    int tm_wday; /* dydlf(second) - [0,6] */

                    int tm_yday; /* 연중날짜(day) - [0,365] */

                    int tm_isdst; /* 일광절약시간 - [0, 59] */

                    };

                    예제

                    #include<stdio.h>

                    #include<time.h>

                    int main(void)

                    {

                    time_t curr;

                    struct tm *d;

                    curr=time(NULL);

                    d=localtime(&curr);

                    printf("현재날짜\n");

                    printf("%d%d%d\n", d->tm_year+1900, d->tm_mon+1, d->tm_mday);

                    printf("현재시간\n");

                    printf("%d%d%d\n", d->tm_hour, d->tm_min, d->tm_sec);

                    return 0;

                    }

                    time과 clock의 차이

                    구분

                    함수 time

                    함수 clock

                    원형

                    time_t time(time_t *timer);

                    clock_t clock(void);

                    기능

                    절대시간 또는 달력시간(calendar time)을 계산

                    프로세서 시간을 계산

                    반환 값

                    1970 1 1일 자정 이후 현재까지 경과된 시간을 초(second)로 반환

                    프로세서 시간을 반환

                    예제 - 함수 time 사용

                    #include<stdio.h>

                    #include<time.h>

                    int main(void)

                    {

                    time_t start, end;

                    long i=0;

                    double pst;

                    start=time(NULL);

                    while(i<30000000)

                    {

                    i++;

                    }

                    end=time(NULL);

                    pst=difftime(end, start);

                    printf("time: %f\n", pst);

                    return 0;

                    }

                    /**초단위의시간을계산하는함수time()의결과는0초**/

                    예제 - 함수 clock 사용

                    #include<stdio.h>

                    #include<time.h>

                    int main(void)

                    {

                    time_t start, end;

                    long i=0;

                    double pst;

                    start=clock();

                    while(i<30000000)

                    {

                    i++;

                    }

                    end=clock();

                    pst=(double)(end-start)/CLK_TCK;

                    printf("time: %f\n", pst);

                    return 0;

                    }

                    시간을 지연시키는 함수 Sleep 예제

                    #include<stdio.h>

                    #include<time.h>

                    #include<windows.h> //Sleep()

                    int main(void)

                    {

                    clock_t start, end;

                    double pst;

                    start = clock();

                    printf("start!\n");

                    Sleep(3500);

                    end = clock();

                    printf("end\n");

                    pst = (double)(end-start)/CLK_TCK;

                    printf("경과시간: %f\n", pst);

                    return 0;

                    }

                    시간을 처리하는 함수와는 다른 개념의 함수이지만 유용하게 사용할 수 있는 함수로 kbhit가 있다. 이 함수의 원형은 다음과 같이 함수의 인자가 없으며, 키보드 상의 어떤 키를 누르면 0이 아닌 값을, 누르지 않은 상태라면 0값을 반환하는 함수다. 함수 kbhit는 <conio.h>를 필요로 한다.

                    kbhit

                    함수원형

                    int kbhit(void);

                    반환 값

                    입력된 키가 있으면 0이 아닌 값을, 입력된 키가 없으면 0을 반환

                    프로그램 실행중에 아무키나 누르기 전까지만 프로그램을 계속 반복시키고자 한다면 반복문 while과 함께 다음과 같이 사용할 수 있다.

                    while(!kbhit())

                    {

                    //반복할프로그램

                    }

                    예제 - 현재 시간을 연속적으로 출력(kbhit, localtime)

                    #include<stdio.h>

                    #include<time.h>

                    #include<stdlib.h>

                    #include<conio.h>

                    int main(void)

                    {

                    time_t now;

                    struct tm *d;

                    while(!kbhit())

                    {

                    system("cls");

                    now=time(NULL);

                    d=localtime(&now);

                    printf("현재날짜와시간: %s\n", asctime(d));

                    }

                    return 0;

                    }

                    날짜 수와 요일 계산

                    기준일(1년 1월 1일)로부터 특정일 사이의 날짜 수의 계싼 함수 total_days

                    long total_days(int year, int month, int day)

                    {

                    int months[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

                    int i;

                    long total=0L;

                    total=(year-1)*365L+(year-1)/4-(year-1)/100+(year-1)/400;

                    if(!(year%4) && year%100 || !(ytear%400))

                    months[1]++;

                    for(i=0;i<month-1;i++)

                    total += months[i];

                    total += day;

                    return total;

                    }

                    특정일의 요일 계산

                    #include<stdio.h>

                    long total_days(int year, int month, int day);

                    int main(void)

                    {

                    int year, month, day;

                    char *week_day[] = {"", "", "", "", "", "", ""};

                    long total;

                    printf("특정일의요일구하는프로그램\n\n");

                    printf("입력될숫자는space bar로분리하고\n");

                    printf("Enter 키를누릅니다.\n");

                    printf("예로2005 5 1 Enter\n");

                    printf("년월일입력: ");

                    scanf("%d %d %d", &year, &month, &day);

                    total=total_days(year, month, day);

                    printf("%s 요일입니다.\n", week_day[total%7]);

                    return 0;

                    }

                    long total_days(int year, int month, int day)

                    {

                    int months[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

                    int i;

                    long total=0L;

                    total=(year-1)*365L+(year-1)/4-(year-1)/100+(year-1)/400;

                    if(!(year%4) && year%100 || !(year%400))

                    months[1]++;

                    for(i=0;i<month-1;i++)

                    total += months[i];

                    total += day;

                    return total;

                    }

                    특정일 사이의 날짜 수를 계산

                    #include<stdio.h>

                    long total_days(int year, int month, int day);

                    int main(void)

                    {

                    long total;

                    total=total_days(2010, 7, 21) - total_days(1987, 4, 16);

                    printf("두날짜사이의날짜수: %ld\n", total);

                    return 0;

                    }

                    long total_days(int year, int month, int day)

                    {

                    int months[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

                    int i;

                    long total=0L;

                    total=(year-1)*365L+(year-1)/4-(year-1)/100+(year-1)/400;

                    if(!(year%4) && year%100 || !(year%400))

                    months[1]++;

                    for(i=0;i<month-1;i++)

                    total += months[i];

                    total += day;

                    return total;

                    }

                    반응형
                    반응형


                    참고 블로그
                    원문 : http://blog.naver.com/kater102?Redirect=Log&logNo=134098328

                    원격에서 프로세스를 스타트 시키는 코드를 작성한 후에 프로세스 핸들을 모니터링 해보니 계속해서 2개씩 누수가 일어나는게 아닌가...
                    이상하다 분명히 KillProcess 할때 해당 프로세스의 핸들은 닫아줬는데 ㅡㅡ ....


                    BOOL CreateProcess (
                       LPCTSTR IpApplicationName,     // 생성할 프로세스의 실행파일 이름 argv[0]
                       LPSTR IpCommanLine,              // 아규먼트로 넘길 값 argv[1~ 이후]
                       LPSECURITY_ATTRIBUTES IpProcessAttributes,   // 프로세스의 보안속성을 의미하며 디폴트로 NULL
                       LPSECURITY_ATTRIBUTES IpThreadAttributes,     // 쓰레드의 보안속성을 의미하며 디폴트로 NULL
                       BOOL bInheritHandles,     // 전달 인자가 TRUE 이면 생성되는 자식 프로세스는 부모 프로세스가 소유하는 핸들 중 일부를 상속한다.
                       DWORD dwCreationFlags,    // 프로세스의 특성 디폴트로 0
                       LPVOID IpEnvironment,        // 프로세스가 환경블록을 통해 관리하는 메모리 블록에 프로세스 실행에 필요한 문자열을 저장한다.
                       LPCTSTR IpCurrentDirectory,     //   생성하는 프로세스의 현재 디렉터리를 설정하는 인자.
                       LPSTARTUPINFO IpStartupInfo,   //  STARTUPINFO 구조체 변수는 생성하는 프로세스의 속성을 지정한다.
                       LPPROCESS_INFOMATION IpProcessinfomation    // 프로세스 구조체 변수의 주소값을 인자로 전달, 해당 변수에 프로세스 정보가 채워짐.
                    );

                    요건 STARTUPINFO 구조체의 정보.



                    CreateProcess를 하게 되면 부모프로세스는 해당 함수를 사용하여 자식 프로세스를 생성하게 되는데 유닉스의 fork의 개념과 유사한 것 같다.
                    그리고 해당 프로세스의 정보를 담는 정보가 담겨있는 핸들이 두개 추가로 생기는데 이걸 따로 사용하지 않을꺼라면 닫아줘야 한다.

                    CreateProcess 할때 사용하는 PROCESS_INFORMATION에 핸들을 두개 추가로 생기게 되는데 이 핸들을 닫아주지 않으면
                    CreateProcess 할때마다 핸들의 Count가 2개씩 증가해서 핸들 누수가 발생할 수 있다. 따라서 일일히 닫아줘야 한다.
                    별도로 해당 정보를 사용하지 않을꺼라면 바로바로 닫아주자.

                     STARTUPINFO   si;
                     PROCESS_INFORMATION pi;

                     char command_line[4096];

                     memset(&si, 0, sizeof(si));
                     si.cb = sizeof(si);

                     memset(&pi, 0, sizeof(pi));
                     sprintf(command_line, "%s %s", ProcessName, Arguments);

                     if(CreateProcess(NULL, command_line, NULL, NULL, 0, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
                     {
                      ProcessId = pi.dwProcessId;
                      ReasonCode = 0;
                      // 메모리 누수를 위해 CreateProcess 핸들 두개 CloseHandle 추가 - JangPD
                      CloseHandle(pi.hThread);
                      CloseHandle(pi.hProcess);

                      return 0;
                     }

                    반응형

                    'Coding Tip > Win Programming' 카테고리의 다른 글

                    CL RF 의미와 사용  (0) 2011.07.13
                    반응형


                    윈도우에서 쓰레드 생존여부를 검사하는 방법은 검색으로도 쉽게 나오고 어디에서나 이렇게 쓰라고 되어 있기 때문에 금방 찾을 수 있었지만
                    유닉스 계열에서는 그 방법을 찾기가 쉽지 않았다. 오늘 그 방법을 드디어 알아내서 기쁜 마음에 까먹을까봐 블로깅 해놓음.
                    너무 기쁘다 으아아

                    ------------------------------------------------------------------------------------
                    윈도우에서 쓰레드의 생존여부를 검사하는 함수는 GetExitCodeThread 함수를 사용하게 됨.
                    GetExitCodeThread 함수는 retval 부분에 현재 쓰레드의 상태를 기록해 주기 때문에 해당 값을 이용해서 쓰레드를 관리할 수 있게 된다.

                    함수 원형
                    BOOL GetExitCodeThread(HANDLE hThread, LPDWORD ipExitCode);

                    리턴 값
                    생존시 : ipExitCode가 STILL_ACTIVE로 셋팅됨.
                    종료시 : 스레드가 시작한 함수의 리턴값, 강제종료시에는 ExitThread 함수의 인수가 리턴.

                    예제)
                    HANDLE MyThread;
                    DWORD ipExitCode;

                    GetExitCodeThread(MyThread,&ipExitCode);
                    if(ipExitCode == STILL_ACTIVE) 
                    {
                    // 살아 있음.
                    }
                    else 
                    {
                    // 죽었음.
                    }
                    ------------------------------------------------------------------------------------



                    ------------------------------------------------------------------------------------
                    유닉스 계열에서는 pthread를 사용하기 때문에 함수가 다름.
                    유닉스 혹은 리눅스에서 pthread.h 파일을 이용해서 코딩하게 될 경우 pthread_kill 함수를 사용하면 되는데
                    해당 함수는 쓰레드 아이디와 시그널을 해당 쓰레드에 전달하여 쓰레드 생존 여부를 확인하는 약간 꽁수 같은 방법이라고 할 수 있겠다.
                    컴파일 할 때는 gcc -o (output) (input) -lpthread(pthread Library를 링크해줄 것)

                    함수 원형
                    int pthread_kill(pthread_t thread_id, int sig);

                    리턴 값
                    성공 : 0
                    실패 : ERROR
                    ESRCH : 해당 THREAD_ID를 찾을 수 없을 때.
                    EINVAL : 잘못된 Signal을 전달할 경우.

                    예제)
                    #include<pthread.h>
                    #include<signal.h>
                    #include<errno.h> // 필요시 추가.

                    pthread_t MyThread;
                    int status;

                    status = pthread_kill(MyThread,0);
                    if ( status == ESRCH ) // 존재하지 않는 쓰레드 아이디일때, 다시 살리면 된다.
                    {   
                    }
                    else if ( status == EINVAL ) // 잘못된 시그널을 전달했을 경우.


                    else // 현재 쓰레드는 생존해 있다.
                    {
                    }
                    ------------------------------------------------------------------------------------

                    반응형
                    반응형


                    리눅스에 자주 사용하는 tail 명령어와 같은 역활을 하는 C 코드.
                    옵션으로 사용하는 몇 라인 긁어오라는건 for문 하나만 추가하면 구현이 가능하다.

                    FILE 객체에 _ptr 필드 가지고 난리를 치다가 결국 안되서 fseek을 사용해서 구현했더니 잘 된다.
                    현재는 One_Line 배열에 한줄만 가져가는 코드인 상태.


                     #define LINE_CHECK_BYTE_NUM 512

                    char *InquireLog()
                    {
                         FILE *fp = NULL;
                         char c;
                         long leng=1l;
                         char One_Line[512];

                         if((fp = fopen("test.txt","rb")) == NULL)
                               fprintf(stderr,"LOG_PATH Open Error\n");

                         while(leng++ < LINE_CHECK_BYTE_NUM){
                               fseek(fp,-(leng),SEEK_END);
                               c = fgetc(fp);
                               if(c == '\n'){
                                    fgets(One_Line,leng,fp);
                                    //printf("One_Line : %s\n",One_Line);
                                    break;
                               }
                         }
                        return One_Line;
                    }

                    반응형
                    반응형

                    CR : Carriage Return
                    LF : Line Feed

                    간단하게 타자기를 생각을 하면 한글자씩 입력하면 먼지 옆으로 한칸씩 밀려나고,
                    한줄을 다 입력하고 나서는 그걸 왼쪽으로 밀어 주고 또 입력을 하는 것을 영화에서 봤을 것이다.

                    간단하게 한줄에서 왼쪽 끝으로 밀어주는것이 CR이고
                    다음 줄에 입력을 하도록 종이를 한줄 밀어주는것이 LF 라고 할수 있다.

                    어떤 연유에서 처리 방법들이 엔터를 CR+LF(\r\n)으로 처리하고
                    Unix/Linux계열에서는 엔터를 LF(\n)으로 처리하고
                    MAC 계열에서는 엔터를 CR(\r)로 처리한다고 한다.

                    굳이 Dos/Windows 상황에서 \r\n의 차이점을 느끼려면 파일을 읽을 때 binary모드로 읽으면
                    파일에 엔터가 \r\n으로 표기되지만 ASCII 모드로 읽으면 \n 으로만 표기가 되는 것을 알 수 있다.

                    Windows 는 개행을 carrige return 과 line feed 이 두개의 문자를 개행코드로 하고,
                    Unix는 line feed를 개행코드로 하며, Mac은 Carrige Return 을 개행코드로 한다

                    퍼온 곳 : http://seongunism.tistory.com/65
                    반응형

                    'Coding Tip > Win Programming' 카테고리의 다른 글

                    CreateProcess시 핸들 누수 현상  (3) 2011.08.17
                    반응형

                    우선 컴터 초짜인 내가 대충 이해하기로는 Libxml2는 기존의 XML 파서들보다 다양한 인터페이스를 가지고 다양한 언어그룹에서 사용할 수 있는 기능을 포함하고 있는 최신형 XML Parser 인듯 하다. 사용법 또한 간단하여 이번 기회에 공부할 겸 사용해보게 되었다.
                    설치부터 사용까지 포스팅을 해보아요

                    -----------------------------------------------------------------------------------------------
                    설치 환경 - Linux Ubuntu 10.10

                    libxml2 설치 (명령문 실행 순서대로)

                    # wget ftp://xmlsoft.org/libxml2/libxml2-2.7.7.tar.gz
                    # tar xvzf libxml2-2.7.3.tar.gz
                    # cd libxml2-2.7.3
                    # ./configure --prefix=/usr/local/xml
                    # make
                    # make install
                    ---------------------------------------------------

                    1. 터미널에서 wget을 통해서 다운을 받아도 되고 직접 FireFox등의 익스플로어를 사용하여 ftp://xmlsoft.org/libxml2/ 에 들어가면
                    최신버전을 확인하여 다운 받을 수 있다. 참고하자. (해당폴더에 압축파일을 다운 받을 수 있다)



                    2. 다운 받은 압축파일의 압축을 풀자.
                    tar 명령어에서 xvzf는 옵션인데 각 옵션의 의미는 아래와 같다.
                    -------------------------------------------------------- 압축풀때
                    -x, --extract, --get : 저장된 것에서 풀어낸다.
                    -v, --verbose : 처리중인 파일을 자세하게 보여준다. (압축푸는 과정 보여줌)
                    -f, --file [HOSTNAME:]F : 저장 파일 혹은 장치 파일 F에 저장한다.
                    -z,--gzip, --unzip : gzip으로 압축하거나 푼다.
                    -j, bzip2 : bzip2 필터를 사용하여 .bz2 파일을 푼다.
                    -p : 퍼미션을 유지시켜준다.

                    ------------------------------------------------------- 압축할때
                    -c, --create : 새 저장파일을 만든다.

                    압축풀기의 예)
                    1. tar -zvxf (압축대상파일).tar.gz -C (목적폴더위치),
                    (삭제 명령어 rm -f -R (목적폴더위치)
                    [출처] 리눅스 tar 명령어 관련 | alan100님 글

                    3. 어쨋든 압축을 푼 뒤 해당 폴더에 들어간다.
                    4. ./configure --prefix=/usr/local/xml (여기서 configure는 환경설정, --prefix는 설치시킬 위치를 의미한다)
                    5. 컴파일 한뒤에 설치까지 끝마친다.

                    6. 끝 이제 Libxml의 설치를 마쳤다.



                    libxml2 설치 참고 usr - http://www.ubuntu.or.kr/viewtopic.php?p=73454
                    libxml2 코드(튜토리얼) 참고 usr - http://kylesoft.springnote.com/pages/5620523
                    libxml2 라이브러리 참고 usr - http://myweb.bcpark.net/~hosuck/gnu3/?doc=bbs/gnuboard.php&bo_table=PG_JAVASCRIPT&page=1&wr_id=29

                    gcc -o (dest) (source) -L/usr/local/xml/lib(prefix 한 링크주소) -lxml2(libxml2.so 혹은 a가 있는 주소)

                    어렵다 -ㅂ-
                    반응형

                    + Recent posts