为什么 Ctrl-C 不起作用?

the*_*ror 11 shell keyboard-shortcuts

我只是Ctrlc在我的外壳上敲了两下,试图停止一个需要很长时间才能完成的进程。

^C 被回响了两次,但这个过程一直在继续。

为什么Ctrlc不像往常那样退出进程?

Sté*_*las 17

进程可以选择:

  • 忽略通常在按下时发送的 SIGINT 信号Ctrl-C(就像trap '' INT在 shell 中一样),或者有自己的处理程序来决定不终止(或未能及时终止)。
  • 告诉终端设备导致 SIGINT 被发送到前台作业的字符是别的东西(比如stty int '^K'在 shell 中)
  • 告诉终端设备不要发送任何信号(就像stty -isig在 shell 中一样)。

或者,它们可以是不可中断的,例如在无法中断的系统调用过程中。

在 Linux(具有相对较新的内核)上,您可以通过查看以下输出来判断进程是否忽略和/或处理SIGINT

$ kill -l INT
2
$ grep Sig "/proc/$pid/status"
SigQ:   0/63858
SigPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000002
SigCgt: 0000000000000000
Run Code Online (Sandbox Code Playgroud)

SIGINT 是 2。上面 SigIgn 的第二位是 1,这意味着 SIGINT 被忽略。

您可以通过以下方式自动化:

$ SIG=$(kill -l INT) perl -lane 'print $1 if $F[0] =~ /^Sig(...):/ && 
    $F[1] & (1<<($ENV{SIG}-1))' < "/proc/$pid/status"
Ign
Run Code Online (Sandbox Code Playgroud)

要检查当前intr字符是什么或是否isig为给定终端启用:

$ stty -a < /dev/pts/0
[...] intr = ^C [...] isig
Run Code Online (Sandbox Code Playgroud)

intr字符上方是^C(当按下CTRL-C和输入信号未禁用时,通常由您的终端(模拟器)发送的字符。

$ stty -a < /dev/pts/1
[...] intr = ^K [...] -isig
Run Code Online (Sandbox Code Playgroud)

intr字符是^K并且isig被禁用/dev/pts/1)。

为了完整起见,进程可以通过另外两种方式停止接收 SIGINT,尽管这不是您通常会看到的。

之后Ctrl+C,SIGINT 信号被发送到终端前台进程组中的所有进程。通常是外壳将进程放入进程组(映射到外壳作业)并告诉终端设备哪个是前台设备。

现在一个过程可以:

  • 离开它的进程组。如果它移动到另一个进程组(除了前台进程组之外的任何进程组),那么它将不再收到 SIGINT Ctrl-C(也不会收到其他与键盘相关的信号,如 SIGTSTP、SIGQUIT)。但是,如果它试图从终端设备(如后台进程所做的那样)读取(也可能写入,取决于终端设备设置),它可能会被挂起。

    举个例子:

    perl -MPOSIX -e 'setpgid(0,getppid) or die "$!"; sleep 10'
    
    Run Code Online (Sandbox Code Playgroud)

    不能被中断Ctrl-C。以上perl将尝试加入ID与其父进程ID相同的进程组。通常,不能保证存在具有该 ID 的进程组。但在这里,如果该perl命令在交互式 shell 的提示下自行运行,则 ppid 将是 shell 的进程,并且 shell 通常已在其自己的进程组中启动。

    如果该命令还不是进程组的领导者(该前台进程组的领导者),那么它启动一个新的进程组将具有相同的效果。

    例如,根据外壳,

    $ ps -j >&2 | perl -MPOSIX -e 'setpgid(0,0) or die "$!"; sleep 10'
      PID  PGID   SID TTY          TIME CMD
    21435 21435 21435 pts/12   00:00:00 zsh
    21441 21441 21435 pts/12   00:00:00 ps
    21442 21441 21435 pts/12   00:00:00 perl
    
    Run Code Online (Sandbox Code Playgroud)

    会有同样的效果。psperl在前台进程组中启动,但在大多数 shell 中,ps将是该组的领导者(如ps上面的输出所示,其中两者的 pgidpsperl是 的 pid ps),因此perl可以启动自己的进程组。

  • 或者它可以更改前台进程组。基本上告诉 tty 设备将 SIGINT 发送到某个其他进程组Ctrl+C

    perl -MPOSIX -e 'tcsetpgrp(0,getppid) or die$!; sleep 5'
    
    Run Code Online (Sandbox Code Playgroud)

    在那里,perl保留在同一个进程组中,而是告诉终端设备前台进程组是其 ID 与其父进程 ID 相同的进程组(请参阅上面的注释)。