Why Linux always output "^C" upon pressing of Ctrl+C?
Asked Answered
M

2

10

I have been studying signals in Linux. And I've done a test program to capture SIGINT.

#include <unistd.h>
#include <signal.h>
#include <iostream>
void signal_handler(int signal_no);
int main() {
  signal(SIGINT, signal_handler);
  for (int i = 0; i < 10; ++i) {
  std::cout << "I'm sleeping..." << std::endl;
  unsigned int one_ms = 1000;
  usleep(200* one_ms);
  }
  return 0;
}
void signal_handler(int signal_no) {
  if (signal_no == SIGINT)
    std::cout << "Oops, you pressed Ctrl+C!\n";
  return;
}

While the output looks like this:

I'm sleeping...
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
^COops, you pressed Ctrl+C!
I'm sleeping...
I'm sleeping...
I'm sleeping...

I understand that when pressing Ctrl+C, processes in foreground process group all receives a SIGINT(if no process chooses to ignore it).

So is it that the shell(bash) AND the instance of the above program both received the signal? Where does the "^C" before each "Oops" come from?

The OS is CentOS, and the shell is bash.

Merrow answered 26/5, 2012 at 11:58 Comment(0)
G
14

It is the terminal (driver) that intercepts the ^C and translates it to a signal sent to the attached process (which is the shell) stty intr ^B would instruct the terminal driver to intercept a ^B instead. It is also the terminal driver that echoes the ^C back to the terminal.

The shell is just a process that sits at the other end of the line, and receives it's stdin from your terminal via the terminal driver (such as /dev/ttyX), and it's stdout (and stderr) are also attached to the same tty.

Note that (if echoing is enabled) the terminal sends the keystrokes to both the process (group) and back to the terminal. The stty command is just wrapper around the ioctl()s for the tty driver for the processes "controlling" tty.

UPDATE: to demonstrate that the shell is not involved, I created the following small program. It should be executed by its parent shell via exec ./a.out (it appears an interactive shell will fork a daughter shell, anyway) The program sets the key that generates the SIGINTR to ^B, switches echo off, and than waits for input from stdin.

#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

int thesignum = 0;
void handler(int signum);

void handler(int signum)
{ thesignum = signum;}

#define THE_KEY 2 /* ^B */

int main(void)
{
int rc;
struct termios mytermios;

rc = tcgetattr(0 , &mytermios);
printf("tcgetattr=%d\n", rc );

mytermios.c_cc[VINTR] = THE_KEY; /* set intr to ^B */
mytermios.c_lflag &= ~ECHO ; /* Dont echo */
rc = tcsetattr(0 , TCSANOW, &mytermios);
printf("tcsetattr(intr,%d) =%d\n", THE_KEY, rc );

printf("Setting handler()\n" );
signal(SIGINT, handler);

printf("entering pause()\n... type something followed by ^%c\n", '@'+THE_KEY );
rc = pause();
printf("Rc=%d: %d(%s), signum=%d\n", rc, errno , strerror(errno), thesignum );

// mytermios.c_cc[VINTR] = 3; /* reset intr to ^C */
mytermios.c_lflag |= ECHO ; /* Do echo */
rc = tcsetattr(0 , TCSANOW, &mytermios);
printf("tcsetattr(intr,%d) =%d\n", THE_KEY, rc );

return 0;
}

intr.sh:

#!/bin/sh
echo $$
exec ./a.out
echo I am back.
Galenical answered 26/5, 2012 at 12:47 Comment(5)
Funny thing is that the other answer (which is plain wrong) still gets upvotes, just because it contains links to the Right Books.Galenical
@Merrow How can the same answer be "almost right" (comment to me) and "plain wrong" (comment to OP). Of course device drivers are involved when communicating with a system via peripherals (how come you didn't mention the keyboard driver?) Providing more details doesn't make an "almost right" answer "plain wrong". If I went to the level of machine language and lookup tables, would that make your answer incorrect? Hardly. I provided links to books so OP could look up more details as needed. OP felt my answer was helpful. Your current comment is contradictory and disingenuous at best.Carillo
The wrong thing is that the shell is not involved. The conversion from ^C to INTR takes place in the terminal driver. The shell is only a receptor (like any other process) The stty is not targeting the shell, but the tty.Galenical
BTW: the "almost right" was caused by politeness on my side. "of course divice drivers are involved" is a very fuzzy statement. I did not mention the keyboard driver because it is not involved in generating the signal, only in converting the scancodes to usable characters. There need not even be a keyboard; the tty could refer to a modem line, or even a pty via telnet (in which case the generation of the INTR would follow a totally different path, but still performed by the pty-driver) Since you refer to APUE: chapter 11 can be useful.Galenical
This article contains a very helpful introduction to TTY(terminal). linusakesson.net/programming/tty/index.phpMerrow
C
4

The shell echoes everything you type, so when you type ^C, that too gets echoed (and in your case intercepted by your signal handler). The command stty -echo may or may not be useful to you depending on your needs/constraints, see the man page for stty for more information.

Of course much more goes on at a lower level, anytime you communicate with a system via peripherals device drivers (such as the keyboard driver that you use to generate the ^C signal, and the terminal driver that displays everything) are involved. You can dig even deeper at the level of assembly/machine language, registers, lookup tables etc. If you want a more detailed, in-depth level of understanding the books below are a good place to start:

The Design of the Unix OS is a good reference for these sort of things. Two more classic references: Unix Programming Environment and Advanced Programming in the UNIX Environment

Nice summary here in this SO question How does Ctrl-C terminate a child process?

"when youre run a program, for example find, the shell:

  • the shell fork itself
  • and for the child set the default signal handling
  • replace the child with the given command (e.g. with find)
  • when you press CTRL-C, parent shell handle this signal but the child will receive it - with the default action - terminate. (the child can implement signal handling too)"
Carillo answered 26/5, 2012 at 12:3 Comment(5)
Do you mean that bash here is in the foreground process group as well, so it received the signal sent by the kernel?Merrow
@Merrow I mean is that you use the keyboard to communicate with your program, and the shell is in between you and the program (i.e., the interface) so it intercepts this action. What your signal handler does is determine the subsequent action that occurs after the ^C is seen. So rather than terminate the program, you can have it do something else, like print a message in your case.Carillo
So it's actually the shell sending the signal to foreground process group(kernel actually implements it). And so the underlying events are, the keyboard caught Ctrl+C pressed and signals the CPU. Then a hardware interrupt invoked the interrupt handler, which tells the shell Ctrl+C has pressed. Then a signal is sent by the shell. So the "Ctrl+C keystroke" to "SIGINT signal" transformation is made by the shell. Am I correct?Merrow
Almost right. It is the terminal (driver) that intercepts the ^C and translates it to a signal sent to the attached process (which is the shell) stty intr ^B would instruct the terminal driver to intercept a ^B instead. It is also the terminal driver that echoes the ^C back to the terminal.Galenical
Yes, the stty command really helps. Thank both of you for explanation!Merrow

© 2022 - 2024 — McMap. All rights reserved.