8

一个问题中,我发布了我自己的大部分 shell 代码。我的下一步是实现前台和后台进程执行并适当地等待它们终止,这样它们就不会像“僵尸”一样停留。

在添加在后台运行它们的可能性之前,所有进程都在前台运行。为此,我在使用 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;
}
4

4 回答 4

5

有多种选择可以waitpid()帮助您(来自 POSIX 标准的引用):

W续

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

万航

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

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

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

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

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

我建议看一下 Rochkind 的书或 Stevens 的书——它们详细介绍了这些问题。

于 2009-05-22T23:53:57.137 回答
3

这应该让你开始。主要区别在于我摆脱了子处理程序并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;
}

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

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

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

于 2009-05-23T00:12:54.400 回答
2

您可以使用:

if(!background)
    pause();

这样,进程会阻塞直到它接收到SIGCHLD信号,而信号处理程序将做等待的事情。

于 2011-05-07T09:58:28.897 回答
0

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

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

    waitpid(pid, NULL, 0);

    signal(SIGCHLD, childSignalHandler);
}

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

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

于 2009-05-24T16:24:38.590 回答