我在C中编写了一个基本的unix shell,我想在shell中捕获Cntrl-C信号并将它们传递给前台进程,但不传递给后台进程。 shell本身应该继续运行(并且确实如此),并且后台进程应该忽略Cntrl-C并且只能被专门发送给它们的kill信号杀死,可能通过命令行" kill pid &#34 ;.但是,前台和后台进程都应该使用SIGCHLD触发处理程序。然而,现在,shell捕获了Cntrl-C信号,并且似乎正确地识别出没有前台进程将信号传递给,但后台进程仍然死亡。
我尝试将后台进程的组ID设置为其他内容,这解决了问题,但它会产生新问题。当我这样做时,我的信号处理程序在后台进程完成时不再捕获信号。
到目前为止,我已经查看了SIGINT的手册页,我已经阅读了大约20个SO答案,我已经尝试将孩子的组ID设置为与父母不同的东西(解决了这个问题,但是现在孩子不能再向父母发送SIGCHLD了,而且我在运行后台进程时检查了这个childid!=前台进程和foregroundProcess == 0。但是后台进程仍然被杀死。有任何想法吗?
我认为我的问题出在我的信号处理程序中,但不是很确定:
在main中:
struct sigaction sa;
sa.sa_handler = &handleSignal; /*passing function ref. to handler */
sa.sa_flags = SA_RESTART;
sigfillset(&sa.sa_mask); /*block all other signals while handling sigs */
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
handleSignal看起来像这样:
void handleSignal(int signal){
int childid;
switch (signal) {
/*if the signal came from a child*/
case SIGCHLD:
/*get the child's id and status*/
childid = waitpid(-1,&childStatus,0);
/*No action for foreground processes that exit w/status 0 */
/*otherwise show pid & showStatus */
if ((childid != foregroundProcess)){
printf("pid %i:",childid);
showStatus(childStatus);
fflush(stdout);
}
break;
/* if signal came from somewhere else, pass it to foreground child */
/* if one exists. */
default:
printf("Caught signal: %i and passing it", signal);
printf(" to child w/pid: %i\n\n:", foregroundProcess);
fflush(stdout);
/*If there is a child, send signal to it. */
if (foregroundProcess){
printf("trying to kill foreground.\n");
fflush(stdout);
kill(foregroundProcess, signal);
}
}
}
答案 0 :(得分:2)
找到了我自己的问题的答案。我已经尝试使用setpid(0,0);
更改后台子进程的组ID,这很有效,但创建了一个不同的问题。在那次电话会议之后,我不再接收来自父母的孩子的SIGCHLD信号。这是因为一旦孩子的过程组被改变,它基本上不再连接到父母以用于信令目的。这解决了后台进程从父进程捕获Cntrl-C(SIGINT)信号的问题(不希望的行为),但是在完成时阻止了后台进程发信号通知父进程。解决了一个问题,只能创造另一个问题。
相反,解决方案是检测孩子是否即将被创建为前景或后台进程,如果是背景,请告诉它忽略SIGINT信号:signal(SIGINT, SIG_IGN);
答案 1 :(得分:0)
由于它可能是tty上的第一个进程,因此内核在发送内核启动的SIGINT,SIGHUP或SIGQUIT时会向其所有子进程发送信号。有关详细信息,请参阅Terminate sudo python script when the terminal closes,以及跟踪/调试正在发生的事情的想法,以确保您做对了。
启动bg子进程忽略SIGINT(和SIGQUIT,也许是SIGHUP?)的另一种方法是避免内核将这些信号传递给shell。
我忘了,但我认为内核只将SIGINT传递给当前的前台进程。如果cat
或其他东西在前台运行,你的shell就不会得到它。所以你只需要担心shell在前台时会发生什么。 (尽管如此,我对此只有大约80%的肯定,如果shell(以及它的所有子代)总是收到SIGINT,这个想法是没用的。)
如果在用户编辑命令提示符时禁用tty的信号发送,则可以避免shell接收SIGINT。可能最好的方法是使用tcsetattr(3)
执行相当于stty -isig
的操作
// disable interrupts
struct termios term_settings;
tcgetattr(fd, &term_settings); // TODO: check errors
term_settings.c_lflag &= ~ISIG; // clear the interactive signals bit
tcsetattr(fd, TCSANOW, &term_settings);
// do the reverse (settings |= ISIG) after forking, before exec
如果你strace
,那么,您只会看到ioctl
系统调用,因为系统调用是在一起实现了termios库函数。
我想这会在子进程结束和wait()
返回以及shell禁用终端中断之间留下一个小时间窗口。
bash
跟踪何时在原始(用于行编辑)和熟(用于运行命令)之间交换终端的问题,但我认为&#39 ; s只是因为工作控制。 (^ z / fg)