从终端行规程到信号语义,一条“控制权回收链”的完整拆解与工程防御范式
摘要
在命令行里敲下 Ctrl+C 却毫无反应,是每个工程师都遭遇过的“数字窒息”时刻。本文不满足于罗列“按 Ctrl+\ 或 kill -9”的速查表,而是提出一条贯穿全文的分析主线——信号即控制权:Ctrl+C 的本质不是“停止程序”,而是终端向前台进程组投递 SIGINT、请求其自愿交还 CPU 控制权的一次协商。当协商失败(信号被屏蔽、被捕获后不退出、进程已脱离终端、陷入不可中断睡眠),控制权便滞留于内核或子进程,命令行随之“卡死”。
围绕这条主线,文章依次展开:终端行规程与作业控制的底层机制、SIGINT 的语义与可中断性边界、死循环为何“杀不死”的六类根因、从 SIGTERM 到 SIGKILL 到 gdb 注入的分层应急路径、/proc 与 ps 的现场取证方法、协程与容器化环境下的新型失效模式,以及面向工程实践的防御性编程范式。全文兼顾理论深度与可操作性,所有关键结论均标注来源,并附可点击的目录与拓展学习链接。
关键词:SIGINT;终端行规程;前台进程组;信号安全;不可中断睡眠;作业控制;容器信号语义
目录
一、引子:一次“按了没用”的现场复盘
设想一个再普通不过的场景:你在终端里运行一个数据处理脚本,屏幕上光标不再刷新,风扇开始狂转。你按下 Ctrl+C,期待熟悉的 ^C 回显和 shell 提示符。然而什么都没有发生——终端像被冻住一样。你连按几次,依然无效。这时你才意识到:这不是“程序运行慢”,而是“控制权已经不在你手里了”。
这个现象看似琐碎,却牵出一条横跨操作系统、终端子系统、进程调度与信号机制的完整技术链。很多速查文章会直接告诉你“用 kill -9”,但如果你不理解为什么 Ctrl+C 会失效,那么下一次失效可能发生在你无法直接拿到 PID 的容器里、发生在你亲手写的信号处理函数里、发生在你调用的某个 C 扩展库里。本文评述:把“按 Ctrl+C”当成一个黑盒操作,是工程能力的天花板;把它拆成一条可观测、可推理的控制链,才是排障能力的起点。
笔者在多年一线排障中反复观察到:绝大多数“Ctrl+C 无效”的案例,都能归入一条统一的因果链——信号即控制权。终端并不拥有“杀死进程”的权力,它只是把一次中断请求(SIGINT)投递给前台进程组;进程是否响应、何时响应、响应后是否退出,全部由进程自身与内核的调度状态决定。理解这条链,比记住任何单条命令都重要。
二、控制权回收链:Ctrl+C 到底发生了什么
2.1 从按键到字节:终端行规程的第一道加工
键盘并不是直接向 shell 或你的程序发送字符的。在经典 Unix 终端模型中,键盘输入先进入内核的 终端行规程(line discipline,又称 tty discipline)。行规程负责回显、行编辑(退格、删除)、以及把某些控制字符翻译成信号。Ctrl+C 对应的 ASCII 码是 0x03(ETX),行规程识别到它后,并不会把 0x03 交给程序,而是触发 SIGINT 的投递(POSIX 术语中称为 ISIG 标志位控制的行为)。
这一点极其关键:Ctrl+C 之所以能“中断”,是因为行规程把它解释成了信号,而不是因为终端有特殊魔法。如果行规程的 ISIG 被关闭(例如程序用 tcsetattr 清掉了 ISIG 位),那么 Ctrl+C 就退化为一个普通字节 0x03,程序不处理它,自然“没反应”。这是第一类失效根因,后文会详细展开。
本文评述:终端行规程是“控制权”的第一道闸门。它决定了按键是“数据”还是“命令”。很多排障者忽略了这一层,导致在错误的层面上寻找原因。
2.2 前台进程组:谁有资格接收信号
行规程投递 SIGINT 时,目标不是某一个进程,而是当前终端的前台进程组(foreground process group)。这是作业控制(job control)的核心概念。每个终端关联一个会话(session),会话内可包含多个进程组;任一时刻只有一个进程组处于前台,拥有读取终端输入的资格,其余为后台组。
当你在 shell 里执行一条命令,shell 通常会为该命令创建一个新进程组并置于前台。Ctrl+C 触发的 SIGINT 会发送给这个前台组的每一个成员进程。这意味着:如果你的命令 fork 出了多个子进程,它们都会收到 SIGINT。这解释了两个常见现象:其一,管道命令(如 a | b | c)中所有进程都会收到中断;其二,如果某个子进程忽略了 SIGINT,整条命令可能仍“卡住”,因为 shell 在等待整个前台组结束。
POSIX 对作业控制有明确定义,IEEE Std 1003.1(POSIX.1)及《Advanced Programming in the UNIX Environment》(Stevens & Rago)对此有系统论述。笔者在此强调一个常被忽视的推论:Ctrl+C 的“失败”往往不是主进程不响应,而是前台组中某个子进程不响应,导致 shell 无法收回前台控制权。这为后文的“分层应急”埋下伏笔——你需要找到的是那个“钉子户”,而不一定是主进程。
2.3 信号投递与进程响应:一次协商而非命令
信号到达进程后,内核并不会“强制”进程做任何事,而是设置进程的待处理信号位(pending signal)。进程在从内核态返回用户态、或从睡眠中被唤醒时,才检查待处理信号并执行默认动作或调用用户注册的处理函数。SIGINT 的默认动作是终止进程(terminate),但这一默认动作可以被改写:进程可以捕获(catch)它、忽略(ignore)它,甚至在临界区临时屏蔽(block)它。
因此,Ctrl+C 在语义上更接近一次“请求”或“协商”:终端请求进程交还控制权,进程可以选择配合,也可以选择拖延。当进程处于不可中断睡眠(uninterruptible sleep,D 状态)时,连内核都无法及时唤醒它去处理信号,协商彻底停滞。这正是“信号即控制权”主线最直观的体现。
三、SIGINT 的语义边界与“可中断性”
3.1 标准信号与实时信号:不可排队 vs 可排队
Linux 将信号分为标准信号(编号 1–31)与实时信号(编号 32–64)。SIGINT 属于标准信号。标准信号的一个重要特性是不排队:如果同一信号在未处理前再次到达,内核只保留一个待处理实例。这意味着你狂按 Ctrl+C 并不会“累积”多次中断,第二次及以后的按键在信号层面可能被合并。这一细节对排障有实际意义:连续按 Ctrl+C 无效,并不代表信号没发出,而可能是进程一直没机会处理它。
实时信号则支持排队,且携带数据。虽然 Ctrl+C 不产生实时信号,但理解这一区分有助于判断:当你用 kill 发送信号时,标准信号的“丢失”是符合规范的正常行为,而非 bug。相关语义在 signal(7) 手册页与 POSIX 标准中有权威描述。
3.2 信号安全:处理函数里不能做什么
如果进程注册了 SIGINT 处理函数,那么该函数运行在“信号上下文”中,可能随时打断主流程。POSIX 规定只有异步信号安全(async-signal-safe)的函数才能在处理函数中安全调用,例如 write、_exit;而 printf、malloc、free 等并非安全函数,在信号上下文中调用可能导致死锁或内存损坏。
笔者在实践中见过大量“Ctrl+C 后程序卡死”的案例,根源正是处理函数里调用了非信号安全函数(典型是 printf 与 malloc),在持有锁时被信号打断,重入后死锁。本文评述:这类“自己给自己下绊子”的失效,比外部强杀更隐蔽,也更值得在代码审查中重点防范。正确做法是:处理函数只设置一个 volatile sig_atomic_t 标志,主循环轮询该标志后优雅退出。
3.3 屏蔽与忽略:两种“听不见”
进程对 SIGINT 有两种“不作为”方式,语义截然不同:
- 忽略(ignore):通过
signal(SIGINT, SIG_IGN)或sigaction设置 SIG_IGN,信号被丢弃,进程永远不响应。这是“永久失聪”。 - 屏蔽(block):通过
sigprocmask把 SIGINT 加入阻塞集,信号变为待处理(pending),一旦解除屏蔽就会立即投递。这是“暂时失聪”。
区分二者对排障至关重要:如果进程只是屏蔽了 SIGINT,那么它可能在某段临界区结束后自动响应;如果是忽略,则必须换用 SIGTERM 或 SIGKILL。判断方法见第六章的 /proc/<pid>/status 解读。
四、死循环为何杀不死:六类根因解剖
把“Ctrl+C 无效”归因于“程序写了死循环”是过于粗糙的。死循环只是表象,真正决定信号能否生效的,是进程所处的状态与信号配置。下表汇总六类根因及其可观测特征,是本文工程实践部分的核心索引。
4.1 信号被忽略或屏蔽:最常见的“装死”
很多长期运行的服务在启动时会主动忽略 SIGHUP、SIGINT,以避免误操作导致退出;nohup 命令则会把 SIGHUP 设为忽略。当你在前台运行这类程序时,Ctrl+C 自然无效。此外,某些语言运行时或第三方库会在内部屏蔽信号,导致你“明明写了处理函数却不触发”。
判断方法非常直接:读取 /proc/<pid>/status,查看 SigIgn(忽略掩码)、SigBlk(阻塞掩码)、SigCgt(捕获掩码)。这三个十六进制掩码是信号状态的“体检报告”。SIGINT 是信号 2,对应掩码中的第 2 位(值 0x2)。若 SigIgn 含 0x2,说明被忽略;若 SigBlk 含 0x2,说明被屏蔽。
# 查看目标进程的信号掩码(SIGINT = 2 = 0x2)
grep -E 'Sig(Ign|Blk|Cgt)' /proc/<pid>/status
# 输出示例(模拟数据,仅示意格式)
SigBlk: 0000000000000002 # SIGINT 被阻塞
SigIgn: 0000000000000000 # 无忽略
SigCgt: 0000000180004a03 # 捕获了若干信号
本文评述:把信号掩码当作排障第一现场,是本文推荐的核心习惯。它能在你敲下任何 kill 命令之前,就告诉你“信号是否还有意义”,避免盲目重试。
4.2 处理函数死锁:自己给自己下的绊子
如前文 3.2 所述,信号处理函数中调用非异步信号安全函数是经典陷阱。设想主线程正在 malloc 内部持有堆锁,此时 SIGINT 到达,处理函数又调用 printf(内部可能触发 malloc),于是同一把锁被重入请求,死锁发生。进程表现为“收到信号后卡住”,用 gdb 抓栈能看到处理函数栈帧压在 malloc/futex 之上。
这类问题的工程解法是“信号处理函数只做最小动作”:设置 volatile sig_atomic_t 标志,或向自管道(self-pipe)写入一个字节,由主循环统一处理。self-pipe 技巧在《Unix Network Programming》与大量现代事件循环(如 libuv、Python asyncio)中广泛采用,是信号与事件驱动模型结合的标准范式。
4.3 不可中断睡眠:连内核都叫不醒
当进程处于 D 状态(TASK_UNINTERRUPTIBLE),它正在等待某个内核事件(典型是磁盘 IO、NFS 响应、驱动操作)。此时信号虽然被标记为 pending,但进程不会被唤醒去处理,直到等待的事件完成。这意味着即使 SIGKILL 也无法立即杀死一个 D 状态进程——SIGKILL 只是设置退出标志,真正的退出要等进程回到可运行状态。
这是很多“kill -9 也杀不掉”现象的真相。遇到 D 状态,正确的做法不是反复强杀,而是排查底层 IO 是否挂起(如 NFS 服务器不可达、磁盘故障)。Linux 内核社区曾长期讨论引入 TASK_KILLABLE 状态以缓解此问题,相关机制在较新内核中已用于部分可中断的 IO 路径。
4.4 脱离前台进程组:信号发错了地址
如果进程调用了 setsid() 创建新会话,或通过 & 转入后台,它就不再是终端的前台进程组。此时 Ctrl+C 的 SIGINT 发给的是当前前台组,与它无关。用 ps -o pid,pgid,tpgid,stat,cmd 可以观察 TPGID 字段:若为 -1,表示该进程不在任何终端前台。
4.5 行规程 ISIG 被关闭:按键退化为普通字节
交互式程序(如 vim、less、以及大量 TUI 工具)会把终端设为 raw 或 cbreak 模式,关闭行规程的 ISIG 标志,自行处理所有按键。此时 Ctrl+C 不再产生 SIGINT,而是作为 0x03 字节交给程序。如果程序没有处理这个字节,就表现为“没反应”。这也是为什么在 vim 里 Ctrl+C 通常不退出,而要用 :q! 或 Ctrl+\ 的原因。
4.6 子进程钉子户:主进程已死,shell 未归
shell 在等待整个前台进程组结束。如果主进程响应 SIGINT 退出了,但某个子进程忽略信号继续运行,shell 仍会阻塞。此时你会看到“没有提示符,但主进程已不在”。解决方法是定位该子进程并单独 kill。使用 ps -eo pid,ppid,pgid,stat,cmd | grep <关键字> 按进程组过滤即可。
五、分层应急路径:从 SIGTERM 到 gdb 注入
基于第四章的根因分析,可以设计一条由轻到重、逐层升级的应急路径。核心原则是:优先给进程“体面退出”的机会,避免数据损坏;只有在确认无效后才升级手段。
5.1 第一层:另开终端,礼貌请求
当当前终端卡死时,第一反应应是另开一个终端窗口(或使用 tmux/screen 的新 pane),而不是在原终端狂按。新终端里先定位进程:
# 按 CPU 占用排序,快速找到失控进程
ps -eo pid,ppid,pgid,stat,pcpu,pmem,etime,cmd --sort=-pcpu | head -20
# 或使用 top/htop 交互查看
top -o %CPU
找到 PID 后,先发 SIGTERM(15),给进程清理资源的机会:
kill -TERM <pid> # 等价于 kill -15 <pid>
# 等待 2-3 秒观察是否退出
ps -p <pid> -o pid,stat,cmd
5.2 第二层:SIGQUIT 与核心转储
若 SIGTERM 无效,可尝试 SIGQUIT(3)。与 SIGINT 不同,SIGQUIT 的默认动作是终止并生成核心转储(core dump),这对事后分析死循环原因极有价值。在终端里,Ctrl+\ 就是发送 SIGQUIT。注意:若进程也捕获或忽略了 SIGQUIT,此法同样无效。
kill -QUIT <pid> # 触发 core dump(若 ulimit 允许)
ulimit -c unlimited # 临时放开 core 大小限制
5.3 第三层:SIGKILL 强杀与其边界
SIGKILL(9)不可被捕获、阻塞或忽略,是内核层面的“最后手段”。但它有两个重要边界:其一,对 D 状态进程无效(见 4.3);其二,强杀可能导致共享资源(临时文件、锁、数据库事务)处于不一致状态。因此 kill -9 应作为确认无其他路径后的选择,而非第一反应。
kill -KILL <pid> # 不可捕获,立即终止(D 状态除外)
# 若进程组内多个进程需一起清理:
kill -KILL -<pgid> # 负号表示进程组
5.4 第四层:gdb 注入与栈回溯
当进程既杀不死又需要定位原因时,gdb 的 attach 能力是利器。它能让你在不终止进程的前提下查看调用栈,判断是死锁、D 状态还是逻辑死循环。
# 附加到目标进程并抓取所有线程栈
gdb -p <pid> -batch -ex "thread apply all bt" 2>&1 | tee /tmp/stack.txt
# 若怀疑死锁,可查看锁持有情况(需调试符号)
gdb -p <pid> -batch -ex "info threads" -ex "thread apply all bt full"
本文评述:gdb attach 的价值在于把“黑盒卡死”变成“白盒证据”。很多团队排障时直接强杀,丢失了唯一的现场,导致同类问题反复发生。养成“先抓栈、再决定杀不杀”的习惯,是工程成熟度的标志。
5.5 第五层:内核级与容器级手段
在容器环境中,若进程位于容器内且无法直接 kill,可通过 docker kill、kubectl delete pod --grace-period=0 --force 等上层接口处理。极端情况下,若进程处于 D 状态且底层存储不可恢复,可能只能重启节点。这些手段代价高,应作为最后选项。
六、现场取证:/proc、ps 与信号追踪
6.1 /proc 文件系统:进程的实时体检报告
Linux 的 /proc/<pid> 目录是排障的金矿。下表列出与信号/状态排障最相关的文件及其用途。
6.2 ps 与 pgrep:快速定位钉子户
# 查看进程组、前台组与状态
ps -eo pid,ppid,pgid,tpgid,stat,wchan:20,cmd | grep -v grep | grep <关键字>
# 按进程组批量查看(定位前台组所有成员)
ps -eo pid,pgid,stat,cmd | awk '$2==<pgid>'
# 查看 D 状态进程
ps -eo pid,stat,wchan:25,cmd | awk '$2 ~ /D/'
6.3 strace 与信号追踪:看信号去哪儿了
若需确认信号是否被投递、进程是否响应,可用 strace 追踪信号相关系统调用。注意 strace 会显著影响性能,且对已卡死进程 attach 可能失败。
# 追踪信号投递与处理
strace -p <pid> -e trace=signal -f 2>&1 | head -50
# 从启动开始追踪,观察信号处理函数注册
strace -f -e trace=rt_sigaction,rt_sigprocmask ./your_program
笔者建议把上述命令整理成团队内部的“排障速查脚本”,在事故发生时按脚本执行,避免临场遗忘。这种“把经验固化为工具”的做法,比任何单篇文档都更能提升团队整体响应速度。
七、新型失效模式:协程、容器与远程终端
7.1 协程与事件循环:信号被“代理”了
在 Python asyncio、Go runtime、Node.js 等运行时中,信号处理往往由运行时统一接管,再转发给用户回调。这带来两个新问题:其一,若事件循环本身被一个 CPU 密集任务阻塞,信号回调无法及时执行,表现为“Ctrl+C 延迟很久才生效”;其二,某些运行时默认忽略 SIGINT 或将其转换为特定异常,行为与裸进程不同。
以 Go 为例,运行时默认会捕获 SIGINT 并触发程序退出,但若主 goroutine 阻塞在不可抢占的循环中(旧版本 Go 在无函数调用的紧循环里无法被抢占),信号处理可能延迟。Go 1.14 引入的异步抢占机制显著改善了这一点。本文评述:协程时代的“可中断性”不再只取决于信号配置,还取决于调度器能否抢占用户代码。这是传统信号知识需要扩展的新维度。
7.2 容器信号语义:PID 1 的特殊性
在容器中,主进程通常是 PID 1。Linux 内核对 PID 1 有特殊保护:PID 1 不会响应未被显式注册处理函数的信号(包括 SIGTERM、SIGINT),因为内核假定 init 进程知道如何处理。这意味着如果你的容器主进程没有正确处理 SIGTERM,docker stop 会等待超时后才 SIGKILL,表现为“停容器很慢”。
工程上的标准解法是使用轻量 init(如 tini、dumb-init)作为 PID 1,负责信号转发与僵尸回收。Docker 官方文档与 Kubernetes 文档均推荐此做法。相关实践在容器社区已成共识。
7.3 远程终端与 SSH:信号链路的额外一跳
通过 SSH 连接时,Ctrl+C 的处理发生在本地终端:本地行规程产生 SIGINT,但该信号无法跨网络传递,SSH 客户端会把 Ctrl+C 作为字节发送到远端,由远端 pty 的行规程再解释为 SIGINT。因此,若本地终端处于 raw 模式(如某些终端模拟器设置),或 SSH 客户端配置了特殊键映射,Ctrl+C 可能根本到不了远端。排查远程场景时,务必区分“本地终端行为”与“远端进程行为”。
八、防御性编程:让 Ctrl+C 永远有效
排障是事后补救,真正的高手在编码阶段就消除了“Ctrl+C 失效”的可能。以下是一套可落地的防御范式。
8.1 信号处理三原则
- 最小动作原则:处理函数只设置
volatile sig_atomic_t标志或写 self-pipe,绝不调用非信号安全函数。 - 可重入原则:处理函数及其调用的所有代码必须可重入,避免共享可变状态。
- 优雅退出原则:主循环检测到退出标志后,按顺序释放资源、刷新缓冲、关闭连接,再退出。
/* 推荐的 SIGINT 处理范式(C 语言示意) */
#include <signal.h>
#include <unistd.h>
static volatile sig_atomic_t g_stop = 0;
static void on_sigint(int sig) {
(void)sig;
g_stop = 1; /* 只做这一件事,异步信号安全 */
}
int main(void) {
struct sigaction sa = {0};
sa.sa_handler = on_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; /* 不设 SA_RESTART,让阻塞调用被打断 */
sigaction(SIGINT, &sa, NULL);
while (!g_stop) {
/* 主工作循环,定期检查 g_stop */
}
/* 优雅清理资源 */
return 0;
}
8.2 长任务的可中断设计
对于数据处理、批量计算等长任务,应把工作拆分为可检查中断点的“块”,在每块之间检查退出标志。这样即使单块耗时较长,整体响应延迟也可控。避免在紧循环中完全不检查标志——那是“Ctrl+C 延迟生效”的常见原因。
8.3 容器与守护进程的信号契约
容器主进程必须显式处理 SIGTERM(而非依赖默认动作),并在收到后启动优雅关闭流程;同时应使用 tini/dumb-init 处理 PID 1 的特殊性。守护进程若确实需要忽略 SIGINT,应在文档中明确说明,并提供替代的停止方式(如信号文件、管理端口)。
8.4 可观测性:让失效可诊断
建议在程序中记录信号接收日志(注意在信号安全上下文中只能写日志到预打开 fd),并暴露当前状态(运行/停止中/阻塞)到健康检查接口。这样当 Ctrl+C 失效时,运维能快速判断是“没收到信号”还是“收到但没退出”。
九、前沿预判与结语
从更宏观的视角看,“Ctrl+C 失效”折射的是控制权在复杂系统中的分布问题。随着系统从单机进程演进到容器编排、Serverless、协程运行时,控制权的层级越来越多,信号只是其中最古老、最底层的一环。笔者预判三个方向值得关注:
其一,内核可中断性的持续改进。Linux 社区对 TASK_KILLABLE、IO 可中断路径的探索仍在继续,未来 D 状态“杀不死”的窗口有望进一步收窄。
其二,运行时层面的抢占与取消原语。Go 的 context、Rust 的 cancellation token、Python 的 asyncio cancellation,正在把“中断”从信号层抽象为语言级原语。未来工程师可能更多与这些原语打交道,而非直接操作信号。
其三,可观测性驱动的自动诊断。eBPF 等技术让信号投递、调度延迟、锁竞争变得可追踪,未来“Ctrl+C 失效”可能被自动诊断工具直接定位到根因,而无需人工逐层排查。
回到最初那条主线——信号即控制权。理解了控制权如何产生、如何传递、如何被拒绝,你就不再是“按 Ctrl+C 的人”,而是“理解系统如何响应中断的人”。这种理解,才是工程师面对任何卡死现场时真正的底气。
拓展学习链接(供进一步查阅):
- Linux 手册页 signal(7):man7.org/linux/man-pages/man7/signal.7.html
- Linux 手册页 tty_ioctl(4) 与 termios(3):man7.org/linux/man-pages/man3/termios.3.html
- GNU C Library 信号处理文档:gnu.org/software/libc/manual
- Docker 官方:PID 1 与信号处理:docs.docker.com
- tini 项目(轻量 init):github.com/krallin/tini
- Go 运行时异步抢占设计文档:go.dev/doc/go1.14
主要参考文献
[1] IEEE Std 1003.1-2017, POSIX.1 Base Specifications, Issue 7, IEEE/Open Group, 2017.
[2] W. R. Stevens, S. A. Rago, Advanced Programming in the UNIX Environment, 3rd ed., Addison-Wesley, 2013.
[3] W. R. Stevens, B. Fenner, A. M. Rudoff, UNIX Network Programming, Vol. 1, 3rd ed., Addison-Wesley, 2003.
[4] Linux man-pages project, signal(7), sigaction(2), termios(3), tty_ioctl(4), 2023–2024 版本.
[5] D. P. Bovet, M. Cesati, Understanding the Linux Kernel, 3rd ed., O'Reilly, 2005.
[6] R. Love, Linux Kernel Development, 3rd ed., Addison-Wesley, 2010.
[7] Docker Documentation, PID 1 and signal handling in containers, 2024. [在线]
[8] Go Team, Go 1.14 Release Notes: Asynchronous Preemption, 2020. [在线]
[9] M. Kerrisk, The Linux Programming Interface, No Starch Press, 2010.
[10] Linux 内核文档, Documentation/admin-guide/ 与 Documentation/filesystems/proc.rst, 2024.
[11] B. Gregg, BPF Performance Tools, Addison-Wesley, 2019.
[12] Kubernetes Documentation, Pod Lifecycle and Termination, 2024. [在线]
[13] A. Silberschatz et al., Operating System Concepts, 10th ed., Wiley, 2018.
[14] J. Corbet, A. Rubini, G. Kroah-Hartman, Linux Device Drivers, 3rd ed., O'Reilly, 2005.
[15] R. Pike, B. Kernighan, The Practice of Programming, Addison-Wesley, 1999.
[16] systemd 文档, systemd.kill 与信号语义, 2024. [在线]
[17] GNU C Library Manual, Signal Handling, 2024. [在线]
[18] tmux 文档, Control Mode 与信号转发, 2024. [在线]
[19] OpenSSH 手册, ssh(1) 与 pty 分配, 2024. [在线]
[20] D. Beazley, B. K. Jones, Python Cookbook, 3rd ed., O'Reilly, 2013.
[21] Python 官方文档, signal — Set handlers for asynchronous events, 2024. [在线]
[22] asyncio 官方文档, Event Loop 与信号处理, 2024. [在线]
[23] Node.js 官方文档, Process Signal Events, 2024. [在线]
[24] Rust 官方文档, std::process 与信号处理, 2024. [在线]
[25] J. M. Smith, Portable C and UNIX System Programming, Prentice Hall, 1987.
[26] L. Torvalds et al., Linux 内核源码 kernel/signal.c, 2024. [在线]
[27] Linux 内核文档, Documentation/driver-api/ 中关于不可中断睡眠的说明, 2024.
[28] B. Cantrill, DTrace: Dynamic Tracing in Oracle Solaris, Mac OS X, and FreeBSD, Prentice Hall, 2011.
[29] eBPF 官方文档, BPF 与信号/调度追踪, 2024. [在线]
[30] GDB 官方手册, Attaching to a Process, 2024. [在线]
[31] strace 官方文档, Signal Tracing, 2024. [在线]
[32] perf 官方文档, perf trace 与信号观测, 2024. [在线]
[33] A. Tanenbaum, H. Bos, Modern Operating Systems, 4th ed., Pearson, 2014.
[34] R. Bryant, D. O'Hallaron, Computer Systems: A Programmer's Perspective, 3rd ed., Pearson, 2015.
[35] M. Tim Jones, GNU/Linux Application Programming, 2nd ed., Charles River Media, 2008.
[36] Linux 内核文档, Documentation/accounting/ 与 taskstats, 2024.
[37] cgroups 文档, cgroup v2 与进程控制, 2024. [在线]
[38] OCI Runtime 规范, 容器信号与生命周期, 2024. [在线]
[39] containerd 文档, 信号转发与 shim, 2024. [在线]
[40] runc 源码, signal 处理路径, 2024. [在线]
[41] Kubernetes 文档, Container Lifecycle Hooks 与 preStop, 2024. [在线]
[42] systemd 文档, KillMode 与信号策略, 2024. [在线]
[43] supervisord 文档, 信号与进程管理, 2024. [在线]
[44] P. McKenney, Is Parallel Programming Hard?, Linux Technology Center, 2023.
[45] J. Preshing, 信号安全与内存模型系列博客, 2022–2024. [在线]
[46] D. Butenhof, Programming with POSIX Threads, Addison-Wesley, 1997.
[47] POSIX 线程与信号交互说明, pthread_sigmask(3), 2024. [在线]
[48] Linux 手册页, proc(5), 2024. [在线]
[49] Linux 手册页, ps(1), 2024. [在线]
[50] Linux 手册页, kill(1), kill(2)
微信扫一扫分享
打开微信「扫一扫」,扫描二维码后在微信中分享给好友或朋友圈。
💬 评论 (0)
评论功能已关闭

