上一章的结尾留下一个问题:程序只会往管子里塞字节,那屏幕上的字是谁画出来的?答案是:终端模拟器(terminal emulator)——一个和你电脑上的浏览器、微信完全同类的普通图形程序。iTerm2、macOS 自带的 Terminal.app、Linux 上的 xterm 和 GNOME Terminal、Windows 上的 Windows Terminal,全都是它。
名字里「模拟」两个字是字面意思:它在用软件模拟一台根本不存在的硬件——第一章里那台 1978 年的 VT100。你的电脑里没有串口线、没有荧光屏,但这个程序认认真真地扮演一台哑终端,认真到内核都分不出来。
模拟器的内心戏:一张字符表格
一台终端的「屏幕状态」其实出奇的简单,就是一个二维表格。比如 80 列 × 24 行,每个格子里记着三样东西:这里显示哪个字符、用什么颜色、要不要加粗。再加一个「光标现在在哪格」。这就是全部。
终端模拟器内部就维护着这样一张表。它从字节流里读字节,每读一个就更新表:
- 读到普通字符(比如
h):写进光标所在的格子,光标右移一格。 - 读到回车:光标跳回本行第一列。读到换行:光标下移一行,到底部就把整张表往上滚一行——这就是「屏幕滚动」的真身,一次表格搬家。
- 读到一串「指令字节」(下一章细讲):改颜色、挪光标、清掉某行——都只是改表格里的记录。
表更新完,模拟器把整张表重新画到窗口上:查字体文件找到每个字符的形状(这叫字形渲染),按格子记录的颜色涂上去。现代模拟器(iTerm2、Alacritty、Windows Terminal)会把这活交给显卡(GPU)干,因为「画一大堆小方块」正是显卡的老本行。
所以终端里其实没有「文字在屏幕上跑」。真相是:字节流进来 → 内存里的表格被改写 → 表格整体重画。你看到的滚动、闪烁、变色,全是这张表格的状态在变化。
滚回去看历史:那是表格的「仓库」
滚出屏幕顶部的行并没有消失。模拟器把它们存进一个叫回滚缓冲区(scrollback buffer)的仓库,所以你才能往上滚,看到十分钟前的输出。这也解释了一个常见误会:往上滚看到的内容,程序早就忘光了——那是模拟器自己的存货,跟正在跑的程序毫无关系。
字节到像素的完整流水线
把这一章和上一章合起来,一次输出的完整旅程是:
- 程序往 stdout 写字节;
- 内核把字节送到终端模拟器(中间经过第七章要讲的假终端);
- 模拟器的「状态机」按 VT100 的规矩解读字节,更新那张字符表格;
- 渲染器查字体、涂颜色,把表格画成窗口里的像素。
程序在整个过程中完全不知道像素的存在——这既是限制(终端里画不了真正的图表),也是自由(同一个程序在任何终端、任何系统上都能跑)。
字符也有「占地面积」:宽字符的坑
那张表格有个隐藏设定:每个格子宽度一样,这样光标位置才好算。但中文字符和 emoji 显然比一个字母宽——于是约定它们占两格(全角宽度)。问题来了:「这个字符占几格」是程序(算排版时)和模拟器(画格子时)各自猜的,两边用的宽字符表版本不同就会猜岔——这就是「中文对齐总是差一格」「某些 emoji 把表格顶歪」的根源。不是玄学,是两个程序对同一个字符的「占地面积」意见不合。
术语大扫除:terminal、console、shell
最后顺手清理三个老被混用的词:
- terminal(终端):本章主角。原本指哑终端硬件,今天指模拟它的那个窗口程序。
- console(控制台):原本指主机机身上那台「 operator 专用」的终端——系统出大事、没人能登录时,最后的喊话通道。今天 Linux 里
/dev/console、服务器机房里接显示器键盘的口,还留着这个意思。 - shell:你在终端里看到的那个
$提示符背后的程序(bash、zsh)。它和终端是两个独立的程序,只是碰巧总一起出现——就像「驾校教练」和「教练车」总一起出现,但你完全可以换车或换教练。这一对在下一章拆开了看。
一句话总结:终端模拟器 = 一张「哪个格子放什么字符」的表格 + 一台按 VT100 规矩改表格的状态机 + 一支把表格画成像素的画笔。iTerm2 们只是这套角色的不同演员。
不过,上面只讲了「普通字符怎么显示」。那串能改颜色、挪光标、清屏的「指令字节」——真正的魔法——值得单独一章。