【问题标题】:Writing own shell - code hangs when handling certain pipes - in C编写自己的 shell - 处理某些管道时代码挂起 - 在 C 中
【发布时间】:2010-11-19 10:05:57
【问题描述】:

我目前正在编写自己的 shell 作为一个类的项目,并且一切都在虚拟地工作。我的问题在于我的管道,有时它们可​​以工作,有时它们只是挂起,直到我打断它们。我已经对此进行了研究,似乎将其写入标准输入的函数没有从第一个进程接收到 EOF;通常,据我所知,问题在于管道没有关闭,但(据我所知)我的代码并非如此。

所有重定向工作及其任何变体:

  • ls -l > file1
  • wc < file1 > file2

以下管道命令有效:

  • w | head -n 4
  • w | head -n 4 > file1

这不起作用:ls | grep file1 它显示正确的输出并且永远不会结束,除非用户向其发送中断信号。 ls | grep file1 > file2 也不起作用。它挂起而不显示输出,创建 file2,但从不写入。

无论如何,我希望我缺少一些其他人可以注意到的东西;我已经有一段时间了。让我知道是否还有我可以提供的代码。我在下面发布的代码是主文件,没有被删除。

/*
 * This code implemenFts a simple shell program
 * At this time it supports just simple commands with 
 * any number of args.
 */

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>

#include "input.h"
#include "myShell.h"
#include "BackgroundStack.h"

/*
 * The main shell function
 */ 
main() {
    char *buff[20];
    char *inputString;

    BackgroundStack *bgStack = malloc(sizeof(BackgroundStack));
    initBgStack(bgStack);

    struct sigaction new_act;
    new_act.sa_handler = sigIntHandler;
    sigemptyset ( &new_act.sa_mask );
    new_act.sa_flags = SA_RESTART;
    sigaction(SIGINT, &new_act, NULL);

    // Loop forever
    while(1) {
        const char *chPath;

        doneBgProcesses(bgStack);

        // Print out the prompt and get the input
        printPrompt();

        inputString = get_my_args(buff);
        if (buff[0] == NULL) continue;

        if (buff[0][0] == '#') continue;

        switch (getBuiltInCommand(buff[0])) {
            case EXIT:
                exit(0);
                break;
            case CD:
                chPath = (buff[1]==NULL) ? getenv("HOME") : buff[1];
                if (chdir(chPath) < 0) {
                    perror(": cd");
                }
                break;
            default:
                do_command(buff, bgStack);
        }

        //free up the malloced memory
        free(inputString);
    }// end of while(1)
}

static void sigIntHandler (int signum) {}

/* 
 * Do the command
 */
