セルフパイプトリックを利用して、アプリケーションの移植可能な実装 (Solaris、MacOSX、Linux、BSD 間) を取得しようとしています。
stderr
したがって、フォークされた子の出力をフェッチするために使用しているとの 2 つのパイプに加えてstdout
(子では no を使用exec
し、子は親と同じコードを実行します)、シグナル用のパイプがあります (enum {SIG_PIPE, STDOUT_PIPE, STDERR_PIPE, MAX_PIPE}
シンボリックを提供します)。名前)。
O_NONBLOCK
を呼び出す前にパイプに設定されhandle_child_output()
ます。stderr
子にはandパイプの書き込み終了があり、 and のフレンドstdout
を使用し続けprintf()
、それぞれのパイプに効果的に書き込みます (setvbuf
は子内のバッファリングをオフにするために使用されます)。
以下のコードは、一般的なセルフパイプ トリックと同様に、少し長くなります。それはすでに凝縮された形です。それでは、何が起こるべきか、どこで行き詰まるかを説明してみましょう。
終了ステータスを収集する必要があり、子プロセスがシグナルで終了したか終了したかを確認できる必要があります。これらの条件の処理は別の場所で行われます。関連するのは、指している byhandle_child_output()
内の子の終了コードを返すことです。int
pstatus
外側のdo
-while
ループインは、呼び出しで使用するhandle_child_output()
を設定します。これは、シグナル パイプの read-end と、子からのandパイプの read-ends を追加します。FD_SET
select
stderr
stdout
次に、if(FD_ISSET(fds[SIG_PIPE], &rd))
シグナル パイプに新しいものが含まれているかどうかを確認し、それを排出して、潜在的なシグナルを処理します。
その後のfor
ループは、残りのファイル記述子をループして、保留中のものがあるかどうかを確認し、親のそれぞれのstdio
チャネルで取得したものをオウム返しして処理します。
その 2 番目のループの読み取り呼び出しは、停止する場所です。
症状は、親が読み取り呼び出しでスタックしていることです。
(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);
}