【读书笔记】《Computer Systems: A Programmer’s Perspective》第一章 A Tour of Computer Systems

《Computer Systems: A Programmer’s Perspective》第一章 A Tour of Computer Systems


概览

这一章通过对计算机系统各个层面(从硬件到软件、从存储到网络)的巡礼,帮助程序员建立起“系统观”,强调理解底层原理对编写高效、可靠程序的重要性。


1.1 信息 = 比特 + 上下文

  • 比特(Bit):信息的最小单位,0 或 1。
  • 上下文(Context):定义比特组合的含义(例如整数、浮点数、字符、指令)。
  • 启示:同一组比特,在不同上下文中含义截然不同。程序员既要关注数据表示,也要理解数据在特定场景下的解释。

1.2 程序由程序翻译成不同形式

  • 源代码 → 汇编 → 机器代码 → 可执行文件:通过编译器、汇编器、链接器等工具链逐步转化。
  • 动态链接:运行时载入系统库,提高可维护性与可复用性。
  • 启示:了解编译流程能帮助排查编译错误、性能问题,以及依赖管理。

1.3 理解编译系统的价值

  • 优化阶段:编译器会进行各种优化(常量折叠、循环展开、寄存器分配等),对性能有显著影响。
  • 调试支持:调试信息(符号表、行号映射)源自编译器。
  • 启示:对编译选项、优化级别和生成代码结构有基本了解,有助于性能调优和定位错误。

1.4 处理器读取并解释存储在内存中的指令

1.4.1 硬件组织
  • CPU:执行指令(算术逻辑单元 ALU、寄存器组、控制单元)。
  • 主板与总线:连接 CPU、内存、I/O 设备。
  • 内存(DRAM)与寄存器:寄存器最快、容量最小;DRAM 容量大、速度较慢。
1.4.2 运行 hello 程序
  1. 用户在终端输入 ./hello
  2. 操作系统加载可执行文件至内存,并将控制权交给程序入口。
  3. CPU 按指令编码依次取指、译码、执行。
  4. “Hello, world!” 字符串通过系统调用打印至屏幕。

1.5 缓存的重要性

  • 缓存层次(L1、L2、L3):靠近 CPU 的缓存速度快、容量小;远层缓存容量大、速度慢。
  • 空间与时间局部性:程序往往访问相近的数据(空间局部性)或近期访问过的数据(时间局部性)。利用这一点,缓存显著提升内存访问性能。
  • 启示:良好的数据布局(如数组优于链表)和访问模式(循环内数据连续)对性能至关重要。

1.6 存储设备层次结构

  • 寄存器 → 缓存 → 主存 → 磁盘 → 网络存储
  • 容量与速度:从寄存器(最快、最贵、最少)到网络存储(最慢、最廉价、最多)。
  • 启示:了解层次结构有助于设计高效的 I/O 策略,如利用内存映射或批量读写。

1.7 操作系统管理硬件

1.7.1 进程(Process)
  • 独立的地址空间、资源集合。
  • 进程切换需要上下文切换,代价较高。
1.7.2 线程(Thread)
  • 轻量级进程,多个线程共享地址空间。
  • 线程切换比进程切换开销小,但需注意并发安全。
1.7.3 虚拟内存(Virtual Memory)
  • 每个进程拥有独立虚拟地址空间,通过页表映射到物理内存。
  • 需求分页页置换策略(如 LRU)保证内存利用率。
1.7.4 文件(File)
  • 抽象持久化存储,统一通过系统调用(open/read/write/close)操作。
  • 文件系统提供目录、权限、安全等功能。

1.8 系统间通信:网络

  • 套接字(Socket):应用层与传输层交互的接口。
  • 协议栈:从应用层到物理层分层(TCP/IP 模型或 OSI 模型)。
  • 启示:网络 I/O 是瓶颈,高并发场景下需掌握异步 I/O、连接复用等技术。

1.9 重要主题

1.9.1 阿姆达尔定律(Amdahl’s Law)
  • 描述性能提升极限:加速比取决于可并行部分比例。
  • 公式:$S = 1 / [(1 - P) + P / N]$,其中 $P$ 为可并行比例,$N$ 为处理单元数。
1.9.2 并发与并行
  • 并发(Concurrency):程序结构上的多个活动。
  • 并行(Parallelism):多个活动真正同时执行。
  • 启示:多核时代,两者需结合:并发性提高结构清晰度,并行性提高性能。
1.9.3 抽象的重要性
  • 抽象层次:汇编、C 语言、高级语言、库、框架……
  • 权衡:抽象带来易用性和可维护性,但可能牺牲部分性能;理解底层有助于在必要时“降级”优化。

