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

发布于 2024-07-21 19:42:55 字数 2367 浏览 4 评论 0原文

这个上一个问题中,我发布了我自己的大部分内容外壳代码。 我的下一步是实现前台和后台进程执行,并正确等待它们终止,这样它们就不会保持“僵尸”状态。

在添加在后台运行它们的可能性之前,所有进程都在前台运行。 为此,我只是在使用 execvp() 执行任何进程后调用 wait(NULL)。 现在,我检查“&” 字符作为最后一个参数,如果它存在,则通过不调用 wait(NULL) 在后台运行该进程,并且该进程可以在后台愉快地运行,我将返回到我的 shell。

这一切都工作正常(我认为),现在的问题是,我还需要以某种方式调用 wait() (或 waitpid() ?),以便后台进程不会保持“僵尸”状态。 这是我的问题,我不知道该怎么做...

我相信我必须处理 SIGCHLD 并在那里做一些事情,但我还没有完全理解何时发送 SIGCHLD 信号,因为我还尝试添加 wait(NULL )到 childSignalHandler() 但它不起作用,因为只要我在后台执行一个进程,就会调用 childSignalHandler() 函数,因此会出现 wait(NULL),这意味着我无法对 shell 执行任何操作,直到“后台”进程完成。 由于信号处理程序中的等待,它不再在后台运行。

在这一切中我缺少什么?

最后一件事,作为本练习的一部分,我还需要打印进程状态的更改,例如进程终止。 因此,对此的任何见解也非常值得赞赏。

这是我目前的完整代码:

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

In this previous question I posted most of my own shell code. 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(). 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.

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". 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. 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;
}

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

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

发布评论

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

评论(4

吻安 2024-07-28 19:42:55

waitpid() 有多种选项可以帮助您(引自 POSIX 标准):

W继续

waitpid() 函数应报告 pid 指定的任何继续子进程的状态,该子进程的状态自作业控制停止后继续运行以来尚未报告。

WNOHANG

如果 pid 指定的子进程之一的状态不能立即可用,则 waitpid() 函数不应挂起调用线程的执行。

特别是,WNOHANG 将允许您查看是否有任何尸体可供收集,而不会导致您的进程阻塞等待尸体。

如果调用进程设置了 SA_NOCLDWAIT 或将 SIGCHLD 设置为 SIG_IGN,并且该进程没有未等待的已转换为僵尸进程的子进程,则调用线程应阻塞,直到包含调用线程的进程的所有子进程终止,并且 wait() 和 waitpid() 将失败并将 errno 设置为 [ECHILD]。

您可能不想忽略 SIGCHLD 等,并且您的信号处理程序可能应该设置一个标志来告诉您的主循环“哎呀;有死孩子 - 去收集尸体!”。

SIGCONT 和 SIGSTOP 信号也与您相关 - 它们分别用于重新启动和停止子进程(在这种情况下,无论如何)。

我建议您查看 Rochkind 的书或 Stevens 的书 - 他们详细介绍了这些问题。

There are various options to waitpid() to help you (quotes from the POSIX standard):

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.

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.

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.

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].

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!".

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).

I'd recommend looking at Rochkind's book or Stevens' book - they cover these issues in detail.

窝囊感情。 2024-07-28 19:42:55

这应该可以帮助您开始。 主要区别是我摆脱了子处理程序并在主循环中添加了 waitpid 并提供了一些反馈。 经过测试和工作,但显然需要更多的 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;
}

编辑:使用 WNOHANG 使用 waitpid() 添加信号处理并不困难。 就像将 waitpid() 内容从循环顶部移动到信号处理程序一样简单。 不过,您应该注意两件事:

首先,即使是“前台”进程也会发送 SIGCHLD。 由于只能有一个前台进程,如果您想对前台与后台进程进行特殊处理,您可以简单地将前台 pid(父级从 fork() 返回的值)存储在信号处理程序可见的变量中。背景。

其次,您当前正在标准输入上执行阻塞 I/O(主循环顶部的 read())。 当 SIGCHLD 发生时,您极有可能在 read() 上被阻塞,从而导致系统调用中断。 根据操作系统的不同,它可能会自动重新启动系统调用,或者可能会发送您必须处理的信号。

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. Tested and working, but obviously needs more 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. It's as simple as moving the waitpid() stuff from the top of the loop into the signal handler. You should be aware of two things, though:

First, even "foreground" processes will send 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.

Second, you're currently doing blocking I/O on standard input (the read() at main loop top). You are extremely likely to be blocked on read() when SIGCHLD occurs, resulting in an interrupted system call. Depending on OS it may restart the system call automatically, or it may send a signal that you must handle.

望喜 2024-07-28 19:42:55

您可以使用:

if(!background)
    pause();

这样,进程会阻塞,直到收到 SIGCHLD 信号,并且信号处理程序将执行等待操作。

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.

梦忆晨望 2024-07-28 19:42:55

我没有使用全局变量,而是想到了一个不同的解决方案:

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

    waitpid(pid, NULL, 0);

    signal(SIGCHLD, childSignalHandler);
}

如果我正在运行前台进程,则“删除”SIGCHLD 的处理程序,这样它就不会被调用。 然后,在 waitpid() 之后,再次设置处理程序。 这样,只会处理后台进程。

您认为这个解决方案有什么问题吗?

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. Then, after waitpid(), set the handler again. This way, only the background processes will be handled.

Do you think there's anything wrong with this solution?

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