4

我正在尝试创建自己的外壳,我相信我已经正确完成了分叉,但我无法弄清楚如何正确管道。任何帮助或提示将不胜感激。

基本上我的管道不工作,我花了很长时间试图弄清楚如何让它们在进程之间正确传输数据。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ourhdr.h" // from Steven's book Advanced programing in the UNIX Enviroment


extern int makeargv(char *, char * , char ***);
int main()  
{   char **argp;
    int i,j,vpret;
    char buf[80];
    pid_t pid;
    int pnum;   
        //pipe number
    int ploc[16];   
        //pipe location 
        //(this has a max of 12 possible pipes)
    int targ;
    int fdleft[2], fdright[2];




    printf("            <(^_^)> \nHello \n I am your console and I am here to help you \n");
    printf("    If you dont need me anymore just say \"bye\" ");
    fflush(stdout);
    write(1,"\n(>^_^)> ",8);
    while(strcmp(fgets(buf, 80, stdin), "bye\n")!=0)
    {        
        j=makeargv(buf," \n",&argp); //this breaks the line up and returns the number of commands 
        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++;
            }

        } 

        for (i = 0; i < (pnum+1); i++) 
        {   
            pipe(fdright);
            if (i != 0)
            {   
                dup2(fdright[1], fdleft[0]);            
            }
            pid = fork();


            switch (pid)
            {
                case -1:
                    err_sys("fork failed");
                    break;
                case 0: // child
                    if (i != pnum)
                    {
                        dup2(fdright[1],1);
                    }
                    if ( i != 0);
                    {
                        dup2(fdright[0],0); 
                    }
                    //printf("(^o^) running pipe[%i]\n" , i);
                    targ =(ploc[i]) ;
                    execvp(argp[targ],&argp[targ]);
                    write(1,"(-_-) I'm sorry the exec failed \n",33);
                    exit(1);
                default:

                    dup2(fdleft[1], fdright[1]);
                    waitpid(pid,NULL, 0);
                    close(fdright[0]);
                    close(fdright[1]);
                    //waitpid(pid,NULL, 0);
            }
        }   
        //waitpid(pid, NULL, 0);
        write(1,"\n(>^_^)> ",8);
    }
    printf("  v(^o^)^ BYE BYE!\n");

}

谢谢

4

1 回答 1

3

各种评论:

  1. while (strcmp(fgets(buf, 80, stdin), "bye\n")!=0)

    如果 shell 被赋予 EOF 而不是bye. 不要像这样组合两个函数调用。如果您想在一个循环条件下完成所有操作,请使用:

    while (fgets(buf, sizeof(buf), stdin) != 0 && strcmp(buf, "bye\n") != 0)
    

    我们将在另一次讨论命令行长度的限制。

  2. 由于您没有提供makeargv()查看,我们必须假设它可以正常工作。

  3. 您将事物拆分为命令和管道的循环是:

    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;
    
  4. 您的代码在数组中有一对文件描述符fdleft,但您从不初始化数组(pipe()例如 call ),即使您在调用dup2().

  5. 然后主for循环必须运行两次,每个命令运行一次。对于管道中三个或更多命令的一般情况(who | grep me | sort例如 ),您的第一个命令(who)需要其标准输入保持不变,但其标准输出将进入连接who和的管道grep。“中间”命令(倒数第二个命令,或grep me在示例中)每个都需要其标准输入来自前一个管道,并且需要为其标准输出创建一个新管道。最后一个命令(在本例中为第三个命令sort)需要其标准输入来自最后一个管道,并且其标准输出保持不变。

    您的代码不会那样做,也不会靠近。

  6. 当您使用pipe()and then dup()ordup2()将任何描述符映射到标准 I/O 描述符时,您需要关闭管道的两端。你也没有足够的close()电话。

  7. 您的父进程必须依次启动每个子进程,并且仅在启动所有子进程后等待它们退出。有不同的方法来组织过程。父母可以分叉一次;孩子可能负责启动管道中的主要命令,最后执行最后一个命令本身。父母只有一个直系子女(其他是孙子女),所以它只需要等待一个命令完成。另一种方法是父进程知道管道中的每个进程,并等待它们全部完成。

  8. 如果您的父进程在启动其余命令之前等待管道中的每个命令完成,则最终可能会出现死锁。一个孩子向它的管道写入了太多数据,以至于它被内核阻塞,直到某个进程从管道读取,但是从管道读取的进程尚未启动,父进程正在等待子进程退出。您也失去了多处理和并发的好处。

  9. 在您的“案例 0”中,您有一个无关的分号。我的编译器警告过我。如果你的没有警告你,你需要使用更多的编译警告或获得更好的编译器。

    case 0: // child
        if (i != pnum)
        {
            dup2(fdright[1], 1);
        }
        if (i != 0);     // Unwanted semi-colon!
        {
            dup2(fdright[0], 0); 
        }
    
  10. 关于 SO 上的 mini-shell 中的管道有很多问题,包括:

    13636252 的答案几乎是通用的。唯一的障碍是它使用char ***容易混淆,而且它写得如此紧凑,它具有相互递归的功能,重复最少。OTOH,它工作正常,你的makeargv()函数也使用了一个char ***参数。


重新编写的代码

这是您的代码重做,以便它工作。它包括err_sys()和的实现makeargv()。我makeargv()只是假设命令行中的单词少于 32 个。无论如何,它都不是一个强大的命令行解析器。它确实允许您输入ls | wc并给出正确答案;它还允许who | grep me | sort并给出正确的答案;它还允许ls并给出正确的答案。不过,管道符号周围的空格是至关重要的(在普通的 shell 中,它们是可选的,因此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!
$
于 2013-03-28T16:25:23.297 回答