'개발/Server'에 해당되는 글 2건

  1. 2012.08.28 [C언어] 좀비 프로세스를 없애는 더블 fork().. (3)
  2. 2012.08.28 [C언어] 쓰레드의 사용 (1)
2012. 8. 28. 18:51

[C언어] 좀비 프로세스를 없애는 더블 fork()..

fork()를 이용하여 서버 프로그래밍을 할 시, 유용한 방법을 소개하기로 한다. 이는 Steven아저씨 책 Advanced Programming in the Unix Environment 202쪽에 나와 있는 방법이기도 하다.


우선 좀비 프로세스에 대해서 알아야 한다.

좀비 프로세스:

프로세스가 뒤졌는데, 아무도 이놈의 장례를 치뤄주지 않아 뒤진 상태로 리소스를 몽땅 가지고 있는 상태..


뭐 이미 알겠지만, 프로세스가 뒤지면 가지고 있던 파일들 할당 받았던 모든 메모리를 자동으로 모두 해제되고 참 좋으다. 하지만, 장례를 치뤄주지 않으면, 그냥 좀비가 되어서 내 서버를 잠식하고, 결국에는 서버 전체적으로 fork()가 안되는 매우 위험한 상황까지 발생한다.


그럼 좀비 프로세스는 누가 장례를 치뤄주는가..?

인간 세상과는 다르게 부모가 치뤄준다. 이를 위한 함수가 waitpid(pid_t childpid)이다. waitpid()를 호출하여 해당 childpid의 자식 프로세스가 죽으면 부모 프로세스가 이 함수를 통해서 자식의 main함수의 리턴값을 결과로 받게 되며, 그와 동시에 자식이 가지고 있던 모든 리소스가 해제된다.


그렇다면 몇가지 의문점이 생긴다.

1. 부모가 먼저 죽은 경우는 어찌되느가?

2. 자식이 waitpid()호출전에 죽으면 어찌 되는가?


첫째, 부모가 먼저 죽으면 자식은 고아가 되고, 이 고아 프로세스들을 init 프로세스가 엄마노릇을 대신 하게 된다. 즉, 자식이 살아 있는데 부모가 죽으면, 자식의 부모가 init프로세스로 자동 변경된다.

둘째, 자식이 먼저 죽었는데 부모가 waitpid()로 이를 해제해주지 않으면 일단 좀비 프로세스가 되고, 그후, waitpid()를 호출하는 순간 바로 리턴되면서 자식의 리소스가 해제된다.


#include 
#include 
#include 

int main(int argc, char **argv)
{
    pid_t pid;

    if( (pid = fork()) > 0 )
    {
        while(1);
    }
    else if( pid == 0 )
    {
        return 0;
    }
    else
    {
        int errsv = errno;
        perror("fork error");
        return errsv;
    }

    return 0;
}


위 소스는 fork를 하고, 부모는 무한루프에 빠진 후에, 자식은 바로 죽게 하였다. 그런 후, ps로 상태를 확인하면 다음과 같이 좀비가 되어 있는 자식을 볼 수 있다.



zombie는 프로세스 이름이고, 중간에 보면 Z+ 라고 표시되어 있는데 바로 현재 좀비상태가 되었다는 것을 의미한다. 


그렇다면 좀비를 없애기 위해서 어찌해야 할 것인가? 단순히 waitpid()를 호출하면 장땡인가? 맞다!! 하지만, 문제는 서버도 자식이 죽기를 기다리고만 있는 것을 원하지 않는다는 것이다. 괜히 불렀다가 block되어 버리면 서버는 놀아야 하니까.. 그렇다고 주기적으로 nonblock형태로 불러주는 것도 비효율적이다.


그래서 다음과 같은 시나리오로 zombie 프로세스가 생성되지 않도록 한다!!

  1. A 프로세스는 fork하여 자식 B를 만든다.
  2. 자식 B는 다시 fork하여 자식 C를 만든다.
  3. 자식 B는 바로 죽어 버린다.(ASAP!!) => C는 부모(B)가 죽었으므로 부모가 init로 변경된다. (A와는 전혀 상관 없음)
  4. A가 자식B를 waitpid()로 장례를 치뤄버린다. (정상적으로 종료)

정말 아름다운 기법이 아닐 수 없다!!!

자 예제로 이를 실현해 보도록 하자.


#include 
#include 
#include 

int main(int argc, char **argv)
{
    pid_t pid;

    if( (pid = fork()) > 0 )
    {
        waitpid(pid, NULL, 0);
        while(1);
    }
    else if( pid == 0 )
    {
        if( (pid = fork()) > 0 )
        {
            return 0;
        }
        else if(pid == 0)
        {
            sleep(2);
        }

        return 0;
    }
    else
    {
        int errsv = errno;
        perror("fork error");
        return errsv;
    }

    return 0;
}



이와 같이 했을 경우 ps로 결과를 보면 다음과 같다.


직후



2초후, 손자 프로세스 사망 후


자 깔끔하게 자식 프로세스가 종료되었으며, zombie는 발생하지 않는다..!!





'개발 > Server' 카테고리의 다른 글

