简体   繁体   English

如何在C中正确等待我自己的shell中的前台/后台进程?

[英]How to properly wait for foreground/background processes in my own shell in C?

In this previous question I posted most of my own shell code. 之前的问题,我最贴出我自己的shell代码。 My next step is to implement foreground and background process execution and properly wait for them to terminate so they don't stay as "zombies". 我的下一步是实现前台和后台进程执行并正确等待它们终止,这样它们就不会像“僵尸”那样停留。

Before adding the possibility to run them in the background, all processes were running in the foreground. 在添加在后台运行它们的可能性之前,所有进程都在前台运行。 And for that, I simply called wait(NULL) after executing any process with execvp(). 为此,我在使用execvp()执行任何进程后简单地调用wait(NULL)。 Now, I check for the '&' character as the last argument and if it's there, run the process in the background by not calling wait(NULL) and the process can run happily in the background will I'm returned to my shell. 现在,我检查'&'字符作为最后一个参数,如果它在那里,通过不调用wait(NULL)在后台运行该进程,并且进程可以在后台运行愉快我将返回到我的shell。

This is all working properly (I think), the problem now, is that I also need to call wait() (or waitpid() ?) somehow so that the background process doesn't remain "zombie". 这一切都正常(我认为),现在的问题是,我还需要以某种方式调用wait()(或waitpid()?),以便后台进程不会保持“僵尸”。 That's my problem, I'm not sure how to do that... 这是我的问题,我不知道该怎么做......

I believe I have to handle SIGCHLD and do something there, but I have yet to fully understand when the SIGCHLD signal is sent because I tried to also add wait(NULL) to childSignalHandler() but it didn't work because as soon as I executed a process in the background, the childSignalHandler() function was called and consequently, the wait(NULL), meaning I couldn't do anything with my shell until the "background" process finished. 我相信我必须处理SIGCHLD并在那里做一些事情,但是我还没有完全理解何时发送SIGCHLD信号,因为我还试图将wait(NULL)添加到childSignalHandler()但是它不起作用因为我一旦在后台执行了一个进程,调用了childSignalHandler()函数,因此等待(NULL),这意味着在“后台”进程完成之前我无法对我的shell做任何事情。 Which wasn't running on the background anymore because of the wait in the signal handler. 由于信号处理程序中的等待,它不再在后台运行。

What am I missing in all this? 我在这一切中缺少什么?

One last thing, part of this exercise I also need to print the changes of the processes status, like process termination. 最后一件事,这个练习的一部分我还需要打印进程状态的变化,比如进程终止。 So, any insight on that is also really appreciated. 因此,对此的任何见解也非常感激。

This is my full code at the moment: 这是我目前的完整代码:

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

#include "data.h" // Boolean typedef and true/false macros


void childSignalHandler(int signum) {
    //
}

int main(int argc, char **argv) {
    char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
    bool background;
    ssize_t rBytes;
    int aCount;
    pid_t pid;

    //signal(SIGINT, SIG_IGN);

    signal(SIGCHLD, childSignalHandler);

    while(1) {
        write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
        rBytes = read(0, bBuffer, BUFSIZ-1);

        if(rBytes == -1) {
            perror("read");
            exit(1);
        }

        bBuffer[rBytes-1] = '\0';

        if(!strcasecmp(bBuffer, "exit")) {
            exit(0);
        }

        sPtr = bBuffer;
        aCount = 0;

        do {
            aPtr = strsep(&sPtr, " ");
            pArgs[aCount++] = aPtr;
        } while(aPtr);

        background = FALSE;

        if(!strcmp(pArgs[aCount-2], "&")) {
            pArgs[aCount-2] = NULL;
            background = TRUE;
        }

        if(strlen(pArgs[0]) > 1) {
            pid = fork();

            if(pid == -1) {
                perror("fork");
                exit(1);
            }

            if(pid == 0) {
                execvp(pArgs[0], pArgs);
                exit(0);
            }

            if(!background) {
                wait(NULL);
            }
        }
    }

    return 0;
}

There are various options to waitpid() to help you (quotes from the POSIX standard): waitpid()有多种选项可以帮助您(POSIX标准引用):

WCONTINUED WCONTINUED

The waitpid() function shall report the status of any continued child process specified by pid whose status has not been reported since it continued from a job control stop. waitpid()函数应报告由pid指定的任何继续子进程的状态,该进程的状态自作业控制停止后继续报告。

WNOHANG WNOHANG

The waitpid() function shall not suspend execution of the calling thread if status is not immediately available for one of the child processes specified by pid. 如果状态不能立即用于由pid指定的其中一个子进程,则waitpid()函数不应挂起调用线程的执行。

In particular, WNOHANG will allow you to see whether there are any corpses to collect without causing your process to block waiting for a corpse. 特别是,WNOHANG将允许您查看是否有任何尸体可以收集而不会导致您的进程阻止等待尸体。

