'System'에 해당되는 글 9건

  1. 2007.07.23 커널 오브젝트 - 핸들
  2. 2007.05.09 서비스 포트 변경
  3. 2007.04.19 Threads Scheduling (1)
  4. 2007.03.25 Condition Variables (1)
  5. 2007.03.18 Read/Write lock (8)
  6. 2007.03.06 libevent and multithread (6)
  7. 2007.01.27 소켓 강제 종료시 파이프(pipe) 깨짐
  8. 2007.01.05 Edge trigger vs Level trigger (2)
  9. 2006.12.11 Writing a Windows Service Program (8)
/**
 갈 수록 윈도우와 리눅스 사이에서 왔다 갔다 해야 할 일이 많아 지고 있다.
 오늘은 윈도우의 커널 오브젝트(Kernel Object)라는 것에 대해서 알아 보도록 하자.
*/


유저 모드와 커널 모드
 '커널 오브젝트'에 대해 알아보기 전에 '유저 모드'와 '커널 모드'의 실행이라는 것에 대해서 알아 보도록 하자. 프로그램을 실행하게 되면 사용자가 직접 접근할 수 있는 유저 영역과 시스템 콜등을 통해서만 접근할 수 있는 커널 영역이 있다. 주로 커널 영역에는 운영체제, 프로세스간에 공유되는 데이터들이 보존되어 함부로 메모리 억세스를 할 수 없도록 제한하고 굳이 그 자원을 사용하기 위해서는 필수적으로 커널에게 자원을 요청하여 그에 대해 구분 할 수 있는 무엇인가를 얻어와 커널의 허가 아래서 사용해야 한다. 그렇지 않다면 사용자가 운영체제가 상주하는 메모리 영역을 침범하여 마음대로 바꾸어 놓는다거나 하여 전체 시스템에 장애를 가져 올 수 있다.

커널 오브젝트와 오브젝트 핸들
 위에서 언급했듯이 커널 모드에서 사용되는 자원들을 구분 해 놓은 것을 커널 오브젝트라고 하고, 사용자가 커널에게 자원을 요청하고 오브젝트를 참조 할 수 있는 무엇인가를 할당 받은 것이 오브젝트 핸들이라고 한다. 커널 오브젝트에는 파일, 쓰레드, 프로세스, 뮤텍스, 세마포어, 이벤트 핸들..등의 다양한 종류가 있고, 사용자가 오브젝트의 핸들을 할 당 받는 방법은 다양하다.

 일반적으로 커널 오브젝트는 프로세스에서 생성하므로 프로세스가 끝나면 소멸 될 것으로 생각하는데, 커널 오브젝트의 소유자는 사용자 프로세스가 아니라 커널이다. 그러므로 사용자 프로세스가 죽더라도 커널 오브젝트는 남아 다른 프로세스에 의해 재사용 될 수 있다(이 말은 곧 프로세스가 끝나도 자원이 해제가 안되는 '자원 누수'라는 뜻이기도 하다).

커널 오브젝트의 상태
 윈도우의 커널 오브젝트는 signaled, non-signaled의 두 가지 상태를 가진다. 글자 그대로 신호를 받은 상태와 그렇지 못 한 상태를 나타낸다. 커널 오브젝트는 이 상태를 표시하기 위해 boolean 변수를 하나 가지고 있고, TRUE면 signaled 상태, FALSE이면 non-signaled 상태를 가리킨다. 일반적으로 커널 오브젝트가 생성 되면 non-signaled 상태가 되며(하지만 사용자가 직접 지정 해서 생성 할 수도 있다), 어떠한 상황이 발생하면 signaled가 된다. 어떠한 상황이라는 것은 각 커널 오브젝트마다 다르다. 예로 Thead와 Process의 커널 오브젝트(Handle)는 non-signaled 상태에 있다가 종료하는 경우에 signaled로 변경된다.

참고 : http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&wr_id=17&page

'진리는어디에' 카테고리의 다른 글

XML 기초  (9) 2007.10.04
정규 표현식(Regular Expressions)  (0) 2007.08.31
커널 오브젝트 - 핸들  (0) 2007.07.23
connected UDP socket  (0) 2007.05.14
C++ operator 사용하기  (3) 2007.04.22
Threads Scheduling  (1) 2007.04.19
Posted by kukuta

댓글을 달아 주세요

 대학교나 기타 공공 기관에서 몰래 서버를 한대 설치하고 서비스를 하다 보면 꼭 방화벽이라는 놈들이 포트를 막아 해당 서비스를 하지 못하도록 막는다. 이럴 경우에는 서비스 포트를 변경해 주면 되는데, 보통 웹서비스를 하는 80포트는 잘 안막으므로 자주 애용해 주도록 하자.

 이런 저런 설명들을 장황히 늘어 놓는 것 보다도 telnet서비스의 포트를 변경하는 법과 포트를 하나 더 띄우는 방법을 설명 하도록 하겠다. 나머지는 알아서 응용하도록...

[텔넷 서비스 포트 변경]

1.서비스 포트 변경은 /etc/services 파일에 있는 텔넷 서비스 포트를 다른 숫자로 변경하면 된다. 텔넷의 경우 기본 포트는 23번을 사용하지만 예를 들어 203으로 지정하면, 원격 접속시 203 포트를 지정하지 않으면 텔넷 접속이 불가능해진다. 포트 변경 할 경우, 혹시나 다른 서비스에서 그 포트를 사용하는지 확인해 보는것도 잊지 말아야 한다.

/etc/services 파일에서 telnet 포트를 다음과 같이 203으로 변경한다.

telnet          203/tcp
telnet          203/udp # 솔찍히 udp는 하나마나다..

 원격지의 서버에서 telnet을 203포트로 접속한다.

2.하나의 서비스에 기본 포트 이외의 포트를 추가 할 경우가 있다. 이 경우 /etc/services 파일을 수정 하는 것 뿐만 아니라, /etc/xinet.d에 있는 서비스 파일을 하나 더 복사해야 한다.

telnet2        2003/tcp
telnet2        2003/upd

cp /etc/xinet.d/telnet /etc/xinet.d/telnet2

 복사한 telnet2 파일을 열어 아래와 같이 수정 하자.

service telnet2
{
    flags = REUSE
    socket_type = stream
    wait = no
    user = root
    server = /usr/sbin/in.telnetd
    log_on_failure += USERID
    disable =no // ==>를 yes가 아닌 no로 수정
}

이렇게 수정을 한 다음, 터미널에서 다음과 같이 입력한다.
/etc/rc.d/init.d/xinetd restart

이렇게 하면 inet데몬이 대기하다 요청이 들어오면 telnet 프로세스를 띄워준다.

'도구의발견' 카테고리의 다른 글

PHP - 쉘 스크립트로 활용 하기  (5) 2007.11.03
vim 문자열 검색 및 치환  (2) 2007.10.25
서비스 포트 변경  (0) 2007.05.09
gcc 라이브러리 만들기와 사용  (0) 2007.04.24
gcc의 실행과 옵션  (0) 2007.04.24
cvs 사용법  (0) 2007.03.03
Posted by kukuta

댓글을 달아 주세요