[C언어] 좀비 프로세스를 없애는 더블 fork()..  (3) 2012.08.28
[C언어] 쓰레드의 사용  (1) 2012.08.28
Trackback 0 Comment 3
  1. Favicon of http://ad.planchasghden.com/ BlogIcon plancha ghd 2013.04.12 14:51 address edit & del reply

    파울로 로베르시와 샤넬의 디자이너 칼 라거펠트 등 세계 최고의 컨텐츠 메이커들과의 작업에 대한 에피소드와 결과물까지 함께 만나볼 수 있다.

  2. Favicon of http://plk.mbablueprint.com/ BlogIcon Michael Kors purses 2013.04.24 21:06 address edit & del reply

    정직을 잃은 자는 더 이상 잃을 것이없다.

  3. Favicon of http://7556.morningcallcoffeesstand.com/ChicagoBlackhawks-us.php BlogIcon Chicago Blackhawks Jersey 2013.07.19 18:20 address edit & del reply

    당신 매력있어, 자기가 얼마나 매력있는지 모르는게 당신매력이야

2012. 8. 28. 14:41

[C언어] 쓰레드의 사용

쓰레드는 사용하기 가장 까다로운 녀석이다.

대부분의 어설픈 개발자들이 무턱대고 쓰다가 프로젝트가 끝나지 못하는 경험을 하게 되며, 그렇지 않더라도 자신의 잘못을 모르고 어설프게 쓰래드를 쓰고 있을 것 같다.


여기서 이야기 하고자 하는 것은 매우 간단한 몇가지만 짚고 넘어간다. 실수하기 쉬운 내용들...


쓰레드의 시작

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

쓰레드는 이 함수로부터 시작된다. 그리고 쓰레드가 죽는 원인은 다음 3가지로 정의할 수 있다.

  1. 쓰레드 자신이 pthread_exit를 호출한 경우: 해당 함수 인자를 종료 값으로 pthread_join()을 통해 넘겨준다.
  2. start_routine에서 리턴한 경우: main()함수가 리턴한 것과 같은 효과인데 이 경우는 쓰레드가 종료되며, 1번과 동일하게 pthread_join()에 의해서 결과값을 전달받을 수 있다.
  3. 취소가 된 경우: pthread_cancel
하지만 머니머니 해도 메인쓰래드가 죽으면 다 죽는다.



여기에서 신경써야 하는 부분은 start_routine와 arg이다. 다음 코드는 정상작동하지 않는다.


static int threadStart(int socket)
{
    pthread_t thread_id;
	
    if( pthread_create(&thread_id, NULL, threadMain, (void*) &socket) != 0 )
    {
        int errsv = errno;
        ERROR_SYSTEM();
		ERROR_ASSERT(FALSE);
		return errsv;
    }
    
    return E_OK;
}

static void *threadMain(void *_socket)
{
    int socket = (int) *((int *)_socket);


이 경우 정상작동하지 않는 이유는 다음과 같다.

pthread_create()를 호출할 때 전달한 socket이 문제가 된다. socket은 함수의 인자이므로 자동변수(로컬변수)이며, 함수가 종료되면 자동 해제된다.

그런데, threadMain()함수의 시작 시점은 공식적으로 전혀 알수가 없다. 다시말해, threadStart함수가 종료된 후 호출될 수 있다. 그런 경우, 다른 프로세스 과정 또는 다른 쓰레드에 의해서 해당 영역(스텍영역)이 침범당해서 전혀 다른 값으로 바뀌게 된다.

이를 방지하기 위한 해결책은 쓰레드에 전달하는 인자를 malloc으로 할당하여 heap영역에 저장한 후, theadMain에서 이를 해제 하는 방식이다.


typedef struct {
	int socket;
}socket_pack_t;

static int threadStart(int socket)
{
    pthread_t thread_id;
	socket_pack_t *thread_param = (socket_pack_t *) malloc(sizeof(socket_pack_t));
	memset(thread_param, '\0', sizeof(socket_pack_t));
	thread_param->socket = socket;

    if( pthread_create(&thread_id, NULL, threadMain, thread_param) != 0 )
    {
        int errsv = errno;
        ERROR_SYSTEM();
		ERROR_ASSERT(FALSE);
		return errsv;
    }
    
    return E_OK;
}

static void *threadMain(void *_socket)
{
	socket_pack_t *param = (socket_pack_t *)_socket;
    int socket =  param->socket;
	
	free(param);	//	해제 한다.



이와 같이 하면, 쓰레드의 파라메터가 heap영역에 할당된 메모리에 저장되며, thread에 전달된 후에, 해제될 수 있다.

'개발 > Server' 카테고리의 다른 글

[C언어] 좀비 프로세스를 없애는 더블 fork()..  (3) 2012.08.28
[C언어] 쓰레드의 사용  (1) 2012.08.28
Trackback 0 Comment 1
  1. Favicon of http://7261.cicnewsst.com BlogIcon toms outlet 2013.07.20 19:23 address edit & del reply

    태양이 바다에 미광을 비추면,나는 너를 생각한다.