If the calling process has SA_NOCLDWAIT set or has SIGCHLD set to SIG_IGN, and the process has no unwaited-for children that were transformed into zombie processes, the calling thread shall block until all of the children of the process containing the calling thread terminate, and wait() and waitpid() shall fail and set errno to [ECHILD]. 如果调用进程设置了SA_NOCLDWAIT或SIGCHLD设置为SIG_IGN,并且进程没有被转换为僵尸进程的未被等待的子进程,则调用线程将阻塞,直到包含调用线程的进程的所有子进程终止,并且wait()和waitpid()将失败并将errno设置为[ECHILD]。

You probably don't want to be ignoring SIGCHLD, etc, and your signal handler should probably be setting a flag to tell your main loop "Oops; there's dead child - go collect that corpse!". 你可能不想忽略SIGCHLD等,你的信号处理程序应该设置一个标志来告诉你的主循环“糟糕;有死孩子 - 去收集那个尸体!”。

The SIGCONT and SIGSTOP signals will also be of relevance to you - they are used to restart and stop a child process, respectively (in this context, at any rate). SIGCONT和SIGSTOP信号也将与您相关 - 它们分别用于重新启动和停止子进程(在此上下文中,无论如何)。

I'd recommend looking at Rochkind's book or Stevens' book - they cover these issues in detail. 我建议看看Rochkind的书或史蒂文斯的书 - 他们详细介绍了这些问题。

You may use: 你可以使用:

if(!background)
    pause();

This way, the process blocks until it receives the SIGCHLD signal, and the signal handler will do the wait stuff. 这样,进程就会阻塞,直到它收到SIGCHLD信号,并且信号处理程序将执行等待。

This should get you started. 这应该让你开始。 The major difference is that I got rid of the child handler and added waitpid in the main loop with some feedback. 主要区别在于我摆脱了子处理程序并在主循环中添加了waitpid并提供了一些反馈。 Tested and working, but obviously needs more TLC. 测试和工作,但显然需要更多的TLC。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <sys/types.h>

int main(int argc, char **argv) {
        char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
        int background;
        ssize_t rBytes;
        int aCount;
        pid_t pid;
        int status;
        while(1) {
                pid = waitpid(-1, &status, WNOHANG);
                if (pid > 0)
                        printf("waitpid reaped child pid %d\n", pid);
                write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
                rBytes = read(0, bBuffer, BUFSIZ-1);
                if(rBytes == -1) {
                        perror("read");
                        exit(1);
                }
                bBuffer[rBytes-1] = '\0';
                if(!strcasecmp(bBuffer, "exit")) 
                        exit(0);
                sPtr = bBuffer;
                aCount = 0;
                do {
                        aPtr = strsep(&sPtr, " ");
                        pArgs[aCount++] = aPtr;
                } while(aPtr);
                background = (strcmp(pArgs[aCount-2], "&") == 0);
                if (background)
                        pArgs[aCount-2] = NULL;
                if (strlen(pArgs[0]) > 1) {
                        pid = fork();
                        if (pid == -1) {
                                perror("fork");
                                exit(1);
                        } else if (pid == 0) {
                                execvp(pArgs[0], pArgs);
                                exit(1);
                        } else if (!background) {
                                pid = waitpid(pid, &status, 0);
                                if (pid > 0)
                                        printf("waitpid reaped child pid %d\n", pid);
                        }
                }
        }
        return 0;
}

EDIT: Adding back in signal handling isn't difficult with waitpid() using WNOHANG. 编辑:使用WNOHANG对waitpid()添加信号处理并不困难。 It's as simple as moving the waitpid() stuff from the top of the loop into the signal handler. 就像将waitpid()东西从循环顶部移动到信号处理程序一样简单。 You should be aware of two things, though: 但是你应该知道两件事:

First, even "foreground" processes will send SIGCHLD. 首先,即使是“前台”流程也会发送SIGCHLD。 Since there can be only one foreground process you can simply store the foreground pid (parent's return value from fork() ) in a variable visible to the signal handler if you want to do special handling of foreground vs. background. 由于只能有一个前台进程,因此如果要对前景与背景进行特殊处理,则可以将前景pid(父对象的fork()返回值)存储在信号处理程序可见的变量中。

Second, you're currently doing blocking I/O on standard input (the read() at main loop top). 其次,您当前正在标准输入上阻塞I / O read()主循环顶部的read() )。 You are extremely likely to be blocked on read() when SIGCHLD occurs, resulting in an interrupted system call. 当SIGCHLD发生时,极有可能在read()上被阻塞,导致系统调用中断。 Depending on OS it may restart the system call automatically, or it may send a signal that you must handle. 根据操作系统,它可能会自动重新启动系统调用,或者它可能会发送您必须处理的信号。

Instead of using a global variable, I thought of a different solution: 我没有使用全局变量,而是考虑了一个不同的解决方案:

if(!background) {
    signal(SIGCHLD, NULL);

    waitpid(pid, NULL, 0);

    signal(SIGCHLD, childSignalHandler);
}

If I'm running a foreground process "delete" the handler for SIGCHLD so it doesn't get called. 如果我正在运行前台进程“删除”SIGCHLD的处理程序,那么它就不会被调用。 Then, after waitpid(), set the handler again. 然后,在waitpid()之后,再次设置处理程序。 This way, only the background processes will be handled. 这样,只处理后台进程。

Do you think there's anything wrong with this solution? 你觉得这个解决方案有什么问题吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM