《程式設計師的自我修養》--讀書筆記
印象
拿到這本書,肯定會想到星爺的《喜劇之王》中的《演員的自我修養》;沒錯,可能我們都是一些死爛演員,但是如果我們堅持,打好最基礎的根基,總有一天,我們會成為優秀的程式設計師。
書的副標題是:連結、裝載與庫,沒錯,這三個連貫的主題構成了文章的核心內容,目前也有這方面的開原始碼(輪子),作為程式設計師,我們並不鼓勵重複造輪子,但是我們應該瞭解輪子的構造,特別是很基礎層次的輪子。文中反覆重複的一個觀點是:一切問題都可使用中間層來解決,通過中間層次逐步遮蔽此層的差異化。瞭解了底層存在的問題,我們會理解中間層使用了什麼方法解決了什麼問題。
必備知識
C/C++、x86組合語言基礎、作業系統基本概念、計算機系統概念
內容
簡介
溫故知新
本章對計算機的軟硬體基礎結構進行回顧。
計算機硬體的3個關鍵部件:中央處理器CPU、記憶體、I/O控制晶片;北橋,高速,協調CPU、記憶體和高速圖形裝置之間高速交換資料;南橋,連線低速裝置如磁碟、USB、鍵盤、滑鼠等,南橋將這些彙總到北橋。
SMP與多核:CPU的頻率似乎不滿足摩爾定律,限制於4GHz,於是從另外一個角度上提高CPU速度:多核。SMP:對稱多處理器;
計算機軟體體系結構:應用程式(作業系統API)執行時庫(系統呼叫)作業系統核心(硬體特性)硬體,引出了中間層的概念;
作業系統的功能:提供抽象介面、管理硬體資源;比如CPU排程、裝置驅動等;
記憶體:怎麼將有限的實體記憶體分配給多個程式使用?幾個問題:地址空間隔離(虛擬地址)、記憶體使用效率低(段頁式管理);
執行緒排程、執行緒安全、執行緒的幾種模型。
靜態連結
編譯和連結
本章回顧了編譯的幾個步驟及其相關部門。
GCC編譯過程分解:預編譯(處理原始碼中得# 預編譯指令)、編譯(詞法分析、語法分析、語義分析、優化)、彙編(將彙編程式碼變成機器碼)、連結(多個目標檔案連結成可執行檔案);
編譯器的工作:掃描、語法分析、語義分析(靜態語義、動態語義)、中間程式碼生成(編譯器前端負責產生機器無關的中間程式碼,編譯器後端將中間程式碼轉換成目標機器程式碼)、目的碼優化;
連結:模組的拼接工作、符號、重定位等;
靜態連結:把一些指令對其它符號地址的引用加以修正,包括:地址和空間分配、符號決議、重定位等;
目標檔案有什麼
本章主要介紹ELF檔案的段結構及相關結構
目標檔案的格式:Windows的PE、Linux的ELF,都是COFF格式的變種
目標檔案將這些資訊按照不同的屬性以Section的形式儲存,比如機器指令放在程式碼段、全域性變數和區域性靜態變數放在資料段裡。而把指令和資料分開,也是有原因的,比如提高快取命中率、共享指令從而節省記憶體、安全等;
反彙編看看程式碼段、資料段和只讀資料段、bss段、其它段的內容;
ELF檔案結構描述:檔案頭(魔數)、段表、重定位表、字串表;
連結的介面–符號:extern c的用法等;
靜態連結
本章主要介紹目標檔案中的段是如何合併的、連結器如何為它們分配在輸出檔案中的空間和地址。 地址確定之後,進行符號解析和重定位,以及“修補”共工作。
空間與地址分配:按序疊加、相似度合併策略;
符號解析與重定位;
處理弱符號時的COMMON塊;
C++相關問題:重複程式碼消除、全域性構造與析構、C++和ABI;
連結過程控制:連結控制指令碼;
BFD庫:一個GNU專案,希望通過一種統一的介面來處理不同的目標檔案格式;
Windows PE/COFF
本章介紹了Windows下的可執行檔案和目標檔案格式PE/COFF
裝載與動態連結
本章介紹程式執行時如何使用記憶體空間、如何被裝載到記憶體中、程序虛擬地址空間的分佈等。
可執行檔案的裝載與程序
程序的虛擬地址空間;
裝載的方式:靜態裝入(指令和資料全部裝入記憶體)、動態裝入(利用區域性性原理,把程式最常用的部分駐留在記憶體中,不常用的放在磁盤裡)。 動態裝載的方法:覆蓋載入和頁對映。頁對映,選擇哪些頁,有多種演算法,比如FIFO、LUR(最少使用演算法)等;
可執行檔案的裝載:建立程序、頁錯誤;
程序虛擬空間分佈:ELF檔案連結檢視和執行試圖、堆和棧;
Linux核心裝載ELF;
WIndows PE的裝載;
動態連結
本章介紹動態連結的優點、解決絕對地址引用問題、動態聯結器的步驟以及實現。
為什麼需要動態連結?靜態連結存在的兩個問題:記憶體和磁碟空間浪費;程式開發、釋出和更新比較麻煩;而動態連結是把連結的過程推遲到了執行時再進行,解決了靜態連結存在的兩個問題,當然動態連結也會帶來一些問題,比如效能;
程式模組在編譯時目標地址不確定而需要在裝載時將模組重定位,叫做裝載時重定位,Windows下叫做基址重置。而動態模組中有絕對地址引用的話,指令部分如何在多個程序之間共享?基本想法是把指令中需要修改的部分分離出來,跟資料部分放在一起,這種方案叫做地址無關程式碼。
動態連結的效能要低於靜態連結,常用的優化手段:延遲繫結實現等;
動態連結實現的相關結構;
動態連結的步驟和實現:1.動態聯結器自舉;2.裝載共享物件;3.重定位和初始化。
Linux共享庫的組織
本章介紹了大量的共享庫怎麼在更新或者升級中相容版本。如何管理和維護這些版本庫。介紹了ELF共享庫的版本命名方式、共享庫的符號版本機制、共享庫路徑、查詢過程、環境變數、共享庫的建立與安裝等。
共享庫的相容性、共享庫的版本命名、SO-NAME表示了一個庫的介面(介面不向後相容)
共享庫的路徑:/lib,/usr/lib,/usr/local/lib
共享庫的建立和安裝
庫和執行庫
記憶體
本章回顧程式的基本記憶體佈局,然後介紹棧和堆
程式的記憶體佈局:棧、堆、可執行檔案映像、保留區
棧:暫存器操作、函式傳參、函式返回值傳遞(返回資料結構)
堆與記憶體管理:執行庫一個演算法來管理堆空間,比如空閒連結串列、點陣圖、物件池等,而實際上,堆的分配演算法是採取多種演算法複合而成。
執行庫
本章介紹執行庫的各個方面:程式入口點的實現、CRT的初始化過程(比如IO初始化)、庫函式的實現、執行庫的構造、執行庫與併發、C++執行庫實現全域性構造的方法。
入口函式是什麼? 怎麼實現?
C語言執行庫包含功能:啟動與退出、標準函式、I/O、堆、語言實現、除錯
執行庫與多執行緒:執行緒區域性儲存實現
C++全域性構造與析構
fread函式的實現(緩衝、文字換行等)
系統呼叫與API
本章介紹了系統呼叫和API,並進而介紹了特權級、中斷等系統呼叫相關的實現原理。同時介紹了Windows下的API的歷史和成因、組織形式、實現原理等。
很多資源是作業系統管理的,作業系統提供系統呼叫的介面給應用程式。而Windows上,在系統呼叫之上又封裝了一個叫做API的東西,API提供介面給應用程式;
系統呼叫是執行在核心態,作業系統通過中斷從使用者態切換到核心態。中斷可以通過輪詢或者訊號。中斷的兩個屬性:中斷號和中斷處理程式,核心中,有一個數組:中斷向量表。中斷有兩種型別:硬體中斷和軟體中斷,比如系統呼叫就是0x80號的軟體中斷。
系統呼叫的實現:觸發中斷、切換堆疊、中斷處理程式。
Windows APi是作業系統提供給應用程式開發者的最底層與Windows打交道的介面。API基於系統呼叫,系統呼叫在Windows上叫做系統服務。API之所以存在是因為,系統呼叫非常依賴硬體介面,Windows為了程式相容性(商業作業系統),加了一箇中間層:API。
執行庫實現
本章實現了一個簡易的CRT,接著添加了C++語言特性的支援。
C 語言執行庫:入口函式、main函式、CRT初始化、結束部分、堆的實現、IO與檔案操作、字串相關操作、格式化字串;
C++執行庫實現:new/delete、C++全域性構造與析構、入口函式修改、stream與string