终端命令 — 从能打字到能干活,以及最后一次空转
终端命令 — 从能打字到能干活,以及最后一次空转
这一章讲什么: 终端已经会闪光标了,本章让它能接收键盘、解析命令、执行命令: 按键怎么找到该收它的任务?靠一张「图层 ID → 任务 ID」的查找表。 按下的字符进行缓冲,回车后拆成「命令名 + 参数」,就是 echo; clear 四行代码、lspci 顺手复用第 6 章的 PCI 表——内核里每成熟一块,命令就多一个。 章末删掉空转的演示任务,CPU 占用从 100%+ 掉到百分之几:这是对「休眠」这门手艺的总结性回报。
1. 先看现象:一行命令的旅程
主线例子,输入 echo hello 并回车:
按键 'e' → 主任务收到 kKeyPush → layer_task_map 查活动图层 ID → 命中终端任务
→ SendMessage(终端) → 终端任务 InputKey():字符进 linebuf_ 并上屏
回车 → InputKey('\n') 提交:ExecuteLine()
linebuf_ = "echo hello\0"
strchr 找空格 → 在空格处写 '\0',拆成 "echo\0hello"
strcmp "echo" → Print("hello") + 换行
提示符 '>' 再次亮起;命令进历史(环形缓冲头部)
↑ 键 → HistoryUpDown(+1) → 把 cmd_history_[index] 拷回 linebuf_ 显示2. 顶层全景
16.1 按键路由:linebuf_ 行缓冲;InputKey()(字符/退格/换行/滚动);
layer_task_map:图层 ID → 任务 ID,std::map
16.2 echo:提示符 '>';Print();ExecuteLine() = 找空格 + 写 NUL 拆分
16.3 clear:四行命令(黑屏 + 光标回左上)
16.4 lspci:遍历第 6 章的 pci::devices,打印总线号与类 ID
16.5 历史:8 条环形缓冲 + cmd_history_index_(-1=未翻阅);
C 语言陷阱:-1 与 size_t 比较会先被转成无符号巨大数
16.6 省电:删除 TaskB;QEMU CPU 占用 >100% → 数 %
图说:命令系统 = 路由(给谁)+ 解析(是什么)+ 执行(现成内核功能的壳)。3. 核心原理
3.1 按键路由:layer_task_map
「键盘输入发给活动窗口」落地成一张查找表:图层 ID → 任务 ID 的 std::map, 窗口属于哪个任务,任务启动时自己注册1。主任务收到 kKeyPush,先查活动图层, 命中就把消息原样转 SendMessage 给对应任务——终端于是有了自己的输入流2。 终端侧 InputKey() 处理四种键:普通字符(进 linebuf_ 尾部并上屏)、退格(擦掉一个)、 换行(提交这一行,滚屏交给 Scroll1())3。linebuf_ 的存在理由:要对输入做处理, 就得留着这行字,不能只画完就丢4。
3.2 命令解析:写个 NUL 就是切开
ExecuteLine() 的解析朴素得可爱:strchr 找第一个空格,找到就在原地把空格改成 '\0'—— 一行字符串就此切成「命令名\0参数」两段,first_arg 指向空格后一位; 然后 strcmp 分发命令5。echo 就是 Print(first_arg);未知命令打印 no such command。 提示符 '>' 在构造终端时先 Print 一次,每条命令执行完再 Print 一次——「系统已准备好接收命令」 的标志6。Print() 与 InputKey() 的分工:Print 不动行缓冲,只有用户输入才记 buffer7。
3.3 三个命令的含金量
- clear:整屏涂黑 + 光标归零,四行8——「迄今为止所做的所有准备工作都得到了回报」;
- lspci:遍历 pci::devices 打印
bus:dev.func vendor=xxxx class=cc.ss.ii, 其中 0c.03.30 就是第 6 章找的 xHC9。命令系统的美妙之处:内核数据此刻对用户可见了; - echo:参数原样打印,长串自动绕右边缘换行10。
3.4 命令历史与一个 C 语言陷阱
历史是 8 条命令的环形数组:新命令插头部(0 号位),末尾挤掉一条; cmd_history_index_ = -1 表示「没在翻历史」,↑= +1(更旧)、↓= −1(更新)11。
这里有一个被认真展开的 C 陷阱:cmd_history_index_ + 1 < cmd_history_.size()
不能写成 cmd_history_index_ < cmd_history_.size() - 1——
index 为 -1 时,与无符号的 size_t 比较,-1 先被转换成 size_t 能表示的最大值,
「明明该真」的条件变假,历史翻阅直接失灵12。
有符号遇上无符号,一律先转无符号——这个坑在系统代码里埋得到处都是。
3.5 省电:最后一次空转
TaskB(那个一直全速计数的演示任务)到此完成历史使命——它「毫无用处,还浪费电」, 笔记本电脑电池供电时尤其碍事,于是整段删除13。 效果立竿见影:QEMU 的 CPU 占用从常年超 100% 掉到最多百分之几14。 机制不神秘:主任务没消息就睡、终端没消息就睡、全睡时空闲任务 hlt—— hlt 是「CPU 真的没有活」的表达方式,到这一章,这套体系终于闭环。 (第 10 章快速计数器拿掉 hlt 的伏笔,第 14 章省电的伏笔,都在此兑现。)
4. 作者的判断与证据
判断:命令 = 内核功能的壳。 lspci 没有一行新硬件代码,全是第 6 章数据的格式化输出; 「自制操作系统」的成就感很大程度来自这种时刻——作者明写「能在自己的操作系统上执行 lspci 命令,是不是很厉害?着实令人印象深刻」15。
判断:该删就删。 演示任务说删就删,性能回报(QEMU 占用)当场测量。功能的生命周期 意识,教学书里少见。
证据: 每节一张截图(输入、echo、clear、lspci、历史翻阅、删除 TaskB); CPU 占用有前后对照;历史 bug 有反例推演(-1 变最大值)而非一句「小心」。
5. 边界与局限
- 命令解析是最简版:没有引号、没有转义、没有管道(第 29 章)、参数只有一个(first_arg);
- 历史不持久:重启即失,不落盘;
- 按键路由只认「活动图层」:没有焦点的概念,文本框和终端抢输入要靠手动切活动窗口;
- 省电依赖所有任务守规矩:谁要是死循环不睡,风扇照旧起飞(第 24 章会收拾这种应用)。
6. 可带走的
本章主线例子,一行写完: 按键 → layer_task_map 路由 → 行缓冲 → Enter → NUL 拆分命令与参数 → 分发执行 → 历史插头部;删 TaskB 后 CPU 占用 >100% → 数 %。
- 「发给活动窗口」= 图层 ID → 任务 ID 查找表;路由表谁启动谁注册;
- 要对输入做处理就得有行缓冲;Enter 提交,提示符表示「等待下一条」;
- 解析的一招鲜:原地写 NUL 切开命令名与参数;
- 命令 = 内核数据的壳:lspci 零新硬件代码,展示既有数据即可;
- 环形历史 + 遍历索引;有符号与 size_t 比较必转无符号,
-1 < size-1是假; - hlt + 全员休眠 = CPU 占用骤降;演示代码的生命周期也该管理。
7. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| linebuf_ 与 InputKey | 16.1 终端中的按键操作(osbook_day16a) | text/104-ch16-01-16-1-osbook-day16a.txt:7(搜「缓冲区linebuf_」) · text/104-ch16-01-16-1-osbook-day16a.txt:23(搜「InputKey」) |
| layer_task_map 路由 | 16.1 终端中的按键操作(osbook_day16a) | text/104-ch16-01-16-1-osbook-day16a.txt:59(搜「layer_task_map」) · text/104-ch16-01-16-1-osbook-day16a.txt:75(搜「检索与活动图层ID相对应」) |
| 提示符与 Print | 16.2 echo命令(osbook_day16b) | text/105-ch16-02-16-2-echo-osbook-day16b.txt:32(搜「提示符」) · text/105-ch16-02-16-2-echo-osbook-day16b.txt:39(搜「Print()是将实现的函数」) |
| ExecuteLine 的 NUL 拆分 | 16.2 echo命令(osbook_day16b) | text/105-ch16-02-16-2-echo-osbook-day16b.txt:67(搜「ExecuteLine」) · text/105-ch16-02-16-2-echo-osbook-day16b.txt:73(搜「NUL字符」) |
| clear 四行 | 16.3 clear命令(osbook_day16c) | text/106-ch16-03-16-3-clear-osbook-day16c.txt:6(搜「四行代码」) |
| lspci 复用 pci::devices | 16.4 lspci命令(osbook_day16d) | text/107-ch16-04-16-4-lspci-osbook-day16d.txt:5(搜「pci::devices」) |
| 历史环形缓冲 | 16.5 命令历史记录(osbook_day16e) | text/108-ch16-05-16-5-osbook-day16e.txt:6(搜「cmd_history_」) · text/108-ch16-05-16-5-osbook-day16e.txt:16(搜「8条」) |
| -1 与 size_t 陷阱 | 16.5 命令历史记录(osbook_day16e) | text/108-ch16-05-16-5-osbook-day16e.txt:54(搜「不好的写法」) · text/108-ch16-05-16-5-osbook-day16e.txt:61(搜「最大数字」) |
| 删除 TaskB 与省电 | 16.6 省电(osbook_day16f) | text/109-ch16-06-16-6-osbook-day16f.txt:2(搜「风扇」) · text/109-ch16-06-16-6-osbook-day16f.txt:11(搜「100%」) · text/109-ch16-06-16-6-osbook-day16f.txt:11(搜「百分之几」) |
Footnotes
-
出处:「16.1 终端中的按键操作(osbook_day16a)」(
text/104-ch16-01-16-1-osbook-day16a.txt:7(搜「缓冲区linebuf_」);text/104-ch16-01-16-1-osbook-day16a.txt:23(搜「InputKey」))。 ↩ -
出处:「16.1 终端中的按键操作(osbook_day16a)」(
text/104-ch16-01-16-1-osbook-day16a.txt:59(搜「layer_task_map」);text/104-ch16-01-16-1-osbook-day16a.txt:75(搜「检索与活动图层ID相对应」))。 ↩ -
出处:「16.2 echo命令(osbook_day16b)」(
text/105-ch16-02-16-2-echo-osbook-day16b.txt:32(搜「提示符」);text/105-ch16-02-16-2-echo-osbook-day16b.txt:39(搜「Print()是将实现的函数」))。 ↩ -
出处:「16.2 echo命令(osbook_day16b)」(
text/105-ch16-02-16-2-echo-osbook-day16b.txt:67(搜「ExecuteLine」);text/105-ch16-02-16-2-echo-osbook-day16b.txt:73(搜「NUL字符」))。 ↩ -
出处:「16.3 clear命令(osbook_day16c)」(
text/106-ch16-03-16-3-clear-osbook-day16c.txt:6(搜「四行代码」))。 ↩ -
出处:「16.4 lspci命令(osbook_day16d)」(
text/107-ch16-04-16-4-lspci-osbook-day16d.txt:5(搜「pci::devices」))。 ↩ -
出处:「16.5 命令历史记录(osbook_day16e)」(
text/108-ch16-05-16-5-osbook-day16e.txt:6(搜「cmd_history_」);text/108-ch16-05-16-5-osbook-day16e.txt:16(搜「8条」))。 ↩ -
出处:「16.5 命令历史记录(osbook_day16e)」(
text/108-ch16-05-16-5-osbook-day16e.txt:54(搜「不好的写法」);text/108-ch16-05-16-5-osbook-day16e.txt:61(搜「最大数字」))。 ↩ -
出处:「16.6 省电(osbook_day16f)」(
text/109-ch16-06-16-6-osbook-day16f.txt:2(搜「风扇」);text/109-ch16-06-16-6-osbook-day16f.txt:11(搜「100%」);text/109-ch16-06-16-6-osbook-day16f.txt:11(搜「百分之几」))。 ↩ -
出处:「16.1 终端中的按键操作(osbook_day16a)」(
text/104-ch16-01-16-1-osbook-day16a.txt:7(搜「缓冲区linebuf_」);text/104-ch16-01-16-1-osbook-day16a.txt:23(搜「InputKey」))。 ↩ -
出处:「16.2 echo命令(osbook_day16b)」(
text/105-ch16-02-16-2-echo-osbook-day16b.txt:32(搜「提示符」);text/105-ch16-02-16-2-echo-osbook-day16b.txt:39(搜「Print()是将实现的函数」))。 ↩ -
出处:「16.4 lspci命令(osbook_day16d)」(
text/107-ch16-04-16-4-lspci-osbook-day16d.txt:5(搜「pci::devices」))。 ↩ -
出处:「16.5 命令历史记录(osbook_day16e)」(
text/108-ch16-05-16-5-osbook-day16e.txt:6(搜「cmd_history_」);text/108-ch16-05-16-5-osbook-day16e.txt:16(搜「8条」))。 ↩ -
出处:「16.6 省电(osbook_day16f)」(
text/109-ch16-06-16-6-osbook-day16f.txt:2(搜「风扇」);text/109-ch16-06-16-6-osbook-day16f.txt:11(搜「100%」);text/109-ch16-06-16-6-osbook-day16f.txt:11(搜「百分之几」))。 ↩ -
出处:「16.3 clear命令(osbook_day16c)」(
text/106-ch16-03-16-3-clear-osbook-day16c.txt:6(搜「四行代码」))。 ↩