第04章 初始化程序(5):按下 Ctrl+C 的连锁反应 —— 拆解进程组、会话期与控制终端
引子当我们在命令行中按下 “CtrlC” 时究竟发生了什么假设你已经成功登录进了 Linux 0.11 系统你迫不及待地想在终端里试一试新学到的管道命令。于是你输入了[plinux root]# cat main.c | grep for | more然后按下了回车键。屏幕上立刻开始飞速滚动代码。过了几秒钟你觉得看够了于是你非常自然地按下了CtrlC。一瞬间所有屏幕滚动停止了。到这里一切似乎都很正常。但请你思考一个问题你的终端只发出了一个中断信号Interrupt Signal为什么同时把cat、grep和more这三个原本看上去各不相干的程序全部一起杀掉了操作系统为什么能如此精准地“团灭”这一批进程如果你在 Linux 0.11 的源码init/main.c中继续向下寻找答案你会发现自己最终遇到了几个全新的概念进程组Process Group和会话期Session。今天我们就从硬件中断和进程组织的角度彻底把这三者之间的关系讲明白。一、 进程的“身份证”与“族谱”从 PID 说起在研究“团灭”之前我们需要先理清进程最基本的身份标识。书中明确提到“在内核中每个进程都使用一个不同的大于零的正整数来标识称为进程标识号pid。”PID是内核分配给每个进程的唯一“身份证号”。比如Linux 0.11 启动后内核会创建一个 PID 为 0 的空闲进程Idle Process然后fork()出一个 PID 为 1 的进程Init 进程即本书中的任务 1。父进程与子进程当我们调用fork()时子进程会获得一个全新的 PID但是它会在自己的进程控制块PCBtask_struct中记录下父进程的 PIDPPID。这就构成了一颗逐层派生的“家族树”。但是无论是 PID 还是父子关系都解决不了文章开头那个“团灭”的问题。家族树代表的是血缘而CtrlC要发送的其实更像是一种“对团队的集体惩罚”。这就引出了一个新的层级——进程组Process Group。二、 团队作战进程组Process Group的诞生书上写道“一个进程可以通过fork()调用创建一个或多个子进程这些进程就可以构成一个进程组。”2.1 什么是进程组进程组顾名思义就是一个或多个进程的集合。内核给每个进程组分配了一个进程组标识号PGID原文称为 gid。一个进程组的组号PGID一定是一个正整数。组长Group Leader每个进程组都有一个组长。组长进程的特点非常简单——它的 PID 等于它的 PGID。也就是说它是这个团队成立的“发起人”。2.2 管道命令背后的“连坐”机制回到我们的案例cat main.c | grep for | more。Shell命令行解释器比如 Linux 0.11 中的/bin/sh在后台执行了一系列操作Shell 首先调用fork()创建了第一个子进程例如为了运行cat。紧接着Shell 调用了一个名为setpgid()的系统调用将这个子进程的 PGID 设置成当前 Shell 设定的一个统一的值。Shell 依次创建grep和more的进程并同样使用setpgid()把它们的 PGID 修改成和cat一样。最终这三个看起来毫无关联的进程拥有了完全相同的进程组 ID。它们共同组成了一个单兵作战的“战斗小组”。2.3 引爆“连锁反应”的扳机CtrlC 做了什么当我们按下键盘上的CtrlC组合键时事情就变得清晰明了了。这个过程可以分为三步中断捕获键盘驱动程序检测到CtrlC对应的 ASCII 码是 0x03它并不只是简单地把这个字符放进缓冲区而是会唤醒当前终端对应的tty驱动。信号生成tty驱动程序得知用户想要中断当前任务于是它就向内核发送一个SIGINT中断信号。信号分发内核接收到SIGINT信号后它做的第一件事就是检查当前终端上前台进程组的 PGID。接着内核会把这个信号广播给该进程组里的每一个进程。内核级揭秘在早期的 Unix 设计中信号Signal是面向进程组的而不是面向单个进程的。只要进程加入了同一个组发给组长的信号组内成员都会收到。这就是我们按一次CtrlC三个进程一起停止的根本原因下面的 Mermaid 流程图展示了这个行为Shell_Session[Shell 会话控制]SIGINT 同时送达SIGINT 同时送达SIGINT 同时送达Process_Group[进程组 (PGID101)]Shell 进程 (PID100)fork setpgid 创建 cat (PID101)fork setpgid 创建 grep (PID102)fork setpgid 创建 more (PID103)用户按下 CtrlC键盘中断处理 / tty 驱动内核生成 SIGINT 信号Process_Group三、 更大的“王国”会话期Session与控制终端理解了进程组我们再往上走一个层级进入 Linux 多任务管理的“王国”——会话期Session。书上对会话期的定义非常精辟“会话期则是一个或多个进程组的集合。通常情况下用户登录后所执行的所有程序都属于一个会话期。”3.1 国王会话首进程Session Leader每一个会话期都有一个“会话首进程”。通常情况下这个首进程正是你的登录 Shell例如/bin/sh。当你的登录 Shell 启动时它调用setsid()系统调用。这个调用的效果非常显著如果调用进程不是一个进程组组长它会创建一个新的会话期。这个调用进程会成为这个新会话期的会话首进程。这个新会话期的 IDSID就是会话首进程的 PID。最关键的一点是这个新会话期会立刻脱离当前的终端即没有控制终端。3.2 领土控制终端Controlling Terminal书中写道“登录 Shell 所使用的终端就是会话期的控制终端Controlling Terminal。”在有了会话期之后setsid()创建的会话首进程通常是 Shell会紧接着打开一个终端设备文件比如/dev/tty0主动宣布这个终端是它的“领土”。一旦一个终端成为了某个会话期的“控制终端”这个终端就像一面旗帜只有这个会话期内的进程组才有资格读写这个控制终端。当你按下CtrlC时信号是沿着控制终端往下传递的具体发给谁就取决于当前处于“前台”的进程组是谁。你可能会疑惑“为什么要有控制终端这个概念呢这有什么好处”3.3 一个经典的例子后台运行我们在 Linux 中经常会用到一个操作command 。把加在命令的末尾可以把程序放到后台运行。这背后其实就是前台进程组和后台进程组的区别。前台进程组它拥有控制终端的读写权限。你输入的任何字符都会由内核转发给前台进程组里的程序。CtrlC信号也只会发给这个组。一个会话期内只能有一个前台进程组。后台进程组它也在运行但是没有控制终端的读写权限。如果后台进程组中的某个程序试图读入用户的键盘输入比如read()内核会立刻向它发送一个SIGTTIN信号强制它暂停运行。这就是为什么后台进程在跑的时候你几乎不会看到它们在终端里突然卡住并抢夺你的键盘输入权——因为操作系统就在底层“冻结”了它们。3.4 游戏结束当我们按下logout书中解释了一个非常重要的设计目的“当我们退出登录logout时所有属于我们这个会话期的进程都将被终止。”现代 Linux 系统也是如此。当我们退出登录时init进程或者现代 Linux 的systemd等会向**当前会话首进程Shell**发送一个挂断信号SIGHUP。Shell 在收到SIGHUP后会按顺序向前台和后台的所有进程组发送信号让它们全部结束。如果后台有某些进程持续在运行比如nohup nohup.out 它们也必须在收到信号后优雅地退出或者是彻底脱离这个会话变成守护进程Daemon。这就是为什么logout会清空所有属于你的工作进程确保系统资源被回收。四、 独立宣言setsid()与守护进程书中告诉我们“setsid()函数就是用于建立一个新的会话期。通常该函数由环境初始化程序进行调用见下节说明。”它到底是怎么用的呢想象一个场景假设你登录到远程服务器启动了一个耗时很长的数据处理程序。由于它属于你当前的登录会话一旦你的网络断了或者你的本地终端关闭了内核就会判定“控制终端丢失”继而给这个数据处理程序发送SIGHUP信号导致它彻底中断。这时候你的数据就白跑了非常尴尬。为了解决这个问题在编写这类长期运行的后台服务程序Daemon 进程时开发者通常会遵循一个固定的编程模式Daemonizefork()一次后台运行的父进程退出让子进程成为孤儿并被init收养确保子进程不是进程组组长。调用setsid()在子进程中调用这个系统调用。后果子进程成为一个新会话的首进程并且摆脱了对当前控制终端的依赖。此时就算你关掉本地终端这个进程也不会收到SIGHUP信号。二次fork()非必须但通常是防御性编程再fork一次彻底确保它不再是会话首进程防止它意外打开控制终端。关闭所有文件描述符重定向输入/输出把stdin,stdout,stderr全部重定向到/dev/null。这种诞生方式就是 Linux 中各种系统服务如 Web 服务器httpd、数据库mysqld的初始化标准流程。五、 理论落地回看 Linux 0.11 的init/main.c现在我们把目光放回本书的主角——Linux 0.11 中。在第 4 章最后我们看init()函数的最后一段代码// 这是4.2.2 节 init() 函数中后半部分的代码逻辑// (注意原main.c中此处有段注释在创建子进程后调用 setsid)if(!pid){// 这里是子进程执行的分支close(0);close(1);close(2);// 关闭标准输入、标准输出、标准错误setsid();// 调用setsid()建立一个全新的会话期(void)open(/dev/tty0,O_RDWR,0);// 重新打开控制终端(void)dup(0);(void)dup(0);// 复制文件描述符恢复0、1、2exit(execve(/bin/sh,argv,envp));// 执行登录 Shell}深度代码拆解setsid()极其重要这里的setsid()系统调用就是 Linux 0.11 中建立会话期的唯一入口。它在父进程任务 1创建的“子进程”中执行这个子进程被创建的目的就是要成为用户登录后的会话首进程Shell。close(0), close(1), close(2)与open(/dev/tty0)这是一个非常典型的标准输入输出重定向流程。它关闭了从父进程继承过来的所有文件描述符然后显式地打开了/dev/tty0这是控制终端设备文件。由于这是进入新会话期后第一次打开终端设备这个/dev/tty0自动成为了该会话的控制终端。execve最后它把当前进程完全替换为/bin/shShell 程序。从这一刻起一个新的、独立的用户登录环境正式建立。在这个环境下再执行cat main.c | grep for | more就会拥有我们之前讨论的进程组和控制终端的行为。补充说明/dev/tty的神奇之处书中最后一句提到的“控制终端对应于/dev/tty设备文件因此若一个进程需要访问控制终端可以直接对/dev/tty文件进行读写操作。”这是一个非常经典的 Linux 设计。不管你的程序在哪个终端上运行比如你在开启的第 3 个虚拟终端上物理设备是/dev/tty3只要你打开/dev/tty内核就会自动重定向到你当前会话对应的那个控制终端。这为编写需要交互的文本界面程序提供了极大的便利。六、 全景视图进程、进程组、会话期的三层体系综合我们上面所有分析我们可以用下图原书图4-4的重构版清晰地展现出这三者的关系。会话期 (Session ID 1001)控制终端 /dev/tty0按键, SIGINT登录/退出可以访问不可访问后台进程组 (BG)(无终端读写权限, 可运行)后台编译程序 make(PID: 1005, PGID: 1006)长时间同步任务 rsync(PID: 1006, PGID: 1006)前台进程组 (FG)(拥有控制终端读写权限)管道命令: cat(PID: 1002, PGID: 1005)管道命令: grep(PID: 1003, PGID: 1005)管道命令: more(PID: 1004, PGID: 1005)终端硬件/模拟器会话首进程: 登录Shell(PID: 1001, PGID: 1001)图解说明大圈会话期代表一次完整的用户登录环境Leader 是 Shell。色块进程组圈内的虚线框代表不同的进程组。控制终端通过物理/虚拟终端与前台进程组交互。后台组只能默默地计算无法接受输入。信号传递当用户按下 CtrlC信号只会从控制终端流向红色高亮的前台进程组导致整个进程组瞬间“集体阵亡”。这也是为何书上着重强调“进程组”概念的最终目的——**实现高效的进程群组管理减少操作系统与用户的交互开销。结语从PID到PGID再到SID会话 IDLinux 内核层层递进地构建了一个极其严密的多任务管理模型。PID赋予了进程独立的人格。PGID将进程组织成了相互配合的“部队”。Session将所有这些部队统一划归为属于某一特定用户或特定登录行为的“大本营”。在 Linux 0.11 的init/main.c中通过调用setsid()系统调用系统成功地从内核模式转移到了真正属于用户的交互模式。刚才还只是停留在内核代码里的fork()和pause()现在终于变成了用户终端里一句一句可运行的cat和grep。