/**
 이래저래 먹고 사는데 바쁘다 보니 글하나 올리기도 빡세구나. 아직도 해야 할 일이 많은데 내가 생각하는 뭔가를 정리하고 올린다면 시간이 더 많이 걸릴 것 같고, 오늘은 웹에서 떠돌아 다니는 원문을 간단하게 해석해 보는 것으로 블로깅을 마무리 해야겠다.
 
 주제는 쓰레드 스케줄링(thread scheduling)에 관련한 것으로, 내가 멀티 쓰레드를 이용해서 뭔가를 하는데 아무리해도 성능이 안나오길래 혹시 scheduling에 관련한 문제가 있지 않나 해서 조사하다 찾은 문서를 설명 할 것이다. 원론적인 스케줄링에 관련된 이야기는 아니고, 여러가지 스케줄링 기법이 있고, 내가 생성한 쓰레드가 어떤 스케줄링 알고리즘을 사용하게 설정 하느냐 하는 API 사용법 정도라고 생각하면 되겠다. 보다 자세한 쓰레드 스케줄링에 대해서 알고 싶으면 'Operating System Concepts'라는 책을 추천한다. 흔히들 공룡책이라고 하면 안다.
*/

Inheritsched Attribute 

inheritsched attribute(사전에 없는 단어지만 상속받은 스케줄 속성 이라고 생각하면 될 듯)가 어떻게 정해 질 것인지를 정한다. 정해질 것을 정한다고 하니 말이 이상하게 꼬이는 감이 있는데, 쓰레드는 기본적으로 쓰레드를 생성하는 객체의 속성을 물려 받겠끔 되어 있다. 하지만 이런 속성들을 변경하고 싶다면 기본 속성을 물려 받지 않고 따로 정의 하겠다고 표시를 해야 한다. 그것이 스케줄 속성이 어떻게 정해 질지(물려 받을지 새로 지정할지)를 정하는 것이라고 하겠다. 속성은 아래와 같이 두 가지가 정의 되어 있다.

PTHREAD_INHERIT_SCHED 새로 생성되는 쓰레드가 스케줄링 속성(정책, 파라메터 속성등)을 부모쓰레드(새로운 쓰레드를 생성하는 쓰레드)에게서 물려 받겠다는 의미. 이 플래그를 지정하면 다른 모든 쓰레드 스케줄링 속성들이 무시되고 부모의 속성만을 물려 받는다.
PTHREAD_EXPLICIT_SCHED 새로 모든 속성들을 재 지정 해주겠다는 의미.

 위에서도 이야기 했듯이 기본 값은 PTHREAD_INHERIT_SCHED다. pthread_attr_setinheritsched 함수에 의해서 셋팅이 되고, 현재 셋팅 되어져 있는 값은 pthread_attr_getinheritsched 함수를 통해 얻어 올 수 있다.
 
 새로운 속성들을 정의 해주기 위해서는 반드시 PTHREAD_EXPLICIT_SCHED로 셋팅 되어야 하고, 그렇지 않으면 모든 값들이 무시된다.

Scheduling Policy and Priority

쓰레드 라이브러리는 아래의 세 가지 스케줄링 방법을 제공하고 있다 :

SCHED_FIFO First-in first-out (FIFO) scheduling. 각 쓰레드들이 고정된 priority를 가지고 있고, 여러 쓰레드들이 같은 prioirity level을 가지고 있으면 FIFO 형태로 돌아 간다.
SCHED_RR Round-robin (RR) scheduling. 각 쓰레드들이 고정된 priority르 가지고 있고, 여러 쓰레드 들이 같은 priority level로, 고정된 time slice 내에서만 동작하면 RR형태로 돌아간다.
SCHED_OTHER 기본 AIX scheduling. 각 쓰레드들이 동적으로 (activity나 기타 요소에 따라) 변경이 가능한 priority를 할당 받고, 쓰레드의 실행 시간이 일정 time-slice 내에서 실행 되게 되는 형태. 그런데 이것은 시스템의 구현 마다 다 다를 수 있다.

기본적인 스케줄링 알고리즘은 SCHED_OTHER로 셋팅되어져 있다.
/**
  사족이지만, 별다른 특별한 이슈가 없지 않는한 기본으로 셋팅되어지는 SCHED_OTHER가 가장 성능이 좋다고 개인적으로 판단한다. 아니 개인적 뿐만 아니라, 이 문서에서도 별다른 특정한 상황이 아니면 SCHED_OTHER가 가장 좋은 성능을 보일 것이라고 이야기 한다.
*/

Priority 는 정수형태의 값으로써 1에서 127까지의 값을 가질 수 있다. 1이 가장 우선순위가 낮고, 127이 가장 우선 순위가 높다. Priority level 0은 시스템에 의해 예약 되어 있기 때문에 사용 할 수 없다.
※ AIX 커널에서는 앞에 이야기 했던 priority level의 순서가 거꾸로 라는 것에 주의 하도록 하자. ps 커맨드를 이용하면 priority를 볼 수 있다.

쓰레드 라이브러리는 sched_param structure로 priority를 조절한다. sched_param은 sys/sched.h 파일에 정의 되어 있고,, 지금은 아래와 같이 두 개의 필드를 가지고 있다 :

sched_priority priority를 지정.
sched_policy 쓰레드 라이브러리에 의해 무시되며, 사용 해서는 안된다.

앞으로 쓰레드의 다른 속성을 나타내기 위해 필드가 더 추가 될 수 있다.
/**
  나의 경우는 sys/sched.h가 아니라 bit/sched.h에 sched_param이 정의 되어 있었고, 필드도 __sched_priority 하나 뿐이 었다. 운영체제는 CentOS2.0 다.
*/

Setting the Scheduling Policy and Priority at Creation Time

 쓰레드가 생성 될 때 pthread_attr_setschedpolicy 함수를 이용해서 위에서 언급한 세 가지 스케줄링 알고리즘 중에 하나를 지정 할 수 있다. 그리고 현재 지정되어 있는 스케줄링 알고리즘 값을 얻어 오려면 pthread_attr_getschedpolicy 함수를 이용하면 된다.

 Scheduling priority는 쓰레드 생성 시점에 sched_param 속성을 통해 지정 될수 있다. pthread_attr_setschedparam 함수로 schedparam 을 셋팅하거나,  pthread_attr_getschedparam 함수로 schedparam 값을 얻어 올 수 있다.

아래 코드는 RR(round-robin) 스케줄링 알고리즘과 priority level 3을 가지고 쓰레드를 생성 시킨다 :

sched_param schedparam;
 
schedparam.sched_priority = 3;
pthread_attr_init(&attr);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&attr, SCHED_RR);
pthread_attr_setschedparam(&attr, &schedparam);
pthread_create(&thread, &attr, &start_routine, &args);
pthread_attr_destroy(&attr);

Setting the Scheduling Attributes at Execution Time

 현재 스케줄링 알고리즘과 priority는 pthread_getschedparam 함수로 알아 낼 수 있다. 역시 이 값들은  pthread_setschedparam 함수로 셋팅될 수 있다. 만일 대상 쓰레드가 현재 프로세서에 의해 처리중이라면 새로 셋팅되는 스케줄링 알고리즘과 priority는 다음 스케줄될 때(프로세서에 의해 처리 될 때) 반영 된다. 반대로 현재 쓰레드가 동작 중(running)이 아니라면, 함수 호출과 동시에 반영이 된다.

 예를 들어, 쓰레드 T는 현재 RR알고리즘으로 동작 중(running)이며, 이것을 FIOF로 변경 하고자 한다. 일단 쓰레드 T는 주어진 타임 슬라이스 동안 계속 RR로 동작 할 것이며, 주어진 타임슬라이스를 다 쓰고 나면 다시 running 상태로 가기 위해 대기 상태로 간다. 만일 쓰레드 T 보다 더 높은 우선 순위를 가진 쓰레드가 없다면 다시 running 상태로 넘어 갈것이고, 이제서야 T는 FIFO로 변경 된다. 한 가지 예를 더 들어 보도록 하자. 낮은 우선 순위를 가진 쓰레드 T가 대기 상태에 있고, 다른 쓰레드가 pthread_setschedparam 함수를 호출하여 T의 우선 순위를 높였다고 하자. 이 경우 T보다 높은 우선 순위를 가진 쓰레드가 동작 중이 아니라면 T는 바로 running 상태로 넘어가게 된다.

Note: Both subroutines use two parameters: a policy parameter and a sched_param structure. Although this structure contains a sched_policy field, programs should not use it. The subroutines use the policy parameter to pass the scheduling policy and ignore the sched_policy field.

Considerations about Scheduling Policies

 무슨 특별한 이유가 있지 않는 이상, 어플리케이션은 디폴트 스케줄링 알고리즘을 사용해야만 한다.

 RR 알고리즘은 같은 우선 순위를 가진 스케줄들이 같은 타임 슬라이스를 할 당 받는 것을 보장 한다. 단, 쓰레드의 activity와 전혀 관계 없이 시간을 할당 하기 때문에 쓸모 없는 자원의 낭비가 발생 할 수도 있다. 이런 RR 알고리즘은 센서에서 값을 읽어 오거나(센서는 보통 일정 주기로 한 번씩 메시지를 보낸다) 동작기에 뭔가를 쓸 때 사용 하면 좋다.

 FIFO는 크리티컬한 작업들을 다룰 때 사용하면 좋다. FIFO를 사용하게 되면 특별한 인터럽트가 들어오기 전까지는 작업이 완료 될때 까지 계속 진행 된다. 높은 우선 순위를 가지고 있는 FIFO 쓰레드는 다른 쓰레드들에 의해 running 상태를 안 빼앗길 확률이 높으며(non-preermptive) 시스템 전체의 성능에 영향을 끼칠 수 있다. 예를 들어 FIFO는 강도가 세고 연산시간을 오래 잡아 먹는 계산(커다란 행렬 연산 같은 것)을 하는데 사용되면 전체 성능을 다운 시길 수 있다.

Contention Scope

쓰레드 라이브러리는 아래의 두 가지 contention scopes(분쟁 범위?)를 정의 한다 :
/**
 영향을 미치는 범위가 한 프로세스 내에서만인지, 아니면 시스템 전역인지를 구분 하는 것을 contention scope라고 한다.
*/

PTHREAD_SCOPE_PROCESS Process (or local) contention scope. 프로세스 내에 있는 모든 다른 쓰레드들에 한정하여 contention scope를 설정한다.
PTHREAD_SCOPE_SYSTEM System (or global) contention scope. 시스템 전역적으로 contention scope를 설정한다.

Setting the Contention Scope

 Contention scope 는 오직 쓰레드가 생성 되는 시점에만 셋팅 될 수 있다. pthread_attr_setscope 함수와 pthread_attr_getscope 함수를 통해 관련된 값을 셋팅하거나 얻어 올 수 있다.

 Contention scope 라는 것은 오직 M:N 쓰레드 구조(커널 쓰레드와 유저 쓰레드의 관계가 다대다 라는 이야기. 자세한 사항은 'Operating System Concepts'라는 책을 추천)에서만 유효하다. single-scope(1:1) 구조(예를 들어 AIX version 4.3)에서는 PTHREAD_SCOPE_PROCESS를 셋팅하면 항상 에러를 리턴한다. 이유는 모든 쓰레드들이 system contention scope를 가지고 있기 때문이다. 아래의 TestImplementation 처럼 함으로써 지원 여부를 쉽게 알아 낼 수 있다 :

int TestImplementation()
{
        pthread_attr_t a;
        int result;
        pthread_attr_init(&a);
        switch (pthread_attr_setscope(&a, PTHREAD_SCOPE_PROCESS))
        {
                case 0:          result = LIB_MN; break;
                case ENOTSUP:    result = LIB_11; break;
                case ENOSYS:     result = NO_PRIO_OPTION; break;
                default:         result = ERROR; break;
        }
        pthread_attr_destroy(&a);
        return result;
}

Prior to AIX Version 4.3, this routine would return LIB_11.

In AIX Version 4.3, this routine returns LIB_MN.

Impacts of Contention Scope on Scheduling

 Contention scope는 쓰레드 스케줄링에 영향을 미치게 된다. 각 system contention scope는 하나의 커널 쓰레드에 묶이게 된다. 그래서 글로벌 유저 쓰레드의 스케줄링 알고리즘과 우선순위를 변경하게 되면 그 밑에 놓여 있는 커널 쓰레드의 스케줄링 알고리즘과 우선 순위를 변경 하게 되는 결과를 낳는다.

AIX에서는, 오직 root권한을 가진 커널 쓰레드만이 FIFO나 RR을 사용 할 수 있다. 아래 코드를 보도록 하자 :

	schedparam.sched_priority = 3;
	pthread_setschedparam(pthread_self(), SCHED_FIFO, schedparam);

 위의 코드를 system contention scope로 설정 되있고, 루트 권한 없이 실행 했다면 EPERM error code를 리턴 할 것이다. 하지만 process contention scope를 가지고 실행하게 된다면 에러를 리턴 하지 않는다. process contention scope에서는 쓰레드 스케줄링을 하기 위해 루트 권한이 필요하지 않다.

 로컬 유저 쓰레드(Local user thread)는 프로세스 내에서 어떠한 스케줄링 알고리즘이나 우선 순위를 설정 할 수 있다. 하지만 두 쓰레드가 같은 스케줄링 알고리즘과 우선순위를 가지고 있고, 다른 contention scope를 자기고 있다면 같은 방법으로 스케줄링 되지 않는다. Process contention scope를 가지고 있는 쓰레드는 커널 쓰레드에 의해 처리된다.

sched_yield Subroutine

 sched_yield 함수는 yield 함수와 같다. 이 함수를 호출한 쓰레드는 프로세서를 반환 하고 다른 쓰레드에게 스케줄링 될 기회를 주게 된다. 다음에 스케줄링 되는 쓰레드는 같은 프로세스 내애 있는 스레드이거나 다른 프로세스의 쓰레드일 수 있다. yield 함수는 멀티 쓰레드 프로그램에서 사용되면 안된다.
/**
 개인적으로 나는 쓰레드가 프로세서를 반납 할 때 usleep(1)을 사용해 왔는데, 이것은 낭비하지 않아도 될 1마이크로세컨드를 낭비한 것이다. 앞으로는 sched_yield()나 yield() 함수를 애용 해야 겠다.
 
 그리고 이 문서에서는 멀티쓰레드 환경에서 yield를 사용하면 안된다라고 나오지만 man page에서는 그런 것에 대한 것은 언급이 되어있지 않고 다만 '멀티 쓰레드인 프로세스에서 호출하게 되면, 호출한 쓰레드만 영향을 받는다' 라고 나와 있다. 보다 자세한 설명이 있었으면 하지만 물어 볼 메일 주소도 없고 물어 볼 사람도 없으니 그냥 내가 테스트 해보는 수 밖에..그런데 테스트도 만만치 않쿤하~아놔~
*/

The interface pthread_yield subroutine is not available in XOPEN VERSION 5.

원문 보기 : http://www.unet.univie.ac.at/aix/aixprggd/genprogc/threads_sched.htm
참고 : http://blog.naver.com/raon_pgm?Redirect=Log&logNo=140010348579

Posted by kukuta

댓글을 달아 주세요

  1. kts123 2007.04.19 10:55  댓글주소  수정/삭제  댓글쓰기

    스케쥴링 정책을 사용자가 고를 수 있다는 것인데,
    이 문서는 AIX에 구현된 내용에 관한 것이고,
    이 내용에서 언급하고 있는 스케쥴링 종류에 대한 것은 본문에서 언급한 대로 공룡책을 보면 되겠지만,
    Programming with Posix Threads
    (http://book.naver.com/bookdb/book_detail.php?bid=222506)
    에 더 정확하고 자세하게 나와 있는 듯 합니다. 그리고 특히 사용상의 주의점과 부작용에 대해 아주 자세히 설명해 주고 있으니 실제로 스케쥴링을 변경하려면 반드시 읽어 보아야 될 듯 싶네요.

    또 하나더 yield 는 C 런타임 라이브러리에 있고, 쓰레드가 아니라 프로세스를 명시적으로 run 상태에서 ready로 만드는 함수이므로 쓰레드에 최적화 되어 있지 않아서 사용하지 말라고 한 듯 합니다. yield 의 메뉴얼에 보면 쓰레드에 대해서도 동작한다고 했지만 pthread 와는 독립적으로 만들어진 것이니 pthread 에서 사용하는 thread와는 궁합이 맞지 않는 부분이 있을 것 같네요.

 Condition variables와 mutex를 같이 사용할 경우 condition variable은 임의의 상태(condition)이 발생하길 기다리는 race-free 한 방법을 제공 한다.
 
 Condition과 mutex의 사용에 대한 간략한 개념을 이야기 하자면, condition 자체는 mutex와 함께 사용되어야만 한다. mutex는 condition variable이 wait로 넘어가기 전의 초기화라던지 기타 등등에 대한 concurrency를 보장한다.

 Condition variable이 사용되기 전에 역시 다른 동기화 객체와 마찬가지로 초기화라는 것이 필요하다. C/C++에 서 Condition variable은 pthread_cond_t data type으로 표시되어 진다. 초기화 방법은 두가지가 있는데 한가지는 PTHREAD_COND_INITIALIZER (static하게 정해져 있는 변수)이고 다른 한가지는 pthread_cond_init (dynamic하게 할당하는 함수)가 있다.

 Condition variable을 해제 할때는 pthread_mutex_destroy 함수를 사용한다.

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

Both return: 0 if OK, error number on failure

만일 별다른 옵션을 줄 필요가 없다면 attr은 단순히 NULL로 설정 하도록 하자(대부분 NULL이면 충분하다. 그리고 Linux Thread에서는 attribute를 지원하지 않기 때문에 attr 값은 무시된다.)

 pthread_cond_wait 를 이용해 conditions이 발생(나중에 나올 pthread_cond_signal함수를 호출하는 것) 하기를 기다릴 수 있다.

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
                           const struct timespec *restrict timeout);

Both return: 0 if OK, error number on failure

 pthread_cond_wait 에게 넘겨지는 mutex는 condition variable을 보호하며 함수에 lock을 건다. 이러한 과정을 거치면 호출 thread를 waiting thread list에 대기 시키고, 자동적으로 mutex를 해제한다(이말인 즉슨 pthread_cond_wait 이후 코드들이 계속 진행 된다는 말이다). thread는 이 과정에서 가사상태(sleep)에 빠지게 되므로 CPU 점유율을 먹지 않고, 더 이상 thread를 진행 하지 않으므로 mutex가 풀린다고 해서 concurrency에 문제가 생기지는 않는다.

 pthread_cond_timedwait 는  pthread_cond_wait 와 동일 하게 동작하지만 timeout이라는 기능을 넣을 수 있다. timeout으로 넘어 가는 timespec 구조체는 아래와 같다.

    struct timespec {
            time_t tv_sec;   /* seconds */
            long   tv_nsec;  /* nanoseconds */
    };

 이 구조체를 이용함에있어서 우리는 상대적 시간이 아니라 절대적 시간을 적어 줘야 한다. 예를 들어서 우리가 3분을 기다릴 것이라고 가정 한다면, timespec 구조체에 3분이라고 표시하는 대신, 현재시간 + 3이라고 적어 줘야 한다는 것에 유의 해야 한다.

 만일 아무런 condition의 발생이 없이 timeout이 된다면, pthread_cond_timedwait 함수는 mutex를 재획득하고  ETIMEDOUT을 리턴 할 것이다.  pthread_cond_wait or pthread_cond_timedwait, 에서 정상적으로 리턴하게 된다면 다른 thread가 이미 run하고 condition을 이미 바꾸었는지에 대해서 condition을 다시 테스트해야 할 필요가 있다.

 Condition이 발생했다는 것을 알리는 것은 아래의 두 함수가 있다. pthread_cond_signal 함수는 하나의 thread를 waiting 상태에서 깨우는 것이고 pthread_cond_broadcast 는 모든 thread를 wait 상태에서 깨우는 것이다.

The POSIX specification allows for implementations of pthread_cond_signal to wake up more than one thread, to make the implementation simpler.

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

Both return: 0 if OK, error number on failure

주의 사항은 pthread_cond_signal or pthread_cond_broadcast을 호출하는 것은 condition이 변경 되고 난 뒤에 호출되어야만 한다는 것이다. 예를들어 queue에 값이 있는 것을 확인 하는 thread 같은 경우는 queue에 값을 넣고 난 뒤에 condition을 변경 해야만 한다.
Example

 Condition은 ThreadSafeQueue의 상태를 나타내는 변수가 된다. while 루프 안에서 condition의 상태를 검사 할 것이고 queue에 data를 넣게 되면 condition의 상태를 만족 시키게 된다. 여기서 signal 함수를 꼭 mutex 사이에 위치 시켜야 할 필요는 없다. pthread_cond_signal 함수를 호출 하기 전에 queue에 data가 들어 갔다는 사실만 확정 되면 된다. Condition을 체크하는 부분이 while 루프 안에 있기 때문에 thread가 깨어나고, queue가 비었는지 확인하고, 다시 waiting 상태로 들어가고 하는 비효율 적인 polling 방법은 사용되지 않을 것이다.

Using condition variables
#include <pthread.h>
#include <queue>
template<class T> class ThreadSafeQueue {
    public :
        ThreadSafeQueue() {
            pthread_mutex_init(&m_mutex, NULL);
            pthread_cond_init(&m_cond, NULL);
        }
        ~ThreadSafeQueue() {
            pthread_cond_destroy(&m_cond);
            pthread_mutex_destroy(&m_mutex);
        }
        void enqueue(T t) {
            pthread_mutex_lock(&m_mutex);
            m_queue.push(t);
            pthread_mutex_unlock(&m_mutex);
            pthread_cond_signal(&m_cond);
        }
        bool dequeue(T* t) {
            pthread_mutex_lock(&m_mutex);
            while(m_queue.empty()) {
                pthread_cond_wait(&m_cond, &m_mutex);
            }
            *t = m_queue.front();
            m_queue.pop();
            pthread_mutex_unlock(&m_mutex);
            return true;
        }
    private :
        pthread_mutex_t m_mutex;
        pthread_cond_t  m_cond;
        std::queue<T>   m_queue;
};









참고 :
http://www.joinc.co.kr/modules.php?name=News&file=article&sid=65&mode=nested

원문 :
Advanced Programming in Unix Enviroment : Stevens


'진리는어디에' 카테고리의 다른 글

