さまざまなコメント:
while (strcmp(fgets(buf, 80, stdin), "bye\n")!=0)
シェルに の代わりに EOF が与えられると、これはクラッシュしbye
ます。そのように 2 つの関数呼び出しを組み合わせないでください。すべてを 1 つのループ条件にしたい場合は、次を使用します。
while (fgets(buf, sizeof(buf), stdin) != 0 && strcmp(buf, "bye\n") != 0)
コマンド ラインの長さの制限については、別の機会に説明します。
あなたはmakeargv()
見るために提供していないので、それがうまく機能すると仮定する必要があります。
コマンドとパイプに分割するループは次のとおりです。
pnum = 0;
ploc[0] = 0;
if (j > 16) j = 16;
for (i = 0; i < j; i++)
{
if (strcmp(argp[i], "|") == 0)
{
argp[i] = NULL;
ploc[pnum+1] = (i+1);
pnum++;
}
}
コマンドライン入力があるとしましょう: ls -l | grep lemon
. makeargv()
5 を返しargp
、次のように設定するようです。
argp[0] = "ls";
argp[1] = "-l";
argp[2] = "|";
argp[3] = "grep";
argp[4] = "lemon";
argp[5] = 0; // Inferred - things will crash sooner or later if wrong
あなたのこのループはあなたに与えるでしょう:
ploc[0] = 0;
ploc[1] = 3;
pnum = 1;
コードのfdleft
配列にはファイル記述子のペアがありますが、 の呼び出しで配列を使用しているにもかかわらず、配列を初期化していません (pipe()
たとえば、 を呼び出します) dup2()
。
メインfor
ループは、コマンドごとに 1 回ずつ、合計 2 回実行する必要があります。パイプライン内の 3 つ以上のコマンドの一般的なケース (who | grep me | sort
たとえば ) では、最初のコマンド ( who
) は標準入力を変更しない必要がありますが、その標準出力はwho
とを接続するパイプに送られgrep
ます。「中間」コマンド (最後から 2 番目のコマンド、またはgrep me
例では) は、それぞれ標準入力が前のパイプから取得される必要があり、その標準出力が移動する新しいパイプを作成する必要があります。最後のコマンド (この例では 3 番目のコマンドsort
) は、標準入力が最後のパイプから取得され、標準出力が変更されていない必要があります。
あなたのコードはそれをしないか、どこかに近づきません。
pipe()
and then dup()
orを使用していずれかの記述子を標準 I/O 記述子にマップする場合は、パイプの両端をdup2()
閉じる必要があります。十分な通話がありません。close()
親プロセスは、子プロセスのそれぞれを順番に起動する必要があり、すべての子プロセスを起動した後、子プロセスが終了するのを待ちます。プロセスを編成するにはさまざまな方法があります。親は 1 回フォークできます。子は、パイプラインの主要なコマンドを起動し、最終的に最後のコマンド自体を実行する責任を負う可能性があります。親には直接の子が 1 つしかない (他は孫) ため、1 つのコマンドが終了するのを待つだけで済みます。別の方法は、親がパイプライン内の各プロセスを認識しており、それらがすべて終了するのを待つことです。
親プロセスがパイプライン内の各コマンドの完了を待ってから残りのコマンドを起動すると、最終的にデッドロックが発生する可能性があります。1 つの子がそのパイプに大量のデータを書き込むため、何らかのプロセスがパイプから読み取るまでカーネルによってブロックされますが、パイプから読み取るプロセスはまだ起動されておらず、親は子が終了するのを待っています。また、マルチプロセッシングと同時実行の利点も失われます。
あなたの「ケース0」には、余分なセミコロンがあります。私のコンパイラはそれについて私に警告しました。警告が表示されなかった場合は、コンパイル警告をさらに使用するか、より優れたコンパイラを入手する必要があります。
case 0: // child
if (i != pnum)
{
dup2(fdright[1], 1);
}
if (i != 0); // Unwanted semi-colon!
{
dup2(fdright[0], 0);
}
SO のミニシェルのパイプラインについては、次のような多くの質問があります。
13636252 の答えは、ほぼ一般的です。唯一の難点はchar ***
、混乱しがちな使用法であり、非常に厳密に記述されているため、最小限の繰り返しで相互に再帰的な機能を備えています。OTOH、正しく機能し、makeargv()
関数もchar ***
引数を使用します。
作り直されたコード
これが機能するように作り直されたコードです。err_sys()
との実装が含まれますmakeargv()
。私makeargv()
は、コマンド ラインの単語数が 32 語未満であると単純に想定しています。どう考えても、これは堅牢なコマンド ライン パーサーではありません。入力するls | wc
と正しい答えが得られます。who | grep me | sort
また、正しい答えを許可して提供します。ls
また、正しい答えを許可して提供します。ただし、パイプ記号の周りのスペースは重要です (通常のシェルではオプションなので、これも機能するwho|grep me|sort
はずですが、このコードでは機能しません。
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
extern int makeargv(char *line, char *seps, char ***args);
extern void err_sys(const char *msg);
static void dump_argv(char **argv);
static void dump_fds(void);
int main(void)
{
char buf[80];
printf(" <(^_^)> \nHello \n I am your console and I am here to help you\n");
printf(" If you don't need me anymore just say \"bye\"\n");
fflush(stdout);
dump_fds();
printf("(>^_^)> ");
while (fgets(buf, sizeof(buf), stdin) != 0 && strcmp(buf, "bye\n") != 0)
{
pid_t pid;
char **argp;
int fdleft[2] = { -1, -1 };
int fdright[2] = { -1, -1 };
int pnum = 0;
int ploc[16];
int j = makeargv(buf, " \n", &argp);
ploc[0] = 0;
if (j > 16)
j = 16;
for (int i = 0; i < j; i++)
{
if (strcmp(argp[i], "|") == 0)
{
argp[i] = NULL;
ploc[++pnum] = i+1;
}
}
printf("pnum = %d\n", pnum);
for (int k = 0; k < pnum+1; k++)
printf("ploc[%d] = %d\n", k, ploc[k]);
for (int i = 0; i < pnum+1; i++)
{
if (i != pnum)
{
if (pnum > 0)
{
if (pipe(fdright) != 0)
err_sys("pipe");
//printf("%d: fdright = { %d, %d }\n", i, fdright[0], fdright[1]);
//dump_fds();
}
}
if ((pid = fork()) < 0)
err_sys("fork failed");
else if (pid == 0)
{
/* Child */
int targ;
//dump_fds();
if (i != pnum)
{
dup2(fdright[1], 1);
close(fdright[0]);
close(fdright[1]);
}
if (i != 0)
{
dup2(fdleft[0], 0);
close(fdleft[0]);
close(fdleft[1]);
}
targ = ploc[i];
dump_argv(&argp[targ]);
dump_fds();
execvp(argp[targ], &argp[targ]);
fprintf(stderr, "(-_-) I'm sorry the exec failed\n");
exit(1);
}
if (i != 0)
{
//dump_fds();
//printf("%d: fdleft = { %d, %d }\n", i, fdleft[0], fdleft[1]);
assert(fdleft[0] != -1 && fdleft[1] != -1);
close(fdleft[0]);
close(fdleft[1]);
//dump_fds();
}
printf("PID %d launched\n", pid);
fdleft[0] = fdright[0];
fdleft[1] = fdright[1];
}
//dump_fds();
//printf("%d: fdleft = { %d, %d }\n", -1, fdleft[0], fdleft[1]);
close(fdleft[0]);
close(fdleft[1]);
free(argp);
//dump_fds();
int corpse;
int status;
while ((corpse = waitpid(0, &status, 0)) > 0)
printf(":-( PID %d status 0x%.4X\n", corpse, status);
printf("\n(>^_^)> ");
}
printf(" v(^o^)^ BYE BYE!\n");
}
static void dump_argv(char **argv)
{
int n = 0;
char **args;
args = argv;
while (*args++ != 0)
n++;
fprintf(stderr, "%d: %d args\n", getpid(), n);
args = argv;
while (*args != 0)
fprintf(stderr, "[%s]\n", *args++);
fprintf(stderr, "EOA\n");
}
/* Report on open file descriptors (0..19) in process */
static void dump_fds(void)
{
struct stat b;
char buffer[32];
sprintf(buffer, "%d: ", getpid());
char *str = buffer + strlen(buffer);
for (int i = 0; i < 20; i++)
*str++ = (fstat(i, &b) == 0) ? 'o' : '-';
*str++ = '\n';
*str = '\0';
fputs(buffer, stderr);
}
int makeargv(char *line, char *seps, char ***args)
{
enum { MAX_ARGS = 32 };
char **argv = malloc(32 * sizeof(char *)); // Lazy!
if (argv == 0)
err_sys("out of memory in makeargv()");
int n;
char **argp = argv;
char *str = line;
for (n = 0; n < MAX_ARGS - 1; n++)
{
str += strspn(str, seps);
if (*str == '\0')
break;
*argp++ = str;
int len = strcspn(str, seps);
if (len == 0)
break;
str[len] = '\0';
str += len + 1;
}
*argp = 0;
dump_argv(argv);
*args = argv;
return(n);
}
void err_sys(const char *msg)
{
int errnum = errno;
char *errmsg = strerror(errnum);
fprintf(stderr, "%s (%d: %s)\n", msg, errnum, errmsg);
exit(1);
}
出力例:
$ ./pipes-15673333
<(^_^)>
Hello
I am your console and I am here to help you
If you don't need me anymore just say "bye"
29191: ooo-----------------
(>^_^)> who | grep jl | sort
29191: 6 args
[who]
[|]
[grep]
[jl]
[|]
[sort]
EOA
pnum = 2
ploc[0] = 0
ploc[1] = 2
ploc[2] = 5
PID 29194 launched
PID 29195 launched
29194: 1 args
[who]
EOA
PID 29196 launched
29194: ooo-----------------
29195: 2 args
[grep]
[jl]
EOA
29195: ooo-----------------
29196: 1 args
[sort]
EOA
29196: ooo-----------------
:-( PID 29194 status 0x0000
jleffler console Mar 27 15:11
jleffler ttys000 Mar 27 16:26
jleffler ttys001 Mar 27 16:26
jleffler ttys002 Mar 27 16:26
jleffler ttys003 Mar 27 16:26
jleffler ttys004 Mar 27 16:26
jleffler ttys005 Mar 27 16:26
:-( PID 29195 status 0x0000
:-( PID 29196 status 0x0000
(>^_^)> ls
29191: 1 args
[ls]
EOA
pnum = 0
ploc[0] = 0
PID 29197 launched
29197: 1 args
[ls]
EOA
29197: ooo-----------------
bash.getopts.update makefile pipeline.c pthread-1.c shuntzeroes.c timezeromoves.c
cmpfltint.c mda.c pipeline.dSYM pthread-2.c so.14304827 uint128.c
const-stuff.c mq-saurabh pipes-13905948.c pthread-3.c so.367309 uname.c
dupdata.sql mqp-saurabh pipes-14312939.c quine.c so.6964747 unwrap.c
fifocircle.c multi-pipe-sort.c pipes-15673333 ranges.sql so.6965001 xxx.sql
idsdb00246324.ec multiopts.sh pipes-15673333.c recv.c so.8854855.sql yyy.sql
incunabulum.c nextpipe.c pipes-15673333.dSYM regress.c strandsort.c
madump.c pipeline powa.c send.c streplace.c
:-( PID 29197 status 0x0000
(>^_^)> ls -C | wc
29191: 4 args
[ls]
[-C]
[|]
[wc]
EOA
pnum = 1
ploc[0] = 0
ploc[1] = 3
PID 29200 launched
PID 29201 launched
29200: 2 args
29201: 1 args
[ls]
[wc]
[-C]
EOA
EOA
29201: ooo-----------------
29200: ooo-----------------
:-( PID 29200 status 0x0000
16 46 581
:-( PID 29201 status 0x0000
(>^_^)> bye
v(^o^)^ BYE BYE!
$