防止SIGINT传播到父进程


10

考虑到以下情况:父子程序(可以是C ++程序或Shell脚本)执行子Shell脚本,当我们在执行子Shell脚本时按Control + C(或配置为INTR的任何字符)时, SIGINT发送到前台进程组中的所有进程。这包括父进程。

资料来源:POSIX.1-2008 XBD第11.1.9节

有没有办法覆盖此默认行为?CHILD进程独自处理信号而没有传播给父进程吗?

参考:堆栈溢出后-子进程中断时父进程未完成(TRAP INT)

Answers:


11

(灵感来自吉尔斯的答案)

ISIG设置了标志的情况下,没有父Child脚本就获取脚本的唯一方法是使其位于自己的进程组中。这可以通过选件完成。SIGINTSIGINTset -m

如果您-mChild外壳程序脚本中打开该选项,它将在不交互的情况下执行作业控制。这将导致它在单独的进程组中运行内容,从而阻止父级SIGINTINTR读取字符时收到。

这是-m选项POSIX描述

-m如果实现支持用户可移植性实用程序选项,则应支持此选项。所有作业应在各自的过程组中运行。在外壳程序在完成后台作业之后发出提示之前,应立即将报告后台作业退出状态的消息写入标准错误。如果前台作业停止,则外壳程序应为此写入一条消息,指示标准错误,其格式如作业实用程序所述。另外,如果作业改变了状态而不是退出(例如,如果它停止输入或输出或被SIGSTOP信号停止),则外壳程序应在写入下一个提示之前立即写入一条类似的消息。默认情况下,此选项对于交互式Shell启用。

-m选项与相似-i,但是它几乎没有改变shell的行为-i

例:

  • Parent脚本:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    ./Child
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
    
  • Child脚本:

    #!/bin/sh -m
    #         ^^        
    # notice the -m option above!
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"
    

当您在等待输入时按Control+ C时,会发生以下情况Child

$ ./Parent
PARENT: pid=12233
PARENT: Spawning child...
CHILD: pid=12234
CHILD: hit enter to exit
^CCHILD: caught SIGINT; exiting
PARENT: child returned
PARENT: exiting normally

请注意,SIGINT永远不会执行父级的处理程序。

另外,如果您想修改Parent而不是Child,可以这样做:

  • Parent脚本:

    #!/bin/sh
    
    trap 'echo "PARENT: caught SIGINT; exiting"; exit 1' INT
    
    echo "PARENT: pid=$$"
    echo "PARENT: Spawning child..."
    sh -m ./Child  # or 'sh -m -c ./Child' if Child isn't a shell script
    echo "PARENT: child returned"
    echo "PARENT: exiting normally"
    
  • Child脚本(正常,没有必要-m):

    #!/bin/sh
    
    trap 'echo "CHILD: caught SIGINT; exiting"; exit 1' INT
    
    echo "CHILD: pid=$$"
    echo "CHILD: hit enter to exit"
    read foo
    echo "CHILD: exiting normally"
    

替代思路

  1. 修改前台进程组中的其他进程,以忽略SIGINT的持续时间Child。这不能解决您的问题,但是可以为您提供所需的东西。
  2. 修改Child为:
    1. 使用stty -g备份当前的终端设置。
    2. 运行stty -isig不生成与信号INTRQUIT以及SUSP文字。
    3. 在此背景下,读出的终端输入和自己发送的信号适当地(例如,运行kill -QUIT 0Control+ \被读出,kill -INT $$Control+ C被读取)。这并非无关紧要,并且如果Child脚本或其运行的任何内容都是交互式的,则可能无法使其顺利运行。
    4. 退出之前恢复终端设置(最好从上的陷阱EXIT)。
  3. 与#2相同,除了运行以外stty -isig,请等用户Enter击键或其他非特殊键再杀死Child
  4. setpgid用C,Python,Perl等编写自己的实用程序,可用于调用setpgid()。这是一个粗略的C实现:

    #define _XOPEN_SOURCE 700
    #include <unistd.h>
    #include <signal.h>
    
    int
    main(int argc, char *argv[])
    {
        // todo: add error checking
        void (*backup)(int);
        setpgid(0, 0);
        backup = signal(SIGTTOU, SIG_IGN);
        tcsetpgrp(0, getpid());
        signal(SIGTTOU, backup);
        execvp(argv[1], argv + 1);
        return 1;
    }
    

    来自的示例用法Child

    #!/bin/sh
    
    [ "${DID_SETPGID}" = true ] || {
        # restart self after calling setpgid(0, 0)
        exec env DID_SETPGID=true setpgid "$0" "$@"
        # exec failed if control reached this point
        exit 1
    }
    unset DID_SETPGID
    
    # do stuff here
    

4

正如您从POSIX引用的那章所解释的那样,SIGINT被发送到整个前台进程组。因此,为避免杀死程序父程序,请安排它在自己的进程组中运行。

Shell setpgrp不能通过内置或语法结构进行访问,但是有一种间接的方法来实现它,即交互运行Shell。(感谢StéphaneGimenez的把戏。)

ksh -ic '
  … the part that needs to be interruptible without bothering the parent …
'

聪明的想法是+1,不过请注意,当它是交互式shell时,shell的行为会发生变化(至少POSIX shell会改变;我对的细节不熟悉ksh)。示例:${ENV}源,外壳程序在遇到错误时不会立即退出SIGQUIT并被SIGTERM忽略。
理查德·汉森

1

好吧,从您引用的堆栈溢出问题中可以清楚地看出,需要配置父对象来处理信号。

其次,POSIX参考明确指出“如果设置了ISIG,则在处理时应丢弃INTR字符”。

这是两个选择。第三是在自己的进程组中运行子进程。


感谢您的答复。我还需要找出一种使用户退出脚本的方法。有没有办法设置ISIG,并同时允许某些CONTRL组合键(可能是CTRL-Q)退出shell脚本,而无需将信号发送给父级?另外,您能告诉我如何将孩子与父母分开在另一个流程组中吗?
Guddu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.