跳转至

多终端与应用管理 — 当「多个应用」照出所有隐忧

这一章讲什么: 按 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 杀应用不杀系统。

  1. 多起来才见真章:单实例能跑的简化,并发一起爆;
  2. 光标这类「每窗口状态」要下放到窗口自己的任务自持,别由全局代理;
  3. 页表是任务的属性:每任务一份 CR3,上下文里记 cr3,切换重设;
  4. 退出清理顺序即语义:先摘任务再 ResetCR3,反了会被切回时复活;
  5. 新窗口创建即置顶,是批量开应用的基本礼貌;
  6. 异常栈帧里的 CS(低两位 CPL)是「谁在闯祸」的判决书;
  7. 杀应用 = 替它执行 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」)

  1. 出处:「24.1 增加终端数量」(text/155-ch24-01-24-1-osbook-day24a.txt:17,搜「F2键」)。 ↩

  2. 出处:「24.2 光标自动闪烁」(text/156-ch24-02-24-2-osbook-day24b.txt:12,搜「终端本身会控制」;:19,搜「kWindowActive」)。 ↩

  3. 出处:「24.3 同时启动多个应用」(text/157-ch24-03-24-3-osbook-day24c.txt:17,搜「CPU异常」;:24,搜「虚拟内存空间」;:29,搜「0xffffffffffffe000」)。 ↩

  4. 出处:「24.3 同时启动多个应用」(text/157-ch24-03-24-3-osbook-day24c.txt:81,搜「TaskContext::cr3」;:90,搜「memcpy」;:124,搜「顺序很重要」;:130,搜「ResetCR3」)。 ↩

  5. 出处:「24.3 同时启动多个应用」(text/157-ch24-03-24-3-osbook-day24c.txt:141,搜「多任务操作系统」)。 ↩

  6. 出处:「24.4 修复窗口重叠的错误」(text/158-ch24-04-24-4-osbook-day24d.txt:3,搜「隐藏在其他窗口之下」;:29,搜「高度为-1」)。 ↩

  7. 出处:「24.5 不需要终端即可启动应用」(text/159-ch24-05-24-5-osbook-day24e.txt:12,搜「不需要打开终端即可启动应用」;:12,搜「很碍事」)。 ↩

  8. 出处:「24.6 冻结操作系统的应用」(text/160-ch24-06-24-6-osbook-day24f.txt:16,搜「冻结整个操作系统」;:34,搜「fault命令」;:50,搜「除零异常」)。 ↩

  9. 出处:「24.7 保护操作系统(2)」(text/161-ch24-07-24-7-2-osbook-day24g.txt:8,搜「CS的低3位」;:9,搜「CPL字段」)。 ↩

  10. 出处:「24.7 保护操作系统(2)」(text/161-ch24-07-24-7-2-osbook-day24g.txt:12,搜「复制和粘贴」;:33,搜「KillApp」;:63,搜「跳转到CallApp的下一行」)。 ↩