我只是敲了两下Ctrlc我的外壳,试图停止一个需要很长时间才能完成的进程。
^C
被重复了两次,但这个过程一直在继续。
为什么Ctrlc不像平常那样退出进程?
答案1
进程可以选择:
- 忽略通常在按下时发送的 SIGINT 信号Ctrl-C(就像在 shell 中一样
trap '' INT
),或者有自己的处理程序决定不终止(或无法及时终止)。 - 告诉终端设备导致 SIGINT 发送到前台作业的字符是其他字符(例如
stty int '^K'
shell 中的 with ) - 告诉终端设备不要发送任何信号(就像
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
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
要检查当前intr
字符是什么或是否isig
为给定终端启用:
$ stty -a < /dev/pts/0
[...] intr = ^C [...] isig
(上面的intr
字符是^C
(通常由您的终端(模拟器)在按下CTRL-C并且输入信号未禁用时发送的字符。
$ stty -a < /dev/pts/1
[...] intr = ^K [...] -isig
(intr
字符 是^K
且isig
已被禁用/dev/pts/1
)。
为了完整起见,进程还可以通过其他两种方式来停止接收 SIGINT,尽管这不是您通常会看到的。
之上Ctrl+C,SIGINT 信号被发送到进程中的所有进程。终端的前台进程组。通常是 shell 将进程放置在进程组中(映射到 shell工作)并告诉终端设备哪一个是前景一。
现在一个流程可以:
离开其进程组。如果它移动到另一个进程组(除当前进程组之外的任何进程组)前景一),那么它将不再接收 SIGINT Ctrl-C(也不再接收其他键盘相关信号,如 SIGTSTP、SIGQUIT)。然而,如果它尝试从终端设备读取(也可能根据终端设备设置写入)(如后台进程所做的那样),它可能会被挂起。
举个例子:
perl -MPOSIX -e 'setpgid(0,getppid) or die "$!"; sleep 10'
不能被中断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
会有同样的效果。
ps
和perl
在前台进程组中启动,但在大多数 shell 上,ps
将是该组的领导者(如上面的输出所示,其中和ps
的 pgid是 的 pid ),因此可以启动自己的进程组。ps
perl
ps
perl
或者它可以更改前台进程组。基本上告诉 tty 设备将 SIGINT 发送到其他进程组Ctrl+C
perl -MPOSIX -e 'tcsetpgrp(0,getppid) or die$!; sleep 5'
在那里,
perl
保留在同一进程组中,但告诉终端设备前台进程组的 ID 与其父进程 ID 相同(请参阅上面的注释)。