我已经成功实现了一个小型shell程序,能够在两个命令之间实现管道,如
ls -l | wc -l </ p>
然而,当我尝试实现一个或多个shell时,我可以,例如,
ls -l | wc -l | wc -l </ p>
我的程序一直挂到I ^ C.
我现在已经把头缠绕了好几个小时,我似乎无法弄清楚我做错了什么。我的方法是创建与我拥有的命令一样多的子进程,所有这些都使用相同的父亲。这是我的完整代码:
#define ARGVMAX 100
#define LINESIZE 1024
#define PIPESYMB "|"
#define EXITSYMB "exit"
int makeargv(char *s, char *argv[]) {
if ( s==NULL || argv==NULL || ARGVMAX==0)
return -1;
int ntokens = 0;
argv[ntokens]=strtok(s, " \t\n");
while ( (argv[ntokens]!=NULL) && (ntokens<ARGVMAX) ) {
ntokens++;
argv[ntokens]=strtok(NULL, " \t\n");
}
argv[ntokens] = NULL;
return ntokens;
}
void changeOutput(int mypipe[]) {
dup2(mypipe[1], 1);
close(mypipe[0]);
close(mypipe[1]);
}
void changeInput(int mypipe[]) {
dup2(mypipe[0], 0);
close(mypipe[1]);
close(mypipe[0]);
}
void pipeFork(char *argv[], int i, int mypipe[]) {
int h = i;
int mypipe1[2];
int found = 0;
while((argv[h] != NULL) && !found) {
if(!(strcmp(argv[h], PIPESYMB))) {
argv[h] = NULL;
found = 1;
}
h++;
}
if (pipe(mypipe1)==-1)
abort();
switch ( fork() ) {
case -1:
perror("fork error");
exit(1);
case 0:
changeInput(mypipe);
if(found)
changeOutput(mypipe1);
execvp( argv[i], &argv[i] );
perror("exec");
exit(1);
default:
if(found)
pipeFork(argv, h, mypipe1);
}
close(mypipe1[0]);
close(mypipe1[1]);
wait(NULL);
}
void runcommand(char *argv[]) {
int i = 0;
int mypipe[2];
int found = 0;
if(!(strcmp(argv[0], EXITSYMB)))
exit(0);
if (pipe(mypipe)==-1)
abort();
while((argv[i] != NULL) && !found) {
if(!(strcmp(argv[i], PIPESYMB))) {
argv[i] = NULL;
found = 1;
}
i++;
}
switch ( fork() ) {
case -1:
perror("fork error");
exit(1);
case 0:
if(found)
changeOutput(mypipe);
execvp( argv[0], argv );
perror("exec");
exit(1);
default:
if(found)
pipeFork(argv, i, mypipe);
}
close(mypipe[0]);
close(mypipe[1]);
wait(NULL);
}
int main(int argc, char *argv[]) {
char line[LINESIZE];
char* av[ARGVMAX];
printf("> "); fflush(stdout);
while ( fgets ( line, LINESIZE, stdin) != NULL ) {
if ( makeargv( line, av) > 0 ) runcommand( av );
printf("> "); fflush(stdout);
}
return 0;
}
这是我第一次使用多个流程,虽然这可能不是最好的方法,但我现在对于错误的地方非常好奇。
非常感谢!
答案 0 :(得分:0)
我希望你早就解决了这个问题,但是......
与以往一样,主要问题是没有关闭足够的文件描述符。第二个问题是证明这一点。我拿了原始代码并添加了大量的仪器 - 打印(标准错误)。这些消息的前缀是进行打印的进程的PID,这在有多个进程时非常重要。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#define ARGVMAX 100
#define LINESIZE 1024
#define PIPESYMB "|"
#define EXITSYMB "exit"
static void dump_fds(int max_fd)
{
char buffer[64];
char *base = buffer + snprintf(buffer, sizeof(buffer), "%d: fds ", (int)getpid());
for (int i = 0; i < max_fd; i++)
{
struct stat sb;
if (fstat(i, &sb) == 0)
*base++ = 'o';
else
*base++ = '-';
}
*base = '\0';
fprintf(stderr, "%s\n", buffer);
}
static void dump_argv(const char *tag, char **argv)
{
fprintf(stderr, "%d: %s:\n", (int)getpid(), tag);
int i = 0;
while (*argv)
fprintf(stderr, "%d: argv[%d] = \"%s\"\n", (int)getpid(), i++, *argv++);
dump_fds(20);
}
static int makeargv(char *s, char *argv[])
{
if (s == NULL || argv == NULL || ARGVMAX == 0)
return -1;
int ntokens = 0;
argv[ntokens] = strtok(s, " \t\n");
while ((argv[ntokens] != NULL) && (ntokens < ARGVMAX))
{
ntokens++;
argv[ntokens] = strtok(NULL, " \t\n");
}
argv[ntokens] = NULL;
return ntokens;
}
static void changeOutput(int mypipe[])
{
fprintf(stderr, "%d: (%d closed) (%d to 1)\n", (int)getpid(), mypipe[0], mypipe[1]);
dup2(mypipe[1], 1);
close(mypipe[0]);
close(mypipe[1]);
}
static void changeInput(int mypipe[])
{
fprintf(stderr, "%d: (%d to 0) (%d closed)\n", (int)getpid(), mypipe[0], mypipe[1]);
dup2(mypipe[0], 0);
close(mypipe[1]);
close(mypipe[0]);
}
static void wait_for(int pid)
{
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
{
fprintf(stderr, "%d: child %d exit status 0x%.4X\n", (int)getpid(), corpse, status);
if (pid == 0 || corpse == pid)
break;
}
}
static void pipeFork(char *argv[], int i, int mypipe[])
{
int h = i;
int mypipe1[2];
int found = 0;
dump_argv("pipeFork", &argv[h]);
while ((argv[h] != NULL) && !found)
{
if (!(strcmp(argv[h], PIPESYMB)))
{
argv[h] = NULL;
found = 1;
}
h++;
}
if (pipe(mypipe1) == -1)
abort();
fprintf(stderr, "%d: %s - pipe (%d,%d)\n", (int)getpid(), __func__, mypipe1[0], mypipe1[1]);
int pid = fork();
switch (pid)
{
case -1:
perror("fork error");
exit(1);
case 0:
fprintf(stderr, "%d: pipeFork - child\n", (int)getpid());
changeInput(mypipe);
if (found)
changeOutput(mypipe1);
dump_argv("pipefork:execvp", &argv[i]);
execvp(argv[i], &argv[i]);
perror("exec");
exit(1);
default:
fprintf(stderr, "%d: forked child %d\n", (int)getpid(), pid);
if (found)
{
dump_argv("recurse-pipeFork", &argv[h]);
pipeFork(argv, h, mypipe1);
}
break;
}
fprintf(stderr, "%d: pipeFork: close %d %d\n", (int)getpid(), mypipe1[0], mypipe1[1]);
close(mypipe1[0]);
close(mypipe1[1]);
fprintf(stderr, "%d: waiting in pipeFork for %d\n", (int)getpid(), pid);
wait_for(0);
}
static void runcommand(char *argv[])
{
int i = 0;
int mypipe[2];
int found = 0;
if (!(strcmp(argv[0], EXITSYMB)))
exit(0);
if (pipe(mypipe) == -1)
abort();
fprintf(stderr, "%d: %s - pipe (%d,%d)\n", (int)getpid(), __func__, mypipe[0], mypipe[1]);
while ((argv[i] != NULL) && !found)
{
if (!(strcmp(argv[i], PIPESYMB)))
{
argv[i] = NULL;
found = 1;
}
i++;
}
int pid = fork();
switch (pid)
{
case -1:
perror("fork error");
exit(1);
case 0:
fprintf(stderr, "%d: runcommand - child\n", (int)getpid());
if (found)
changeOutput(mypipe);
dump_argv("about to exec", argv);
execvp(argv[0], argv);
perror("exec");
exit(1);
default:
fprintf(stderr, "%d: forked child %d\n", (int)getpid(), pid);
if (found)
{
dump_argv("call-pipeFork", &argv[i]);
pipeFork(argv, i, mypipe);
}
break;
}
fprintf(stderr, "%d: runcommand: close %d %d\n", (int)getpid(), mypipe[0], mypipe[1]);
close(mypipe[0]);
close(mypipe[1]);
fprintf(stderr, "%d: waiting in runcommand for %d\n", (int)getpid(), pid);
wait_for(0);
}
int main(void)
{
char line[LINESIZE];
char *av[ARGVMAX];
printf("> ");
fflush(stdout);
while (fgets(line, LINESIZE, stdin) != NULL)
{
if (makeargv(line, av) > 0)
{
dump_argv("after reading", av);
runcommand(av);
}
printf("> ");
fflush(stdout);
}
return 0;
}
dump_fds()
函数构建缓冲区并打印一行,因为当它逐个输出字符时,我在同时运行循环的进程之间受到干扰。
随着仪器的到位,我们可以看到ls | wc
:
$ ./shell23
> ls | wc
6418: after reading:
6418: argv[0] = "ls"
6418: argv[1] = "|"
6418: argv[2] = "wc"
6418: fds ooo-----------------
6418: runcommand - pipe (3,4)
6418: forked child 6419
6418: call-pipeFork:
6418: argv[0] = "wc"
6418: fds ooooo---------------
6418: pipeFork:
6418: argv[0] = "wc"
6418: fds ooooo---------------
6418: pipeFork - pipe (5,6)
6418: forked child 6420
6418: pipeFork: close 5 6
6419: runcommand - child
6419: (3 closed) (4 to 1)
6418: waiting in pipeFork for 6420
6419: about to exec:
6419: argv[0] = "ls"
6419: fds ooo-----------------
6420: pipeFork - child
6420: (3 to 0) (4 closed)
6420: pipefork:execvp:
6420: argv[0] = "wc"
6420: fds ooo--oo-------------
6418: child 6419 exit status 0x0000
6418: runcommand: close 3 4
6418: waiting in runcommand for 6419
8 8 81
6418: child 6420 exit status 0x0000
>
> …
如果在execvp()
之前查看进程6420的fds输出,可以看到文件描述符5和6仍处于打开状态。幸运的是,在这种情况下,它不会造成任何损害; ls
(6419)只有三个desriptor打开(应该这样),当它退出时,wc
(6420)的输入被关闭。
ls | wc | wc
的跟踪问题更多:
> …
>
> ls | wc | wc
6418: after reading:
6418: argv[0] = "ls"
6418: argv[1] = "|"
6418: argv[2] = "wc"
6418: argv[3] = "|"
6418: argv[4] = "wc"
6418: fds ooo-----------------
6418: runcommand - pipe (3,4)
6418: forked child 6421
6418: call-pipeFork:
6418: argv[0] = "wc"
6418: argv[1] = "|"
6418: argv[2] = "wc"
6418: fds ooooo---------------
6418: pipeFork:
6418: argv[0] = "wc"
6418: argv[1] = "|"
6418: argv[2] = "wc"
6418: fds ooooo---------------
6418: pipeFork - pipe (5,6)
6418: forked child 6422
6421: runcommand - child
6421: (3 closed) (4 to 1)
6418: recurse-pipeFork:
6418: argv[0] = "wc"
6418: fds ooooooo-------------
6418: pipeFork:
6418: argv[0] = "wc"
6418: fds ooooooo-------------
6421: about to exec:
6418: pipeFork - pipe (7,8)
6421: argv[0] = "ls"
6421: fds ooo-----------------
6422: pipeFork - child
6422: (3 to 0) (4 closed)
6418: forked child 6423
6418: pipeFork: close 7 8
6418: waiting in pipeFork for 6423
6422: (5 closed) (6 to 1)
6422: pipefork:execvp:
6422: argv[0] = "wc"
6422: fds ooo-----------------
6423: pipeFork - child
6423: (5 to 0) (6 closed)
6423: pipefork:execvp:
6423: argv[0] = "wc"
6423: fds ooooo--oo-----------
6418: child 6421 exit status 0x0000
6418: pipeFork: close 5 6
6418: waiting in pipeFork for 6422
^C
$
进程6421(ls
)和6422(wc
第一个)是正常的,但6423有文件描述符3,4,7,8打开(以及0,1,2),而这一次,这些引起了麻烦;其中一个是标准输入管道的写入端,因此无法报告EOF,因为如果在读取时没有挂起,可以写入管道的进程(6423)。
父进程(本例中为6418)也有可能不正确地打开管道文件描述符 - 用于处方的代码的修改版本(如下)显示父进程带有描述符0等待第二个wc
完成时打开-4。
简单的处方是&#34;足够接近文件描述符&#34;。
实际上关闭正确的文件描述符是一个更大的问题。在pipeFork()
内递归调用pipeFork()
时,重要的是
关闭mypipe
文件描述符;递归函数使用传入的mypipe1
文件描述符,对其他函数一无所知,也不需要它们。同样,父shell必须在等待之前关闭mypipe
文件描述符(以及mypipe1
文件描述符)。
另外,最后一个孩子不会像以前那样创建一个未使用的管道,这是明智的。
/* SO 4672-0692 */
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#define ARGVMAX 100
#define LINESIZE 1024
#define PIPESYMB "|"
#define EXITSYMB "exit"
static void logmsg(const char *func, const char *fmt, ...)
{
char buffer[1024];
char *base = buffer + snprintf(buffer, sizeof(buffer), "%d: %s(): ", (int)getpid(), func);
va_list args;
va_start(args, fmt);
vsnprintf(base, sizeof(buffer) - (base - buffer), fmt, args);
va_end(args);
fprintf(stderr, "%s\n", buffer);
}
static void dump_fds(const char *func, int max_fd)
{
char buffer[64] = "fds ";
char *base = buffer + strlen(buffer);
for (int i = 0; i < max_fd; i++)
{
struct stat sb;
if (fstat(i, &sb) == 0)
*base++ = 'o';
else
*base++ = '-';
}
*base = '\0';
logmsg(func, "%s", buffer);
}
static void dump_argv(const char *func, const char *tag, char **argv)
{
logmsg(func, "%s:", tag);
int i = 0;
while (*argv != 0)
logmsg(func, "argv[%d] = \"%s\"", i++, *argv++);
dump_fds(func, 20);
}
static int makeargv(char *s, char *argv[])
{
if (s == NULL || argv == NULL || ARGVMAX == 0)
return -1;
int ntokens = 0;
argv[ntokens] = strtok(s, " \t\n");
while ((argv[ntokens] != NULL) && (ntokens < ARGVMAX))
{
ntokens++;
argv[ntokens] = strtok(NULL, " \t\n");
}
argv[ntokens] = NULL;
return ntokens;
}
static void changeOutput(int mypipe[])
{
logmsg(__func__, "(%d closed) (%d to 1)", mypipe[0], mypipe[1]);
dup2(mypipe[1], 1);
close(mypipe[0]);
close(mypipe[1]);
}
static void changeInput(int mypipe[])
{
logmsg(__func__, "(%d to 0) (%d closed)", mypipe[0], mypipe[1]);
dup2(mypipe[0], 0);
close(mypipe[1]);
close(mypipe[0]);
}
static void wait_for(const char *func, int pid)
{
int corpse;
int status;
dump_fds(__func__, 20);
while ((corpse = waitpid(pid, &status, 0)) > 0)
{
logmsg(func, "child %d exit status 0x%.4X", corpse, status);
if (pid == 0 || corpse == pid)
break;
}
}
static void pipeFork(char *argv[], int i, int mypipe[])
{
int h = i;
int mypipe1[2];
int found = 0;
dump_argv(__func__, "entry", &argv[h]);
while ((argv[h] != NULL) && !found)
{
if (!(strcmp(argv[h], PIPESYMB)))
{
argv[h] = NULL;
found = 1;
}
h++;
}
if (found)
{
if (pipe(mypipe1) == -1)
abort();
logmsg(__func__, "pipe (%d,%d)", mypipe1[0], mypipe1[1]);
}
int pid = fork();
switch (pid)
{
case -1:
perror("fork error");
exit(1);
case 0:
logmsg(__func__, "- child");
changeInput(mypipe);
if (found)
changeOutput(mypipe1);
dump_argv(__func__, "execvp", &argv[i]);
execvp(argv[i], &argv[i]);
perror("exec");
exit(1);
default:
logmsg(__func__, "forked child %d", pid);
if (found)
{
close(mypipe[0]);
close(mypipe[1]);
dump_argv(__func__, "recurse", &argv[h]);
pipeFork(argv, h, mypipe1);
}
break;
}
if (found)
{
logmsg(__func__, "close %d %d", mypipe1[0], mypipe1[1]);
close(mypipe1[0]);
close(mypipe1[1]);
}
logmsg(__func__, "close %d %d", mypipe[0], mypipe[1]);
close(mypipe[0]);
close(mypipe[1]);
logmsg(__func__, "waiting for %d", pid);
wait_for(__func__, pid);
}
static void runcommand(char *argv[])
{
int i = 0;
int mypipe[2];
int found = 0;
fflush(0);
if (!(strcmp(argv[0], EXITSYMB)))
exit(0);
if (pipe(mypipe) == -1)
abort();
logmsg(__func__, "pipe (%d,%d)", mypipe[0], mypipe[1]);
while ((argv[i] != NULL) && !found)
{
if (!(strcmp(argv[i], PIPESYMB)))
{
argv[i] = NULL;
found = 1;
}
i++;
}
int pid = fork();
switch (pid)
{
case -1:
perror("fork error");
exit(1);
case 0:
logmsg(__func__, "- child");
if (found)
changeOutput(mypipe);
dump_argv(__func__, "execvp", argv);
execvp(argv[0], argv);
perror("exec");
exit(1);
default:
logmsg(__func__, "forked child %d", pid);
if (found)
{
dump_argv(__func__, "call-pipeFork", &argv[i]);
pipeFork(argv, i, mypipe);
}
break;
}
logmsg(__func__, "close %d %d", mypipe[0], mypipe[1]);
close(mypipe[0]);
close(mypipe[1]);
logmsg(__func__, "waiting for %d", pid);
wait_for(__func__, pid);
}
int main(void)
{
char line[LINESIZE];
char *av[ARGVMAX];
printf("> ");
fflush(stdout);
while (fgets(line, LINESIZE, stdin) != NULL)
{
if (makeargv(line, av) > 0)
{
dump_argv(__func__, "after reading", av);
runcommand(av);
}
printf("> ");
fflush(stdout);
}
putchar('\n');
return 0;
}
构建并运行时,shell79
的示例跟踪为:
$ ./shell79
> ls | wc | wc
7182: main(): after reading:
7182: main(): argv[0] = "ls"
7182: main(): argv[1] = "|"
7182: main(): argv[2] = "wc"
7182: main(): argv[3] = "|"
7182: main(): argv[4] = "wc"
7182: main(): fds ooo-----------------
7182: runcommand(): pipe (3,4)
7182: runcommand(): forked child 7183
7182: runcommand(): call-pipeFork:
7182: runcommand(): argv[0] = "wc"
7182: runcommand(): argv[1] = "|"
7182: runcommand(): argv[2] = "wc"
7182: runcommand(): fds ooooo---------------
7182: pipeFork(): entry:
7182: pipeFork(): argv[0] = "wc"
7182: pipeFork(): argv[1] = "|"
7182: pipeFork(): argv[2] = "wc"
7182: pipeFork(): fds ooooo---------------
7182: pipeFork(): pipe (5,6)
7182: pipeFork(): forked child 7184
7182: pipeFork(): recurse:
7182: pipeFork(): argv[0] = "wc"
7182: pipeFork(): fds ooo--oo-------------
7182: pipeFork(): entry:
7182: pipeFork(): argv[0] = "wc"
7183: runcommand(): - child
7183: changeOutput(): (3 closed) (4 to 1)
7182: pipeFork(): fds ooo--oo-------------
7183: runcommand(): execvp:
7183: runcommand(): argv[0] = "ls"
7183: runcommand(): fds ooo-----------------
7182: pipeFork(): forked child 7185
7182: pipeFork(): close 5 6
7182: pipeFork(): waiting for 7185
7184: pipeFork(): - child
7184: changeInput(): (3 to 0) (4 closed)
7182: wait_for(): fds ooo-----------------
7184: changeOutput(): (5 closed) (6 to 1)
7184: pipeFork(): execvp:
7184: pipeFork(): argv[0] = "wc"
7184: pipeFork(): fds ooo-----------------
7185: pipeFork(): - child
7185: changeInput(): (5 to 0) (6 closed)
7185: pipeFork(): execvp:
7185: pipeFork(): argv[0] = "wc"
7185: pipeFork(): fds ooo-----------------
1 3 25
7182: pipeFork(): child 7185 exit status 0x0000
7182: pipeFork(): close 5 6
7182: pipeFork(): close 3 4
7182: pipeFork(): waiting for 7184
7182: wait_for(): fds ooo-----------------
7182: pipeFork(): child 7184 exit status 0x0000
7182: runcommand(): close 3 4
7182: runcommand(): waiting for 7183
7182: wait_for(): fds ooo-----------------
7182: runcommand(): child 7183 exit status 0x0000
> ^D
$
这看起来很干净;没有杂散的打开文件描述符。