跳转到内容

计算机系统一页通

这一页不追求把计算机专业课程压缩成几段结论,而是把后面会反复遇到的概念先放到正确位置:程序从哪里获得算力,数据放在哪一层,操作系统怎样隔离进程,网络怎样把两台机器连起来,源码又怎样变成可执行行为。

不需要会设计 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 内核态、用户态与系统调用”

普通程序运行在用户态,不能随意访问硬件或其他进程的内存。需要读写文件、创建进程、访问网络或申请内存时,程序通过系统调用请求内核执行。一次系统调用意味着特权级切换,不是普通函数调用;高频小系统调用可能成为性能瓶颈。

进程是资源隔离的基本单位,拥有地址空间、打开的文件、环境变量和权限。线程是 CPU 调度单位,同一进程内的线程共享地址空间和文件资源。

进程之间默认隔离,通信需要管道、共享内存、socket 或消息系统。线程之间共享内存更方便,但共享可变数据时必须同步,否则会出现竞态条件、死锁或数据损坏。

操作系统调度器决定哪个线程何时运行。时间片轮转只是最直观的模型;真实调度会考虑优先级、等待时间、CPU 亲和性和 I/O 阻塞。线程数量远远超过 CPU 核心数时,上下文切换和排队成本会上升。

文件系统把数据块组织成目录、文件、权限和元数据。路径是用户看到的抽象;底层还有 inode、块分配、日志、缓存和挂载点。打开文件会获得文件描述符,它是进程内的句柄,不只是“一个路径字符串”。

写入文件也不自动等于安全落盘。应用写入、操作系统页缓存、磁盘缓存和 fsync 控制着不同阶段。数据库需要在“性能”和“断电后不丢已经承诺的数据”之间明确取舍。

3.4 信号与进程生命周期

跳转到“3.4 信号与进程生命周期”

进程可以被启动、暂停、终止,也可以接收信号。例如 SIGINT 常由 Ctrl+C 发出,SIGTERM 请求进程正常退出,SIGKILL 强制结束。服务收到终止信号后应先停止接收新请求、完成或取消正在处理的任务,再关闭连接。

4. 从按下电源到浏览器打开

跳转到“4. 从按下电源到浏览器打开”

开机能串成一条路径,但每一段都可以单独排查:

电源与固件(UEFI)
↓ 找到启动设备
启动加载器
↓ 加载内核
操作系统内核初始化
↓ 启动第一个用户态进程
init / systemd 启动服务
↓ 拉起登录、桌面、网络
浏览器发出第一个请求

固件负责最基本的硬件初始化,并找到可启动设备。启动加载器把内核读入内存。内核建立内存管理、调度器、驱动和文件系统,再启动用户态 init。systemd 等 init 系统按依赖关系启动网络、日志、数据库和应用服务。

桌面环境、浏览器、代理和 DNS 都在更上层。系统“黑屏”“SSH 连不上”“服务没启动”“网页 502”是完全不同的故障层,不应从同一个“重装试试”开始。

计算机最终只处理位和字节。一个字节是 8 个比特,可以表示 0 到 255。字符、整数、浮点数、图片和程序都是对字节的解释方式。

ASCII 只覆盖早期英文字符。Unicode 给世界上的字符分配码点,UTF-8 则把码点编码成 1 到 4 个字节。UTF-8 与 ASCII 兼容,并且英文字符仍然只占一个字节,因此成为 Web 和大多数源代码的默认选择。

“字符数”与“字节数”不是一回事。一个中文字符在 UTF-8 中通常占 3 个字节;在 JavaScript 字符串和某些语言里,还可能按 UTF-16 代码单元计数。处理长度限制、截断和数据库字段时,要明确单位是字符、码点还是字节。

整数有位数、符号和溢出问题。无符号整数不能表示负数;有符号整数溢出后的行为取决于语言和运行时。时间戳、金额和 ID 要特别关注精度与范围。

浮点数使用 IEEE 754 等格式近似表示实数,不能精确表示所有十进制小数。0.1 + 0.2 !== 0.3 不是语言“坏了”,而是二进制浮点的正常结果。金额通常使用整数最小货币单位或十进制定点类型;需要精确比较的 ID 也不要依赖浮点。

图片通常保存像素、颜色空间和压缩信息;音频按采样率与位深记录波形;视频由帧、编码参数和时间戳组成。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 不包含常数,也不考虑网络和磁盘,所以工程中还要看实际数据量、缓存命中和访问模式。

递归把问题拆成同类的子问题,必须定义终止条件,否则会栈溢出。贪心每步选择当前最优,只有在问题满足特定性质时才保证全局最优。排序、搜索、图遍历和动态规划都是常用工具,但大多数业务系统优先使用成熟库,而不是手写算法。

源码本身只是文本。语言运行时或编译器决定它怎样变成机器可执行的操作。

典型编译流程是:

源码 → 词法分析 → 语法分析 → 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、指标

先确定层,再选工具。不要在没有证据时同时重启、清缓存、重装依赖和改代码,那会把真正的故障现场一起破坏。

  1. 计算机按层次组合:晶体管与逻辑门 → CPU → 内存与 I/O → 操作系统 → 网络 → 应用。
  2. 计算、内存、磁盘和网络的速度差很大,性能问题先看数据在哪里、访问有多频繁。
  3. 进程提供隔离,线程提供并发;共享状态必须同步,异步不等于并行。
  4. 字节、字符、整数和浮点是不同概念,边界处要验证编码与精度。
  5. 网络问题先分清 DNS、连接、TLS、HTTP 和服务处理。
  6. 数据结构的价值取决于访问模式,复杂度必须结合实际常数和 I/O。
  7. 类型检查只能发现一部分错误,运行时输入仍然必须校验。