반응형


출처 : 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; }



                    반응형
                    반응형

                    _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;

                    };

                    반응형
                    반응형

                    출처 : 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()|작성자 달마

                     

                    반응형
                    반응형



                    윈도운 버전!

                    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 디렉토리 만든다.

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

                    반응형
                    반응형


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

                    ------------------------------------------------------------------------------------
                    윈도우에서 쓰레드의 생존여부를 검사하는 함수는 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;
                    }

                    반응형

                    + Recent posts