Tech Notes And Miscellaneous Thoughts
 

Introduction to Linux Signals 101

or

^C doesn’t kill processes, SIGINT does

On the LUV mailing list recently, the perils of tty handling when booting with “init=/bin/bash” came up, and somebody asked why running ‘ping’ (without remembering to run stty or openvt first) will result in you having to reboot.

i.e. “why doesn’t ^C work in emergency mode”?

I wrote a fairly detailed, but comprehensible to a novice, answer and a friend suggested i should have written it as a blog post – which was the point of me setting up a blog in the first place.

So here’s a slightly edited version of it:

> Couldn’t you just open another console, run “top” and find the PID for
> ping and kill it? Or ps aux |grep ping and kill it? Is a reboot the
> only way to kill a ping where ^C is not available?

there’s nothing special about ^C except that by long convention it is mapped to the interrupt signal aka intr aka SIGINT. So, ^C itself doesn’t kill a program, the fact that it generates an interrupt signal does.

If you’ve rebooted for emergency maintenance (e.g. “init=/bin/bash” on the LILO or GRUB command line) then all you get is ONE instance of /bin/bash. Nothing else is running and nothing else has been run (because you’ve overriden the usual init with /bin/bash). No setup has been done on the terminal, and no other virtual terminals have been opened. The emergency environment is minimal (and deliberately so – you asked for it, you got it). It’s up to you to do whatever is needed to set up the environment so that you can do the required maintenance/repair work.

So if you run ping *before* remembering to set up the terminal (‘stty sane’) then ^C doesn’t work because ^C doesn’t generate SIGINT so there’s no way of telling ping to quit, and if you haven’t opened another virtual terminal with openvt (aka “open” – it got renamed some time ago) then there is no other shell available to run top or ps or kill.

BTW, it’s not just ping. You’ll see the same problem in the same environment with ANY program that runs continuously until it gets a signal to terminate it. ping’s just the most obvious and common example.

Most programs, especially simple programs like ping, don’t have a ^C handler. They don’t need one. They have a signal handler which, amongst other things, handles SIGINT however it’s generated – it can be generated by pressing ^C in a normal tty environment, or by sending the program a SIGINT (e.g. “kill -2”).

That, BTW, is a core difference between a SIGTERM (just plain ‘kill’ or ‘kill -15’) and SIGKILL (‘kill -9’).

SIGTERM is usually handled by the program by setting up a signal handler for it, and it voluntarily terminates when it receives the signal (usually cleaning up, closing files, etc before it does so). If the program hasn’t set up a handler for SIGTERM then the default action is to just terminate. A program can set up a handler which does nothing on SIGTERM, which effectively just ignores the signal.

SIGKILL is handled by the kernel and it just kills the process immediately – it can not be blocked or intercepted by the program.

(By common convention, SIGHUP or ‘kill -1’ tells a long running daemon process to just reload it’s config files. It’s also generated when the tty that the program is running on is terminated – e.g. you log out or the modem hangs up or the ssh session dies, hence the name “HUP” aka “HANGUP”. That’s why you need to use something like nohup or screen if you want a program to continue running after you log out)

In a way, ‘kill’ is a bit of a misnomer. It’s actually a generic tool for sending signals to running processes, but the default action for most of those signals is to terminate the program. Here’s a list of the signals which can be sent.

$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL
 5) SIGTRAP	 6) SIGABRT	 7) SIGBUS	 8) SIGFPE
 9) SIGKILL	10) SIGUSR1	11) SIGSEGV	12) SIGUSR2
13) SIGPIPE	14) SIGALRM	15) SIGTERM	16) SIGSTKFLT
17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU
25) SIGXFSZ	26) SIGVTALRM	27) SIGPROF	28) SIGWINCH
29) SIGIO	30) SIGPWR	31) SIGSYS	34) SIGRTMIN
35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3	38) SIGRTMIN+4
39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12
47) SIGRTMIN+13	48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14
51) SIGRTMAX-13	52) SIGRTMAX-12	53) SIGRTMAX-11	54) SIGRTMAX-10
55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7	58) SIGRTMAX-6
59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

See the man pages for init(8), kill(1), and especially signal(7) for more info on this topic.

3 Comments

  1. Turns out I had reason to go into such an emergency mode tonight (I’m about to submit a bug report to mount — grarrr). And an stty sane didn’t work. stty -a showed that ^C was indeed mapped to intr.

  2. cas

    i’m guessing that mount(8) called the kernel to actually mount a filesystem, or perhaps query the kernel for a list of mounted filesystems….and the syscall never returned, perhaps because of hardware errors on the disk or disk controller.

    SIGINT won’t kill a process if it’s trapped in the kernel. it won’t even get the signal until the syscall returns.

    even SIGKILL (‘kill -9’) won’t kill a zombie process.

  3. Nope. The bug report about mount was the reason for me being in emergency mode (turns out you can’t remount / rw if there’s a syntax error with the / entry in /etc/fstab, and you can’t tell mount to ignore /etc/fstab).

    ctrl-c wasn’t sufficient to tell bash to cancel a line. Eg, go to bash shell, type “askjhaksjdhkj”, press ctrl-C, that should clear the line. No acknowledgement at all in my case.

Comments are closed.