C shell,父母捕获的信号仍然进入子进程。

时间:2015-11-19 16:10:08

标签: c linux shell

我在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);
          }     
      }
    }

2 个答案:

答案 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禁用终端中断之间留下一个小时间窗口。

IIRC,我读到了bash跟踪何时在原始(用于行编辑)和熟(用于运行命令)之间交换终端的问题,但我认为&#39 ; s只是因为工作控制。 (^ z / fg)