이중fork와 pipe 사용시 hang걸리는 현상

smartksh의 이미지

안녕하세요.

아래의 코드는 c++코드입니다.
이중 fork를 이용하여 손자 프로세스를 생성하고,
pipe를 이용하여 손자 프로세스의 pid를 부모 프로세스로 전달하는 함수입니다.

이 함수가 1분 마다 호출되는 프로그램을 구현하여 Linux ES3에서 장시간 확인한 결과,
평소에는 문제가 없는데, 어느 순간부터는 부모 프로세스는 read() 상태에서 멈춰있고,
자식프로세스는 write() 상태에서 멈춰있는 현상이 발생하고 있습니다.

자식 프로세스를 kill 시키면 부모 프로세스는 멈췄던 동작을 이어서 수행합니다.

제가 보기에는 자식 프로세스가 write() 상태에서 blocking되어 버린것 같습니다.

코드상 잘못된 부분이 있는지, 개선방안이나 회피 방법은 없는지 알고 싶습니다.
여러분의 관심과 답변 부탁드립니다.


int fork2()
{
    pid_t pid;
    int status;
    int fd[2];

    if ( pipe(fd) == -1 )
        return -1;

    pid=fork();

    if ( pid == 0 )
    {
        pid_t pid2 = fork();

        if ( pid2 == 0 )
        {
            close(fd[0]);
            close(fd[1]);

            // 손자 프로세스의 실행
            execve(..........);
            exit(0);
        }
        else
        {
            close(fd[0]);

            char writebuffer[16];
            sprintf(writebuffer, "child pid = %d%c", pid2, 0);

            // 손자의 pid를 전달한다.
            write(fd[1], writebuffer, strlen(writebuffer));
            close(fd[1]);

            exit(0);
        }
    }
    else if ( pid > 0 )
    {
        close(fd[1]);

        char readbuffer[16];
        memset(readbuffer, 0, 16);

        // 손자의 pid를 읽는다.
        int nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
        close(fd[0]);

        printf("%s\n", readbuffer);
    }

    if ( pid < 0 || waitpid(pid, &status, 0) < 0 )
        return -1;


    .............

}
aswip의 이미지

1. pipe를 생성하는 위치가 잘못됨.

pipe는 부모와 자식 프로세스간의 통신밖에 지원하지 않습니다.
때문에, 할아버지와 손자사이에서 파이프를 직접적으로 공유할 수 있는 방법은 없습니다.

2. waitpid 를 한군데 더 넣어주어야 함.

할아버지의 자식 프로세스가 종료되는 시점을 체크하는 부분이 추가되어야 함.

이러한 상황이라면.. Named Pipe가 적절한 대안이 될 수 있으며, 좀 더 자세한 내용은 다음 사이트를 통해서 확인이 가능합니다.

http://wiki.kldp.org/wiki.php/%B8%AE%B4%AA%BD%BA%C7%C1%B7%CE%B1%D7%B7%A1%B8%D3%B8%A6%C0%A7%C7%D1%B0%A1%C0%CC%B5%E5#s-6.3

아울러 다음은, 참고가 될 만한 소스입니다. 작업 환경이 제공되지 않아.. 컴파일은 안해보았습니다. 에러가 약간 있을 듯 하지만..
의미 전달하는 무리가 없으니.. ^^;;; 알아서 보세요.. ^^;;;

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/wait.h>

#define PIPE_IN		0
#define PIPE_OUT	1

int fork2()
{
	pid_t pid = 0;
	if ( (pid = fork()) == 0 )
	{
		int nFD[2] = {0};
		if ( pipe(nFD) < 0 ) // <=== 요기
		{
			printf("Fail to create pipe, %s\n", strerror(errno));
			return -1;
		}
		
		pid_t pid2 = fork();
		if ( pid2 == 0 )
		{
			close(nFD[PIPE_OUT]);
			char sz[10] = {0};
			read(nFD[0], sz, 10);
			printf("Read : %s\n", sz);
			close(nFD[PIPE_IN]);
		}
		else
		{
			close(nFD[PIPE_IN]);
			write(nFD[PIPE_OUT], "test", 4);
			close(nFD[PIPE_OUT]);
		}
	
		int n = 0;
		waitpid(pid2, &n, 0);  // <=== 요기..
		printf("Close child proc.\n");
	}
	else
	{
		// do nothing.
	}
	
	int n = 0;
	waitpid(pid, &n, 0); 
	printf("Close main proc.\n");
	return 1;
}

int main(int argc, char **argv)
{
	while ( 1 )
	{
		fork2();
		sleep(60);
	}
	
	return 1;	
}

- 인생은 스스로 -

wariua의 이미지

