multi-pipe() C 程序中的死循环

Endless loop in multi-pipe() C program

我有一个程序需要 executeCommand3 执行的命令列表。目标是执行一个命令列表,这些命令目前存储在这个列表中,但将来会在类似 shell 的环境中输入以执行,这里是代码:

MAIN.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>    //for getpid()
#include <sys/types.h> // for pid_t

#include "routines.h" //for executeCommand3()

#define SUB_PROCESSES_NUMBER 2

char *command0[] = {"ls", "tos", NULL};
char *command1[] = {"wc", NULL}; 
char *command2[] = {"wc", NULL};
char **commands[SUB_PROCESSES_NUMBER + 1] = {command0, command1, command2};

int main()
{   

    char input[1024];
    printf("scan: ");
    fgets(input,1024,stdin);
    int value=0;
    while((strcmp(input,"q\n")!=0))
    {
        value=executeCommand3(commands, SUB_PROCESSES_NUMBER+1);

        char buffer[1024];
        printf("value %i\nscan: ",value);
        fgets(input,1024,stdin);
        fgets(buffer,1024,stdin);
        printf("%s\n",input);
    }
    return 0;
}

routines.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h> // for open, close,read,write on FD
#include <error.h>     // for error handling
#include <sys/types.h> // structs like time_t
#include <sys/wait.h>  // wait, waitpid, waitid - wait for process to change state

#include "routines.h"

#define READ 0  // for file descriptor index
#define WRITE 1 // for file descriptor index

#define STDIN 0
#define STDOUT 1

int executeCommand3(char ***args, int processNumb)
{
    pid_t pidList[processNumb];    //list of id of cmd processes (one for each child)
    int fdBackLog[processNumb][2]; // list of file desrciptors relative for each cmd (a pair for each child)

    int lastProcessFlag = 0;
    int i;
    for (i = 0; i < processNumb; i++) //cycle through the list of commands
    {
        if ((i + 1) == processNumb){
            lastProcessFlag = 1;
        }

        if (lastProcessFlag != 1)
        {
            int retPipe = pipe(fdBackLog[i]);
            if (retPipe < 0) // generating pipe for comunication between cmd(i) and cmd(i+1)
            {
                perror("pipe error");

                if (i > 0)
                {
                    close(fdBackLog[i - 1][READ]); 
                }
                exit(EXIT_FAILURE); //exit with failure code
            }
        }

        pidList[i] = fork();

        if (pidList[i] < 0) // error fork
        {
            perror("error fork()");

            if (i > 0)
            {                                  
                close(fdBackLog[i - 1][READ]); 
            }
            close(fdBackLog[i][READ]);  // close the read pipe end of cmd(i)
            close(fdBackLog[i][WRITE]); //close the write pipe end of cmd(i)
            exit(EXIT_FAILURE);
        }

        if (pidList[i] == 0) // CHILD PROCESS
        {
            printf("children %i process parent %i for: %s \n", getpid(), getppid(), args[i][0]);
            if (lastProcessFlag != 1)
            {


                close(fdBackLog[i][READ]); 

                dup2(fdBackLog[i][WRITE], STDOUT);
                close(fdBackLog[i][WRITE]); // duplicated pipes are not useful any more

            }
            else
            {
                close(fdBackLog[i][WRITE]); //last process has nothing to write on pipe
            }

            if (i > 0)
            {
                // Also need to redirect stdin if this is not first process
                dup2(fdBackLog[i - 1][READ], STDIN);
                close(fdBackLog[i - 1][READ]);
            }

            int exitValue = execvp(args[i][0], args[i]); 
            perror(args[i][0]);
            exit(EXIT_FAILURE); // Should not be reached;
        }

        close(fdBackLog[i][WRITE]); 
        if (i > 0)
        {
            close(fdBackLog[i - 1][READ]);
        }
    }

    int wPid, status;

    while((wPid = wait(&status))>0)
    {
        printf("Child #%i (%i)\n", wPid, status);
    }
}

我的问题是错误处理,实际上第一个命令列表的第二项是一些随机词。

当我执行命令时,输出很好(就像正常的 shell 一样)但问题是 MAIN.c 中的 while 循环继续调用函数 executeCommand3()。我认为这可能会发生,因为 executeCommand3() 中的某些进程在 MAIN.c 的标准输入中留下了一些东西,所以 fgets 从流中获取它并保留 while 循环的 cicle。所以我添加了另一个 fget 来中断循环,但问题仍然存在....