相关新闻

国内口碑好的异构烷烃IP40.IP60.IP80.IP100供应商哪家强

国内口碑好的异构烷烃IP40.IP60.IP80.IP100供应商哪家强

在精密制造、高端日化美妆、新材料萃取等多个领域,高纯度的异构烷烃产品因其优异的性能和环保特性,逐渐成为众多企业的首选原料。然而,在众多供应商中,如何选择一家真正值得信赖的合作伙伴呢?本文将从多个维度对比分析…

2026/9/24 2:57:29 阅读更多 →
高速ADC选型与性能评估:从数据手册到系统设计实战

高速ADC选型与性能评估:从数据手册到系统设计实战

1. 高速ADC选型与性能评估:从数据手册到系统设计在雷达、软件无线电或者高端测试测量设备的设计中,选型一颗合适的高速模数转换器(ADC)往往是决定整个系统性能上限的关键一步。很多工程师拿到一份动辄上百页的数据手册&#xff0c…

2026/9/24 2:19:37 阅读更多 →
AI对话系统中的权力转移与决策依赖研究

AI对话系统中的权力转移与决策依赖研究

1. 项目概述:人机对话中的权力转移现象 最近Anthropic公司发布的一项涉及150万次人机对话的研究报告,揭示了一个令人深思的现象:在与AI系统的日常交互中,人类用户正在不知不觉地将决策主导权让渡给人工智能。这种现象不仅发生在简…

2026/9/22 0:23:19 阅读更多 →

最新新闻

Spring Boot昆虫标本管理系统:库表设计、CRUD接口与权限检索实战

Spring Boot昆虫标本管理系统:库表设计、CRUD接口与权限检索实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 1:50:43 阅读更多 →
SquareLine Studio与LVGL深度适配:从UI生成到硬件移植全解析

SquareLine Studio与LVGL深度适配:从UI生成到硬件移植全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 1:50:43 阅读更多 →
计算机二级Python备考指南:题型分值、选择题门槛与上机避坑全解析

计算机二级Python备考指南:题型分值、选择题门槛与上机避坑全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 1:50:43 阅读更多 →
随机过程教材选择与学习路径:从入门到进阶的实用指南

随机过程教材选择与学习路径:从入门到进阶的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 1:50:43 阅读更多 →
网心云OES Plus刷Armbian后系统迁移至SATA硬盘扩容实战

网心云OES Plus刷Armbian后系统迁移至SATA硬盘扩容实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 1:50:43 阅读更多 →
STM32H7高速HID实战:USB3300+ULPI物理层详解

STM32H7高速HID实战:USB3300+ULPI物理层详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 1:49:42 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →