多终端与应用管理 — 当「多个应用」照出所有隐忧¶
这一章讲什么: 按 F2 开第二个终端,一切开始真实起来: 光标在第二个终端不闪(#UD 异常)、多开几个应用整个系统崩、新窗口躲在旧窗口底下、 应用一个除零就把全系统冻住——压力测试是一面照妖镜,本章就是把镜子举起来的过程。 每修一个问题,系统就离「能给别人用」近一步:每任务一份页表、异常只杀应用不杀系统。
1. 先看现象:一串连环 bug¶
主线例子:
F2 → 第二终端出现,但光标不闪;另一个终端跑 cube、这边跑 lines → #UD 异常
修闪烁:光标定时器从「主任务代管」改为「终端任务自持」
(任务自己 AddTimer;窗口活动状态经 kWindowActive 消息告知;非活动不闪)
三终端各跑 lines/cube/rpn → 全系统崩
病因:应用都用「当时 CR3 里的页表」,还都把栈放在 0xffffffffffffe000
→ 第二个应用加载时,页表和栈全和第一个撞车
药方:每任务一份 CR3 —— 新任务复制内核 PML4、LoadELF 往这份里加应用映射,
上下文里记 cr3、切换时重设;退出时「先从任务摘掉、再 ResetCR3」
(顺序反了,切回任务时旧 PML4 又会被装回 CR3)
新窗口被旧窗口压住 → 新建图层即激活置顶
终端只是启动器的浪费 → 支持不开终端直接启应用
fault 命令(hlt / 写内核内存 / 除零)→ 异常处理程序看栈帧 CS: CPL=3 → KillApp
(复用 exit 的栈切换:ExitApp 把 RSP 换回 OS 栈、pop 还原、ret 回 ExecuteFile)
→ 应用死,系统活,终端打一行 app killed
2. 顶层全景¶
24.1 F2 多终端;24.2 光标自持定时器 + kWindowActive
24.3 多应用崩 → 每任务 CR3(复制内核页表;任务上下文带 cr3;退出顺序)
24.4 新窗口置顶;24.5 免终端启动应用(exit_after_command 雏形)
24.6 fault 应用证明「异常 = 全系统冻结」;24.7 KillApp/ExitApp(CPL=3 判定)
图说:这一章没有新理论,全是「多起来之后」的工程账。
3. 核心原理¶
3.1 多终端与自持光标¶
F2 生成新终端任务(终端任务的创建早已函数化)1。 两个 bug 跟着来:光标闪烁是「主任务一个定时器,到点转发给终端」—— 多终端时代理逻辑复杂且非活动窗口也闪;修法是终端任务自己管自己的 0.5s 定时器, 用 kWindowActive 消息跟踪活动状态,非活动不闪2。
3.2 每任务一份页表:多应用的生死线¶
三个应用同跑的崩溃,根因是第 19 章的简化:加载时往「当时 CR3 指向的页表」里加映射, 而所有应用还把栈放在同一个虚拟地址 0xffffffffffffe000——应用一多,页表和栈双双撞车3。 修法是把页表变成任务的属性:
新任务开工:复制内核 PML4 memcpy 出一份新的 → LoadELF 往里加应用映射
→ 这份 PML4 写进任务上下文的 cr3 → 每次任务切换重设 CR3
应用退出:先从任务结构摘除 PML4、再 ResetCR3(装回内核 PML4)
——顺序不能反:先 Reset 再摘除,任务被切回时旧 PML4 又会被装回 CR3, Reset 白做
4 「多个应用同时运行,是不是很神奇?我们终于可以称之为多任务操作系统了」5。
3.3 窗口置顶与免终端启动¶
新图层默认隐藏,显示时若不置顶,批量开应用时新窗口埋在底下—— 创建即激活补上6。 终端只是「启动应用并等它结束」的话,窗口纯浪费——支持不开终端直接启动应用 (一条命令行拉起应用任务)7。
3.4 KillApp:异常只杀应用¶
fault 命令三连(hlt = 特权指令、wr_kernel = 写内核低半空间、div0 = 除零) 证明:应用引发的 CPU 异常,当前会顺着内核的异常处理程序把全系统冻住8。 修法优雅地借了 exit 的遗产:
- 判定:中断/异常发生时的栈帧里有当时的 CS;CS 低两位是 CPL——0 = 内核闯的祸 (该慌,照旧打印栈帧),3 = 应用闯的祸(该杀)9;
- 处决:ExitApp() 是 SyscallEntry.exit 的翻版——把 RSP 换成 OS 栈指针、 pop 还原 CallApp 保存的寄存器、ret 直接回到 ExecuteFile 里 CallApp 的下一行; 效果是应用「替自己调用了 exit」,终端显示 app killed,系统纹丝不动10。
——第 21 章留的习题「异常杀应用」,本章交了卷。
4. 作者的判断与证据¶
判断:压力测试是最好的架构评审。 单应用时代所有「先这么着」的简化(共用页表、 固定栈地址、主任务代管定时器)在多应用面前一起爆开;本章几乎没有新理论, 全是还债——还第 19 章的简化债。
判断:杀应用的正确姿势是复用 exit。 ExitApp 复制 SyscallEntry.exit 而非新造机制: 「执行最后一个 ret 后,将返回 CallApp 的下一行」——进程的两种死亡(自杀、他杀) 在汇编层面共享同一段收尾。
证据: 多应用并发截图(图 24.5)、fault 命令的除零现场(图 24.7)、 KillApp 后系统存活;CR3 顺序的讲解用了反事实推演(顺序反了会怎样)。
5. 边界与局限¶
- 每任务复制整份内核 PML4 是内存浪费(12 章内存进阶才有更省的方案影子);
- argv 页、应用栈地址仍然是固定虚拟地址——应用再多,地址规划还会撞(教学尺度);
- KillApp 统一报一个错误码(脚注说未来该是 128+SIGSEGV 那样);
- 免终端启动只是雏形:没有应用管理器、没有桌面图标。
6. 可带走的¶
本章主线例子,一行写完: F2 多终端 → 光标自持 → 多应用撞页表/撞栈 → 每任务 CR3 + 退出顺序 → 新窗置顶 → fault 三连 → CPL=3 判应用 + ExitApp 杀应用不杀系统。
- 多起来才见真章:单实例能跑的简化,并发一起爆;
- 光标这类「每窗口状态」要下放到窗口自己的任务自持,别由全局代理;
- 页表是任务的属性:每任务一份 CR3,上下文里记 cr3,切换重设;
- 退出清理顺序即语义:先摘任务再 ResetCR3,反了会被切回时复活;
- 新窗口创建即置顶,是批量开应用的基本礼貌;
- 异常栈帧里的 CS(低两位 CPL)是「谁在闯祸」的判决书;
- 杀应用 = 替它执行 exit:换 OS 栈、还原寄存器、ret 回 CallApp 的下一行。
7. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| F2 多终端 | 24.1 增加终端数量(osbook_day24a) | text/155-ch24-01-24-1-osbook-day24a.txt:17(搜「F2键」) |
| 光标自持与 kWindowActive | 24.2 光标自动闪烁(osbook_day24b) | text/156-ch24-02-24-2-osbook-day24b.txt:12(搜「终端本身会控制」) · text/156-ch24-02-24-2-osbook-day24b.txt:19(搜「kWindowActive」) |
| 多应用崩溃与栈冲突 | 24.3 同时启动多个应用(osbook_day24c) | text/157-ch24-03-24-3-osbook-day24c.txt:17(搜「CPU异常」) · text/157-ch24-03-24-3-osbook-day24c.txt:24(搜「虚拟内存空间」) · text/157-ch24-03-24-3-osbook-day24c.txt:29(搜「0xffffffffffffe000」) |
| 每任务 CR3 与顺序 | 24.3 同时启动多个应用(osbook_day24c) | text/157-ch24-03-24-3-osbook-day24c.txt:81(搜「TaskContext::cr3」) · text/157-ch24-03-24-3-osbook-day24c.txt:91(搜「memcpy」) · text/157-ch24-03-24-3-osbook-day24c.txt:124(搜「顺序很重要」) · text/157-ch24-03-24-3-osbook-day24c.txt:130(搜「ResetCR3」) |
| 多任务 OS 宣言 | 24.3 同时启动多个应用(osbook_day24c) | text/157-ch24-03-24-3-osbook-day24c.txt:141(搜「终于可以称之为多任务操作系统」) |
| 新窗口置顶 | 24.4 修复窗口重叠的错误(osbook_day24d) | text/158-ch24-04-24-4-osbook-day24d.txt:3(搜「隐藏在其他窗口之下」) |
| 免终端启动 | 24.5 不需要终端即可启动应用(osbook_day24e) | text/159-ch24-05-24-5-osbook-day24e.txt:12(搜「不需要打开终端」) |
| fault 应用三连 | 24.6 冻结操作系统的应用(osbook_day24f) | text/160-ch24-06-24-6-osbook-day24f.txt:16(搜「冻结整个操作系统」) · text/160-ch24-06-24-6-osbook-day24f.txt:34(搜「fault命令」) · text/160-ch24-06-24-6-osbook-day24f.txt:50(搜「除零异常」) |
| CPL=3 判定与 ExitApp | 24.7 保护操作系统(2)(osbook_day24g) | text/161-ch24-07-24-7-2-osbook-day24g.txt:8(搜「CS的低3位」) · text/161-ch24-07-24-7-2-osbook-day24g.txt:12(搜「复制和粘贴」) · text/161-ch24-07-24-7-2-osbook-day24g.txt:33(搜「KillApp」) |
-
出处:「24.1 增加终端数量」(
text/155-ch24-01-24-1-osbook-day24a.txt:17,搜「F2键」)。 ↩ -
出处:「24.2 光标自动闪烁」(
text/156-ch24-02-24-2-osbook-day24b.txt:12,搜「终端本身会控制」;:19,搜「kWindowActive」)。 ↩ -
出处:「24.3 同时启动多个应用」(
text/157-ch24-03-24-3-osbook-day24c.txt:17,搜「CPU异常」;:24,搜「虚拟内存空间」;:29,搜「0xffffffffffffe000」)。 ↩ -
出处:「24.3 同时启动多个应用」(
text/157-ch24-03-24-3-osbook-day24c.txt:81,搜「TaskContext::cr3」;:90,搜「memcpy」;:124,搜「顺序很重要」;:130,搜「ResetCR3」)。 ↩ -
出处:「24.3 同时启动多个应用」(
text/157-ch24-03-24-3-osbook-day24c.txt:141,搜「多任务操作系统」)。 ↩ -
出处:「24.4 修复窗口重叠的错误」(
text/158-ch24-04-24-4-osbook-day24d.txt:3,搜「隐藏在其他窗口之下」;:29,搜「高度为-1」)。 ↩ -
出处:「24.5 不需要终端即可启动应用」(
text/159-ch24-05-24-5-osbook-day24e.txt:12,搜「不需要打开终端即可启动应用」;:12,搜「很碍事」)。 ↩ -
出处:「24.6 冻结操作系统的应用」(
text/160-ch24-06-24-6-osbook-day24f.txt:16,搜「冻结整个操作系统」;:34,搜「fault命令」;:50,搜「除零异常」)。 ↩ -
出处:「24.7 保护操作系统(2)」(
text/161-ch24-07-24-7-2-osbook-day24g.txt:8,搜「CS的低3位」;:9,搜「CPL字段」)。 ↩ -
出处:「24.7 保护操作系统(2)」(
text/161-ch24-07-24-7-2-osbook-day24g.txt:12,搜「复制和粘贴」;:33,搜「KillApp」;:63,搜「跳转到CallApp的下一行」)。 ↩