これは、パイプラインを実行するための適度に一般的ですが単純なコードであり、私が呼び出しているプログラムですpipeline
。提示された単一のファイルの SSCCE ですが、すべてのプログラムとリンクされるようにライブラリ内のファイルstderr.h
と個別のファイルを持っています。stderr.c
(実際には、「実際の」stderr.c
とstderr.h
にはより複雑な関数のセットがありますが、これは良い出発点です。)
コードは 2 つの方法で動作します。引数を指定しない場合、組み込みのパイプラインが実行されます。
who | awk '{print $1}' | sort | uniq -c | sort -n
これは、各ユーザーがシステムにログインした回数をカウントし、セッション数の増加順にリストを表示します。または、呼び出したいコマンド ラインの一連の引数を使用して呼び出すこともできます。コマンドを区切るには、引用符で囲まれたパイプ'|'
(または) を使用します。"|"
有効:
pipeline
pipeline ls '|' wc
pipeline who '|' awk '{print $1}' '|' sort '|' uniq -c '|' sort -n
pipeline ls
無効:
pipeline '|' wc -l
pipeline ls '|' '|' wc -l
pipeline ls '|' wc -l '|'
最後の 3 つの呼び出しでは、「区切りとしてのパイプ」が強制されます。このコードは、すべてのシステム コールでエラー チェックを行うわけではありません。エラーチェックfork()
、execvp()
および を行いますpipe()
が、 および のチェックをスキップしdup2()
ますclose()
。生成されたコマンドの診断出力は含まれません。-x
オプション toはpipeline
賢明な追加であり、それが何をするかのトレースを出力します。また、パイプラインの最後のコマンドの終了ステータスで終了しません。
コードは、フォークされた子から始まることに注意してください。子はパイプラインの最後のプロセスになりますが、最初にパイプを作成し、別のプロセスをフォークして、パイプラインの前のプロセスを実行します。相互再帰関数が物事を整理する唯一の方法である可能性は低いですが、コードの繰り返しは最小限に抑えられます (コードの初期のドラフトでは、と でexec_nth_command()
大部分が繰り返される内容がexec_pipeline()
ありましたexec_pipe_command()
)。
ここでのプロセス構造は、元のプロセスがパイプラインの最後のプロセスしか認識しないようなものです。元のプロセスがパイプライン内のすべてのプロセスの親になるように再設計できるため、元のプロセスはパイプライン内の各コマンドのステータスを個別に報告できます。その構造を可能にするためにコードをまだ変更していません。恐ろしいほどではありませんが、もう少し複雑になります。
/* One way to create a pipeline of N processes */
/* stderr.h */
#ifndef STDERR_H_INCLUDED
#define STDERR_H_INCLUDED
static void err_setarg0(const char *argv0);
static void err_sysexit(char const *fmt, ...);
static void err_syswarn(char const *fmt, ...);
#endif /* STDERR_H_INCLUDED */
/* pipeline.c */
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
/*#include "stderr.h"*/
typedef int Pipe[2];
/* exec_nth_command() and exec_pipe_command() are mutually recursive */
static void exec_pipe_command(int ncmds, char ***cmds, Pipe output);
/* With the standard output plumbing sorted, execute Nth command */
static void exec_nth_command(int ncmds, char ***cmds)
{
assert(ncmds >= 1);
if (ncmds > 1)
{
pid_t pid;
Pipe input;
if (pipe(input) != 0)
err_sysexit("Failed to create pipe");
if ((pid = fork()) < 0)
err_sysexit("Failed to fork");
if (pid == 0)
{
/* Child */
exec_pipe_command(ncmds-1, cmds, input);
}
/* Fix standard input to read end of pipe */
dup2(input[0], 0);
close(input[0]);
close(input[1]);
}
execvp(cmds[ncmds-1][0], cmds[ncmds-1]);
err_sysexit("Failed to exec %s", cmds[ncmds-1][0]);
/*NOTREACHED*/
}
/* Given pipe, plumb it to standard output, then execute Nth command */
static void exec_pipe_command(int ncmds, char ***cmds, Pipe output)
{
assert(ncmds >= 1);
/* Fix stdout to write end of pipe */
dup2(output[1], 1);
close(output[0]);
close(output[1]);
exec_nth_command(ncmds, cmds);
}
/* Execute the N commands in the pipeline */
static void exec_pipeline(int ncmds, char ***cmds)
{
assert(ncmds >= 1);
pid_t pid;
if ((pid = fork()) < 0)
err_syswarn("Failed to fork");
if (pid != 0)
return;
exec_nth_command(ncmds, cmds);
}
/* Collect dead children until there are none left */
static void corpse_collector(void)
{
pid_t parent = getpid();
pid_t corpse;
int status;
while ((corpse = waitpid(0, &status, 0)) != -1)
{
fprintf(stderr, "%d: child %d status 0x%.4X\n",
(int)parent, (int)corpse, status);
}
}
/* who | awk '{print $1}' | sort | uniq -c | sort -n */
static char *cmd0[] = { "who", 0 };
static char *cmd1[] = { "awk", "{print $1}", 0 };
static char *cmd2[] = { "sort", 0 };
static char *cmd3[] = { "uniq", "-c", 0 };
static char *cmd4[] = { "sort", "-n", 0 };
static char **cmds[] = { cmd0, cmd1, cmd2, cmd3, cmd4 };
static int ncmds = sizeof(cmds) / sizeof(cmds[0]);
static void exec_arguments(int argc, char **argv)
{
/* Split the command line into sequences of arguments */
/* Break at pipe symbols as arguments on their own */
char **cmdv[argc/2]; // Way too many
char *args[argc+1];
int cmdn = 0;
int argn = 0;
cmdv[cmdn++] = &args[argn];
for (int i = 1; i < argc; i++)
{
char *arg = argv[i];
if (strcmp(arg, "|") == 0)
{
if (i == 1)
err_sysexit("Syntax error: pipe before any command");
if (args[argn-1] == 0)
err_sysexit("Syntax error: two pipes with no command between");
arg = 0;
}
args[argn++] = arg;
if (arg == 0)
cmdv[cmdn++] = &args[argn];
}
if (args[argn-1] == 0)
err_sysexit("Syntax error: pipe with no command following");
args[argn] = 0;
exec_pipeline(cmdn, cmdv);
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc == 1)
{
/* Run the built in pipe-line */
exec_pipeline(ncmds, cmds);
}
else
{
/* Run command line specified by user */
exec_arguments(argc, argv);
}
corpse_collector();
return(0);
}
/* stderr.c */
/*#include "stderr.h"*/
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
static const char *arg0 = "<undefined>";
static void err_setarg0(const char *argv0)
{
arg0 = argv0;
}
static void err_vsyswarn(char const *fmt, va_list args)
{
int errnum = errno;
fprintf(stderr, "%s:%d: ", arg0, (int)getpid());
vfprintf(stderr, fmt, args);
if (errnum != 0)
fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
putc('\n', stderr);
}
static void err_syswarn(char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
err_vsyswarn(fmt, args);
va_end(args);
}
static void err_sysexit(char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
err_vsyswarn(fmt, args);
va_end(args);
exit(1);
}
シグナルと SIGCHLD
POSIXシグナルの概念セクションでは、SIGCHLD について説明しています。
SIG_DFL の下:
デフォルトのアクションがシグナルを無視することである場合、シグナルの配信はプロセスに影響を与えません。
SIG_IGN の下:
SIGCHLD シグナルのアクションが SIG_IGN に設定されている場合、呼び出しプロセスの子プロセスは、終了時にゾンビ プロセスに変換されません。呼び出しプロセスがその後その子を待機し、プロセスにゾンビ プロセスに変換された待機していない子がない場合、そのプロセスはすべての子が終了するまでブロックし、wait()、waitid()、およびwaitpid()は、失敗し、errno を に設定します[ECHILD]
。
の説明に<signal.h>
は、シグナルのデフォルト処理の表があり、SIGCHLD の場合、デフォルトは I (SIG_IGN) です。
上記のコードに別の関数を追加しました。
#include <signal.h>
typedef void (*SigHandler)(int signum);
static void sigchld_status(void)
{
const char *handling = "Handler";
SigHandler sigchld = signal(SIGCHLD, SIG_IGN);
signal(SIGCHLD, sigchld);
if (sigchld == SIG_IGN)
handling = "Ignored";
else if (sigchld == SIG_DFL)
handling = "Default";
printf("SIGCHLD set to %s\n", handling);
}
への呼び出しの直後に呼び出したところerr_setarg0()
、Mac OS X 10.7.5 と Linux (RHEL 5、x86/64) の両方で「デフォルト」と報告されました。次を実行して、その操作を検証しました。
(trap '' CHLD; pipeline)
両方のプラットフォームで、「無視」が報告され、pipeline
コマンドは子の終了ステータスを報告しなくなりました。それはそれを取得しませんでした。
そのため、プログラムが SIGCHLD を無視している場合、ゾンビは生成されませんが、「すべて」の子が終了するまで待機します。つまり、その直接の子がすべて終了するまでです。プロセスは、孫やより遠い子孫、兄弟、祖先を待つことはできません。
一方、SIGCHLD の設定がデフォルトの場合、シグナルは無視され、ゾンビが作成されます。
書かれているように、これはこのプログラムにとって最も便利な動作です。このcorpse_collector()
関数には、すべての子からステータス情報を収集するループがあります。このコードでは、一度に 1 つの子のみが存在します。パイプラインの残りの部分は、パイプラインの最後のプロセスの (子の、子の、...) 子として実行されます。
ただし、ゾンビ/死体に問題があります。私の先生は、「 」の場合のcmd1
親ではないため、あなたと同じ方法で実装するように指示しました。最後のプロセス ( ) を待機するのではなく、各プロセス ( 、、および)を待機するようにシェルに指示しない限り、出力が最後に到達する前にパイプライン全体がシャットダウンします。彼らを待つ良い方法を見つけるのに苦労しています。私の先生はWNOHANGを使うように言いました。cmd2
cmd1 | cmd2 | cmd3
cmd1
cmd2
cmd3
cmd3
問題を理解しているかどうかわかりません。私が提供したコードでは、cmd3
は の親であり、cmd2
3コマンド パイプラインでは の親であり (シェルは の親です)、シェルは でしか待機できません。私は最初に述べました:cmd2
cmd1
cmd3
cmd3
ここでのプロセス構造は、元のプロセスがパイプラインの最後のプロセスしか認識しないようなものです。元のプロセスがパイプライン内のすべてのプロセスの親になるように再設計できるため、元のプロセスはパイプライン内の各コマンドのステータスを個別に報告できます。その構造を可能にするためにコードをまだ変更していません。恐ろしいほどではありませんが、もう少し複雑になります。
シェルがパイプラインの 3 つのコマンドすべてを待機できる場合は、別の組織を使用している必要があります。
waitpid()
説明には次のものが含まれます。
pid 引数は、ステータスが要求される一連の子プロセスを指定します。waitpid() 関数は、このセットから子プロセスのステータスのみを返します。
pid が (pid_t)-1 に等しい場合、子プロセスのステータスが要求されます。この点で、waitpid() は wait() と同等です。
pid が 0 より大きい場合、ステータスが要求される単一の子プロセスのプロセス ID を指定します。
pid が 0 の場合、呼び出しプロセスのプロセス グループ ID と等しいプロセス グループ ID を持つすべての子プロセスのステータスが要求されます。
pid が (pid_t)-1 より小さい場合、プロセス グループ ID が pid の絶対値と等しい子プロセスのステータスが要求されます。
options 引数は、ヘッダーで定義された次のフラグの 0 個以上のビットごとの包括的 OR から構築されます。
...
WNOHANG waitpid()
pid で指定された子プロセスのいずれかのステータスがすぐに取得できない場合、関数は呼び出しスレッドの実行を中断しません。
...
これは、プロセス グループを使用していて、シェルがパイプラインが実行されているプロセス グループを認識している場合 (たとえば、パイプラインが最初のプロセスによって独自のプロセス グループに配置されているため)、親は適切なプロセス グループを待機できることを意味します。終了する子供たち。
...とりとめのない...ここにはいくつかの有用な情報があると思います。私が書いていることはおそらくもっとあるはずですが、私の心は空白になっています。