도메인 이름을 이용해 IP 주소 얻기(gethostbyname)  (3) 2007.04.03
Leader/follower pattern  (0) 2007.04.01
Condition Variables  (1) 2007.03.25
sendto & recvfrom  (0) 2007.03.23
half-close  (0) 2007.03.20
소켓 종료와 TIME_WAIT(Socket termination and TIME_WAIT)  (10) 2007.03.19
Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://control.tistory.com BlogIcon 04규민 2011.07.05 16:28  댓글주소  수정/삭제  댓글쓰기

    인곤선배님 ㅎ

    OS 공부 한다고 google에 condition variable 검색 했는데 선배 블로그네요 ㅎ

Read/Write lock

진리는어디에 2007. 3. 18. 17:57

Read/Write Lock(이하 rwlock)의 기본적인 생각은 아래와 같이 간단하다.

  * Read 작업은 값을 변경하지 않으니 몇개가 붙어도 동기화에 문제가 생기지 않는다.
  * Write 작업은 값을 변경 할 수도 있으니, 하나의 크리티컬 섹션에 하나만 붙어야 한다.
  * Read 작업은 끼리는 여러개가 하나의 크리티컬 섹션에 접근 가능하나, Read하는 것을 도중에 바꾸면 안된다.

 

 위의 세 가지 사항 정도만 제외 한다면 rwlock은 mutex랑 비슷하다. 개념도 비슷하고, 사용법도 비슷하고, 하는
일도 비슷하다.

 rwlock의 장점은 read 작업에 있어서 여러개의 쓰레드가 서로 블록킹 하는 일이 없어 괜한 오버헤드를 가지고 오
지 않고, 단점은
그 특성상 read작업이 많다면 거기에 눌려 write 오퍼레이션은 뒤로 밀리기 마련이다.
 
 write lock을 걸려고 하는 쓰레드가 자원을 못 받아 말라 죽는
시나리오를 만들어 보자.

 1) 1번 쓰레드가 A라는 공유 자원을 참조 하려고 한다.
   *
아직 아무런 lock이 걸려 있지 않으므로 커널은 lock을 허락한다.
 2) 2번 쓰레드가 A라는 공유 자원을 변경 하려고 한다.
   * 변경이라는 것은 wrtie lock을 걸어야 하는데 아직 read 작업이 있으므로 write를 잠시 멈춘다.
     
하지만 그 사이에 다른 read 작업이 또 들어 온다. read 작업은 공유가 되므로 커널은 뒤 늦게 온 read 작업을
     크리티컬섹션 안으로 넣어 버린다. 이런 현상의 반복으로 write를 요청하는 thread는 굶어 죽는 경우가 발생
     해 버릴수 있다.(현실적으로는 이런 경우를 대비하여 만들었 겠지만, 그래도 rwlockmodify작업보
    다는 read가 더 많은 곳에 사용하는 것이 가장  이상적이다
.)


#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict    rwlock, const pthread_rwlockattr_t    *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);


 API가 상당히 직관적이다. mutex같은 것들과 마찬가지로 pthread_rwlock_t 객체를 사용하기 이전에
pthread_rwlock_init으로 객체르르 초기화 해주고, 사용이 끝나고 커널에 반환 해야 할 경우에는 pthread_rwlock_destroy로 객체를 해제해 준다.

 read 작업을 하는 경우에는 pthread_rwlock_rdlock을 호출 하고, write작업을 할경우에는
pthread_rwlock_wrlock을 호출 하면 된다. 그리고 어떠한 read 작업이든, write작업이든 lock을 풀어 줄때는
pthread_rwlock_unlock을 호출 한다. read작업을 다른 말로 shared 작업, 즉 공유가 가능한 작업이라고 하는데 구현상 그 갯수에 제한을 두고 있다.
 
 
Single Unix
계열에서는 아래와 같은 함수를 지원한다..

#include <pthread.h>

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

  try라는 이름과 같이 단시 시도를 할 뿐이다. 만일 lock을 잡을 수 없는 상황이라면 바로 리턴을 한다. 리턴 값은
lock를 잡을 경우에는 0을 리턴하고, 그렇지 못하다면 EBUSY를 리턴한다.

 아래의 예제 코드는 두개의 read lock과 하나의 write lock을 사용한다. 하나의 read lock을 걸고 다른 read lock
를 걸었을 경우 과연 예상대로 lock이 잡히는지 안잡히는지, 그리고 read lock가 걸려 있는 상태에서 write lock는 과연 대기 상태에 있는지를 알아 보고자 하는 코드다.


참고 : Advance Programming in Unix Enviroment(section 11.6)

'진리는어디에' 카테고리의 다른 글

half-close  (0) 2007.03.20
소켓 종료와 TIME_WAIT(Socket termination and TIME_WAIT)  (10) 2007.03.19
Read/Write lock  (8) 2007.03.18
libevent and multithread  (6) 2007.03.06
extern  (0) 2007.02.23
소켓 강제 종료시 파이프(pipe) 깨짐  (0) 2007.01.27
Posted by kukuta

