尝试使用两个或更多管道实现一个shell,但程序挂起 - C

时间:2017-10-13 00:12:36

标签: c linux shell pipe fork

我已经成功实现了一个小型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;
}

这是我第一次使用多个流程,虽然这可能不是最好的方法,但我现在对于错误的地方非常好奇。

非常感谢!

1 个答案:

答案 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
$

这看起来很干净;没有杂散的打开文件描述符。