编写自己的 shell - 处理某些管道时代码挂起 - 用 C

发布于 2024-10-03 11:50:26 字数 6327 浏览 8 评论 0原文

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

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

  • ls -l >文件1
  • wc <文件1> file2

以下管道命令有效:

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

这不起作用:ls | grep file1 它显示正确的输出,并且永远不会结束,除非用户向它发送中断信号。 ls | grep 文件1 > 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

I'm currently writing my own shell as a project for a class, and have everything virtually working. My problem is with my pipes, sometimes they work, and sometimes, they just hang until I interrupt them. I've done research on this, and it seems that the function that is getting it's stdin written to isn't receiving an EOF from the first process; usually as I've learned the problem is that the pipe isn't being closed, but this isn't the case (to my knowledge) with my code.

All redirection works and any variation thereof:

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

The following piped commands work:

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

This doesn't work: ls | grep file1 it shows the correct output and never ends unless an interrupt signal is sent to it by the user. ls | grep file1 > file2 also does not work. It hangs without showing output, creates the file2, but never writes to it.

Anyway, I hope there's something I'm missing that someone else can notice; I've been at this for a while. Let me know if there's anymore code I can provide. The code I've posted below is the main file, nothing removed.

/*
 * 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

玩物 2024-10-10 11:50:26

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

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

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

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

The second child should not wait for the first child to exit - it should just start running straight away (it will block until some output is produced on the pipe by the first child), so remove that waitpid() executed by childb. Instead, the parent process should wait for both child processes (or perhaps just the second one). (Indeed, as noted by JeremyP, this waitpid() call is failing anyway, since childb is not the parent of child).

Your problem, though, is that the parent process is mainintaining open file descriptors to the pipe. Right before the comment // Wait for the child process to complete, if necessary, the parent process should close its pipe file descriptors:

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

The open file descriptor in the parent means that the child grep process never sees EOF, so it doesn't exit.

小女人ら 2024-10-10 11:50:26

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

您会同意 for 的条件

if(child_id == 0 || (childb_id == 0 && hasPipe))

仅对于两个子进程为 true,但在 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 命令在其输入关闭之前不会终止这一事实有关。可能存在某种死锁情况,阻止这种情况发生。您需要在调试器中运行它或进行一些日志记录以查看父进程挂起的位置。

编辑

咖啡馆的答案告诉了我们一切。

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

I don't know the answer but I have spotted one issue.

You'll agree that the condition for

if(child_id == 0 || (childb_id == 0 && hasPipe))

is true only for the two child processes, but inside the if statement block you have this:

    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);
    }

The waitpid() call is incorrect because it is called from the second child to wait for the first child. It's probably failing with ECHILD because the first child is not a child of the second child.

As for your real problem, I suspect it has to do with the fact that the grep command will not terminate until its input is closed. There might be some deadlock condition going on that stops that from happening. You need to run this in a debugger or put some logging in to see where the parent process is hanging.

Edit

caf's answer tells us everything.

I was assuming that the input to grep was being closed because ls will close its output when it terminates, but of course, the parent process also has grep's input file descriptor open. The version using head works properly because head -n 4 terminates after four lines regardless of whether its input file descriptor is closed or not.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文