먼저 aswip님 의견에 대한 반론입니다 :twisted:

aswip wrote:
1. pipe를 생성하는 위치가 잘못됨.

pipe는 부모와 자식 프로세스간의 통신밖에 지원하지 않습니다.
때문에, 할아버지와 손자사이에서 파이프를 직접적으로 공유할 수 있는 방법은 없습니다.


정말 그런 제한이 있나요? 아시다시피 pipe로 만든 파일 디스크립터는 FD_CLOEXEC 플래그를 설정해 주지 않는 한 fork() 과정에서 자식 프로세스에게 복사되고 그 pipe는 공유됩니다. 그렇다면 (자식이 손자를 낳기 전에 연락을 끊지만 않는다면) 손자 프로세스 역시도 복사된 파일 디스크립터를 물려받게 됩니다. 그 파일 디스크립터를 이용하면 2대 사이에 걸친 통신에 별 문제가 없지 않을까요?

더불어 smartksh님의 소스는 조부보-손자간이 아니라 부모-자식간에서 pipe 통신을 하고 있습니다. :twisted:

aswip wrote:
2. waitpid 를 한군데 더 넣어주어야 함.

할아버지의 자식 프로세스가 종료되는 시점을 체크하는 부분이 추가되어야 함.

smartksh님의 소스에서 할아버지는 자기 자식의 뒷수습을 잘 하고 있습니다. 자식(제2대)이 손자(제3대) 프로세스에 대해서 waitpid()를 하지 않는 걸 말씀하시는 게 아닐까 싶기도 합니다만...

정석대로 하자면 자식들에 대해서 waitpid()를 해주어야 합니다만, smartksh님 소스의 경우 자식 프로세스가 자식을 낳고서 얼른 종료되기에 손자 프로세스는 init에게 입양되고, 사망 후의 뒷수습도 init 프로세스가 알아서 해 주는 것이 예상 실행 방식이라고 추측됩니다.

다만 자식 프로세스가 종료하기 전에 손자 프로세스가 execve()에 실패하거나 execve()로 실행된 프로세스가 일찍 종료하는 경우는 손자가 한동안 좀비놀이를 하게 됩니다. 이게 문제의 원인이라면 block되어 있는 상태에서 ps를 했을 때 좀비 프로세스가 보일 테니... startksh님이 확인을 해주시겠군요-

----

startksh님의 소스로 돌아가자면,

재현이 쉽지 않네요. 올려주신 소스에서 찝찝한 부분이라면 자식 프로세스가 sprintf() 하면서 buffer overflow가 날 수 있다는 정도입니다.
"strace -f" 정도로 block되는 부분을 정확히 확인하고 block 되는 부분에서 시스템콜로 매개변수들이 정확히 넘어가고 있는지 확인해 보시는 건 어떨까 합니다. block된 순간 출력 결과의 뒷쪽 부분을 올려 주셔도 좋겠습니다.

$PWD `date`

ssehoony의 이미지

자식 프로세스를 종료하는 코드가 없군요.

오래 두면 이상하다구요?
혹시 해당 프로세스가 몇개 떠있는지 보셨나요?
저거 계속해서 프로세스 개수가 증가합니다.
프로그램 실행 후에 몇분 있다가 ps aux 를 한번 해보세요.

아래 소스 컴파일 해서 실행해 보세요.
원하시는데 상상하신대로 작동하는지 화면에 출력되는 문구 확인해 보시구요.
아마 예상하지 못한 방식으로 작동하고 있을 겁니다.

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/wait.h>

extern int errno;

#define PIPE_IN      0
#define PIPE_OUT   1

int fork2()
{
   pid_t pid = 0;
   pid_t pid2 = 0;

   if ( (pid = fork()) == 0 )
   {
      int nFD[2] = {0};
      if ( pipe(nFD) < 0 ) // <=== 요기
      {
         printf("Fail to create pipe, %s\n", strerror(errno));
         return -1;
      }

      pid2 = fork();
      if ( pid2 == 0 )
      {
         close(nFD[PIPE_OUT]);
         char sz[10] = {0};
         read(nFD[0], sz, 10);
         printf("Read : %s\n", sz);
         close(nFD[PIPE_IN]);
      }
      else
      {
         close(nFD[PIPE_IN]);
         write(nFD[PIPE_OUT], "test", 4);
         close(nFD[PIPE_OUT]);
      }

      int n = 0;
      if(waitpid(pid2, &n, 0)<0)
      {
          printf("error : %s\n", strerror(errno));
      }

      if(pid2 == 0)
          printf("I'm child : Close child proc.\n");
      else
          printf("I'm parent : Close child proc.\n");
   }
   else
   {
      // do nothing.
   }

   int n = 0;
   if(waitpid(pid, &n, 0) < 0)
   {
      printf("error : %s\n", strerror(errno));
   }

   if(pid != 0)
       printf("I'm grand parent : Close main proc.\n"); // pid > 0, pid2 == 0
   else if(pid2 != 0)
       printf("I'm parent : Close main proc.\n"); // pid == 0, pid2 > 0
   else
       printf("I'm child main proc.\n"); // pid == 0, pid2 == 0


   return 1;
}

