如何正确地等待前台/后台进程在用C我自己的壳呢?
-
23-08-2019 - |
题
在这个的前一个问题我最贴我自己的shell代码。我的下一步是实现前台和后台流程执行和适当等待他们终止让他们不留称为“僵尸”。
添加到在后台运行它们的可能性之前,所有过程被在前台运行。执行与execvp任何处理结束后,对于这一点,我简单地称为等待(NULL)()。现在,我检查了“&”字符作为最后一个参数,如果它的存在,运行通过不调用wait(NULL),这个过程可以在后台开心地跑,我将我回到了我的壳在后台进程。
这是所有工作正常(我认为),现在的问题是,我还需要调用wait()(或者waitpid函数()?)不知何故,使后台进程不留“僵尸”。这是我的问题,我不知道该怎么做......
我相信我有处理SIGCHLD,做一些事情,但我还没有完全理解当发出SIGCHLD信号,因为我想也增加等待(NULL)至childSignalHandler(),但它并没有因为工作当我在后台执行的过程中,childSignalHandler()函数被调用,因此,等待(NULL),这意味着我不能用我的壳做任何事情,直到“背景”的过程完成。这是不是在后台,因为在信号处理程序中等待的运行了。
那我在这一切失踪了?
最后一两件事,这个练习我还需要打印的过程中地位的变化,比如终止进程的一部分。因此,在任何的见解也确实理解。
这是我此刻的全码:
#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;
}
解决方案
有各种选项waitpid()
帮你(从POSIX标准引号):
WCONTINUED
在waitpid函数()函数应报告由pid,其地位,因为它从作业控制停止持续尚未见报道指定的任何持续的子进程的状态。
WNOHANG
如果状态不是立即可用于由pid指定的子进程的一项所述的waitpid函数()函数应不暂停调用线程的执行。
在特别WNOHANG将让你看看是否有任何尸体收集,而不会引起你的进程阻塞,等待尸体。
如果调用进程具有SA_NOCLDWAIT设置或具有SIGCHLD设置为SIG_IGN,并且该过程没有unwaited-对于转化到僵尸程序中的儿童,则调用线程应,直到所有的处理的含调用线程的孩子的方框终止,并等待()和waitpid函数()失败,将errno设置为[ECHILD]。
您可能不希望被忽略SIGCHLD等,以及信号处理程序可能应该设置一个标志,告诉主循环“哎呀,有死了的孩子 - 去收集尸体!”
。在SIGCONT和SIGSTOP信号也将是相关的,以你 - 它们被用来重新启动和停止子进程,分别为(在此上下文中,在任何速率)
我建议在看Rochkind的书或史蒂文斯的书 - 他们详细地讨论这些问题。
。其他提示
这应该让你开始。主要的区别是,我摆脱了孩子处理程序,并与一些反馈的主循环加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()
主循环顶部)。您是极有可能的read()
被阻塞时发生SIGCHLD,导致中断的系统调用。根据OS它可能会自动重新启动系统调用,或者它可以发送一个信号,你必须处理。
您可以使用:
if(!background)
pause();
此方式,该处理块,直到它接收到SIGCHLD
信号,并且将信号处理程序将执行所述等待东西。
代替使用全局变量的,我想了不同的解决方案的:
if(!background) {
signal(SIGCHLD, NULL);
waitpid(pid, NULL, 0);
signal(SIGCHLD, childSignalHandler);
}
如果我正在前台进程的SIGCHLD“删除”的处理程序,以便它不会被调用。然后,waitpid函数()之后,再次设置处理。这种方式,只有后台进程将被处理。
你认为有什么不对这个解决方案?