1.10 小结

第一章为全书奠定基础,强调程序员不仅要写“正确”的代码,更要关注代码在系统中的运行机制。理解底层硬件、存储层次、操作系统和并发模型,能够帮助我们编写更高效、更可靠、更可维护的软件。


练习题

练习题 1:比特与上下文

题目简述
给出同一 8 位比特串如 0x41(即二进制 01000001)在至少三种不同上下文下的含义。

解题思路
  1. 列举常见数据类型的表示:有符号整数、无符号整数、ASCII 字符、IEEE‑754 单精度浮点数(低 8 位)等。
  2. 说明在每种上下文下如何解读同一比特串。
考察点
  • 理解“比特”与“上下文”的关系
  • 熟悉基本数据表示
参考答案
上下文含义解读方法
无符号整数 (uint8_t)65直接把 0x41 当作 0–255 范围的整数
有符号整数 (int8_t)65高位符号位为 0,值仍为 65
ASCII 字符'A'0x41 对应 ASCII 表中的大写字母 A
二进制指令编码一条假设指令的一部分在某种 CPU ISA 下,0x41 可能是 opcode
浮点数低 8 位仅部分字节,需结合高 24 位才能解读浮点数须按 IEEE‑754 完整 32 位解读

练习题 2:查看编译后代码

题目简述
编写一个简单的 C 程序 hello.c,打印 "Hello, CS:APP!"。分别用 gcc -O0-O1-O2 编译,并用 objdump -dgcc -S 查看生成的汇编代码差异。

解题思路
  1. 编写 hello.c:只包含 mainprintf

  2. 分别执行:

    gcc -O0 -o hello_O0 hello.c
    gcc -O2 -o hello_O2 hello.c
    objdump -d hello_O0 > asm_O0.s
    objdump -d hello_O2 > asm_O2.s
    
  3. 比较 asm_O0.sasm_O2.s:关注函数调用、栈帧设置、常量折叠等优化。

考察点
  • 熟悉 GCC 优化选项
  • 理解编译器如何通过优化减少指令、简化栈操作
