我正在尝试利用自管道技巧来获得我的应用程序的可移植实现(跨 Solaris、MacOSX、Linux、BSD)。
因此,除了两个管道用于获取分叉子节点stderr
的stdout
输出(我在子节点中使用 no exec
,子节点执行与父节点相同的代码)之外,我还有信号管道(enum {SIG_PIPE, STDOUT_PIPE, STDERR_PIPE, MAX_PIPE}
提供符号名称)。
O_NONBLOCK
在调用之前设置在管道上handle_child_output()
。- 孩子拥有
stderr
andstdout
管道的写端并继续使用printf()
和朋友,有效地写入每个相应的管道(setvbuf
用于关闭孩子内部的缓冲)。
接下来的代码有点长,就像一般的自管道技巧一样。已经是浓缩的形式了。所以让我试着解释一下应该发生什么以及它在哪里卡住了。
我需要收集退出状态,并且必须能够找出孩子是通过信号终止还是通过退出终止。这些情况的处理在其他地方进行。相关的是返回指向 byhandle_child_output()
内的孩子的退出代码。int
pstatus
外层do
-while
循环 inhandle_child_output()
将设置FD_SET
在select
调用中使用。它添加了信号管道读取端以及子管道的读取stderr
端stdout
。
然后if(FD_ISSET(fds[SIG_PIPE], &rd))
检查信号管道是否包含任何新内容并将其排出,处理任何潜在信号。
之后的for
循环遍历剩余的文件描述符以查看是否有任何待处理的内容,然后通过模仿它在父级各自stdio
通道上获得的内容进行处理。
第二个循环中的读取调用是它停止的地方。
症状是父母卡在读取调用中:
(gdb) bt 1
#0 0x00007f2daaa9e530 in __read_nocancel () from /lib64/libpthread.so.0
就好像在读取信号管道和其他管道之间存在竞争条件。在检查文件描述符时,孩子似乎已经退出,因此最终成为僵尸,因为父母仍然卡在其中read()
并且永远不会到达其中一个wait()
调用。
我究竟做错了什么?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 < sizeof(old_sigactions)/sizeof(old_sigactions[0]); i++) {
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = select_signal_handler;
if(0 > 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 < MAX_PIPE; i++) {
if(fds[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 < MAX_PIPE; i++) {
if(FD_ISSET(fds[i], &rd)) {
do {
const size_t max_tries = 5;
char buf[0x1000];
ssize_t bytesWritten, bytesRead = read(fds[i], buf, sizeof(buf));
if(0 == bytesRead)
break;
int outchan = STDERR_FILENO;
if(0 > 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 < max_tries; tries++) {
bytesWritten = write(outchan, buf, bytesRead);
if((0 > bytesWritten) && (EAGAIN == errno))
continue;
break;
}
}
if(0 > bytesWritten) {
avoid_zombie(pstatus);
_exit(EXIT_FAILURE); /* actual code also shows error etc */
}
}
if(bytesWritten < bytesRead) {
const ssize_t bytesToWrite = bytesRead - bytesWritten;
/* try to write the remainder */
ssize_t bytesWritten2 = write(outchan, &buf[bytesWritten], bytesToWrite);
if((0 > 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);
}