int main(int argc, char **argv)
{
   while ( 1 )
   {
      fork2();
      sleep(10);
   }

   return 1;
}
ssehoony의 이미지

아마 이렇게 작동하는 코드를 원하셨을 겁니다.

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/wait.h>

extern int errno;

#define PIPE_IN      0
#define PIPE_OUT   1

int fork2()
{
   pid_t pid = 0;
   pid_t pid2 = 0;

   if ( (pid = fork()) == 0 )
   {
      int nFD[2] = {0};
      if ( pipe(nFD) < 0 ) // <=== 요기
      {
         printf("Fail to create pipe, %s\n", strerror(errno));
         return -1;
      }

      pid2 = fork();
      if ( pid2 == 0 )
      {
         close(nFD[PIPE_OUT]);
         char sz[10] = {0};
         read(nFD[0], sz, 10);
         printf("Read : %s\n", sz);
         close(nFD[PIPE_IN]);

         sleep(1);
         exit(0);
      }
      else
      {
         close(nFD[PIPE_IN]);
         write(nFD[PIPE_OUT], "test", 4);
         close(nFD[PIPE_OUT]);
      }

      int n = 0;
      if(waitpid(pid2, &n, 0)<0)
      {
          printf("error : %s\n", strerror(errno));
      }

      if(pid2 == 0)
          printf("I'm child : Close child proc.\n");
      else
          printf("I'm parent : Close child proc.\n");

      sleep(1);
      exit(0);
   }
   else
   {
      // do nothing.
   }

   int n = 0;
   if(waitpid(pid, &n, 0) < 0)
   {
      printf("error : %s\n", strerror(errno));
   }

   if(pid != 0)
       printf("I'm grand parent : Close main proc.\n"); // pid > 0, pid2 == 0
   else if(pid2 != 0)
       printf("I'm parent : Close main proc.\n"); // pid == 0, pid2 > 0
   else
       printf("I'm child main proc.\n"); // pid == 0, pid2 == 0


   return 1;
}

int main(int argc, char **argv)
{
   while ( 1 )
   {
      fork2();
      sleep(10);
   }

   return 1;
}
smartksh의 이미지

제의 질문에 대한 관심에 감사드립니다.

이중fork을 사용하는 이유는,
부모보다 자식이 먼저 종료될 때 발생하는 defunct현상을 없애기 위한 것입니다.

wariua님의 말씀대로, 현재의 코드상에서도
부모(2대)보다 자식(3대)이 먼저 종료될 경우에는 자식(3대) 프로세스가 defunct되는 현상이 발생합니다.
이런 현상은 정상적인 경우 임시적인 것이므로 큰 문제가 되지 않는다고 생각합니다.
이중fork을 1분마다 반복하면서 몇 일간 지켜보면서
ps 명령으로 확인해보면 프로세스가 늘어나지는 않습니다.
strace 명령으로도 확인해 보았는데, 부모(1대)는 pause인가 sleep 상태였고,
자식(2대)는 MUTEX_WAIT(?) 인가 하는 상태였던걸로 기억합니다.

hang이 걸리는 현상이 서버의 사양이나 상태(리소스가 별로 없는 상태 등..)와 관계가 있을지 궁금합니다.

ssehoony의 이미지

처음 올려주신 소스를 구동하면 프로세스가 계속 증가하지 않는 다고요?
음..
처음 소스대로라면 계속 증가해야 맞는건데요.
자식의 프로세스가 종료하지 않고 계속 살아 있기 때문에
부모는 waitpid 에서 영원히 대기 중이며, 자식은 1분 슬립하고 다시 구동하여 포크해서 또 부모는 waitpid 로 영원히 대기 하고 자식은 다시 1분 슬립하는게 반복되는 코드인데요.
블럭 상태에서 자식을 죽이면 다시 작동하는건 소스상으로 영원히 대기해야하는 부모가 외부적인요건으로 자식이 죽어서 대기 상태가 풀려서 다음 일을 진행하기 때문일거라고 보이는데요.
물론, 자식이 왜 블럭 상태가 되는지는 모르겠지만요.

제가 올린 처음 두개의 코드를 실행해서 화면에 찍히는 것을 올려주실 수 있나요?

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.