【问题标题】:C shell, signal caught by parent still goes to child process.C shell,被父进程捕获的信号仍然进入子进程。
【发布时间】:2015-11-19 16:10:08
【问题描述】:

我正在用 C 编写一个基本的 unix shell,我想在 shell 中捕获 Cntrl-C 信号并将它们仅传递给前台进程,而不是后台进程。 shell 本身应该继续运行(确实如此),并且后台进程应该忽略 Cntrl-C 并且只被专门发送给它们的终止信号杀死,可能通过命令行“kill pid”。但是,前台和后台进程都应该使用 SIGCHLD 触发处理程序。然而,现在,shell 捕获了 Cntrl-C 信号,并且似乎正确地识别出没有将信号传递给的前台进程,但后台进程仍然死亡。

我尝试将后台进程的组 id 设置为其他值,这解决了问题,但它产生了一个新问题。当我这样做时,我的信号处理程序在后台进程完成时不再捕获信号。

到目前为止,我已经查看了 SIGINT 的手册页,我已经阅读了大约 20 个 SO 答案,我尝试将子项的组 id 设置为与父项不同的值(解决了问题,但现在child 不能再向 parent 发送 SIGCHLD),并且当我运行后台进程时,我已经检查了 childid != 前台进程和 foregroundProcess == 0 。但是后台进程仍然被杀死。有任何想法吗?

我认为我的问题出在信号处理程序的某个地方,但不太确定:

主要内容:

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

【问题讨论】:

  • 在不相关的说明中,如果您没有更改 stdout 的缓冲模式,只要您以换行符结束所有输出,您就不需要显式刷新它。 stdout 默认是行缓冲的。
  • 谢谢约阿希姆。由于 printf 用于调试目的,因此我在对缓冲区进行 fflush 时格外小心。我不希望某些实际到达的打印行不打印,因为同时另一个过程做了一些奇怪的事情。无论如何,所有这些 printf 都可能在最终产品中消失。

标签: c linux shell


【解决方案1】:

找到了我自己问题的答案。我已经尝试使用setpid(0,0); 更改后台子进程的组 ID,这很有效,但产生了不同的问题。在那次通话之后,我不再从父母的孩子那里捕捉到 SIGCHLD 信号。这是因为一旦子进程组发生变化,它本质上就不再连接到父进程以用于信令目的。这解决了后台进程从父进程捕获 Cntrl-C (SIGINT) 信号的问题(不良行为),但阻止了后台进程在完成时向父进程发出信号。解决了一个问题只是为了创造另一个问题。

相反,解决方案是检测一个子进程是要创建为前台还是后台进程,如果是后台,则告诉它忽略 SIGINT 信号:signal(SIGINT, SIG_IGN);

【讨论】:

  • 我认为像 Bash 这样的交互式 shell 通过在用户编辑提示时将 tty 置于原始模式来处理它。 stackoverflow.com/questions/31907212/…。因此,如果您愿意,您仍然可以kill -INT 后台进程。如果你 fg 一个后台进程,它仍然可以被 ^C-ed。对于不会实现作业控制(bg / fg)或花哨的行编辑的玩具外壳,您的方法听起来很合理。
  • 这就是我正在构建的内容。一个基本的、自己编写的 shell,作为类的一部分没有太多的特性。谢谢你的信息。我需要做更多的研究。
【解决方案2】:

因为它可能是 tty 上的第一个进程,所以当内核发送内核发起的 SIGINT、SIGHUP 或 SIGQUIT 时,内核会将信号发送给它的所有子进程。请参阅Terminate sudo python script when the terminal closes 了解更多详细信息,以及跟踪/调试正在发生的事情以确保您做对的想法。


忽略 SIGINT(和 SIGQUIT,也许还有 SIGHUP?)而启动 bg 子进程的另一种方法是避免让内核将这些信号传递给 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 跟踪何时在原始(用于行编辑)和熟化(用于运行命令)之间交换终端的情况,但我认为这只是因为工作控制。 (^z / fg)


【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多