猜你喜欢
计算机底层的秘密

计算机底层的秘密

书籍作者:陆小风 ISBN:9787121452772
书籍语言:简体中文 连载状态:全集
电子书格式:pdf,txt,epub,mobi,azw3 下载次数:5025
创建日期:2024-03-26 发布日期:2024-03-26
运行环境:PC/Windows/Linux/Mac/IOS/iPhone/iPad/Kindle/Android/安卓/平板
内容简介

本书以图解的方式通俗易懂的讲解计算机系统中各项技术的本质,包括编程语言的本质是什么、操作系统、进程线程协程等的本质是什么、到底什么是内存、什么是堆区栈区、内存分配等是怎么一回事、怎样从晶体管构建出CPU、I/O是如何实现的等等,从根源出发,一步步讲解一项技术到底是怎么来的,同时内容可视化――辅助大量精心设计的插图,几乎做到了平均一页有一图,把对技术的理解门槛尽量降低。

作者简介

陆小风(@码农的荒岛求生),硕士毕业于北京航空航天大学计算机学院,先后就职于VMware和京东,具有多年软件系统研发经验,擅长用通俗易懂的语言讲解计算机技术。

编辑推荐
适读人群 :适合任何对计算机系统感兴趣的同学、程序员,如果有一定的编程经验效果更佳。

京东一线专家带你轻松理解计算机底层

从根源出发,探索计算机系统赋予程序员的超能力

像高手那样,对每一行代码产生的影响了如指掌

遇到棘手问题时不再束手无策,直击本质,瞬间化解

发现CPU、内存、程序运行、Cache、I/O背后的奥秘

341幅精心设计的插图帮你越读越轻松


前言

前言

本书源自笔者的两个疑问:我的代码看上去能正常运行,可这是为什么呢?计算机在执行我写的代码时在底层发生了什么?

现代计算机系统的结构就像一个汉堡包一样,实际上是被层层抽象过的,程序员在最上层用高级语言编写代码时根本不用关心底层细节,这极大地提高了开发效率,但有时遇到一些较为棘手的问题,很多人往往束手无策,这其中大部分情况是因为对底层了解不够而导致的,我们有时甚至都不能理解产生的问题本身,更何谈解决问题呢?

这些看上去很难解决的问题在那些编程高手眼里往往不值一提,他们几乎能脱口而出直指本质,你一两天都搞不定的问题在这些编程高手那里可能会被瞬间解决掉,因为他们对自己写下的每一行代码到底会对计算机系统产生什么样的影响了如指掌,如他们非常清楚地知道分配一块内存在底层发生的一系列故事等。英文中有一个词很形象—— mental model (心智模型),本书更多地为你揭示那些编程高手的心智模型和计算机系统 底层的奥秘。

在讲解方式上,首先笔者认为内容可视化非常重要,一图抵千言,因此本书中有多达341张图,以图解的方式来讲解所涉及的内容;其次内容的可读性也很重要,本书会 以通俗易懂的方式从概念的起源开始讲解,不仅告诉你是什么、为什么,还会告诉你这是怎么来的,把对内容阅读理解的门槛降到最低。

当然,除了上述较为“功利”的目的,笔者认为有趣的东西还是值得了解一下的,计算机系统其实就是这样一个很有趣的东西,如果你不这么认为的话,那么很可能是你 还不够了解它。计算机系统中的许多设计是如此的有趣,即便是出于好奇,也应该去了 解一下,就像 Linus 所说的那样——Just for fun !

本书配套资料

一款操作系统发布后往往需要打补丁、定时升级,而一本书的出版往往与之类似,由于笔者能力有限,因此在本书出版后可能也需要打补丁,在微信公众号“码农的荒岛求生”后台回复“补丁”二字可获取笔者关于本书相关话题的扩展内容,相信这些内容 可以更好地帮助读者理解本书。

路线图

本书分为6章:

● 第1章关注编程语言,重点阐述到底什么是编程语言、编译器的工作原理,以及如何从代码生成最终的可执行程序。

● 第2章重点讲解程序运行起来后,也就是运行时的奥秘,包括程序到底是以什么样的形式运行起来的,操作系统、进程、线程、协程到底是什么,我们为什么需要了解这些概念,回调函数、同步、异步、阻塞、非阻塞又是怎么一回事,这些又能赋予程序员什么样的能力等。

● 第3章将带你认识内存。程序的运行离不开内存,因此我们要了解内存的本质是什么,到底什么是指针,为什么会有堆区、栈区,函数调用的实现原理是什么,申请内存时底层到底发生了什么,该怎样实现一个自己的malloc内存分配器等。

● 第4章介绍计算机系统中最重要的CPU,CPU的实现原理是什么,怎样一步步打造出CPU,CPU是如何认识数字的,CPU空闲时在干什么,以及CPU是如何演变进化的,为什么会出现复杂指令集及精简指令集,如何利用CPU与栈的组合实现函数调用、中断处理、线程切换及系统调用等机制。

● 第5章讲解计算机系统中的cache,为什么需要cache,以及程序员该如何编 写出对cache 友好的代码。

● 第6章关注I/O,计算机系统是如何实现I/O的,程序员调用read函数时在底层是如何一步步读取到文件内容的,程序员该如何高效处理I/O等。

勘误

由于笔者水平有限,书中难免会有疏漏之处,恳请广大读者批评指正。

在微信公众号“码农的荒岛求生”底部菜单栏中有一项关于本书勘误的菜单入口,读者可通过此渠道查看本书的bug或者反馈问题。

致谢

首先感谢微信公众号“码农的荒岛求生”的忠实读者,是你们让我一直坚持到现在,是你们让我能感受到自己做的事情是有价值的,是你们让本书出版成为可能。

其次特别感谢我的爱人,是你的鼓励让我踏上了写作之路,在此之前我从没想过自己此生会与写作有什么关联,是你让我发现了全新的自己,这无异于重生。

最后感谢我的父母,是你们的辛苦付出让我远离生活琐事。“当你轻装上阵时必定 有人为你负重前行”,我无以为报,谨将此书献给你们。


目录
第 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
产品特色