深入理解计算机系统
Computer Systems: A Programmer's Perspective
勿以浮沙筑高台
从程序员视角讲解计算机系统的经典教材。涵盖数据的机器级表示、汇编语言、处理器体系结构、存储器层次结构、链接、异常控制流、虚拟内存、系统级I/O、网络编程与并发编程。已被全球200多所大学用作教材。
为什么读这本书
理解程序在硬件上如何运行,是写出高性能代码的基础。这本书会告诉你:要想成为一名优秀的程序员,你必须理解哪些计算机底层原理。
章节导览
计算机系统漫游
勿以浮沙筑高台
本章是全书的导论,通过一个简单的hello程序展示了计算机系统的各个层次如何协同工作。从硬件到操作系统再到应用程序,你会看到一个完整的系统视图。
信息的表示和处理
我们生活在比特组成的世界
你可能会有疑惑:为什么作者要花那么多篇幅来介绍大家(仿佛)都已经理解的二进制系统呢?实际上第二章写得非常好,当你阅读之后会发现,你所谓的「懂」只是你的一厢情愿而已。数值系统远没有你想象的那么简单。
程序的机器级表示
有时候你要学会像机器一样思考
某种程度上讲,本章是本课程的核心内容之一。今天我们还要花力气来学习机器级别的编程方式,因为它们是连接你所编写的高级语言代码和机器之间的纽带,是实实在在的基石。
处理器体系结构
万丈高楼平地起
本章从硬件角度理解处理器如何工作。通过Y86-64这个简化的指令集,你可以理解现代处理器的设计原理。
优化程序性能
我们可以做的更好
本章讲解如何写出高性能的代码。编译器很聪明,但程序员的理解同样重要。
存储器层次结构
万能的金字塔模型
存储器层次结构是理解程序性能的关键。从寄存器到磁盘,每一层都比下一层慢,但容量更大、更便宜。
链接
欢迎来到灰色地带
链接是很多人忽视的环节,但它对理解程序如何组合在一起至关重要。
异常控制流
操作系统:唯一的使命就是帮助程序运行
异常控制流是操作系统的基础。理解异常、中断、进程和信号,才能理解程序如何与系统交互。
虚拟内存
假装我们有用不完的物理内存
虚拟内存是操作系统最重要的抽象之一。它让每个进程都觉得自己独占整个内存空间。
系统级I/O
务必搞清楚你自己正在和整个系统的哪一层打交道
Unix的I/O模型简单而强大。理解文件描述符和系统调用,才能写出正确的I/O代码。
网络编程
实现古人天涯若比邻的梦想
现代应用都是网络应用。理解Socket编程和HTTP协议,才能构建分布式系统。
并发编程
并发与并行,傻傻分不清楚?
并发是现代编程的核心挑战。理解线程、同步和并发模型,才能写出正确的并发程序。
读书笔记
计算机系统漫游
勿以浮沙筑高台
重点内容
- •程序的生命周期:从源代码到可执行文件再到运行
- •存储层次结构:缓存、内存、磁盘的层次关系
- •操作系统的作用:抽象硬件、管理资源
- •网络的重要性:现代系统不是孤立的
- •并发的挑战:多核时代的编程模型
延伸阅读
- →CMU 15-213 课程讲义
- →《计算机组成与设计:硬件/软件接口》
信息的表示和处理
我们生活在比特组成的世界
重点内容
- •为什么一个字节是8位?这源于IBM System/360的历史
- •原码、反码、补码的演进:为什么补码成为主流
- •浮点数的设计权衡:范围、精度、比较大小的权衡
- •整型溢出的危险:阿丽亚娜5号火箭爆炸的教训
- •字节序问题:大小端在网络编程中的重要性
代码示例
// 带符号数产生意外结果的例子
int n = 10, i;
for (i = n - 1 ; i - sizeof(char) >= 0; i--)
printf("i: 0x%x\n",i);
// sizeof返回unsigned,导致无限循环// 浮点数不满足结合律
(3.14+1e10)-1e10 = 0
3.14+(1e10-1e10) = 3.14
// 编译器优化时需要注意这一点实验解读
Data Lab:理解C语言数据类型的位级表示形式。要求只用位运算实现各种功能,让你站在「比特级」来理解二进制数值系统。
延伸阅读
- →香农的硕士论文《继电器与开关电路的符号分析》
- →IEEE-754浮点数标准
- →位运算的奇技淫巧:Bit Twiddling Hacks
程序的机器级表示
有时候你要学会像机器一样思考
重点内容
- •ISA是软硬件之间的「合同」
- •编译过程:源代码 → 编译 → 汇编 → 链接 → 可执行文件
- •程序就是一系列字节序列(冯诺依曼结构)
- •条件码(CF、ZF、SF、OF)的作用
- •函数调用过程:控制权转移、参数传递、栈管理
- •缓冲区溢出的安全问题
代码示例
// 逆向工程:从汇编理解C代码
// 使用 objdump -d 或 gdb 查看反汇编
// 理解编译器如何优化你的代码实验解读
Bomb Lab:通过逆向工程拆除炸弹,理解汇编语言和GDB调试。Attack Lab:学习代码注入攻击和ROP攻击,理解如何预防安全漏洞。
延伸阅读
- →Intel 64 and IA-32 Architectures Software Developer's Manuals
- →《C专家编程》关于指针的深入讨论
处理器体系结构
万丈高楼平地起
重点内容
- •单周期处理器:简单但慢
- •流水线设计:提高吞吐量的关键
- •流水线冒险:数据冒险、控制冒险、结构冒险
- •分支预测的重要性
- •乱序执行的原理
延伸阅读
- →《计算机体系结构:量化研究方法》
优化程序性能
我们可以做的更好
重点内容
- •消除循环的低效率:代码移动
- •减少过程调用
- •减少内存引用
- •理解现代处理器:超标量、流水线
- •存储器山:缓存的时间局部性和空间局部性
- •循环展开和多次迭代并行
代码示例
// 代码移动:将循环不变量移出循环
for (i = 0; i < n; i++) {
// 不好:每次循环都调用函数
// x = a[i] * getMultiplier();
// 好:移到循环外
int mult = getMultiplier();
for (i = 0; i < n; i++) {
x = a[i] * mult;
}
}存储器层次结构
万能的金字塔模型
重点内容
- •存储技术:SRAM、DRAM、磁盘、SSD
- •局部性原理:时间局部性和空间局部性
- •缓存的组织方式:直接映射、组相联、全相联
- •缓存写入策略:写回 vs 写穿
- •存储器山:理解性能的关键模型
实验解读
Cache Lab:实现一个缓存模拟器,然后优化矩阵转置的缓存命中率。
链接
欢迎来到灰色地带
重点内容
- •编译器驱动程序的作用
- •静态链接的过程
- •目标文件的格式(ELF)
- •符号解析规则
- •重定位的过程
- •静态库和动态库的区别
异常控制流
操作系统:唯一的使命就是帮助程序运行
重点内容
- •异常的分类:中断、陷阱、故障、终止
- •进程的概念:独立的逻辑控制流
- •系统调用的机制
- •信号的处理
- •非本地跳转:setjmp和longjmp
实验解读
Shell Lab:实现一个简单的Unix shell,理解进程控制和信号处理。
虚拟内存
假装我们有用不完的物理内存
重点内容
- •虚拟内存的作用:隔离、共享、高效
- •地址翻译的过程
- •页表的设计:多级页表
- •TLB的作用:加速地址翻译
- •内存映射文件
- •动态内存分配:malloc的实现
实验解读
Malloc Lab:实现自己的malloc和free,理解内存分配器的设计。
系统级I/O
务必搞清楚你自己正在和整个系统的哪一层打交道
重点内容
- •Unix I/O模型:一切皆文件
- •文件描述符的概念
- •read和write系统调用
- •RIO(Robust I/O)包的设计
- •I/O重定向和管道
网络编程
实现古人天涯若比邻的梦想
重点内容
- •客户端-服务器模型
- •Socket接口
- •TCP vs UDP
- •HTTP协议基础
- •并发服务器的设计
实验解读
Proxy Lab:实现一个简单的Web代理服务器,理解网络编程和并发。
并发编程
并发与并行,傻傻分不清楚?
重点内容
- •基于进程的并发
- •基于I/O多路复用的并发
- •基于线程的并发
- •多线程编程的挑战
- •同步原语:互斥锁、信号量
- •生产者-消费者模型
- •线程安全