| [ 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' |