int do_command(char **args, BackgroundStack *bgStack) {
    int status, statusb;  
    pid_t child_id, childb_id;
    char **argsb;
    int pipes[2];

    int isBgd = isBackgrounded(args);
    int hasPipe = hasAPipe(args);

    if (isBgd) removeBackgroundCommand(args);
    if (hasPipe) {
        int cmdBi = getSecondCommandIndex(args);
        args[cmdBi-1] = NULL;
        argsb = &args[cmdBi];
        pipe(pipes);
    }

    // Fork the child and check for errors in fork()
    if((child_id = fork()) == -1) {
        switch(errno) {
            case EAGAIN:
                perror("Error EAGAIN: ");
                return;
            case ENOMEM:
                perror("Error ENOMEM: ");
                return;
        }
    }

    if (hasPipe && child_id != 0) {
        childb_id = fork();
        if(childb_id == -1) {
            switch(errno) {
                case EAGAIN:
                    perror("Error EAGAIN: ");
                    return;
                case ENOMEM:
                    perror("Error ENOMEM: ");
                    return;
            }
        }
    }

    if(child_id == 0 || (childb_id == 0 && hasPipe)) {
        if (child_id != 0 && hasPipe) args = argsb;
        if (child_id == 0 && isBgd) {
            struct sigaction new_act;
            new_act.sa_handler = SIG_IGN;
            sigaction(SIGINT, &new_act, 0);
        }

        if (child_id == 0 && hasPipe) {
            if (dup2(pipes[1], 1) != 1) fatalPerror(": Pipe Redirection Output Error");
            close(pipes[0]);
            close(pipes[1]);
        }
        if (child_id != 0 && hasPipe) {
            if (dup2(pipes[0], 0) != 0) fatalPerror(": Pipe Redirection Input Error");
            close(pipes[0]);
            close(pipes[1]);
            waitpid(child_id, NULL, 0);
        }

        if ((child_id != 0 && hasPipe) || !hasPipe) {
            if (hasAReOut(args)) {
                char outFile[100];
                getOutFile(args, outFile);

                int reOutFile = open(outFile, O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);
                if (reOutFile<0) fatalPerror(": Redirection Output Error");

                if (dup2(reOutFile,1) != 1) fatalPerror(": Redirection Output Error");
                close(reOutFile);
            }
        }

        if ( (child_id == 0 && hasPipe) || !hasPipe) {
            if (hasAReIn(args)) {
                char inFle[100];
                getInFile(args, inFle);

                int reInFile = open(inFle, O_RDWR);
                if (reInFile<0) fatalPerror(": Redirection Input Error");

                if (dup2(reInFile,0) != 0) fatalPerror(": Redirection Input Error");
                close(reInFile);
            } else if (isBgd && !hasPipe) {
                int bgReInFile = open("/dev/null", O_RDONLY);
                if (bgReInFile<0) fatalPerror(": /dev/null Redirection Input Error");

                if (dup2(bgReInFile,0) != 0) fatalPerror(": /dev/null Redirection Input Error");
                close(bgReInFile);
            }
        }

        // Execute the command
        execvp(args[0], args);
        perror(args[0]);

        exit(-1);
    }

    // Wait for the child process to complete, if necessary
    if (!isBgd) waitpid(child_id, &status, 0);
    else if (!hasPipe) {
        printf("Child %ld started\n", (long)child_id);
        BackgroundProcess *bgPrs = malloc(sizeof(BackgroundProcess)); 
        bgPrs->pid = child_id;
        bgPrs->exitStatus = -1;

        addProcessToBgStack(bgStack, bgPrs);
    }
    if (hasPipe) waitpid(childb_id, &statusb, 0);
    if ( WIFSIGNALED(status) && !isBgd )    printf("Child %ld terminated due to signal %d\n", (long)child_id, WTERMSIG(status) );
    if ( hasPipe && WIFSIGNALED(statusb) ) printf("Child %ld terminated due to signal %d\n", (long)childb_id, WTERMSIG(status) );

} // end of do_command

【问题讨论】:

标签: c linux redirect pipe fork


【解决方案1】:

第二个孩子应该等待第一个孩子退出 - 它应该立即开始运行(它会阻塞,直到第一个孩子在管道上产生一些输出),所以删除waitpid()childb 执行。相反,父进程应该等待两个子进程(或者可能只是第二个)。 (事实上​​,正如 JeremyP 所指出的,这个waitpid() 调用无论如何都失败了,因为childb 不是child 的父级。

但是,您的问题是父进程正在维护管道的打开文件描述符。在注释// Wait for the child process to complete, if necessary 之前,父进程应该关闭它的管道文件描述符:

close(pipes[0]);
close(pipes[1]);

父进程中的打开文件描述符意味着子grep进程永远不会看到EOF,因此它不会退出。

【讨论】:

  • 谢谢,正如你所建议的,我在父进程等待它的子进程之前关闭了管道(如果命令包含管道);它工作得很好。我忘了记得管道在三个不同的地方打开。
【解决方案2】:

我不知道答案,但我发现了一个问题。

你会同意

的条件
if(child_id == 0 || (childb_id == 0 && hasPipe))

仅适用于两个子进程,但在 if 语句块中您有:

    if (child_id != 0 && hasPipe) {
        if (dup2(pipes[0], 0) != 0) fatalPerror(": Pipe Redirection Input Error");
        close(pipes[0]);
        close(pipes[1]);
        waitpid(child_id, NULL, 0);
    }

waitpid() 调用不正确,因为它是从第二个孩子调用以等待第一个孩子。 ECHILD 可能会失败,因为第一个孩子不是第二个孩子的孩子。

至于您的真正问题,我怀疑这与grep 命令在其输入关闭之前不会终止这一事实有关。可能会发生一些死锁情况,阻止这种情况的发生。您需要在调试器中运行它或进行一些登录以查看父进程挂起的位置。

编辑

caf 的回答告诉了我们一切。

我假设 grep 的输入被关闭,因为 ls 在终止时会关闭它的输出,但是当然,父进程也打开了 grep 的输入文件描述符。使用head 的版本可以正常工作,因为head -n 4 在四行后终止,无论其输入文件描述符是否关闭。

【讨论】:

  • 我不太清楚为什么我觉得第二个孩子有必要等待第一个孩子,我想这只是我试图修复程序的疯狂行为。我继续删除它。
猜你喜欢
  • 1970-01-01
  • 2020-07-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-25
  • 2013-01-22
相关资源
最近更新 更多