댓글을 달아 주세요

  1. h,ts 2009.12.04 09:26  댓글주소  수정/삭제  댓글쓰기

    오 -ㅁ-~~!! 이렇게 활용하는거군요~
    감사합니다. ^^

  2. Favicon of https://skywish25.tistory.com BlogIcon 허은호 2010.02.09 15:26 신고  댓글주소  수정/삭제  댓글쓰기

    구글링하다보니 주인장님 자료만 벌써 두번째 보게 되네요.
    좋은 정보 감사합니다다

  3. Favicon of http://www.opensource.apple.com/source/Libc/Libc-498.1.7/pthreads/pthread_rwlo.. BlogIcon 음흠~ 2010.04.12 16:33  댓글주소  수정/삭제  댓글쓰기

    http://www.opensource.apple.com/source/Libc/Libc-498.1.7/pthreads/pthread_rwlock.c
    를 보면(음.. 다른소스는 어떤지 모르겠지만 ^^)
    blocked_writer 가 있으면 read lock 이 기다립니다.

    while (rwlock->blocked_writers || rwlock->state < 0)
    {
    /* give writers priority over readers */
    PLOCKSTAT_RW_BLOCK(rwlock, READ_LOCK_PLOCKSTAT);
    ret = pthread_cond_wait(&rwlock->read_signal, &rwlock->lock);

  4. 상해 2011.02.10 16:00  댓글주소  수정/삭제  댓글쓰기

    ㅎㅎ설명 너무 잘해주시네요

  5. dfdf 2012.05.04 19:48  댓글주소  수정/삭제  댓글쓰기

    감사합니다.
    책에서 설명이 부족했었는데 여기 글 읽고 이해했습니다.

/**
  libevet 멀티 쓰레드 환경에서 사용코자 했는데, 이것이 생각 처럼 동작
하지 않았다. connection이 하나만 들어 왔을 때는 정상동작 했지만, 둘 이
상이되면서 부터는 event_dispatch() 에서 1을 리턴(set 되어 있는 이벤트
가 없다라는 의미)하면서 계속 종료  되었다.
  혹시나 싶어 libevent의 멀티 쓰레드 환경에 대해 찾아 보니 아래와 같은
글이 있어 짧은 영어 실력이나마 번역을 해 보았다.
 
  혹시나 누군가가 잘 못된 내용을 진실로 받아 들이고 그걸 다른 사람에게
진실인양 전파 한다면 세상에 잘못된 지식들이 판치게 되고, 지식을 추구하
는 사람들에게 있어 그것만큼 나쁜 일이 없다.

  아래의 글 중 잘 못된 내용이 있다면 언제든지
kukuta@gmail.com으로 알
려주면 고맙겠다.
*/


As the guy who added thread support to memcached, I feel qualified to
answer this one:

 memcached(libevent 이용해서 만든 일종의 메모리 DB)에 멀티쓰레드를
사용하려는 사람들에게 추천합니다.

What libevent doesn't support is sharing a libevent instance across
threads. It works just fine to use libevent in a multithreaded process
where only one thread is making libevent calls.

 libevent는 쓰레드간에 공유를 지원히지 않습니다. 만일 당신이 쓰레드를 써
야 하겠다면 한 쓰레드만이 libevent 호출하도록 만들어야 합니다.

What also works, and this is what I did in memcached, is to use multiple
instances of libevent. Each call to event_init() will give you back an
"event base," which you can think of as a handle to a libevent instance.
(That's documented in the current manual page, but NOT in the older
manpage on the libevent home page.) You can have multiple threads each
with its own event base and everything works fine, though you probably
don't want to install signal handlers on more than one thread.

 이것이 제가 memcached에서 libevent 인스턴스를 여러개 만들어서 사용하
려는 이유입니다. 각각 event_init() 호출은 개개별로 "event base" 객체를 생
성 합니다(이것은 요즘에 나오는 libevent man page에도 기록되어 있습니다).
 이런 방법으로 각각의 event base 가진 쓰레드를 생성 할 수 있으며, 비록
signal handler 하나의 쓰레드 이상에 인스톨 하는 것을 원치 않았더라도,
모든 것들이 순조롭게 잘 돌아 갈겁니다.

 In the case of memcached, I allocate one event base per thread at
startup time. One thread handles the TCP listen socket; when a new
request comes in, it calls accept() then hands the file descriptor to
another thread to handle from that point on -- that is, each client is
bound to a particular thread. All you have to do is call
event_base_set() after you call event_set() and before you call event_add().
 
 memcached의 경우, 나는 쓰레드의 시작점에서 event base 각 쓰레드 마다
할당 했습니다. 하나의 쓰레드가 listen 소켓을 들고, 새로운 접속이 시도 될 때
accept() 호출 하고 파일디스크립터(클라이언트소켓)을 생성하여 다른 쓰레드
에 넘깁니다. 이런 식으로 해서 각 클라이언트는 각자의 쓰레드에 바인드 됩니다.
 여기서 해야 할 일은 event_set()함수 호출 후 event_add() 호출 하기 전에
event_base_set() 호출하는 것 뿐입니다
.

Unfortunately, you pretty much have to use pipe() to communicate between
libevent threads, That's a limitation, and honestly it's a pretty big
one: it makes a master/worker thread architecture, where one thread
handles all the I/O, much less efficient than you'd like. My initial
implementation in memcached used an architecture like that and it chewed
lots of CPU time. That's not really libevent's fault -- no UNIX-ish
system I'm aware of has an equivalent to the Windows
WaitForMultipleObjects API that allows you to wake up on semaphores /
condition variables and on input from the network. Without that, any
solution is going to end up using pipes (or maybe signals, which have
their own set of issues in a multithreaded context) to wake up the
libevent threads.

 하지만 불행하게도, libevent 쓰레드간의 통신에는 pipe() 사용해야만 합니다.
그게 한계점이고, 상당히 까칠합니다. 이런 이유로 하나의 쓰레드가 모든 IO 쓰레
드들을 관리 하는 master/worker 쓰레드 아키텍쳐가 만들어 졌는데, 이게 성능에
있어서 또한 상당히 까칠 합니다. CPU 많이 잡아 먹는다는 말입니다. 이것은
결코 libevent의 결함이 아닙니다. 어떤 UNIX-ish(?) 시스템에서도 윈도우의
WaitForMutipleObject API 같이 세마포어/condition variables wake 할수 있는
기능을 제공 하지 않습니다. WaitForMultipleObject 같은 지원이 없고서는 어떠한
시도도 결국 pipe 사용하는 것으로 끝났습니다(그렇지 않다면 아마도 각각의
쓰레드 마다 발행 셋(?set of issues) 가진 signal을 이용하던지 말입니다)

-Steve

원문 보기 : http://monkeymail.org/archives/libevent-users/2007-January/000450.html
추가 사항 : 2007. 3. 9 : libevent가 사용하고 있는 epoll 라이브러리 자체는 threadsafe하다고 한다.
                http://www-gatago.com/linux/kernel/6116742.html

'진리는어디에' 카테고리의 다른 글

소켓 종료와 TIME_WAIT(Socket termination and TIME_WAIT)  (10) 2007.03.19
Read/Write lock  (8) 2007.03.18
libevent and multithread  (6) 2007.03.06
extern  (0) 2007.02.23
소켓 강제 종료시 파이프(pipe) 깨짐  (0) 2007.01.27
Singleton vs Critical section  (0) 2007.01.10
Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://pudae.net BlogIcon pudae 2007.03.08 12:16  댓글주소  수정/삭제  댓글쓰기

    leader/follow pattern 사용 추천

  2. Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2007.03.16 01:56  댓글주소  수정/삭제  댓글쓰기

    트랙백 한 두번 하시는 것도 아니고...
    자신의 글을 보낼 해당 유저 글의 트랙백 주소를 찾는다.
    관리자로 자신의 블로그에 로그인 한다.
    관리자 페이지 접속->글 목록->보낼 글의 라인에 보면 IE의 새로고침 버튼 비스므리 한게 보인다.
    클릭한다.
    주소창이 아래에 뜨면 자신의 글을 보낼려고 하는 해당 유저 글의 트랙백 주소를 넣는다.
    전송한다.

    =3=;;

상황 :
 서버 프로세스가 떠 있고, 거기에는 수많은(이라고 해봐야 1000개 미만)의 클라이언트 들이 물려 있다.
 서버는 select로 read 이벤트를 구분하고, 서버의 listen socket은 blocking이다.

 정상적으로 동작하다가 클라이언트(테스트 프로그램으로써 수백개의 connection thread를 생성 시켜 서버로 미친듯이 데이터를 주고 받는 놈)를 강제 종료 하면 항상 그런 것은 아니지만 종종 '파이프 깨짐'이라는 에러를 내고 서버가 종료 된다.

문제 :
 프로세스는 종료(그것이 타의든 자의든)를 하게 되면 모든 열려있는 파일디스크립터를 닫게 된다. socket 역시  일종의 파일디스크립터이므로 예외는 아니다.
 정상적인 수순을 따르자면

1. client가 죽는다.
2. 모든 열린 파일디스크립터들을 닫는다.
3. 파일디스크립터는 닫히면서 FIN을 날리게 된다.
4. server의 select에서는 FIN도 하나의 readable한 것이라고 판단하고 리턴한다.
5. read()는 0을 리턴, 이것으로 peer가 close했음을 알아 차림.

 위와 같지만, 서버가 client에 write하기 전이나, 하고 있는 중에는 selec에서 기다리고 있는 상황이 아니므로 write() 함수 호출시 SIGPIPE가 발생한다.

해결 :
 sigaction이나 signal같은 함수를 이용해 SIGPIPE 시그널을 ignore하도록 하고, write시에 리턴 값이 -1이고 errno가 EPIPE 경우를 처리 하도록한다.

추가 사항 : 2007.1.27
 write() 함수가 리턴하는 값이 -1이거나 errno를 체크하여 pipe가 깨짐을 확인하고 소켓을 close하였다.
 하지만 어찌된 일인지 정상적으로 동작하지 않았으며, 한번 컨넥션이 끊어지고 난뒤에는 재 접속이 안되었다.
 write를 확인하고 종료 할때만 그렇길래 write에 대한 처리를 하지 않으니 정상적으로 동작했다.
 왜 그런지 이유는 모르겠다. 혹시나 아시는 분..트랙백 좀 달아주오~

원문 :
 http://kldp.org/node/25608

참고 :
 Advance Programming in Unix Enviroment에 따르면 SIGPIPE 는 'reader가 없는데 쓸때' 발생 한다고 한다.

 If we write to a pipeline but the reader has terminated, SIGPIPE is generated. We describe pipes in Section 15.2. This signal is also generated when a process writes to a socket of type SOCK_STREAM that is no longer connected. We describe sockets in Chapter 16.

'진리는어디에' 카테고리의 다른 글

libevent and multithread  (6) 2007.03.06
extern  (0) 2007.02.23
소켓 강제 종료시 파이프(pipe) 깨짐  (0) 2007.01.27
Singleton vs Critical section  (0) 2007.01.10
TIME_WAIT state vs SO_REUSEADDR option  (4) 2007.01.08
Edge trigger vs Level trigger  (2) 2007.01.05
Posted by kukuta

댓글을 달아 주세요

문제 :
  read가 가능한지를 알아 보기위해 select를 호출 하고 wait time을 NULL로 주어 무한히 대기 하게 했다.
  하지만 어찌 된 일인지 아무런 입력이 없을때는 가만히 있다가 한번 입력을 하고 나면 입력 read가 가능 하다는 메시지가 미친듯이 나오기 시작했다.
  코드를 잠시 살펴 보자면 아래와 같다.

 fd_set rset;
 FD_ZERO(&rset); // 0 은 stdin을 나타낸다.
 FD_SET(0, &rset);

 while(1) {
        fd_set tmp_rset = rset;
 
        int state = select(1024, &tmp_rset, &tmp_wset, NULL, NULL);

        if(state > 0) {
            for(int fd=0; fd<1024; fd++) {
                if(FD_ISSET(fd, &tmp_rset)) {
                    std::cout << "read" << std::endl;
                }
            }
        }
    }

 위와 같이 코드를 작성하면 처음은 괜찮다가 한 글자라도 입력을 받게 되면(입력을 받을 것이 있다고 알려지면) 미친듯이 read를 출력해대기 시작한다.

 왜 그럴까?

 우리의 천재 해결사 pudae군의 말을 따르자면 입력을 읽어 들이는 방식에는 edge trigger와 level trigger 이렇게 두 가지 방식이 있다고 한다.
 시그널에 있어서 edge는 신호가 바뀌는 지점을 말한다. 만일 신호가 바뀌는 그 시점을 catch하고 싶다면 edge trigger를 사용하고, 신호가 일정 level에 머물러 있는 것을 알아 내고 싶다면 level trigger를 사용 하면 된다. 결론은  아래와 같다.

 * Edge Triggered event
   이벤트(변화)가 발생 했을때만 리턴
 * Level Triggered event
   버퍼에 입력 받을 것이 있을 경우에는 그것이 없어질 때 까지 리턴
 
 위의 select는 Level Triggered event를 사용하는 있다고 한다.
 그렇다면 처음 입력이 들어온 경우가 일정한 level에 도달한 상태가 되고, 그것을 읽어 버퍼를 비워 주는 과정이 없으니 계속적으로 일정 level 이상의 상태가 된다. 그렇기 때문에 select 함수는 미친듯이 읽어 들일 것이 있으니 어서 읽으라고 소리치는 것이다.
 
 그럼 코드를 아래와 같이 변경해 보도록 하자.
 fd_set rset;
 FD_ZERO(&rset); // 0 은 stdin을 나타낸다.
 FD_SET(0, &rset);

 while(1) {
        fd_set tmp_rset = rset;
 
        int state = select(1024, &tmp_rset, &tmp_wset, NULL, NULL);

        if(state > 0) {
            for(int fd=0; fd<1024; fd++) {
                if(FD_ISSET(fd, &tmp_rset)) {
                    //std::cout << "read" << std::endl;
                    char buffer[1024];
                int n = ::read( 0, buffer, 1023);
                buffer[n] = '\0';
                std::cout << "read : " << buffer << std::endl;
                   
                }
            }
        }
    }

 붉은색 부분이 변경 된 곳이다. 위 처럼 버퍼에서 읽어들이는(비록 완벽하게 읽는 것은 아니지만) 코드를 삽입한다면 더 이상 select 함수는 미친듯이 메시지를 출력 해대지 않을 것이다. 다만 한번의 입력에 한번의 read 메시지를 출력 하겠지..

참고 :
 http://kldp.org/node/74537
 http://www.die.net/doc/linux/man/man4/epoll.4.html

email : kukuta@gmail.com

'진리는어디에' 카테고리의 다른 글

Singleton vs Critical section  (0) 2007.01.10
TIME_WAIT state vs SO_REUSEADDR option  (4) 2007.01.08
Edge trigger vs Level trigger  (2) 2007.01.05
const vs mutable  (4) 2006.12.21
PHP - 문자열 취급하기  (0) 2006.12.14
가변인자를 이용한 함수(va_list)  (10) 2006.12.13
Posted by kukuta
TAG C/C++, System

댓글을 달아 주세요

  1. 방랑자 2010.06.14 18:55  댓글주소  수정/삭제  댓글쓰기

    글 감사합니다. 잘 읽고 갑니다~

/**

얼마 전 리눅스의 데몬(Daemon)이라는 것에 대해 약간이나마 공부를 해야 할 일이 있었습니다. 그리고 그 과정에서 윈도우에서는 그와 비슷한 서비스라는 것이 있다는 것을 알았습니다. 이번 포스팅에서는 서비스 프로그램을 작성하는 방법에 대해 알아 보도록 하겠습니다. 원문은 코드구루(http://www.codeguru.com)의 'Writing a Service Program(Jonathan Ng)' 을 기본으로 하고 있습니다.

*/

서비스(services)라는 것은 일반적으로 시스템 부팅시점 부터 시작해 시스템이 끝나는 시점 까지 그 수명을 같이 하는 프로세스를 가리키기도 합니다. 예를 들어 윈도우 베이스 프로그램과 컴포넌트가 남기는 로그를 기록해주는 '로그 이벤트'같은 프로세스 같은 것이 있습니다. 리눅스(혹은 유닉스) 시스템에서 윈도우의 서비스와 비슷한 역할을 하는 것으로는 데몬(daemon) 프로세스가 있습니다.

서비스 에플릿은 Control Panel > Administrator tools > Service에서 찾아 볼 수 있으며, 새로운 서비스 설치는 이 애플릿과 Registry(HKEY_LOCAL_MACHINE\System\CurrentControlSet\Service)에 앤트리를 생성합니다. 이렇게 해서 시스템은 부트타임에 어떤 서비스들이 등록 되어야 하는지 알 수 있게 되는 것이지요.

모든 서비스들은 반드시 아래의 standard event에 응답하도록 디자인 되어야만 합니다. 아래의 이벤트들은 윈도우 서비스에서 버튼의 형태로 보여지게 됩니다 :

  • START : 서비스를 수동으로 시작하거나, 서비스가 중단 되었다면 서비스를 시작한다.
  • STOP : 서비스를 중단한다.
  • PAUSE : 서비스를 일시 중지한다.
  • CONTINUE : 중단되었던 서비스를 재 시작한다.

모든 서비스는 Service Control Manager 시스템에 의해 관리 됩니다. Service Control Manager는 레지스트리에 서비스 목록을 유지하며, 부트타임이나 아니면 따로 매뉴얼하게 스타트가 요구 될 경우 레지스트리에 등록되어 있는 서비스 프로그램을 실행하게 됩니다.

일반적으로 서비스 프로그램은 일반 exe 확장자 형태를 가지고 있지만 Service Sonctrol Manager(SCM)에 정상적으로 접속하기 위해서는 아래의 특정한 요구사항들을 만족 시켜야 합니다 :

  1. EXE 는 반드시 main 또는 WinMain 함수를 가지고 있어야하고, StartServiceCtrlDiespatcher 함수를 호출 해야 한다. StartServiceCtrlDiespatcher 함수는 EXE 파일을 SCM에 등록하고, SCM이 ServiceMain 함수에게 포인터를 넘겨 주는 역할을 한다.
    void main(int argc, char *argv[])
    {
        SERVICE_TABLE_ENTRY serviceTable[] =
        {
            { ServiceName, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
            { NULL, NULL}
        };

        BOOL success;
        if(argc == 2)
           //..check arguments
        else
        {
            //register with SCM
            success = StartServiceCtrlDispatcher(serviceTable);
            if (!success)
                ErrorHandler("StartServiceCtrlDispatcher", GetLastError());
        }
    }
  2. SCM은 서비스 시작 요청이 들어 오면 ServiceMain 함수를 호출한다. ServiceMain 함수는 그 즉시 RegisterServiceCtrlHandler 함수를 호출하여 Handler 함수를 SCM에 등록한다.
    void ServiceMain(DWORD argc, LPTSTR *argv)
    {
        BOOL success;

        //레지스터 함수 호출
        serviceStatusHandle =
             RegisterServiceCtrlHandler(ServiceName, (LPHANDLER_FUNCTION)ServiceCtrlHandler);

        //SCM에게 통보 및 종료 이벤트 생성
        terminateEvent = CreateEvent (0, TRUE, FALSE, 0);
        .
        .
        //notify SCM
        .
        .
        //startup parameter 체크
        //start service
        //notify SCM service is runnning
        SendStatusToSCM(SERVICE_RUNNING, NO_ERROR, 0 , 0, 0);

        //wait for stop signal and then terminate
        WaitForSingleObject(terminateEvent, INFINITE);

        terminate(0);
    }
  3. Handler 함수는 SCM으로 부터 넘어오는 요청을 처리하는 switch 구문으로 이루어진다.
    void ServiceCtrlHandler(DWORD controlCode)
    {
        DWORD currentState = 0;
        BOOL success;

        switch(controlCode)
        {
            // START = ServiceMain()

            // STOP
            case SERVICE_CONTROL_STOP:
                currentState = SERVICE_STOP_PENDING;
                //notify SCM
                SendStatusToSCM(SERVICE_STOP_PENDING,
                                            NO_ERROR,
                                            0,
                                            1,
                                            5000);
                //stop service
                StopService();
                return;

            // PAUSE
            case SERVICE_CONTROL_PAUSE:
                if (runningService && !pauseService)
                {
                     //notify SCM
                     success = SendStatusToSCM(SERVICE_PAUSE_PENDING,
                                                                 NO_ERROR,
                                                                 0,
                                                                 1,
                                                                 1000);
                     PauseService();
                     currentState = SERVICE_PAUSED;
                }
                break;

            // RESUME
            case SERVICE_CONTROL_CONTINUE:
                if (runningService && pauseService)
                {
                     //notify SCM
                     success = SendStatusToSCM(SERVICE_CONTINUE_PENDING,
                                                                 NO_ERROR,
                                                                 0,
                                                                 1,
                                                                 1000);
                     ResumeService();
                     currentState = SERVICE_RUNNING;
                }
                break;

            // UPDATE
            case SERVICE_CONTROL_INTERROGATE:
                //update status out of switch()
                break;

            case SERVICE_CONTROL_SHUTDOWN:
                //do nothing
                return;
            default:
                break;
        }
        //notify SCM current state
        SendStatusToSCM(currentState, NO_ERROR, 0, 0, 0);
    }
  4. 서비스 프로그램은 main, ServiceMain, Handler 함수를 뿐만 아니라 서비스 자신을 위한 쓰레드를 갖추었을때 비로소 서비스 프로그램으로써 모든 것을 다 갖추었다고 말 할 수 있습니다.

위와 같이 서비스 프로그램을 만들었다고 해서 모든 것이 끝난 것은 아닙니다. 서비스는 특별한 프로세스로 취급되기에 일반 exe 처럼 더블클릭이나 커맨드 창에 이름을 적어 주는 것으로 실행 되지 않습니다. 서비스 프로세스는 서비스 매니저에 의해서 실행 되어져야 하며, 서비스 매니저에 의해 관리 되기 위해서는 등록 과정이 필요합니다. 이런 등록을 위해서 'sc'라는 바이너리가 기본적으로 제공되어지고 있습니다. 프로그램 내에서 특별한 인자를 받아 스스로 등록하도록 하는 방법도 있지만 여기서는 sc 에 대해서만 다루고 나머지는 따로 자리를 마련하도록 하겠습니다.

c:\> sc create [service name] [binPath= ] ..

기본 형태는 위와 같습니다. 서비스 등록을 위해서 sc 뒤에 create라는 인자를 주고 등록할 서비스의 이름과 실행할 바이너리 경로를 적어줍니다. 이때 주의할 점은 binPath= 와 뒤에 들어가는 패스 사이에는 반드시 스페이스 문자가 있어야 한다는 것입니다. 예를 들어 binPath= C:\bin\path\some.exe 와 같은 형식으로 말입니다. sc /? 를 커맨드 창에 입력하시면 보다 자세한 설명이 출력 됩니다.

원문 보기 : http://www.codeguru.com/Cpp/W-P/system/services/article.php/c5785/

'진리는어디에' 카테고리의 다른 글

const vs mutable  (4) 2006.12.21
PHP - 문자열 취급하기  (0) 2006.12.14
가변인자를 이용한 함수(va_list)  (10) 2006.12.13
SQL command  (0) 2006.12.13
Writing a Windows Service Program  (8) 2006.12.11
Python 정규 표현식  (1) 2006.12.11
Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc/ BlogIcon 깜쏘 2006.12.13 17:55  댓글주소  수정/삭제  댓글쓰기

    선배, 오늘 zdnet에서 기사를 봤는데, 선배 경력 관리해야 할 것 같아요.
    볼 때마다 새로운 걸 하고 있으니...
    아직 취업한지 얼마 안되었지만 관리 하세욧!!!

  2. Favicon of https://kukuta.tistory.com BlogIcon kukuta 2006.12.14 10:08 신고  댓글주소  수정/삭제  댓글쓰기

    음...이거는 그냥 취미 생활이야..
    내가 원래 윈도우였으니까 윈도우를 잊지 않기 위해서 공부하는거지..

    나의 주 임무는...잡무다...-_-

  3. Favicon of https://kukuta.tistory.com BlogIcon kukuta 2006.12.15 14:28 신고  댓글주소  수정/삭제  댓글쓰기

    한번 댓글 단 글은 더 이상 댓글을 달 수 없나??

  4. Favicon of http://www.thesispaperswriting.com/assignments_writing.htm BlogIcon Custom Assignment Writing 2011.05.26 00:47  댓글주소  수정/삭제  댓글쓰기

    yes...:) great work ! I definitely enjoying every little bit of it I have you bookmarked to check out new stuff you post