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

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

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

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

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

我在这一切中缺少什么?

最后一件事,这个练习的一部分我还需要打印进程状态的变化,比如进程终止。 因此,对此的任何见解也非常感激。

这是我目前的完整代码:

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

waitpid()有各种选项来帮助你(来自POSIX标准的引用):

WCONTINUED

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

WNOHANG

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

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

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

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

SIGCONT和SIGSTOP信号也将与您相关 – 它们分别用于重新启动和停止子进程(在此上下文中,无论如何)。

我建议看一下Rochkind的书或史蒂文斯的书 – 他们会详细介绍这些问题。

这应该让你开始。 主要区别在于我摆脱了子处理程序并在主循环中添加了waitpid并提供了一些反馈。 经过测试和工作,但显然需要更多的TLC。

 #include  #include  #include  #include  #include  #include  #include  #include  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()主循环顶部的read() )。 当SIGCHLD发生时,极有可能在read()上被阻塞,导致系统调用中断。 根据操作系统,它可能会自动重新启动系统调用,或者它可能会发送您必须处理的信号。

你可以使用:

 if(!background) pause(); 

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

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

 if(!background) { signal(SIGCHLD, NULL); waitpid(pid, NULL, 0); signal(SIGCHLD, childSignalHandler); } 

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

你觉得这个解决方案有什么问题吗?

Interesting Posts