《计算机底层的秘密》简介:
《计算机底层的秘密》以图解的方式通俗易懂地讲解计算机系统中各项技术的本质,包括编程语言的本质是什么、操作系统、进程线程协程等的本质是什么、到底什么是内存、什么是堆区栈区、内存分配等是怎么一回事、怎样从晶体管构建出CPU、I/O是如何实现的等等,从根源出发,一步步讲解一项技术到底是怎么来的,同时内容可视化——辅助大量精心设计的插图,几乎做到了平均一页有一图,把对技术的理解门槛尽量降低。
《计算机底层的秘密》目录:
第1章 从编程语言到可执行程序,这是怎么一回事 / 1
1.1 假如你来发明编程语言 / 2
1.1.1 创世纪: CPU是个聪明的笨蛋 / 3
1.1.2 汇编语言出现了 / 3
1.1.3 底层的细节 vs 高层的抽象 / 4
1.1.4 套路满满:高级编程语言的雏形 / 6
1.1.5 《盗梦空间》与递归:代码的本质 / 7
1.1.6 让计算机理解递归 / 9
1.1.7 优秀的翻译官:编译器 / 9
1.1.8 解释型语言的诞生 / 10
1.2 编译器是如何工作的 / 12
1.2.1 编译器就是一个普通程序,没什么大不了的 / 12
1.2.2 提取出每一个符号 / 13
1.2.3 token 想表达什么含义 / 14
1.2.4 语法树是不是合理的 / 14
1.2.5 根据语法树生成中间代码 / 15
1.2.6 代码生成 / 15
1.3 链接器不能说的秘密 / 16
1.3.1 链接器是如何工作的 / 17
1.3.2 符号决议:供给与需求 / 18
1.3.3 静态库、动态库与可执行文件 / 20
1.3.4 动态库有哪些优势及劣势 / 25
1.3.5 重定位:确定符号运行时地址 / 27
1.3.6 虚拟内存与程序内存布局 / 29
1.4 为什么抽象在计算机科学中如此重要 / 32
1.4.1 编程与抽象 / 32
1.4.2 系统设计与抽象 / 33
1.5 总结 / 34
第2章 程序运行起来了,可我对其一无所知 / 35
2.1 从根源上理解操作系统、进程与线程 / 36
2.1.1 一切要从 CPU说起 / 36
2.1.2 从 CPU到操作系统 / 37
2.1.3 进程很好,但还不够方便 / 40
2.1.4 从进程演变到线程 / 41
2.1.5 多线程与内存布局 / 44
2.1.6 线程的使用场景 / 44
2.1.7 线程池是如何工作的 / 45
2.1.8 线程池中线程的数量 / 46
2.2 线程间到底共享了哪些进程资源 / 47
2.2.1 线程私有资源 / 47
2.2.2 代码区:任何函数都可放到线程中执行 / 49
2.2.3 数据区:任何线程均可访问数据区变量 / 49
2.2.4 堆区:指针是关键 / 50
2.2.5 栈区:公共的私有数据 / 50
2.2.6 动态链接库与文件 / 52
2.2.7 线程局部存储: TLS / 53
2.3 线程安全代码到底是怎么编写的 / 55
2.3.1 自由与约束 / 55
2.3.2 什么是线程安全 / 56
2.3.3 线程的私有资源与共享资源 / 57
2.3.4 只使用线程私有资源 / 58
2.3.5 线程私有资源 + 函数参数 / 58
2.3.6 使用全局变量 / 60
2.3.7 线程局部存储 / 61
2.3.8 函数返回值 / 62
2.3.9 调用非线程安全代码 / 63
2.3.10 如何实现线程安全代码 / 64
2.4 程序员应如何理解协程 / 65
2.4.1 普通的函数 / 65
2.4.2 从普通函数到协程 / 66
2.4.3 协程的图形化解释 / 68
2.4.4 函数只是协程的一种特例 / 69
2.4.5 协程的历史 / 69
2.4.6 协程是如何实现的 / 70
2.5 彻底理解回调函数 / 71
2.5.1 一切要从这样的需求说起 / 72
2.5.2 为什么需要回调 / 73
2.5.3 异步回调 / 74
2.5.4 异步回调带来新的编程思维 / 75
2.5.5 回调函数的定义 / 77
2.5.6 两种回调类型 / 78
2.5.7 异步回调的问题:回调地狱 / 79
2.6 彻底理解同步与异步 / 80
2.6.1 辛苦的程序员 / 80
2.6.2 打电话与发邮件 / 81
2.6.3 同步调用 / 83
2.6.4 异步调用 / 84
2.6.5 同步、异步在网络服务器中的应用 / 86
2.7 哦!对了,还有阻塞与非阻塞 / 91
2.7.1 阻塞与非阻塞 / 92
2.7.2 阻塞的核心问题: I/O / 92
2.7.3 非阻塞与异步 I/O / 93
2.7.4 一个类比:点比萨 / 94
2.7.5 同步与阻塞 / 95
2.7.6 异步与非阻塞 / 96
2.8 融会贯通:高并发、高性能服务器是如何实现的 / 97
2.8.1 多进程 / 97
2.8.2 多线程 / 98
2.8.3 事件循环与事件驱动 / 99
2.8.4 问题 1 :事件来源与 I/O 多路复用 / 100
2.8.5 问题 2:事件循环与多线程 / 101
2.8.6 咖啡馆是如何运作的: Reactor 模式 / 102
2.8.7 事件循环与 I/O / 103
2.8.8 异步与回调函数 / 103
2.8.9 协程:以同步的方式进行异步编程 / 106
2.8.10 CPU、线程与协程 / 107
2.9 计算机系统漫游:从数据、代码、回调、闭包到容器、虚拟机 / 108
2.9.1 代码、数据、变量与指针 / 108
2.9.2 回调函数与闭包 / 110
2.9.3 容器与虚拟机技术 / 112
2.10 总结 / 114
第3章 底层?就从内存这个储物柜开始吧 / 115
3.1 内存的本质、指针及引用 / 116
3.1.1 内存的本质是什么?储物柜、比特、字节与对象 / 116
3.1.2 从内存到变量:变量意味着什么 / 117
3.1.3 从变量到指针:如何理解指针 / 120
3.1.4 指针的威力与破坏性:能力与责任 / 122
3.1.5 从指针到引用:隐藏内存地址 / 123
3.2 进程在内存中是什么样子的 / 124
3.2.1 虚拟内存:眼见未必为实 / 125
3.2.2 页与页表:从虚幻到现实 / 125
3.3 栈区:函数调用是如何实现的 / 127
3.3.1 程序员的好帮手:函数 / 128
3.3.2 函数调用的活动轨迹:栈 / 128
3.3.3 栈帧与栈区:以宏观的角度看 / 130
3.3.4 函数跳转与返回是如何实现的 / 131
3.3.5 参数传递与返回值是如何实现的 / 133
3.3.6 局部变量在哪里 / 134
3.3.7 寄存器的保存与恢复 / 134
3.3.8 Big Picture:我们在哪里 / 134
3.4 堆区:内存动态分配是如何实现的 / 136
3.4.1 为什么需要堆区 / 136
3.4.2 自己动手实现一个 malloc 内存分配器 / 137
3.4.3 从停车场到内存管理 / 138
3.4.4 管理空闲内存块 / 139
3.4.5 跟踪内存分配状态 / 141
3.4.6 怎样选择空闲内存块:分配策略 / 142
3.4.7 分配内存 / 144
3.4.8 释放内存 / 146
3.4.9 高效合并空闲内存块 / 149
3.5 申请内存时底层发生了什么 / 150
3.5.1 三界与 CPU运行状态 / 150
3.5.2 内核态与用户态 / 151
3.5.3 传送门:系统调用 / 152
3.5.4 标准库:屏蔽系统差异 / 153
3.5.5 堆区内存不够了怎么办 / 154
3.5.6 向操作系统申请内存: brk / 155
3.5.7 冰山之下:虚拟内存才是终极 BOSS / 156
3.5.8 关于分配内存完整的故事 / 156
3.6 高性能服务器内存池是如何实现的 / 157
3.6.1 内存池 vs 通用内存分配器 / 158
3.6.2 内存池技术原理 / 158
3.6.3 实现一个极简内存池 / 159
3.6.4 实现一个稍复杂的内存池 / 160
3.6.5 内存池的线程安全问题 / 161
3.7 与内存相关的经典 bug / 162
3.7.1 返回指向局部变量的指针 / 163
3.7.2 错误地理解指针运算 / 163
3.7.3 解引用有问题的指针 / 164
3.7.4 读取未被初始化的内存 / 165
3.7.5 引用已被释放的内存 / 166
3.7.6 数组下标是从 0 开始的 / 167
3.7.7 栈溢出 / 167
3.7.8 内存泄漏 / 168
3.8 为什么 SSD 不能被当成内存用 / 169
3.8.1 内存读写与硬盘读写的区别 / 169
3.8.2 虚拟内存的限制 / 171
3.8.3 SSD 的使用寿命问题 / 171
3.9 总结 / 171
第4章 从晶体管到 CPU,谁能比我更重要 / 173
4.1 你管这破玩意叫 CPU / 174
4.1.1 伟大的发明 / 174
4.1.2 与、或、非: AND 、OR、NOT / 174
4.1.3 道生一、一生二、二生三、三生万物 / 175
4.1.4 计算能力是怎么来的 / 175
4.1.5 神奇的记忆能力 / 176
4.1.6 寄存器与内存的诞生 / 177
4.1.7 硬件还是软件?通用设备 / 178
4.1.8 硬件的基本功:机器指令 / 179
4.1.9 软件与硬件的接口:指令集 / 179
4.1.10 指挥家,让我们演奏一曲 / 180
4.1.11 大功告成,CPU诞生了 / 180
4.2 CPU空闲时在干吗 / 181
4.2.1 你的计算机 CPU使用率是多少 / 181
4.2.2 进程管理与进程调度 / 182
4.2.3 队列判空:一个更好的设计 / 183
4.2.4 一切都要归结到 CPU / 184
4.2.5 空闲进程与 CPU低功耗状态 / 184
4.2.6 逃出无限循环:中断 / 185
4.3 CPU是如何识数的 / 186
4.3.1 数字 0 与正整数 / 186
4.3.2 有符号整数 / 187
4.3.3 正数加上负号即对应的负数:原码 / 187
4.3.4 原码的翻转:反码 / 188
4.3.5 不简单的两数相加 / 188
4.3.6 对计算机友好的表示方法:补码 / 189
4.3.7 CPU真的识数吗 / 191
4.4 当 CPU遇上 if语句 / 192
4.4.1 流水线技术的诞生 / 193
4.4.2 CPU——超级工厂与流水线 / 195
4.4.3 当 if 遇到流水线 / 196
4.4.4 分支预测:尽量让 CPU猜对 / 197
4.5 CPU核数与线程数有什么关系 / 199
4.5.1 菜谱与代码、炒菜与线程 / 199
4.5.2 任务拆分与阻塞式 I/O / 200
4.5.3 多核与多线程 / 201
4.6 CPU进化论(上):复杂指令集诞生 / 202
4.6.1 程序员眼里的CPU / 202
4.6.2 CPU的能力圈:指令集 / 202
4.6.3 抽象:少就是多 / 203
4.6.4 代码也是要占用存储空间的 / 203
4.6.5 复杂指令集诞生的必然 / 205
4.6.6 微代码设计的问题 / 205
4.7 CPU进化论(中):精简指令集的诞生 / 206
4.7.1 化繁为简 / 206
4.7.2 精简指令集哲学 / 207
4.7.3 CISC 与 RISC 的区别 / 208
4.7.4 指令流水线 / 209
4.7.5 名扬天下 / 210
4.8 CPU进化论(下):绝地反击 / 211
4.8.1 打不过就加入:像 RISC 一样的CISC / 211
4.8.2 超线程的绝技 / 212
4.8.3 取人之长,补己之短: CISC 与 RISC 的融合 / 214
4.8.4 技术不是全部: CISC 与 RISC 的商业之战 / 214
4.9 融会贯通:CPU、栈与函数调用、系统调用、线程切换、中断处理 / 215
4.9.1 寄存器 / 215
4.9.2 栈寄存器: Stack Pointer / 216
4.9.3 指令地址寄存器: Program Counter / 216
4.9.4 状态寄存器: Status Register / 217
4.9.5 上下文: Context / 218
4.9.6 嵌套与栈 / 218
4.9.7 函数调用与运行时栈 / 220
4.9.8 系统调用与内核态栈 / 220
4.9.9 中断与中断函数栈 / 223
4.9.10 线程切换与内核态栈 / 224
4.10 总结 / 227
第5章 四两拨千斤,cache / 228
5.1 cache,无处不在 / 229
5.1.1 CPU与内存的速度差异 / 229
5.1.2 图书馆、书桌与 cache / 230
5.1.3 天下没有免费的午餐: cache 更新 / 232
5.1.4 天下也没有免费的晚餐:多核 cache 一致性 / 233
5.1.5 内存作为磁盘的cache / 235
5.1.6 虚拟内存与磁盘 / 237
5.1.7 CPU是如何读取内存的 / 238
5.1.8 分布式存储来帮忙 / 238
5.2 如何编写对cache 友好的程序 / 240
5.2.1 程序的局部性原理 / 240
5.2.2 使用内存池 / 241
5.2.3 struct 结构体重新布局 / 241
5.2.4 冷热数据分离 / 242
5.2.5 对 cache 友好的数据结构 / 243
5.2.6 遍历多维数组 / 243
5.3 多线程的性能“杀手” / 245
5.3.1 cache 与内存交互的基本单位: cache line / 246
5.3.2 性能“杀手”一: cache 乒乓问题 / 247
5.3.3 性能“杀手”二:伪共享问题 / 250
5.4 烽火戏诸侯与内存屏障 / 253
5.4.1 指令乱序执行:编译器与 OoOE / 255
5.4.2 把 cache 也考虑进来 / 257
5.4.3 四种内存屏障类型 / 259
5.4.4 acquire-release 语义 / 263
5.4.5 C++ 中提供的接口 / 264
5.4.6 不同的CPU,不同的秉性 / 265
5.4.7 谁应该关心指令重排序:无锁编程 / 266
5.4.8 有锁编程 vs 无锁编程 / 267
5.4.9 关于指令重排序的争议 / 267
5.5 总结 / 268
第6章 计算机怎么能少得了 I/O / 269
6.1 CPU是如何处理 I/O 操作的 / 270
6.1.1 专事专办: I/O 机器指令 / 270
6.1.2 内存映射 I/O / 270
6.1.3 CPU读写键盘的本质 / 271
6.1.4 轮询:一遍遍地检查 / 272
6.1.5 点外卖与中断处理 / 273
6.1.6 中断驱动式 I/O / 274
6.1.7 CPU如何检测中断信号 / 275
6.1.8 中断处理与函数调用的区别 / 276
6.1.9 保存并恢复被中断程序的执行状态 / 277
6.2 磁盘处理I/O 时 CPU在干吗 / 279
6.2.1 设备控制器 / 280
6.2.2 CPU应该亲自复制数据吗 / 281
6.2.3 直接存储器访问: DMA / 281
6.2.4 Put Together / 283
6.2.5 对程序员的启示 / 284
6.3 读取文件时程序经历了什么 / 285
6.3.1 从内存的角度看 I/O / 285
6.3.2 read 函数是如何读取文件的 / 286
6.4 高并发的秘诀:I/O 多路复用 / 291
6.4.1 文件描述符 / 291
6.4.2 如何高效处理多个 I/O / 292
6.4.3 不要打电话给我,有必要我会打给你 / 293
6.4.4 I/O 多路复用 / 294
6.4.5 三剑客: select 、poll 与 epoll / 294
6.5 mmap:像读写内存那样操作文件 / 295
6.5.1 文件与虚拟内存 / 296
6.5.2 魔术师操作系统 / 297
6.5.3 mmap vs 传统 read/write 函数 / 298
6.5.4 大文件处理 / 299
6.5.5 动态链接库与共享内存 / 299
6.5.6 动手操作一下 mmap / 301
6.6 计算机系统中各个部分的时延有多少 / 302
6.6.1 以时间为度量来换算 / 303
6.6.2 以距离为度量来换算 / 304
6.7 总结 / 305
· · · · · ·