参考答案
  • -O0

    • 明确的栈帧分配(push rbp; mov rbp, rsp
    • printf 参数压栈
    • 没有常量折叠
  • -O2

    • 可能省略显式栈帧(使用“红树林帧”)
    • 直接把字符串地址加载到寄存器后调用
    • 常量合并,少量指令即可完成

练习题 3:缓存行与访问步长实验

题目简述
编写一个程序,用不同的步长(stride)遍历一个大数组,测量访问时间,验证缓存行大小对性能的影响。

解题思路
  1. 申请一个足够大的数组(如 int a[32*1024*1024])。
  2. 对步长为 1, 4, 16, 64, 256 等循环访问,外层循环多次,确保总访问量足够大。
  3. clock_gettime()rdtsc() 测量总耗时,算平均每次访问延迟。
  4. 绘制步长 vs 平均延迟曲线,观察跨越缓存行(通常 64 字节)时延迟陡增。
考察点
  • 理解空间局部性与缓存行
  • 掌握高精度时间测量方法
参考答案(伪码)
#include <time.h>
#define N (32*1024*1024)
int a[N];
long measure(int stride) {
    struct timespec t0, t1;
    clock_gettime(CLOCK_MONOTONIC, &t0);
    for (int i = 0; i < N; i += stride)
        a[i]++;
    clock_gettime(CLOCK_MONOTONIC, &t1);
    return (t1.tv_nsec - t0.tv_nsec) + 
           (t1.tv_sec - t0.tv_sec) * 1e9;
}
int main() {
    for (int s : {1,4,16,64,256}) {
        long ns = measure(s);
        printf("stride=%d, time_per_access=%.2f ns\n",
               s, (double)ns / (N/ s));
    }
}
  • 预期结果

    • 步长 1、4、16(<=64字节)延迟较小且接近
    • 步长 ≥64 时延迟明显增大

练习题 4:I/O 缓冲与无缓冲性能对比

题目简述
分别使用标准 C 库缓冲 I/O(fread/fwrite)和低级系统调用无缓冲 I/O(read/write)读写大文件,比较性能差异。

解题思路
  1. 编写两个版本:

    • BufferedFILE *fp = fopen(...); fread(buf, 1, BUFSIZE, fp); 循环直至文件末尾;
    • Unbufferedint fd = open(...); read(fd, buf, BUFSIZE); 循环。
  2. 使用 /usr/bin/time -pgettimeofday() 测量程序运行总时间。

  3. 调整 BUFSIZE(4KB、64KB、1MB)观察对性能的影响。

考察点
  • 理解用户态缓冲与内核态缓存的区别
  • 熟悉文件 I/O 接口及性能测量
参考答案
  • 结论

    • 适当的用户态缓冲(如 64KB)下,fread 性能最佳
    • 小缓冲区(<4KB)导致系统调用频繁,性能下降
    • 无缓冲 I/O 在大缓冲下仍比标准库略慢,因为缺少二级缓冲和行缓冲优化

练习题 5:理解进程与线程

题目简述
简述进程(process)与线程(thread)的区别,以及各自的优缺点。

解题思路
  • 对比它们的资源隔离、上下文切换开销、内存共享情况。
  • 举例说明何时使用进程、何时使用线程。
考察点
  • 掌握操作系统对并发的基本支持
  • 理解上下文切换原理
参考答案
特性进程线程
地址空间独立共享(同一进程内)
资源(文件描述符等)独立副本或通过复制(fork)共享
上下文切换开销较高(切换页表、TLB 刷新)较低(只切换寄存器)
通信方式IPC(管道、消息队列、共享内存等)直接读写共享内存
使用场景需要高度隔离、安全性的场景高性能并发、轻量级任务

练习题 6:虚拟内存与页面置换

题目简述
解释虚拟内存工作机制,包括页表映射、TLB、缺页异常(page fault)以及常见置换算法(如 LRU)。

解题思路
  • 描述从虚拟地址到物理地址的转换流程。
  • 说明 TLB 的作用和缺失处理流程。
  • 简述 LRU、FIFO 等置换策略的优缺点。
考察点
  • 理解内存管理单元(MMU)和操作系统的协作
  • 掌握虚拟内存带来的隔离与性能开销
参考答案
  1. 地址转换

    • CPU 生成虚拟地址 VA → 查 TLB → 命中则得 PA;不命中则查页表并更新 TLB。
  2. 缺页异常

    • 若页表标记为不在内存,触发 page fault → OS 从磁盘调入页 → 更新页表、TLB → 重新执行指令。
  3. 置换算法

    • LRU:最近最少使用,命中率高但硬件实现复杂;
    • FIFO:先进先出,简单易实现但可能出现 Belady 异常;
    • CLOCK:LRU 的近似实现,硬件/软件配合。

练习题 7:简单网络编程

题目简述
使用 BSD Socket API,写一个简单的 echo 客户端或服务器。

解题思路
  • 对服务器:socket()bind()listen()accept()read()/write()循环→close()
  • 对客户端:socket()connect()write()read()close()
考察点
  • 熟悉 TCP 三次握手、连接管理
  • 掌握套接字接口和基本 I/O 流程
参考答案(服务器伪码)
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, ...);
listen(listenfd, 10);
while (1) {
    int connfd = accept(listenfd, NULL, NULL);
    char buf[1024];
    ssize_t n;
    while ((n = read(connfd, buf, sizeof(buf))) > 0)
        write(connfd, buf, n);
    close(connfd);
}
经典计算机操作系统教材第三版,详细内容可见亚马逊。 https://www.amazon.com/Computer-Systems-Programmers-Perspective-Engineering/dp/0134123832/ref=sr_1_2?ie=UTF8&qid=1541476471&sr=8-2&keywords=computer+systems+a+programmer's+perspective Computer systems: A Programmer’s Perspective explains the underlying elements common among all computer systems and how they affect general application performance. Written from the programmer’s perspective, this book strives to teach readers how understanding basic elements of computer systems and executing real practice can lead them to create better programs. Spanning across computer science themes such as hardware architecture, the operating system, and systems software, the Third Edition serves as a comprehensive introduction to programming. This book strives to create programmers who understand all elements of computer systems and will be able to engage in any application of the field--from fixing faulty software, to writing more capable programs, to avoiding common flaws. It lays the groundwork for readers to delve into more intensive topics such as computer architecture, embedded systems, and cyber security. This book focuses on systems that execute an x86-64 machine code, and recommends that programmers have access to a Linux system for this course. Programmers should have basic familiarity with C or C++. Personalize Learning with MasteringEngineering MasteringEngineering is an online homework, tutorial, and assessment system, designed to improve results through personalized learning. This innovative online program emulates the instructor’s office hour environment, engaging and guiding students through engineering concepts with self-paced individualized coaching With a wide range of activities available, students can actively learn, understand, and retain even the most difficult concepts.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值