计算机系统一页通
这一页不追求把计算机专业课程压缩成几段结论,而是把后面会反复遇到的概念先放到正确位置:程序从哪里获得算力,数据放在哪一层,操作系统怎样隔离进程,网络怎样把两台机器连起来,源码又怎样变成可执行行为。
不需要会设计 CPU 或手写编译器,但必须知道一件事发生在哪一层。否则看到“服务很慢”时,只能在重装依赖、改代码和重启服务器之间乱猜。
1. 从晶体管到 CPU
跳转到“1. 从晶体管到 CPU”计算机最底层利用的是可控开关。晶体管可以表示开与关,组合起来形成与门、或门、非门等逻辑门;逻辑门再组成加法器、寄存器、选择器和控制电路。大量电路协同工作,才形成能执行指令的处理器。
CPU 不需要理解“用户”“文件”或“网页”。它只做一件重复的事:取指令、译码、执行、写回,然后继续下一条。这个循环叫指令周期。
取指令 → 译码 → 取操作数 → 执行 → 写回 → 更新下一条地址CPU 能执行的指令集合叫指令集架构(ISA)。CISC 倾向用较复杂、长度可变的指令完成更多工作;RISC 倾向使用简单、规整的指令,把复杂操作交给多条指令或编译器。两者不是“谁绝对更快”,而是设计取舍。
现代 CPU 还会使用流水线:把取指、译码、执行等阶段重叠起来,让同一时刻有不同指令处于不同阶段。流水线会遇到三类冒险:
- 结构冒险:两个阶段争用同一个硬件资源。
- 数据冒险:后一条指令需要前一条尚未写回的结果。
- 控制冒险:分支跳转让 CPU 不知道下一条该取哪里。
分支预测、乱序执行和多核并行都建立在“尽量不让流水线停下来”这个目标上。多核提高并行能力,但共享内存、缓存一致性和锁也会带来新的复杂度。
2. 存储层次:CPU 与真实世界之间的速度差
跳转到“2. 存储层次:CPU 与真实世界之间的速度差”计算机各层存储的速度和容量差异极大。寄存器最快但极少,缓存次之,内存更大更慢,SSD 和机械盘更慢,跨网络访问则可能慢几个数量级。
| 层级 | 大致角色 | 工程含义 |
|---|---|---|
| 寄存器 | CPU 正在使用的值 | 编译器与指令调度 |
| L1/L2/L3 缓存 | 热点数据与指令 | 顺序访问、数据局部性很重要 |
| 主存 | 当前进程的工作区 | 内存不足会换页甚至被杀进程 |
| SSD / HDD | 持久化文件与数据库 | 随机读写、顺序写、fsync 成本不同 |
| 网络与远端存储 | 其他机器上的数据 | 延迟、丢包、超时和带宽成为主问题 |
缓存通常按缓存行读取,而不是只读一个字节。顺序遍历数组往往比随机追逐链表节点更快,因为前者更容易利用空间局部性。数据库索引、日志追加写和列式存储,本质上都在优化数据访问模式。
CPU 访问内存时使用虚拟地址。虚拟内存让每个进程看到连续、独立的地址空间,操作系统和硬件通过页表把虚拟页映射到物理页。页表项不在缓存中时还要查 TLB。这样进程之间不必知道彼此占用哪块物理内存,也难以直接越界访问。
当内存压力过大时,系统会把暂时不用的页换到磁盘。换页一旦频繁发生,程序表面上还在运行,实际却会慢到接近卡死。这也是为什么“看 CPU 占用不高”不能证明服务健康。
3. 操作系统:把硬件包装成可用资源
跳转到“3. 操作系统:把硬件包装成可用资源”操作系统主要管理四类资源:CPU、内存、文件与设备、网络。它同时提供隔离和抽象,让应用程序不用直接操作硬件。
3.1 内核态、用户态与系统调用
跳转到“3.1 内核态、用户态与系统调用”普通程序运行在用户态,不能随意访问硬件或其他进程的内存。需要读写文件、创建进程、访问网络或申请内存时,程序通过系统调用请求内核执行。一次系统调用意味着特权级切换,不是普通函数调用;高频小系统调用可能成为性能瓶颈。
3.2 进程与线程
跳转到“3.2 进程与线程”进程是资源隔离的基本单位,拥有地址空间、打开的文件、环境变量和权限。线程是 CPU 调度单位,同一进程内的线程共享地址空间和文件资源。
进程之间默认隔离,通信需要管道、共享内存、socket 或消息系统。线程之间共享内存更方便,但共享可变数据时必须同步,否则会出现竞态条件、死锁或数据损坏。
操作系统调度器决定哪个线程何时运行。时间片轮转只是最直观的模型;真实调度会考虑优先级、等待时间、CPU 亲和性和 I/O 阻塞。线程数量远远超过 CPU 核心数时,上下文切换和排队成本会上升。
3.3 文件系统
跳转到“3.3 文件系统”文件系统把数据块组织成目录、文件、权限和元数据。路径是用户看到的抽象;底层还有 inode、块分配、日志、缓存和挂载点。打开文件会获得文件描述符,它是进程内的句柄,不只是“一个路径字符串”。
写入文件也不自动等于安全落盘。应用写入、操作系统页缓存、磁盘缓存和 fsync 控制着不同阶段。数据库需要在“性能”和“断电后不丢已经承诺的数据”之间明确取舍。
3.4 信号与进程生命周期
跳转到“3.4 信号与进程生命周期”进程可以被启动、暂停、终止,也可以接收信号。例如 SIGINT 常由 Ctrl+C 发出,SIGTERM 请求进程正常退出,SIGKILL 强制结束。服务收到终止信号后应先停止接收新请求、完成或取消正在处理的任务,再关闭连接。
4. 从按下电源到浏览器打开
跳转到“4. 从按下电源到浏览器打开”开机能串成一条路径,但每一段都可以单独排查:
电源与固件(UEFI) ↓ 找到启动设备启动加载器 ↓ 加载内核操作系统内核初始化 ↓ 启动第一个用户态进程init / systemd 启动服务 ↓ 拉起登录、桌面、网络浏览器发出第一个请求固件负责最基本的硬件初始化,并找到可启动设备。启动加载器把内核读入内存。内核建立内存管理、调度器、驱动和文件系统,再启动用户态 init。systemd 等 init 系统按依赖关系启动网络、日志、数据库和应用服务。
桌面环境、浏览器、代理和 DNS 都在更上层。系统“黑屏”“SSH 连不上”“服务没启动”“网页 502”是完全不同的故障层,不应从同一个“重装试试”开始。
5. 数据怎样表示
跳转到“5. 数据怎样表示”计算机最终只处理位和字节。一个字节是 8 个比特,可以表示 0 到 255。字符、整数、浮点数、图片和程序都是对字节的解释方式。
5.1 文本编码
跳转到“5.1 文本编码”ASCII 只覆盖早期英文字符。Unicode 给世界上的字符分配码点,UTF-8 则把码点编码成 1 到 4 个字节。UTF-8 与 ASCII 兼容,并且英文字符仍然只占一个字节,因此成为 Web 和大多数源代码的默认选择。
“字符数”与“字节数”不是一回事。一个中文字符在 UTF-8 中通常占 3 个字节;在 JavaScript 字符串和某些语言里,还可能按 UTF-16 代码单元计数。处理长度限制、截断和数据库字段时,要明确单位是字符、码点还是字节。
5.2 数字与浮点
跳转到“5.2 数字与浮点”整数有位数、符号和溢出问题。无符号整数不能表示负数;有符号整数溢出后的行为取决于语言和运行时。时间戳、金额和 ID 要特别关注精度与范围。
浮点数使用 IEEE 754 等格式近似表示实数,不能精确表示所有十进制小数。0.1 + 0.2 !== 0.3 不是语言“坏了”,而是二进制浮点的正常结果。金额通常使用整数最小货币单位或十进制定点类型;需要精确比较的 ID 也不要依赖浮点。
5.3 媒体与压缩
跳转到“5.3 媒体与压缩”图片通常保存像素、颜色空间和压缩信息;音频按采样率与位深记录波形;视频由帧、编码参数和时间戳组成。JPEG、PNG、WebP 的差异不只是文件名,压缩方式、透明通道和适用场景都不同。
压缩分为无损与有损。无损要求解码后完全还原,适合源码和配置;有损会丢弃部分信息换取更小体积,适合照片和音频。传输时还常用校验和或哈希发现损坏,但校验和只能证明内容是否变化,不能证明来源可信。
6. 网络:数据怎样跨机器移动
跳转到“6. 网络:数据怎样跨机器移动”网络协议可以按层理解,但不必从背 OSI 七层开始。工程上更常问的是:域名怎样变成 IP,包怎样到达主机,哪个进程接收,连接是否可靠,应用层消息是什么格式。
URL → DNS 解析 → IP 与端口 → TCP/UDP 传输 → TLS(HTTPS 时) → HTTP 请求/响应 → 浏览器或客户端处理DNS 把域名解析成 IP。IP 负责把数据包送到目标主机,端口把流量交给具体进程。TCP 提供有序、可靠、面向连接的字节流,适合 HTTP 和数据库;UDP 不保证送达与顺序,但开销小,适合实时音视频、游戏和部分 DNS 查询。
HTTP 是应用层协议,定义方法、路径、头部、状态码和消息体。HTTPS 在 HTTP 与 TCP 之间加入 TLS,完成身份验证、密钥协商和加密。TLS 不是“把整个网站加密一遍”这么简单,证书链、域名匹配、过期时间、代理终止和协议版本都会影响结果。
延迟和带宽是两个不同指标。带宽决定大文件传得多快,延迟决定一次往返要多久。小请求很多时,往返次数和排队往往比带宽更重要。
7. 数据结构与算法:为访问模式选结构
跳转到“7. 数据结构与算法:为访问模式选结构”数据结构不是面试清单,而是对“数据怎样被访问”的回答。
| 结构 | 典型操作 | 优势 | 代价 |
|---|---|---|---|
| 数组 | 下标访问、遍历 | 连续内存、缓存友好 | 中间插入删除要搬移 |
| 链表 | 已知节点后插入删除 | 结构灵活 | 随机访问差,指针开销大 |
| 栈 | 后进先出 | 调用栈、解析、撤销 | 只适合受限访问顺序 |
| 队列 | 先进先出 | 任务调度、消息缓冲 | 可能积压,需要背压 |
| 哈希表 | 按 key 查找 | 平均查找快 | 无序、冲突、内存开销 |
| 二叉搜索树 | 有序查找与范围查询 | 中序遍历有序 | 不平衡会退化 |
| 堆 | 取最大/最小值 | 优先队列、调度 | 不支持高效任意查找 |
| 图 | 节点与边关系 | 路径、依赖、社交网络 | 存储与遍历复杂 |
| 倒排索引 | 关键词 → 文档 | 全文搜索 | 构建、更新与存储成本 |
复杂度描述输入增大时成本怎样增长。二分查找要求数据有序,每次排除一半,时间复杂度是 O(log n);哈希表平均 O(1) 查找的前提是哈希分布合理且没有严重冲突。大 O 不包含常数,也不考虑网络和磁盘,所以工程中还要看实际数据量、缓存命中和访问模式。
递归把问题拆成同类的子问题,必须定义终止条件,否则会栈溢出。贪心每步选择当前最优,只有在问题满足特定性质时才保证全局最优。排序、搜索、图遍历和动态规划都是常用工具,但大多数业务系统优先使用成熟库,而不是手写算法。
8. 语言、编译与类型
跳转到“8. 语言、编译与类型”源码本身只是文本。语言运行时或编译器决定它怎样变成机器可执行的操作。
典型编译流程是:
源码 → 词法分析 → 语法分析 → AST → 语义分析 → 中间表示 → 优化 → 目标代码词法分析把字符流拆成 token;语法分析根据语法构建抽象语法树 AST;语义分析检查名称、作用域和类型;优化阶段在不改变程序语义的前提下减少计算;最后生成机器码、字节码或其他中间表示。
编译型语言通常在运行前完成大部分翻译,解释型语言由解释器边读边执行,JIT 则在运行时把热点代码编译成机器码。现代语言的边界很模糊:Java 使用字节码和 JIT,JavaScript 引擎会优化热点函数,Python 也有多种编译与加速路径。
内存管理分为手动管理和自动垃圾回收等模式。引用计数能及时释放,但可能遇到循环引用;追踪式 GC 能处理循环,却可能带来暂停和额外内存。Rust 等语言通过所有权和借用检查在编译期约束内存使用,代价是学习与编码成本。
类型系统用于在编译期或静态分析阶段发现一部分错误。静态类型、动态类型、强类型和弱类型不是四个互斥标签。TypeScript 是静态类型但编译后类型基本消失;Python 有类型提示,但运行时仍需校验。任何来自网络、文件、环境变量或用户输入的数据,都不能仅凭类型标注证明有效。
9. 用这张地图排查问题
跳转到“9. 用这张地图排查问题”假设一个网页打开很慢,可以按层缩小范围:
| 现象 | 先看哪一层 | 证据 |
|---|---|---|
| 域名解析失败 | DNS | dig / nslookup、解析记录 |
| 连接被拒绝 | 端口、进程、防火墙 | 监听地址、curl -v、安全组 |
| TLS 报错 | 证书、时间、代理 | 证书链、过期时间、SNI |
| 请求很慢 | 网络、服务、数据库 | 各阶段耗时、trace、慢查询 |
| 服务占满 CPU | 算法、循环、并发 | profiler、火焰图、线程状态 |
| 内存不断上涨 | 缓存、对象生命周期 | 堆快照、GC 指标 |
| 偶发错误 | 并发、重试、依赖 | 日志上下文、请求 ID、指标 |
先确定层,再选工具。不要在没有证据时同时重启、清缓存、重装依赖和改代码,那会把真正的故障现场一起破坏。
这一页要留下的东西
跳转到“这一页要留下的东西”- 计算机按层次组合:晶体管与逻辑门 → CPU → 内存与 I/O → 操作系统 → 网络 → 应用。
- 计算、内存、磁盘和网络的速度差很大,性能问题先看数据在哪里、访问有多频繁。
- 进程提供隔离,线程提供并发;共享状态必须同步,异步不等于并行。
- 字节、字符、整数和浮点是不同概念,边界处要验证编码与精度。
- 网络问题先分清 DNS、连接、TLS、HTTP 和服务处理。
- 数据结构的价值取决于访问模式,复杂度必须结合实际常数和 I/O。
- 类型检查只能发现一部分错误,运行时输入仍然必须校验。