計算機底層的秘密

陸小風(@碼農的荒島求生)

買這商品的人也買了...

商品描述

本書以圖解的方式通俗易懂的講解電腦系統中各項技術的本質,包括編程語言的本質是什麽、操作系統、進程線程協程等的本質是什麽、到底什麽是內存、什麽是堆區棧區、內存分配等是怎麽一回事、怎樣從晶體管構建出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