使用自管道,我怎样才能避免事件循环在read()上停止?
我正在尝试利用自管技巧来获得我的应用程序的可移植实现(跨Solaris,MacOSX,Linux,BSD)。
所以除了stderr
和stdout
的两个管道,我用它来获取分叉子的输出(我在子节点中不使用exec
,子节点执行与父节点相同的代码),我有信号管道( enum {SIG_PIPE, STDOUT_PIPE, STDERR_PIPE, MAX_PIPE}
提供符号名称)。
- 在调用
handle_child_output()
之前,在管道上设置O_NONBLOCK
。 - 子进程具有
stderr
和stdout
管道的写入端,并继续使用printf()
和朋友,有效地写入每个相应的管道(setvbuf
用于关闭子进程内的缓冲)。
接下来的代码有点冗长,一般的自管技巧也是如此。 它已经是浓缩forms。 因此,让我试着解释一下应该发生什么以及卡在哪里。
我需要收集退出状态,我必须能够找出孩子是通过信号终止还是通过退出终止。 在其他地方处理这些条件。 有意义的是handle_child_output()
返回pstatus
指向的int
内的子pstatus
的退出代码。
handle_child_output()
的外部do
– while
循环将设置要在select
调用中使用的FD_SET
。 它添加了信号管道读取端以及来自子节点的stderr
和stdout
管道的读取端。
然后if(FD_ISSET(fds[SIG_PIPE], &rd))
检查信号管道是否包含任何新的并排出它,处理任何潜在的信号。
之后的for
循环遍历剩余的文件描述符,以查看是否有任何stdio
处理,然后通过parroting它在父对象的stdio
通道上得到的内容进行处理。
第二个循环中的读取调用是停止的位置。
症状是父母卡在读取调用中:
(gdb) bt 1 #0 0x00007f2daaa9e530 in __read_nocancel () from /lib64/libpthread.so.0
就好像在读取信号管和其他管道之间存在竞争条件。 当文件描述符被检查时,孩子似乎已经退出并因此最终成为僵尸,因为父母仍然停留在read()
并且永远不会到达wait()
调用。
我究竟做错了什么? 添加if(exitloop) break;
合法if(exitloop) break;
在for
循环之前突破外部do
– while
循环? 在我看来,这可能会在管道中留下未读的内容,不是吗?
#define __MAX__(x,y) ((x) > (y) ? (x) : (y)) int childpid; typedef enum { READ, WRITE, BOTH } pipefd_type_t; static void avoid_zombie(int* pstatus) { int temp; pstatus = (pstatus) ? pstatus : &temp; if(0 > childpid && 0 != childpid) { kill(childpid, SIGKILL); /* kill the child */ } wait(pstatus); /* wait to avoid lingering zombies */ } static void select_signal_handler(int sig) { int savedErrno = errno; const int sigw = sig; if(0 > write(sigpipe[WRITE], &sigw, sizeof(sigw))) { avoid_zombie(NULL); _exit(EXIT_FAILURE); /* actual code also shows error etc */ } errno = savedErrno; } void handle_child_output(int *pstatus) { enum {SIG_PIPE, STDOUT_PIPE, STDERR_PIPE, MAX_PIPE}; fd_set rd; int ready, n = 0, fds[MAX_PIPE]; size_t i, exitloop, sigint; struct sigaction sa; struct { int sig; struct sigaction oldsa; } old_sigactions[3]; old_sigactions[0].sig = SIGINT; old_sigactions[1].sig = SIGCHLD; old_sigactions[2].sig = SIGQUIT; /* fds have been initialized at this point (read-ends) */ for(i = 0; i sigaction(old_sigactions[i].sig, &sa, &old_sigactions[i].oldsa)) { avoid_zombie(pstatus); _exit(EXIT_FAILURE); /* actual code also shows error etc */ } } do { sigint = 0; exitloop = 0; FD_ZERO(&rd); for(i = 0; i = FD_SETSIZE) { avoid_zombie(pstatus); _exit(EXIT_FAILURE); /* actual code also shows error etc */ } FD_SET(fds[i], &rd); n = __MAX__(n, fds[i]); } while(0 > (ready = select(n+1, &rd, NULL, NULL, NULL))) if (EINTR == errno) continue; if(0 > ready) { avoid_zombie(pstatus); _exit(EXIT_FAILURE); /* actual code also shows error etc */ } if(FD_ISSET(fds[SIG_PIPE], &rd)) { do { /* drain the signal pipe */ int sig = -1; if(0 > read(fds[SIG_PIPE], &sig, sizeof(sig))) { if(EAGAIN == errno) break; else { avoid_zombie(pstatus); _exit(EXIT_FAILURE); /* actual code also shows error etc */ } } switch(sig) { case SIGINT: if(0 > childpid && 0 != childpid) { kill(childpid, SIGINT); /* pass to child */ wait(pstatus); } sigint++; exitloop++; break; case SIGCHLD: exitloop++; break; } } while(1); } for(i = STDOUT_PIPE; i bytesRead) { /* Retry (inner do-while loop) if we get an EAGAIN */ if(EAGAIN == errno) break; /* fatal error */ avoid_zombie(pstatus); _exit(EXIT_FAILURE); /* actual code also shows error etc */ } if(STDOUT_PIPE == i) outchan = STDOUT_FILENO; bytesWritten = write(outchan, buf, bytesRead); if(0 > bytesWritten) { /* Retry if we get an EAGAIN */ if(EAGAIN == errno) { size_t tries; for(tries = 0; tries bytesWritten) && (EAGAIN == errno)) continue; break; } } if(0 > bytesWritten) { avoid_zombie(pstatus); _exit(EXIT_FAILURE); /* actual code also shows error etc */ } } if(bytesWritten bytesWritten2) || (bytesWritten2 != bytesToWrite)) { /* fatal error */ avoid_zombie(pstatus); _exit(EXIT_FAILURE); /* actual code also shows error etc */ } } } while(1); } } } while(0 == exitloop); /* restore old signal handlers */ for(i = 0; i < sizeof(old_sigactions)/sizeof(old_sigactions[0]); i++) { if (sigaction(old_sigactions[i].sig, &old_sigactions[i].oldsa, NULL) == -1) { avoid_zombie(pstatus); _exit(EXIT_FAILURE); /* actual code also shows error etc */ } } avoid_zombie(pstatus); }