我怎样才能修复程序,即使我从 executeCommand3() 中遇到错误,while 循环停止并且我可以正常重做循环? 问题出在 executeCommand3() 函数内(可能在某些 dup2()pipe()...)或 MAIN.c?

一个问题是您没有错误检查您的 fgets() 调用。如果这样做,您会发现 stdin 已关闭。下面是一些以 fcntl() 调用形式进行诊断的代码,用于检查标准输入文件描述符是否仍然有效:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define READ 0
#define WRITE 1

#define STDIN 0
#define STDOUT 1

#define SUB_PROCESSES_NUMBER 2

void executeCommand3(char ***args, int processNumb);

char *command0[] = {"ls", "tos", NULL};
char *command1[] = {"wc", NULL};
char *command2[] = {"wc", NULL};
char **commands[SUB_PROCESSES_NUMBER + 1] = {command0, command1, command2};

int main(void)
{
    char input[1024];
    printf("scan: ");
    fgets(input, 1024, stdin);
    if (fcntl(0, F_GETFD, 0) < 0)
    {
        perror("fcntl() - 1");
        exit(1);
    }
    while ((strcmp(input, "q\n") != 0))
    {
        executeCommand3(commands, SUB_PROCESSES_NUMBER + 1);
        char buffer[1024];
        printf("\nscan: ");
        if (fcntl(0, F_GETFD, 0) < 0)
        {
            perror("fcntl() - 2");
            exit(1);
        }
        fgets(input, 1024, stdin);
        fgets(buffer, 1024, stdin);
        printf("%s\n", input);
    }
    return 0;
}

void executeCommand3(char ***args, int processNumb)
{
    pid_t pidList[processNumb];
    int fdBackLog[processNumb][2];

    int lastProcessFlag = 0;
    int i;
    for (i = 0; i < processNumb; i++)
    {
        if ((i + 1) == processNumb)
        {
            lastProcessFlag = 1;
        }

        if (lastProcessFlag != 1)
        {
            int retPipe = pipe(fdBackLog[i]);
            if (retPipe < 0)
            {
                perror("pipe error");
                if (i > 0)
                {
                    close(fdBackLog[i - 1][READ]);
                }
                exit(EXIT_FAILURE);
            }
        }

        pidList[i] = fork();

        if (pidList[i] < 0)
        {
            perror("error fork()");
            if (i > 0)
            {
                close(fdBackLog[i - 1][READ]);
            }
            close(fdBackLog[i][READ]);
            close(fdBackLog[i][WRITE]);
            exit(EXIT_FAILURE);
        }

        if (pidList[i] == 0)
        {
            printf("children %i process parent %i for: %s \n", getpid(), getppid(), args[i][0]);
            if (lastProcessFlag != 1)
            {
                close(fdBackLog[i][READ]);
                dup2(fdBackLog[i][WRITE], STDOUT);
                close(fdBackLog[i][WRITE]);
            }
            else
            {
                close(fdBackLog[i][WRITE]);
            }
            if (i > 0)
            {
                dup2(fdBackLog[i - 1][READ], STDIN);
                close(fdBackLog[i - 1][READ]);
            }

            execvp(args[i][0], args[i]);
            perror(args[i][0]);
            exit(EXIT_FAILURE);
        }

        close(fdBackLog[i][WRITE]);
        if (i > 0)
        {
            close(fdBackLog[i - 1][READ]);
        }
    }

    int wPid, status;

    while ((wPid = wait(&status)) > 0)
    {
        printf("Child #%i (%i)\n", wPid, status);
    }
}

当我 运行 在没有子目录 tos 的目录中时,我得到这样的输出(我的程序版本称为 pipe71):

$ ./pipe71
scan: pifflebunk
children 35712 process parent 35711 for: ls 
children 35713 process parent 35711 for: wc 
children 35714 process parent 35711 for: wc 
ls: tos: No such file or directory
Child #35712 (256)
Child #35713 (0)
       1       3      25
Child #35714 (0)

fcntl() - 2: Bad file descriptor
scan: $
$

注意 fcntl() - 2: Bad file descriptor 行。这表示 executeCommand3() 函数中的某些内容关闭了父进程的标准输入。这可能不是你想要的。可能您应该在调用 executeCommand3() 之前进行分叉,然后从子进程调用该函数,让父进程等待。或者您可以在 executeCommand3() 中更加注意标准输入的关闭位置。