16

I just hit Ctrlc twice at my shell in an attempt to halt a process that is taking a long time to finish.

^C was echoed twice, but the process just kept going.

Why didn't Ctrlc quit the process like it normally does?

jasonwryan
  • 73,126
themirror
  • 6,988
  • 4
    My solution for annoying programs that don't want to die is usually to suspend them with CTRL+Z then kill -9 % to kill it. Signal 9 cannot be ignored, nor can the suspend signal. The CTRL+Z keyboard sequence can be ignored in theory - but isn't in practice. – David Sainty Sep 26 '13 at 22:49
  • @DavidSainty The suspend signal can be ignored (or, at least not stopped on). Example: perl -E '$SIG{TSTP} = sub { say "ha ha" }; sleep 1 while 1'. You're probably thinking of SIGSTOP, which is a different signal. – derobert Sep 27 '13 at 15:50
  • Ah, right you are :) – David Sainty Sep 29 '13 at 11:24
  • I've noticed something. On my Ryzen 5 system ^C just doesn't work for fast moving output, but on the Intel system it works instantly. I also see loads of xterm performance issues on Ryzen 5 system - even with the same operating systems installed. – Owl May 24 '21 at 10:25

1 Answers1

19

Processes can choose to:

  • ignore the SIGINT signal usually sent upon pressing Ctrl-C (like with trap '' INT in a shell) or have their own handler for it that decides not to terminate (or fails to terminate in a timely fashion).
  • tell the terminal device that the character that causes a SIGINT to be sent to the foreground job is something else (like with stty int '^K' in a shell)
  • tell the terminal device not to send any signal (like with stty -isig in a shell).

Or, they can be uninterruptible, like when in a middle of a system call that can't be interrupted.

On Linux (with a relatively recent kernel), you can tell if a process is ignoring and/or handling SIGINT by looking at the output of

$ kill -l INT
2
$ grep Sig "/proc/$pid/status"
SigQ:   0/63858
SigPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000002
SigCgt: 0000000000000000

SIGINT is 2. The second bit of SigIgn above is 1, which mean SIGINT is ignored.

You can automate that with:

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

To check what the current intr character is or if isig is enabled for a given terminal:

$ stty -a < /dev/pts/0
[...] intr = ^C [...] isig

(above the intr character is ^C (the character usually sent by your terminal (emulator) when pressing CTRL-C and input signals are not disabled.

$ stty -a < /dev/pts/1
[...] intr = ^K [...] -isig

(intr character is ^K and isig is disabled for /dev/pts/1).

For completeness, there are two other ways a process may do something to stop receiving SIGINTs though that's not something you would typically see.

Upon Ctrl+C, the SIGINT signal is sent to all the processes in the foreground process group of the terminal. It's usually the shell that place processes in process groups (mapped to shell jobs) and tell the terminal device which is the foreground one.

Now a process could:

  • Leave its process group. If it moves to another process group (any process group but the one that is the foreground one), then it will no longer receive the SIGINT upon Ctrl-C (nor the other keyboard-related signals like SIGTSTP, SIGQUIT). It could however get suspended if it tried to read (possibly write as well depending on the terminal device settings) from the terminal device (as background processes do).

    As an example:

    perl -MPOSIX -e 'setpgid(0,getppid) or die "$!"; sleep 10'
    

    could not be interruptible with Ctrl-C. Above perl will try to join the process group whose ID is the same as its parent process ID. In general, there's no guarantee that there be such a process group with that id. But here, in the case of that perl command run on its own at the prompt of an interactive shell, the ppid will be the shell's process and the shell will typically have been started in its own process group.

    If the command is not already a process group leader (the leader of that foreground process group), then it starting a new process group would have the same effect.

    For instance, depending on the 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
    

    would have the same effect. ps and perl are started in the foreground process group, but on most shells, ps would be the leader of that group (as seen in the ps output above where the pgid of both ps and perl is the pid of ps), so perl can start its own process group.

  • Or it could change the foreground process group. Basically tell the tty device to send the SIGINT to some other process group upon Ctrl+C

    perl -MPOSIX -e 'tcsetpgrp(0,getppid) or die$!; sleep 5'
    

    There, perl remains in the same process group but instead is telling the terminal device that the foreground process group is the one whose ID is the same as its parent process ID (see note above about that).

  • 1
    A good example of an uninterruptible call is accessing a hardware device that doesn't respond. For example, if you try to use hdparm or smartctl on a defective hard disk that doesn't respond they will hang forever, and you can't kill them with CTRL+C. You can tell if a process is in uninterruptible sleep by looking at the stat column of ps aux or at the S column of top/htop - D means uninterruptible sleep. This isn't necessarily a bad thing though, it could just mean that the process is doing a lot of IO. – Martin von Wittich Sep 26 '13 at 21:56
  • Excellent answer! So, how to make it work? – Kirby Sep 12 '20 at 14:37