尝试使用两个或更多管道实现一个shell,但程序挂起 - C
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了尝试使用两个或更多管道实现一个shell,但程序挂起 - C相关的知识,希望对你有一定的参考价值。
我已经成功实现了一个小shell程序,能够在两个命令之间实现管道
ls -l | wc -l
但是,当我尝试实现一个或多个shell时,我可以这样做
ls -l | wc -l | wc -l
我的程序一直持续到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;
}
这是我第一次使用多个流程,虽然这可能不是最好的方法,但我现在只是对于错误所在的超级好奇。
非常感谢你!
我希望你早就解决了这个问题,但......
Diagnosis
与以往一样,主要问题是没有关闭足够的文件描述符。第二个问题是证明这一点。我拿了原始代码并添加了大量的仪器 - 打印(标准错误)。消息的前缀是执行打印的进程的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)只有三个desriptors打开(应该),当它退出时,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-4第二次wc
完成。
Prescription
简单的处方是“足够接近文件描述符”。
实际上关闭正确的文件描述符是一个更大的问题。在pipeFork()以上是关于尝试使用两个或更多管道实现一个shell,但程序挂起 - C的主要内容,如果未能解决你的问题,请参考以下文章