The easiest way to understand Linux terminal devices (/dev/tty*
, /dev/pts/*
) is if you think of them as streaming sockets. As if the /dev/tty12
would be a TCP port, like 127.0.0.1:8080
.
Processes are connecting to them, get input from them, write into them, and then finally disconnect from them. A socket (terminal) can have a connection with multiple processes.
Other processes can listen on them, they are typically the terminal emulator programs, but not always. In the case of the character terminals, the Linux kernel itself works as a "listener daemon" on the terminal device.
The "extra feature" that a terminal has, but a socket hasn't: the kernel keeps track of which processes are connected to it, and it is capable to signal them on need. It so happens if, for example, you press Ctrl+c.
You can read a detailed list of the signals passed there answer.
What exactly happens if you press Ctrl+c?
It is not as if somebody presses a normal button, for example, an a key. In this case, the pressed key is simply written into the terminal device and can be read out by the processes reading from it (which is typically the process in the foreground).
In the case of special keypresses, for example, Ctrl+z, or when you close/resize the terminal window, and so on, the terminal gets the request and the kernel sends to all processes attached to it the corresponding signal. To all of them, it will be later important later.
These devices can be also controlled by well-directed ioctl()
calls.
If bash runs a subprocess (i.e. external command), then the following happens:
- bash starts the process in the background, giving to it the terminal
- bash stops getting input from the terminal
- after the process stops, bash sets everything back.
However, the bash still remains attached to the terminal device, and if a Ctrl+c is coming, it will get the signal. Also the external command will get the signal.
However, these signals can be overridden (signal()
, sigaction()
system calls). Bash overrides them, i.e. it overrides the default signal handling routine (which would simply stop it) with its own. This is why it doesn't exit if you press the Ctrl+c in a command prompt.
However, a sleep 60
will exit. It doesn't change its signal handlers.
If you run a bash internal command, this signal handler will work as you said (it stops the internal command execution and gets you back to the prompt).