1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本篇分析基于 Linux 4.14 内核代码进行分析。

3. Ctrl + C 的内幕

有过基于 Linux 内核的操作系统发布版 (如 Ubuntu)使用经验的童鞋都知道,从 Bash 启动一个程序(假定程序包含无限循环,不会退出),然后在控制台输入 Ctrl + C 按键组合,系统会向程序发送一个 SIGINT 信号,在默认设置下,SIGINT信号会导致程序终止。当然,可以通过 signal()/sigaction() 配置程序对 SIGINT 信号的处理,使得程序不会退出。
世界上从来没有无缘无故的爱和恨,按下 Ctrl + C 会向程序发送 SIGINT 信号,它背后的逻辑是 TTY 驱动 的支持。在进一步对问题进行讨论之前,先来看一下 Linux TTY 驱动框架图:

			       		   app                   
					        ^
				        	|                  user space
		--------------------|-------------------------------
					        v                  kernel space
			     ------------------------
			    |	     tty core        |
			     ------------------------
					        ^
					        |                    
					        v                        
			     -----------------------
		        |          | tty line   |
		        |          | discipline |
		        |	        ------------|
		        |                       |
		        |	   tty driver       |
		        |                       |
                 -----------------------
				            ^
							|
		--------------------|-------------------------------
		                    v
						hardware

其中:

1. 用户空间
   应用层 app 过操作 tty core 提供的字符设备文件(如 /dev/ttyS1),来和 tty 设备交互。
2. tty core
   在 TTY 驱动框架中,起到承上启下的作用:用于在用户空间和驱动之间传递数据。
3. tty line discipline
   将和 tty driver 交互的数据进行过滤、格式转换。如用于协议数据的处理,如 PPP 和 Bluetooth 。
4. tty driver
   tty 硬件设备驱动。

在我们的场景中,正是 tty line disciplineCtrl + C 输入进行了特殊处理。简单的看一下它的处理流程:

/* 
 * 1. open TTY 设字符设备的过程中,绑定了 tty line discipline 为 N_TTY
 */
tty_open()
	tty_open_by_driver(device, inode, filp)
		/* 通过字符设备号 @device 找到对应的 TTY 驱动 */
		driver = tty_lookup_driver(device, filp, &index);
		tty = tty_init_dev(driver, index)
			tty = alloc_tty_struct(driver, idx)
				/* 设定 tty line discipline 为 N_TTY */
				tty_ldisc_init(tty)
					struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY);
					tty->ldisc = ld;
				tty->driver = driver; /* 设定 tty 的驱动 */
				tty->ops = driver->ops; /* 设定 tty 的操作接口为驱动接口 */
				tty->index = idx;
				tty_line_name(driver, idx, tty->name); /* 设定 tty 名称, 如 ttyGS0, ttyS1 等 */
				tty->dev = tty_get_device(tty); /* 关联 tty 的设备对象 */
			tty_driver_install_tty(driver, tty)
			tty->port = driver->ports[idx];
			tty_ldisc_setup(tty, tty->link)

/*
 * 2. 按下 Ctrl + C 触发 SIGINT 的发送
 */
n_tty_read()
		tty_buffer_flush_work(tty->port)
			...
			n_tty_receive_char_special()
				if (L_ISIG(tty)) {
					if (c == INTR_CHAR(tty)) {
						n_tty_receive_signal_char(tty, SIGINT, c)
							isig(signal, tty)
								__isig(sig, tty)
									kill_pgrp(tty_pgrp, sig, 1) /* 发送 SIGINT 信号 */
					} 
				}

4. 后记

本篇涉及到 Linux TTY 驱动和信号处理的知识,本篇未对它们进行展开,它们都非常复杂,感兴趣的读者可以自行查阅相关资料进行学习。

Logo

更多推荐