CnUnix

[알림판목록 I] [알림판목록 II] [글목록][이 전][다 음]
[ CnUnix ] in KIDS
글 쓴 이(By): zeo (ZeoDtr)
날 짜 (Date): 2002년 4월 25일 목요일 오후 07시 05분 29초
제 목(Title): Re: sigchld 질문 


아... 그리고, childTerminated function에서는 child의 linked list만
좀 손을 댑니다. 실제 terminate된 것에 대한 action은 나중에 한꺼번에
부르죠. 또, pollChild는 그 이름이 제시하는 것처럼, signal handler에서
뿐 아니라, 다른 곳에서도 SIGC(H)LD를 놓칠 가능성이 있는 경우 -
그러니까 signal이 disable되어 있다든가 할 때 - 에 call하지요.
제 프로그램에서는 select()에 들어가기 전에 signal을 enable하고,
select에서 빠져 나올 때 disable하는 것 같습니다.(같습니다?)
그리고... race condition을 막기 위해 꽤나 애를 쓴 듯...
암튼... 복잡하군요. (도움이 안 될 것 같은...)

에잇, 그냥 해당 code의 일부를 올려 봅니다.
참고가 되면 좋고, 누가 이 code의 잘못된 부분을 지적하면 그것도 또 좋겠죠.

---
void
Dispatcher::dispatch()
{
  // 그동안 terminate된 child가 있나 polling해 본다.
  // Dispatcher는 doWait() 내에서만 SIGCHLD/SIGCLD를 catch하기 때문에, 
  // 그동안 terminate된 child가 zombie로 남아 있을 수 있다. SYSV 계열이라면 
  // doWait()에서 signal handler를 지정할 때 각 zombie에 대해 SIGCLD가 날아오기
  // 때문에 그냥 거기서 처리하면 되지만, 다른 machine은 그런다는 보장이
  // 없으므로, 여기서 polling할 수 밖에 없다.
  // 만일 doWait() 밖에서, 말하자면 Dispatcher object의 수명동안 SIGCHLD
  // handler를 지정하고 있으면 polling을 안해도 되겠지만, signal handler
  // 내에서 childWaitQueue의 linked list를 manage하기가 거의 
  // 불가능해지므로, 그렇게 하지 않는다.
  // (참고로, polling은 waitpid()가 nonblocking option이 있기 때문에
  // 가능하다. 만일 wait()밖에 없는 system이 있다면 망한 거다.)
  pollChild();

  if(! sm_childWaitQueue->isAnyReady())
    doWait();

  notify();
}

