《UNIX環境高級編程》(簡稱APUE)幾乎是Linux領域程序員人手必備的一本書。但在掌握和理解APUE的內容后,又該如何繼續提高自己的技能, 如何更深入地理解Linux環境編程及其背后的工作機制呢?本書將從一個全新的角度帶領讀者重新進入Linux環境編程,從應用出發,深入內核源碼,研究 Linux各接口的工作機制和原理,讓讀者不僅知其然,還知其所以然。作為Linux開發工程師,如果不僅掌握Linux的應用層開發,同時還熟悉 Linux的內核源碼,那么其在Linux環境下設計開發任何產品都將游刃有余,穩定且高效。
本書是Linux技術專家高峰和李彬的合力之 作,是兩個人多年開發經驗的總結和分享,也是市場上一本將Linux應用態與內核態相結合的技術圖書,選擇這種寫作方式是為了向APUE的作者致敬。 本書涵蓋了APUE中大部分章節的內容,并針對Linux環境,以作者多年經驗,詳細解析了Linux常用接口的使用方法和陷阱。為了讓讀者更清楚地理解 接口的工作原理,對于絕大部分接口,作者都會深入C庫或內核源碼進行分析。希望本書可以幫助讀者打通Linux環境的應用和內核兩條脈絡,使兩條線融 會貫通,進一步提高開發水平。
高峰,北京理工大學通信與信息系統專業碩士學位。畢業后在A10 Networks公司工作六年多,任職Staff Software Engineer,目前在創業公司全訊匯聚(愛快路由)擔任技術總監。多年來一直專注于網絡領域,熟悉Linux內核、應用及服務端的設計、開發和架構, 對TCP/IP網絡協議有深刻的認識和理解。編碼功力深厚,知識領域廣博,擅長產品的性能改進和調優。撰寫過大量技術文章,并為多個知名開源項目貢獻過代 碼。
李彬,東南大學信號與信息處理專業碩士。畢業后先后任職中興通訊、趨勢科技,目前在存儲公司Bigtera擔任SEG部門技術負 責人。一直專注于Linux平臺下的開發,多年分布式存儲開發經驗,熟悉Linux內核,編程基本功扎實,對性能優化、bug定位有異乎尋常的愛好,屬于 “死磕派”研發工程師。喜歡技術分享和交流,在社區和公司內部分享過大量技術文章。
前 言
第0章 基礎知識1
0.1 一個Linux程序的誕生記1
0.2 程序的構成2
0.3 程序是如何“跑”的4
0.4 背景概念介紹5
0.4.1 系統調用5
0.4.2 C庫函數6
0.4.3 線程安全7
0.4.4 原子性9
0.4.5 可重入函數9
0.4.6 阻塞與非阻塞11
0.4.7 同步與非同步11
第1章 文件I/O12
1.1 Linux中的文件12
1.1.1 文件、文件描述符和文件表12
1.1.2 內核文件表的實現13
1.2 打開文件14
1.2.1 open介紹14
1.2.2 更多選項15
1.2.3 open源碼跟蹤16
1.2.4 如何選擇文件描述符17
1.2.5 文件描述符fd與文件管理結構file18
1.3 creat簡介19
1.4 關閉文件19
1.4.1 close介紹19
1.4.2 close源碼跟蹤19
1.4.3 自定義files_operations21
1.4.4 遺忘close造成的問題22
1.4.5 如何查找文件資源泄漏25
1.5 文件偏移26
1.5.1 lseek簡介26
1.5.2 小心lseek的返回值26
1.5.3 lseek源碼分析27
1.6 讀取文件29
1.6.1 read源碼跟蹤29
1.6.2 部分讀取30
1.7 寫入文件31
1.7.1 write源碼跟蹤31
1.7.2 追加寫的實現33
1.8 文件的原子讀寫33
1.9 文件描述符的復制34
1.10 文件數據的同步38
1.11 文件的元數據41
1.11.1 獲取文件的元數據41
1.11.2 內核如何維護文件的元數據42
1.11.3 權限位解析43
1.12 文件截斷45
1.12.1 truncate與ftruncate的簡單介紹45
1.12.2 文件截斷的內核實現45
1.12.3 為什么需要文件截斷48
第2章 標準I/O庫50
2.1 stdin、stdout和stderr50
2.2 I/O緩存引出的趣題51
2.3 fopen和open標志位對比52
2.4 fdopen與fileno55
2.5 同時讀寫的痛苦56
2.6 ferror的返回值57
2.7 clearerr的用途57
2.8 小心fgetc和getc60
2.9 注意fread和fwrite的返回值60
2.10 創建臨時文件61
第3章 進程環境66
3.1 main是C程序的開始嗎66
3.2 “活雷鋒”exit70
3.3 atexit介紹75
3.3.1 使用atexit75
3.3.2 atexit的局限性76
3.3.3 atexit的實現機制77
3.4 小心使用環境變量78
3.5 使用動態庫80
3.5.1 動態庫與靜態庫80
3.5.2 編譯生成和使用動態庫80
3.5.3 程序的“平滑無縫”升級82
3.6 避免內存問題84
3.6.1 尷尬的realloc84
3.6.2 如何防止內存越界85
3.6.3 如何定位內存問題86
3.7 “長跳轉”longjmp90
3.7.1 setjmp與longjmp的使用90
3.7.2 “長跳轉”的實現機制91
3.7.3 “長跳轉”的陷阱93
第4章 進程控制:進程的一生96
4.1 進程ID96
4.2 進程的層次98
4.2.1 進程組99
4.2.2 會話102
4.3 進程的創建之fork()103
4.3.1 fork之后父子進程的內存關系104
4.3.2 fork之后父子進程與文件的關系107
4.3.3 文件描述符復制的內核實現110
4.4 進程的創建之vfork()115
4.5 daemon進程的創建117
4.6 進程的終止119
4.6.1 _exit函數119
4.6.2 exit函數120
4.6.3 return退出122
4.7 等待子進程122
4.7.1 僵尸進程122
4.7.2 等待子進程之wait()124
4.7.3 等待子進程之waitpid()126
4.7.4 等待子進程之等待狀態值129
4.7.5 等待子進程之waitid()131
4.7.6 進程退出和等待的內核實現133
4.8 exec家族141
4.8.1 execve函數141
4.8.2 exec家族142
4.8.3 execve系統調用的內核實現144
4.8.4 exec與信號151
4.8.5 執行exec之后進程繼承的屬性152
4.9 system函數152
4.9.1 system函數接口153
4.9.2 system函數與信號156
4.10 總結157
第5章 進程控制:狀態、調度和優先級158
5.1 進程的狀態158
5.1.1 進程狀態概述159
5.1.2 觀察進程狀態171
5.2 進程調度概述173
5.3 普通進程的優先級181
5.4 公平調度的實現186
5.4.1 時間片和虛擬運行時間186
5.4.2 周期性調度任務190
5.4.3 新進程的加入192
5.4.4 睡眠進程醒來198
5.4.5 喚醒搶占202
5.5 普通進程的組調度204
5.6 實時進程207
5.6.1 實時調度策略和優先級207
5.6.2 實時調度相關API211
5.6.3 限制實時進程運行時間213
5.7 CPU的親和力214
第6章 信號219
6.1 信號的完整生命周期219
6.2 信號的產生220
6.2.1 硬件異常220
6.2.2 終端相關的信號221
6.2.3 軟件事件相關的信號223
6.3 信號的默認處理函數224
6.4 信號的分類227
6.5 傳統信號的特點228
6.5.1 信號的ONESHOT特性230
6.5.2 信號執行時屏蔽自身的特性232
6.5.3 信號中斷系統調用的重啟特性233
6.6 信號的性236
6.6.1 信號的性實驗236
6.6.2 信號性差異的根源240
6.7 信號的安裝243
6.8 信號的發送246
6.8.1 kill、tkill和tgkill246
6.8.2 raise函數247
6.8.3 sigqueue函數247
6.9 信號與線程的關系253
6.9.1 線程之間共享信號處理函數254
6.9.2 線程有獨立的阻塞信號掩碼255
6.9.3 私有掛起信號和共享掛起信號257
6.9.4 致命信號下,進程組全體退出260
6.10 等待信號260
6.10.1 pause函數261
6.10.2 sigsuspend函數262
6.10.3 sigwait函數和sigwaitinfo函數263
6.11 通過文件描述符來獲取信號265
6.12 信號遞送的順序267
6.13 異步信號安全272
6.14 總結275
第7章 理解Linux線程(1)276
7.1 線程與進程276
7.2 進程ID和線程ID281
7.3 pthread庫接口介紹284
7.4 線程的創建和標識285
7.4.1 pthread_create函數285
7.4.2 線程ID及進程地址空間布局286
7.4.3 線程創建的默認屬性291
7.5 線程的退出292
7.6 線程的連接與分離293
7.6.1 線程的連接293
7.6.2 為什么要連接退出的線程295
7.6.3 線程的分離299
7.7 互斥量300
7.7.1 為什么需要互斥量300
7.7.2 互斥量的接口304
7.7.3 臨界區的大小305
7.7.4 互斥量的性能306
7.7.5 互斥鎖的公平性310
7.7.6 互斥鎖的類型311
7.7.7 死鎖和活鎖314
7.8 讀寫鎖316
7.8.1 讀寫鎖的接口317
7.8.2 讀寫鎖的競爭策略318
7.8.3 讀寫鎖總結323
7.9 性能殺手:偽共享323
7.10 條件等待328
7.10.1 條件變量的創建和銷毀328
7.10.2 條件變量的使用329
第8章 理解Linux線程(2)333
8.1 線程取消333
8.1.1 函數取消接口333
8.1.2 線程清理函數335
8.2 線程局部存儲339
8.2.1 使用NPTL庫函數實現線程局部存儲340
8.2.2 使用__thread關鍵字實現線程局部存儲342
8.3 線程與信號343
8.3.1 設置線程的信號掩碼344
8.3.2 向線程發送信號344
8.3.3 多線程程序對信號的處理345
8.4 多線程與fork()345
第9章 進程間通信:管道349
9.1 管道351
9.1.1 管道概述351
9.1.2 管道接口352
9.1.3 關閉未使用的管道文件描述符356
9.1.4 管道對應的內存區大小361
9.1.5 shell管道的實現361
9.1.6 與shell命令進行通信(popen)362
9.2 命名管道FIFO365
9.2.1 創建FIFO文件365
9.2.2 打開FIFO文件366
9.3 讀寫管道文件367
9.4 使用管道通信的示例372
第10章 進程間通信:System V IPC375
10.1 System V IPC概述375
10.1.1 標識符與IPC Key376
10.1.2 IPC的公共數據結構379
10.2 System V消息隊列383
10.2.1 創建或打開一個消息隊列383
10.2.2 發送消息385
10.2.3 接收消息388
10.2.4 控制消息隊列390
10.3 System V信號量391
10.3.1 信號量概述391
10.3.2 創建或打開信號量393
10.3.3 操作信號量395
10.3.4 信號量撤銷值399
10.3.5 控制信號量400
10.4 System V共享內存402
10.4.1 共享內存概述402
10.4.2 創建或打開共享內存403
10.4.3 使用共享內存405
10.4.4 分離共享內存407
10.4.5 控制共享內存408
第11章 進程間通信:POSIX IPC410
11.1 POSIX IPC概述411
11.1.1 IPC對象的名字411
11.1.2 創建或打開IPC對象413
11.1.3 關閉和刪除IPC對象414
11.1.4 其他414
11.2 POSIX消息隊列415
11.2.1 消息隊列的創建、打開、關閉及刪除415
11.2.2 消息隊列的屬性418
11.2.3 消息的發送和接收422
11.2.4 消息的通知423
11.2.5 I/O多路復用監控消息隊列427
11.3 POSIX信號量428
11.3.1 創建、打開、關閉和刪除有名信號量430
11.3.2 信號量的使用431
11.3.3 無名信號量的創建和銷毀432
11.3.4 信號量與futex433
11.4 內存映射mmap436
11.4.1 內存映射概述436
11.4.2 內存映射的相關接口438
11.4.3 共享文件映射439
11.4.4 私有文件映射455
11.4.5 共享匿名映射455
11.4.6 私有匿名映射456
11.5 POSIX共享內存456
11.5.1 共享內存的創建、使用和刪除457
11.5.2 共享內存與tmpfs458
第12章 網絡通信:連接的建立462
12.1 socket文件描述符462
12.2 綁定IP地址463
12.2.1 bind的使用464
12.2.2 bind的源碼分析465
12.3 客戶端連接過程468
12.3.1 connect的使用468
12.3.2 connect的源碼分析469
12.4 服務器端連接過程477
12.4.1 listen的使用477
12.4.2 listen的源碼分析478
12.4.3 accept的使用480
12.4.4 accept的源碼分析480
12.5 TCP三次握手的實現分析483
12.5.1 SYN包的發送483
12.5.2 接收SYN包,發送SYN ACK包485
12.5.3 接收SYN ACK數據包494
12.5.4 接收ACK數據包,完成三次握手499
第13章 網絡通信:數據報文的發送505
13.1 發送相關接口505
13.2 數據包從用戶空間到內核空間的流程506
13.3 UDP數據包的發送流程510
13.4 TCP數據包的發送流程517
13.5 IP數據包的發送流程527
13.5.1 ip_send_skb源碼分析528
13.5.2 ip_queue_xmit源碼分析531
13.6 底層模塊數據包的發送流程532
第14章 網絡通信:數據報文的接收536
14.1 系統調用接口536
14.2 數據包從內核空間到用戶空間的流程537
14.3 UDP數據包的接收流程540
14.4 TCP數據包的接收流程544
14.5 TCP套接字的三個接收隊列553
14.6 從網卡到套接字556
14.6.1 從硬中斷到軟中斷556
14.6.2 軟中斷處理557
14.6.3 傳遞給協議棧流程559
14.6.4 IP協議處理流程564
14.6.5 大師的錯誤?原始套接字的接收568
14.6.6 注冊傳輸層協議571
14.6.7 確定UDP套接字571
14.6.8 確定TCP套接字576
第15章 編寫安全無錯代碼582
15.1 不要用memcmp比較結構體582
15.2 有符號數和無符號數的移位區別583
15.3 數組和指針584
15.4 再論數組首地址587
15.5 “神奇”的整數類型轉換588
15.6 小心volatile的原子性誤解589
15.7 有趣的問題:“x == x”何時為假?591
15.8 小心浮點陷阱593
15.8.1 浮點數的精度限制593
15.8.2 兩個特殊的浮點值593
15.9 Intel移位指令陷阱595
李彬是一個內斂且內秀的人,平日不多的話語并不影響他雋秀的文筆和縝密的思路。他通過各種隱喻的方式,將很多復雜甚至龐雜的問題,抽絲剝繭,層層分解,將 其內在的部分展現在你的面前。文中有大道至簡的錦章佳句,也不乏詼諧幽默的流行俗語,雅俗并濟但不顯突兀和生硬,所有的一切都是為了讓讀者更好地理 解書中的內容。區別于其他編程圖書中大量代碼示例的堆疊和羅列、各種長篇大論的代碼走讀分析,本書從環境入手,對環境進行剖析,一切娓娓道來,由淺入深; 通過精致的小工具或者小程序,讓讀者快速探索環境,了解環境,熟悉環境,從而進一步利用環境,改造環境。
——李銅舒(Bruce Lee) Bigtera研發副總裁
高峰根據自己多年的編程經驗, 深入淺出地介紹了Linux下C編程常用的方方面面。這是一本很棒的學習用書,特別是他還總結了自己編程中遇到的有趣問題,了解這些問題對提高程序員的編程技能是很有幫助的。后一章值得精讀。
——李海濤 A10 Networks Senior Manager
本書可以說是繼承了W. Richard Stevens的《UNIX環境高級編程》的傳統并有所超,它重點介紹并剖析了Linux內核所提供的API以及API在內核中的實現,同時描述了 glibc對Linux API的封裝,以及使用某些glibc函數時遇到的問題。不管是內容的廣度還是深度,本書都達到了《UNIX環境高級編程》的高度。而且由于Linux的 開源特性,讓我們有機會更深入地了解API實現的細節,這對我們寫出更高效、更健壯的程序很有幫助。“知其然,知其所以然”應該是每個程序員的追求,如果 你對Linux環境下的編程感興趣,并希望了解更多的話,本書不容錯過。
——朱小平 高級技術專家
本書非常地介紹了Linux環境下編程所需的技術和知識,分析深入淺出,理論與實踐相結合,且都是作者經驗之談,即使如我這般在Linux下使用C編程超過10年的程序員也是受益良多。因此,無論你是初學還是提升,本書都值得一讀。
——夏艦波 華為博學研發工程師