### [Peilin Ye's blog](https://hackmd.io/@ypl/Sk8YAobw9)
# Understanding Control-C
> **arch:** x86_64
> **Bash:** 5.1, commit [9439ce094c9a](https://git.savannah.gnu.org/cgit/bash.git/commit/?id=9439ce094c9aa7557a9d53ac7b412a23aa66e36b) ("Bash-5.1 patch 16: fix interpretation of multiple instances of ! in \[\[ conditional commands")
> **Linux:** 5.18-rc5, commit [9c095bd0d4c4](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9c095bd0d4c451d31d0fd1131cc09d3b60de815d) ("Merge branch 'hns3-next'")
> **QEMU:** 7.0.50, commit [2d20a57453f6](https://repo.or.cz/qemu/armbru.git/commit/2d20a57453f6a206938cbbf77bed0b378c806c1f) ("Merge tag 'pull-fixes-for-7.1-200422-1' of ht<span>tps://github.com/stsquad/qemu into staging")
```
ypl@home:~$ sudo make isntall
make: *** No rule to make target 'isntall'. Stop.
ypl@home:~$ sudo make isntal^C
ypl@home:~$
ypl@home:~$ aargh
-bash: aargh: command not found
```
This post briefly describes what happens in Linux when you press `Control-C`.
## Customizing Your ^C
...but why not have some fun first? Ever wondered where does that `^C` string come from? Tired of it? Apply this to your Bash:
```diff
diff --git a/lib/readline/signals.c b/lib/readline/signals.c
index f9174ab8a014..93b4b637122a 100644
--- a/lib/readline/signals.c
+++ b/lib/readline/signals.c
@@ -765,7 +765,7 @@ rl_echo_signal_char (int sig)
if (CTRL_CHAR (c) || c == RUBOUT)
{
- cstr[0] = '^';
+ cstr[0] = '%';
cstr[1] = CTRL_CHAR (c) ? UNCTRL (c) : '?';
cstr[cslen = 2] = '\0';
}
```
```
ypl@home:~/bash$ ^C
ypl@home:~/bash$ ./bash
ypl@home:~/bash$ %C
ypl@home:~/bash$
```
Yay! You just customized your `^C`!
## 8250 UART Serial Driver
I'm using `qemu-system-x86_64` (`-nographic`) because it's easier. It seems that, whenever I press `Control-C`, my guest Linux's [8250 UART](https://en.wikipedia.org/wiki/8250_UART) serial driver receives an interrupt, so let's start there!
> Feeling adventurous? Start from `common_interrupt()`, or even QEMU instead!
```
drivers/tty/serial/8250/8250_core.c:serial8250_interrupt() /* port->handle_irq() */
8250_port.c:serial8250_default_handle_irq()
:serial8250_handle_irq()
:serial8250_rx_chars()
```
Let's take a closer look at `serial8250_rx_chars()`:
```c
unsigned char serial8250_rx_chars(struct uart_8250_port *up, unsigned char lsr)
{
struct uart_port *port = &up->port;
int max_count = 256;
do {
serial8250_read_char(up, lsr);
if (--max_count == 0)
break;
lsr = serial_in(up, UART_LSR);
} while (lsr & (UART_LSR_DR | UART_LSR_BI));
tty_flip_buffer_push(&port->state->port);
return lsr;
}
```
Here,
1. `serial8250_read_char()` pushes this `Control-C` to the [TTY flip buffer](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/tty/tty_buffer.rst?id=9c095bd0d4c451d31d0fd1131cc09d3b60de815d#n4) by calling `uart_insert_char()`;
2. `tty_flip_buffer_push()` then queues a work to push this TTY flip buffer to the [line discipline (LDISC)](https://en.wikipedia.org/wiki/Line_discipline).
## TTY Core
Next, we traverse a few TTY core functions before getting to the line discipline:
```
drivers/tty/tty_buffers.c:tty_flip_buffer_push() /* queue_work() */
...
:flush_to_ldisc()
:receive_buf() /* port->client_ops->receive_buf() */
tty_port.c:tty_port_default_receive_buf()
tty_buffer.c:tty_ldisc_receive_buf() /* ld->ops->receive_buf2() */
```
We will be dealing with [N_TTY](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/tty/n_tty.rst?id=9c095bd0d4c451d31d0fd1131cc09d3b60de815d#n4), the default line discipline. See `tty_ldisc_init()`:
```c
int tty_ldisc_init(struct tty_struct *tty)
{
struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY); /* default to N_TTY */
if (IS_ERR(ld))
return PTR_ERR(ld);
tty->ldisc = ld;
return 0;
}
```
## N_TTY
```
drivers/tty/n_tty.c:n_tty_receive_buf2()
:n_tty_receive_buf_common()
:__receive_buf()
:n_tty_receive_buf_standard()
:n_tty_receive_char_special()
:n_tty_receive_signal_char()
:isig()
:__isig()
```
In `n_tty_receive_buf_standard()`, N_TTY realizes that `Control-C` is a [control character](https://en.wikipedia.org/wiki/Control_character) (ASCII 3, the [End-of-Text character](https://en.wikipedia.org/wiki/End-of-Text_character)) and handles it specially. Look for `ldata->char_map`.
Eventually, as specified in [POSIX 1003.1 Section 7.1.1.9, Special Characters](https://www.govinfo.gov/content/pkg/GOVPUB-C13-bf1fc57a5dbcaa993cebad99aca83f64/pdf/GOVPUB-C13-bf1fc57a5dbcaa993cebad99aca83f64.pdf#page=155):
> It generates a SIGINT signal that is sent to all processes in the foreground process group for which the terminal is the controlling terminal.
`__isig()` sends a `SIGINT` to all processes in the foreground process group using `kill_pgrp()`:
```c
static void __isig(int sig, struct tty_struct *tty)
{
struct pid *tty_pgrp = tty_get_pgrp(tty);
if (tty_pgrp) {
kill_pgrp(tty_pgrp, sig, 1);
put_pid(tty_pgrp);
}
}
```
## Signal Handling
Briefly,
```
kernel/signal.c:kill_pgrp()
:__kill_pgrp_info()
:group_send_sig_info()
:do_send_sig_info()
:send_signal()
:__send_signal()
include/linux/signal.h:sigaddset()
```
Let's say I pressed `Control-C` to stop `yes`. `__send_signal()` adds a pending `SIGINT` for `yes` to be handled later. For example, when `yes` returns from a system call:
```
arch/x86/entry/common.c:syscall_exit_to_user_mode()
kernel/entry/common.c:__syscall_exit_to_user_mode_work()
:exit_to_user_mode_prepare()
:exit_to_user_mode_loop()
arch/x86/kernel/signal.c:arch_do_signal_or_restart()
kernel/signal.c:get_signal()
kernel/exit.c:do_group_exit()
:do_exit()
kernel/sched/core.c:do_task_dead()
```
`get_signal()` calls `do_group_exit()` to terminate `yes`, since that's the default action for `SIGINT`, see [POSIX 1003.1 Table 3-1, Required Signals](https://www.govinfo.gov/content/pkg/GOVPUB-C13-bf1fc57a5dbcaa993cebad99aca83f64/pdf/GOVPUB-C13-bf1fc57a5dbcaa993cebad99aca83f64.pdf#page=74). In this case, `get_signal()` never returns.
On the other hand, if the process registered a handler for `SIGINT`, `get_signal()` returns to `arch_do_signal_or_restart()` instead:
```c
void arch_do_signal_or_restart(struct pt_regs *regs, bool has_signal)
{
struct ksignal ksig;
if (has_signal && get_signal(&ksig)) {
/* Whee! Actually deliver the signal. */
handle_signal(&ksig, regs);
return;
}
...
```
`handle_signal()` will "actually deliver the signal" to user space. This is true for Bash, for example.
That's it! "Whee!"
## Appendix A: Signal Handling in [GNU Readline](https://tiswww.case.edu/php/chet/readline/rltop.html)
Interestingly, when I press `Control-C` while Bash is reading from `stdin`, the `bash` process actually receives `SIGINT` **twice**. See my `printk()` output:
```
[ 17.337664] [bash, 0xffff96428638ba00] arch_do_signal_or_restart(): Whee! delivering SIGINT.
[ 17.348565] [bash, 0xffff96428638ba00] arch_do_signal_or_restart(): Whee! delivering SIGINT.
```
Bash uses the GNU Readline library to read from `stdin`. When I press `Control-C`, GNU Readline catches the first `SIGINT` with its own handler (`rl_signal_handler()`, I think), [performs some special processing](https://docs.rtems.org/releases/4.5.1-pre3/toolsdoc/gdb-5.0-docs/readline/readline00030.html), reinstalls Bash's `SIGINT` handler, then sends a second `SIGINT` to the `bash` process.
Conceptually, it's GNU Readline "forwarding" a `SIGINT` to Bash, but it happens within the same `bash` process.