//
// select()가 reentrant한 function이 아니므로, (Advanced Programming in 
// the UNIX Environment 책의 278쪽 참조) sigsetjmp/siglongjmp를 쓰는 것은 
// 위험하다. 따라서, signal handler에서 그냥 return하는 수 밖에 없으므로,
// 아무리 해도 race condition이 생기게 된다.
// 예를 들어, 아래 doWait()의 while() block 바로 앞에서 signal handler를
// 지정했을 때, (tvPoll을 이용한 polling이 없다고 가정하자) 그 시점 바로 
// 후와 select() 바로 전 사이에 child termination signal이 deliver될 수 있다.
// 그렇게 되면 실제 notify할 event(childTerminated)가 있는데도 select()에서
// 장기/무기한 잠자는 꼴이 된다. 확률은 희박하지만, 그래도 그런 경우가 
// 생길 수 있다는 것이 문제다.
// 따라서, terminate될 child가 하나라도 있는 경우는 마음 놓고 장기/무기한
// select()를 할 수 없다. 그래서 여기서는 기본적으로 signal handler에 의해
// select()가 interrupt되는 것으로 하고, '예외적인 경우'를 위해 적당한 
// 기간씩 쪼개서 select()를 하면서 child가 그 사이에 terminate되었는지 
// polling한다.
// 이렇게 하면 대부분의 경우는 child terminate 즉시 notify가 이루어지고, 
// 최악의 경우라도 그 '쪼갠 시간'만큼의 delay만 손해를 보게 된다.
//
void
Dispatcher::doWait()
{
#if LCONFIG_PosixSignal
  struct sigaction act;
  struct sigaction oact;
#else
  PSigFunc osig = NULL;
#endif
  Bool mustRecoverSig;

  // SIGCHLD가 deliver되도록 signal window를 연다.
  // SIGCHLD를 catch하는 이유는 child가 하나라도 끝났을 경우 select()를
  // 멈추고 그 event를 notify하기 위해서다.
  if(! sm_childWaitQueue->isEmpty())
  {
    mustRecoverSig = TRUE;
      // isEmpty는 child들이 모두 terminate되면 TRUE가 되므로, recover
      // 시에는 isEmpty()에 의존할 수 없다. 그래서 이 변수를 만들었다.

#if LCONFIG_PosixSignal
    act.sa_handler = sigCHLD;
#ifdef SA_INTERRUPT
    act.sa_flags = SA_INTERRUPT;
#else
    act.sa_flags = 0;
#endif
    if(sigaction(SIGCHLD, &act, &oact))
      Log::fatal("Dispatch::doWait: cannot set signal handler (%d: %s)",
        errno, Sys::strerror(errno));
#else  /* LCONFIG_PosixSignal */
    osig = signal(SIGCLD, sigCLD);
#endif  /* LCONFIG_PosixSignal */
  }
  else
    mustRecoverSig = FALSE;

  while(1)
  {
    int ret;
    TimeVal *ptvOrg, *ptv;
    TimeVal tvPoll(sm_pollPeriod, 0);

    if(sm_childWaitQueue->isAnyReady())  // 그새 끝난 child가 있으면...
      break;
      // SYSV 계열의 경우, 위의 signal handler setting시, 이미
      // terminate된 child가 있으면 signal()이 return하기 전에 바로
      // signal이 deliver된다.
      // 이 경우는 첫번째 isAnyReady() 검사에서 걸리게 된다.

    ptvOrg = ptv = sm_timerQueue->calculateTimeout();
    if(! sm_childWaitQueue->isEmpty())  // child가 하나라도 있으면...
    {
      // 이때는 무작정 길게 기다릴 수 없으므로, 적당한 시간대로
      // 끊어서 select()를 수행한다. (하지만, 원래 기다리는 시간이
      // 충분히 짧은 경우에는 그냥 놔 두고.)
      if((! ptvOrg) || (*ptvOrg > tvPoll))
        ptv = &tvPoll;
    }

    if(sm_rSet) *sm_rSetReady = *sm_rSet;
    if(sm_wSet) *sm_wSetReady = *sm_wSet;
    if(sm_eSet) *sm_eSetReady = *sm_eSet;

    ret = select(sm_curMaxFdPlus1, sm_rSetReady, sm_wSetReady,
      sm_eSetReady, ptv);

    // fd가 ready 되었거나, timerQueue에서 권유한 기간을 다 썼으면
    // 이제 그만 select.
    if((ret > 0) || ((ret == 0) && (ptv == ptvOrg)))
      break;
    if(ret < 0)
    {
      if(handleSelectError())
        break;
    }
  }

  // child terminated signal을 다시 block한다.
  if(mustRecoverSig)
#if LCONFIG_PosixSignal
    sigaction(SIGCHLD, &oact, NULL);
#else
    signal(SIGCLD, osig);
#endif
}

//
// SIGCHLD/SIGCLD signal handler
//
// 당연하지만, 이 signal handler 내에서는 SIGCHLD/SIGCLD가 다시 
// deliver되지 않으므로, 안심하고 linked list를 다루는 function을 call할 수
// 있다.
// (unreliable signal의 경우는 signal이 deliver될 때 signal handler 정보가
// reset되기 때문에 안전하고, POSIX signal의 경우는 자동으로 SIGCHLD가 mask
// 되므로 안전하다.)
//
void
#if LCONFIG_PosixSignal
Dispatcher::sigCHLD(...)
#else
Dispatcher::sigCLD(int)
#endif
{
  pollChild();
}

void
Dispatcher::pollChild()
{
  pid_t pid;
  int status;

  while((pid = waitpid(-1, &status, WNOHANG)) > 0)
    sm_childWaitQueue->childTerminated(pid, status);
}


---


ZZZZZ             "Why are they trying to kill me?"
  zZ  eeee  ooo   "Because they don't know you are already dead."
 zZ   Eeee O  O
ZZZZZ Eeee OOO        - Devil Doll, 'The Girl Who Was...Death'
[알림판목록 I] [알림판목록 II] [글 목록][이 전][다 음]
키 즈 는 열 린 사 람 들 의 모 임 입 니 다.