iOS老司機的<<程序員的自我修養:鏈接、裝載與庫>>讀書分享

語言: CN / TW / HK

持續創作,加速成長!這是我參與「掘金日新計劃 · 10 月更文挑戰」的第6天,點擊查看活動詳情

前言

  • 當我們在開發iOS App時, cmd + R的背後發生了什麼?
  • 這是個值得思考的問題. iPhone是一部手機, 也是一台移動計算機.
  • <<程序員的自我修養>>這本書就站在計算機操作系統底層的角度, 幫我們分析了程序運行背後的故事.
  • 近年來很多iOS同仁都在感歎大前端要學習的東西實在太多, 感覺學不過來.
  • 但是此書裏就有着這樣的視角: ``` CPU體系結構、彙編、C語言(包括C++)和操作系統, 永遠都是編程 大師們的護身法寶,就如同少林寺的《易筋經》,是最為上乘的武功; 學會了《易筋經》,你將無所不能,任你創造武功; 學會了編程“易筋經”,大師們可以任意開發操作系統、編譯器,甚至是開發一種新的程序設計語言!

——佚名

- 也提出了以下幾點大家習以為常, 而又陌生的問題: 1. 為什麼程序是從main開始執行?

  1. “malloc分配的空間是連續的嗎?”

  2. “目標文件是什麼?鏈接又是什麼?” “為什麼這段程序鏈接時報錯?” ```

  3. 其實, 正如本書的核心觀點程序入口、運行 庫初始化、全局/靜態對象構造析構、靜態和動態鏈接時程序的初始化 和裝載等。我們把這些問題歸結起來,發現主要是三個很大的而且連貫 的主題,那就是“鏈接、裝載與庫”。
  4. 本書的內容的確不是介紹一門新的編程語言或展示一些實用的編程技術, 而是介紹程序運行背後的機制 和由來, 可以看作是程序員的一種修養.
  5. ps: 學習筆記純手打, 閲讀中如若發現錯別字, 還望評論區指正, 先行謝過了:)

image.png

第一部分 簡介

第1章 温故而知新

  • 1.1 從Hello World説起
  • 1.2 萬變不離其宗

  • 1.3 站得高, 望得遠

  • 1.4 操作系統做什麼

    • 1.4.1 不要讓CPU打盹
    • 1.4.2 設備驅動
  • 1.5 內存不夠怎麼辦

    • 1.5.1 關於隔離
    • 1.5.2 分段(Segmentation)
    • 1.5.3 分頁(Paging)
  • 1.6 眾人拾柴火焰高

    • 1.6.1 線程基礎
    • 1.6.2 線程安全
    • 1.6.3 多線程內部情況
  • 1.7 本章小結

    • 一個字節
      • 存儲8位無符號數,儲存的數值範圍為0-255。
      • 如同字元一樣,字節型態的變數
      • 只需要用一個位元組(8位元)的內存空間儲存。
      • 1字節(Byte)=8位(bit) 
      • 1KB( Kilobyte,千字節)=1024B (2^10)B
      • 1MB( Megabyte,兆字節)=1024KB (2^10)KB
      • 1GB( Gigabyte,吉字節,千兆)=1024MB (2^10)MB
      • 4GB = 4*(2^30)B = (2^32)B

第二部分 靜態鏈接

第2章 編譯和鏈接

  • 2.1 被隱藏了的過程

    • hello.c文件
    • 2.1.1 預編譯 生成hello.i文件
    • 2.1.2 編譯 生成hello.s 目標文件
      • array[index] = (index + 4) * (2 + 6)
      • 掃描
      • 語法分析
      • 語義分析
      • 源代碼優化

      • 代碼生成
      • 目標代碼優化
    • 2.1.3 彙編 生成hello.o文件
    • 2.1.4 鏈接 生成a.out 可執行文件
  • 2.2 編譯器做了什麼

    • 2.2.1 詞法分析
    • 2.2.2 語法分析
    • 2.2.3 語義分析
    • 2.2.4 中間語言生成
    • 2.2.5 目標代碼生成與優化
  • 2.3 鏈接器年齡比編譯器長

  • 2.4 模塊拼裝--靜態鏈接

  • 2.5 本章小結

第3章 目標文件裏有什麼

  • 3.1 目標文件的格式 COFF(Common File Format)

    • 可重定位文件
    • 可執行文件
    • 共享目標文件
    • 核心轉儲文件
  • 3.2 目標文件是什麼樣的

  • 3.3 挖掘SimpleSection.o

    • 3.3.1 代碼段
    • 3.3.2 數據段和只讀數據段
    • 3.3.3 BSS段
    • 3.3.4 其他段
  • 3.4 ELF文件結構描述

    • 3.4.1 頭文件
    • 3.4.2 段表
    • 3.4.3 重定位表
    • 3.4.4 字符串表
  • 3.5 鏈接的接口--符號

    • 3.5.1 ELF符號表結構
    • 3.5.2 特殊符號
    • 3.5.3 符號修飾與函數簽名
      • 由於不同的編譯器採用不同的名字修飾方法, 
      • 這就導致由不同編譯期編譯產生的目標文件無法正常相互鏈接,
      • 這是導致不同編譯器之間不能互操作的主要原因之一.
    • 3.5.4 extern"C"
      • C++編譯器會將在extern "C"的大括號內部的代碼,
      • 當做C語言代碼處理. C++的名稱修飾機制將不會起作用.
    • 3.5.5 弱符號(Weak Symbol)與強符號
  • 3.6 調試信息

  • 3.7 本章小結

第4章 靜態鏈接

  • 4.1 空間與地址分配

    • 問題
      • 對於多個輸入目標文件,
      • 鏈接器如何將它們的各個段合併到輸出文件?
    • 4.1.1 按序疊加
    • 4.1.2 相似段合併
    • 符號地址的確定
  • 4.2 符號解析與重定位

    • 4.2.1 重定位
      • objdump -h a.o
      • 把ELF文件的各個段的基本信息打印出來
      • objdump -d a.o
      • 查看"a.o"的代碼段反彙編結果
    • 4.1.3 4.2.2 重定位表
      • objdump -r a.o
      • 查看"a.o"裏面要重定位的地方
      • 即"a.o"所有引用到外部符號的地址
    • 4.2.3 符號解析
    • 4.2.4 指令修正方式
  • 4.3 COMMON塊

  • 4.4 C++相關問題

    • 4.4.1 重複代碼消除
    • 4.4.2 全局構造與析構
    • 4.4.3 C++與ABI
      • ABI(Application Binary Interface)
        • 符號修飾標準
        • 變量內存佈局
        • 函數調用方式等
        • 跟可執行代碼二進制兼容性相關的內容
        • 更關注二進制層面
      • API(Application Programming Interface)
        • 更關注源代碼層面
  • 4.5 靜態庫鏈接

  • 4.6 鏈接過程控制

    • 4.6.1 鏈接控制腳本
    • 4.6.2 最"小"的程序
    • 4.6.3 使用ld鏈接腳本
    • 4.6.4 ld鏈接腳本語法簡介
  • 4.7 BFD庫(Binary File Descriptor library)

  • 4.8 本章小結

    • 靜態鏈接中的第一個步驟
      • 目標文件在被鏈接成最終可執行文件時,
      • 輸入目標文件中的各個段是如何被合併到輸出文件中的,
      • 鏈接器如何為它們分配在輸出文件中的空間和地址.
  • 一旦輸入段的最終地址被確定, 

  • 接下來就可以進行符號的解析與重定位,

    • 鏈接器會把各個輸入目標文件中對於外部符號的引用進行解析,
    • 把每個段中須重定位的指令和數據進行修補,
    • 使它們都指向正確的位置.

第5章 Windows PE/COFF

  • 5.1 Windows的二進制文件格式PE/COFF

  • 5.2 PE的前身--COFF

5.3 鏈接指示信息

".drectve段"(指令Directive的縮寫), 

這個段保存的是編譯期傳遞給鏈接器的命令行參數,

可以通過這個段實現指定運行庫等功能.

  • 5.4 調試信息

  • 5.5 大家都有符號表

  • 5.6 Windows下的ELF--PE

    • 5.6.1 PE數據目錄
  • 5.7 本章小結

    • Windows下的可執行文件、動態鏈接庫等都使用PE文件格式,
    • PE文件格式是COFF文件格式的改進版本, 
    • 增加了PE頭文件、數據目錄等一些結構,
    • 使得能夠滿足程序執行時的需求.

第三部分 裝載與動態鏈接

第6章 可執行文件的裝載與進程

  • 6.1 進程虛擬地址空間

    • 虛擬地址空間的大小由CPU的位數決定
    • 硬件決定了地址空間的最大理論上限,
    • 即硬件的尋址空間大小,
    • 比如32位的硬件平台決定了虛擬地址空間的地址為2^32 - 1,
    • 即0x00000000~0xFFFFFFFF,
    • 也就是我們常説的4GB虛擬空間大小.
    • 64位的硬件平台具有64位尋址能力,
    • 它的虛擬地址空間達到了264字節,
    • 即0x0000000000000000~0xFFFFFFFFFFFFFFFF,
    • 總共17 179 869 184 GB.
  • 6.2 裝載的方式

    • 6.2.1 覆蓋裝入
    • 6.2.2 頁映射
      • FIFO(先進先出算法)
      • LUR(最少使用算法)
      • 目前硬件規定的頁的大小有4096字節、8192字節、2MB、4MB等
      • 最常見的IntelA32處理器一般都使用4096字節的頁,
      • 那麼512MB的物理內存就有 51210241024 / 4096 = 131 072個頁
  • 6.3 從操作系統角度看可執行文件的裝載

    • 6.3.1 進程的建立
        1. 創建一個獨立的虛擬地址空間.
        1. 讀取可執行頭文件, 並且建立虛擬空間與可執行文件的映射關係.
        1. 將CPU的指令寄存器設置成可執行文件的入口地址, 啟動運行.
      • 虛擬內存區域VMA(Virtual Memory Area)
    • 6.3.2 頁錯誤
  • 6.4 進程虛存空間分佈

    • 6.4.1 ELF文件鏈接視圖和執行視圖
    • 6.4.2 堆和棧
      • 小結進程虛擬地址空間的概念:
      • 操作系統通過給進程空間劃分出一個個VMA來管理進程的虛擬空間;
      • 基本原則是將相同權限屬性的、有相同映像文件的映射成一個VMA;
      • 一個進程基本上可以分為如下幾種VMA區域:
      • 代碼VMA, 權限只讀、可執行; 有映像文件.
      • 數據VMA, 權限可讀寫、可執行; 有映像文件.
      • 堆VMA, 權限可讀寫、可執行; 無映像文件, 匿名, 可向上擴展.
      • 棧VMA, 權限可讀寫、不可執行; 無映像文件, 匿名, 可向下擴展.
    • 6.4.3 堆的最大申請數量
    • 6.4.4 段地址對齊
    • 6.4.5 進程棧初始化
  • 6.5 Linux內核裝載ELF過程簡介

  • 6.6 Windows PE的裝載

  • 6.7 本章小結

    • 程序運行時如何使用內存空間的問題,
    • 即進程虛擬地址空間問題.
    • 覆蓋裝入 => 頁映射.
    • 在這一章中, 我們假設程序都是靜態鏈接的,
    • 那麼它們都只有一個單獨的可執行文件模塊.

第7章 動態鏈接

  • 7.1 為什麼要動態鏈接

    • 靜態鏈接
      • 使得不同的程序開發者和部門能夠相對獨立地開發和測試自己的程序模塊,
      • 從某種意義上來講大大促進了程序開發的效率, 
      • 原先限制程序的規模也隨之擴大.
      • 但浪費內存和磁盤空間,模塊更新困難.
      • 整個進程只有一個文件可執行文件本身要被映射.
    • 動態鏈接
      • 簡單地講, 就是不對那些組成程序的目標文件進行鏈接,
      • 等到程序要運行時才進行鏈接.
      • 把鏈接這個過程推遲到運行時再進行.
      • 除了映射可執行文件本身之外, 還有它所依賴的共享目標文件.
  • 7.2 簡單的動態鏈接例子

  • 7.3 地址無關代碼

    • 7.3.1 固定裝載地址的困擾
    • 7.3.2 裝載時重定位
    • 7.3.3 地址無關代碼
      • -fPIC 參數產生地址無關代碼
      • 模塊內部
        • 指令跳轉、調用: 相對跳轉和調用
        • 數據訪問: 相對地址訪問
      • 模塊外部
        • 指令跳轉、調用: 間接跳轉和調用(GOT)
        • 數據訪問: 間接訪問(GOT)
    • 7.3.4 共享模塊的全局變量問題
    • 7.3.5 數據段地址無關性
  • 7.4 延遲綁定(PLT Procedure Linkage Table)

  • 7.5 動態鏈接相關結構

    • 7.5.1 ".interp"段(interpreter解釋器)
    • 7.5.2 ".dynamic"段
    • 7.5.3 動態符號表
    • 7.5.4 動態鏈接重定位表
    • 7.5.5 動態鏈接時進程堆棧初始化信息
  • 7.6 動態鏈接的步驟和實現

    • 動態鏈接的步驟
        1. 啟動動態鏈接器本身
        1. 裝載所有需要的共享對象
        1. 重定位和初始化
    • 7.6.1 動態鏈接器自舉(Bootstrap)
    • 7.6.2 裝載共享對象
    • 7.6.3 重定位和初始化
    • 7.6.4 Linux動態鏈接器實現
        1. 動態鏈接器本身是動態鏈接的還是靜態鏈接的
        1. 動態鏈接器本身必須是PIC的嗎?
        1. 動態鏈接器可以被當做可執行文件運行, 
      • 那麼裝在地址應該是多少? 0x00000000
  • 7.7 顯式 運行時 鏈接(Explicit Run-time Linking)

    • 7.7.1 dlopen() (用來打開一個動態庫)
    • 7.7.2 dlsym() 
    • 7.7.3 dlerror()
    • 7.7.4 dlclose()
    • 7.7.5 運行時
  • 7.8 本章小結

    • 使用動態鏈接技術的原因?
    • 更加有效地利用內存和磁盤資源裝載的演示程序
    • 更加方便地維護升級程序
    • 讓程序的重用變得更加可行和有效
    • 動態鏈接中裝載地址不確定時如何解決地址引用問題?
    • 裝載時重定位
    • 缺點是無法共享代碼段, 
    • 但是它的運行速度較快
    • 地址無關代碼
    • 缺點是運行速度稍慢, 
    • 但它可以實現代碼段在各個進程之間的共享

第8章 LINUX共享庫的組織

  • 8.1 共享庫版本(Shared Library)

    • 8.1.1 共享庫兼容性
    • 8.1.2 共享庫版本命名
      • libname.so.x.y.z
    • 8.1.3 SO-NAME
  • 8.2 符號版本

    • 8.2.1 歷史回顧
    • 8.2.2 Solaris中的符號版本機制
    • 8.2.3 Linux中的符號版本
    • 8.3 共享庫系統路徑
      • FHS(File Hierarchy Standard)
        • 這個標準規定了一個系統中的系統文件,
        • 應該如何存放, 包括各個目錄的結構、組織和作用.
        • FHS規定, 一個系統中主要有兩個存放共享庫的位置,
        • /lib, 存放系統最關鍵和基礎的共享庫
        • /usr/lib, 一些非系統運行時所需要的關鍵性的共享庫
        • /usr/local/lib, 一些跟操作系統本身並不十分相關的庫,三方程序的庫
      • /lib和/usr/lib是一些很常用的、成熟的, 一般是系統本身所需要的庫
      • /usr/local/lib是非系統所需的第三方程序的共享庫
  • 8.4 共享庫查找過程

    • ldconfig程序
  • 8.5 環境變量

  • 8.6 共享庫的創建和安裝

    • 8.6.1 共享庫的創建
    • 8.6.2 清除符號信息
    • 8.6.3 共享庫的安裝
    • 8.6.4 共享庫構造和析構函數
    • 8.6.5 共享庫腳本
  • 8.7 本章小結

    • 由於系統中存在大量的共享庫, 並且每個共享庫都會隨着更新和升級
    • 形成不同的相互兼容或不兼容的版本.
    • 如何管理和維護這些共享庫, 讓它們的不同版本之間不會相互衝突,
    • 是使用共享庫的一個重要問題.

第9章 Windows下的動態鏈接

  • 9.1 DLL(Dynamic-Link Library動態鏈接庫)簡介

    • 9.1.1 進程地址空間和內存管理
    • 9.1.2 基地址和RVA(Relative Virtual Address相對地址)
    • 9.1.3 DLL共享數據段
    • 9.1.4 DLL的簡單例子
    • 9.1.5 創建DLL
    • 9.1.6 使用DLL
    • 9.1.7 使用模塊定義文件
    • 9.1.8 DLL顯式運行時鏈接
  • 9.2 符號導出導入表

    • 9.2.1 導出表
    • 9.2.2 EXP文件
    • 9.2.3 導出重定向
    • 9.2.4 導入表
      • 如果我們在某個程序中使用到了來自DLL的函數或者變量,
      • 那麼我們就把這種行為叫做符號導入(Symbol Importing)。
      • FirstThunk指向一個導入地址數組(Import Address Table),
      • IAT是導入表中最重要的結構。
      • 對於一個只讀的段,動態連接器是怎麼改寫它呢?
      • 對於Windows來説,由於它的動態連接器其實是Windows內核的一部分,
      • 所以他可以隨心所欲地修改PE裝載以後的任意一部分內容,
      • 包括內容和它的頁面屬性。裝載時,將導入表所在的頁面改成刻度寫的,
      • 一旦導入表的IAT被改寫完畢,再將這些頁面設回至只讀屬性。
      • ELF運行程序隨意修改.got,而PE則不允許,PE的做法更安全。
    • 9.2.5 導入函數的調用
  • 9.3 DLL優化

    • 二分查找法
    • 9.3.1 重定基地址(Rebasing)
      • 空間換時間
    • 9.3.2 序號(Ordinal Number)
    • 9.3.3 導入函數綁定
  • 9.4 C++與動態鏈接

    • 共享庫可以單獨更新是它的一大優勢,
    • 但是如果這是一個C++編寫的共享庫,那又是另外一回事了,
    • 它有可能是一場噩夢。這一切噩夢的根源還是由於:
    • C++的標準只規定了語言層面的規則,而對二進制級別卻沒有任何規定。
    • 《COM(Component Object Model組件對象模型)本質論》
      • 如果StringFind.DLL所使用的CRT版本與用户主程序或者其他DLL所使用的CRT版一樣,
      • 程序就會發生內存釋放錯誤。
      • 可以吧COM的一些精神提取去來,用於指導我們使用C++編寫動態鏈接庫。
      • 在Windows平台下,要儘量遵循以下幾個寄到意見:
          1. 所有的接口函數都應該是抽象的。所有的方法都應該是純虛的。
          1. 所有的全局函數都應該使用extern“C”來防止名字修飾的不兼容。
          1. 不要使用C++標準庫STL。
          1. 不要使用異常。
          1. 不要使用虛析構函數。
          1. 不要在DLL裏面申請內存,而且在DLL外釋放(或者相反)。
          1. 不要在接口中使用重載方法(Overloaded Mothods)。
        • 因為不同的編譯器對於vtable的安排可能不同。
      • 在DLL中使用C++的其他特性
        • 數函數、多繼承、異常、重載、模版
  • 9.5 DLL HELL(DLL噩夢)

    • 版本更新時發生不兼容的問題。
    • Manifest機制
  • 9.6 本章小結 ``` 動態鏈接機制對於Windows操作系統來説極其重要,

整個Windows系統本身即基於動態鏈接機制,

Windows的API也以DLL的形式提供給程序開發者,

而不像Linux等系統是以系統調用作為操作系統的最終入口。

DLL比Linux下的ELF共享庫更加複雜,提供的功能也更為完善。

在這一章中介紹了DLL在進程地址空間中的分佈、基地址和RVA、共享數據段、

如何創建和使用DLL、如何使用模塊文件控制DLL的產生。

接着我們還詳細分析了DLL的符號導入導出機制以及DLL的

重定基地址、序號和導入函數綁定、DLL與C++等問題。

最後我們探討了DLL HELL問題,並且介紹了

解決DLL HELL問題的方法、manifest及相關問題。 ```

第四部分 庫與運行時

  • 提問 ```
  • malloc是如何分配出內存的?

  • 局部變量存放在哪裏?

  • 為什麼一個編譯好的簡單的HelloWorld程序也需要佔據好幾KB的空間?

  • 為什麼程序一啟動就有堆、I/O或異常系統可用? ```

  • 程序的環境由以下三個部分組成:內存、運行庫、系統調用。

  • 內存是承載程序運行的介質,也是程序進行各種運算和表達的場所。

第10章 內存

  • 10.1 程序的內存佈局

    • 棧(Stack):棧用於維護函數調用的上下文,離開了棧函數調用就沒法實現。
      • 棧通常在用户空間的最高地址處分配,通常有數兆字節的大小。
    • 堆:堆是用來容納應用程序動態分配的內存區域,
      • 當程序使用malloc或new分配內存時,得到的內存來自堆裏。
      • 堆通常存在於棧的下方(低地址方向),在某些時候,
      • 堆也可能沒有固定統一的存儲區域。
      • 堆一般比棧大很多,可以有幾十至是百兆字節的容量。
  • 10.2 棧與調用慣例

    • 10.2.1 什麼是棧 ``` 在經典的操作系統裏,棧總是向下增長的。

在i386下,棧頂由稱為esp的寄存器進行定位。

壓棧(push)的操作使棧頂的地址減小,

彈出(pop)的操作使棧頂地址增大。

棧保存了一個函數調用需要的維護信息,

這常常被稱為堆棧幀(Stack Frame)或活動記錄(Activate Record)

在i386中,一個函數的活動記錄用ebp和esp這兩個寄存器劃定範圍。

esp寄存器始終指向棧的頂部,同時也就指向了當前函數的活動記錄的頂部。

而相對的,ebp寄存器指向了函數活動記錄的一個固定位置,

ebp寄存器又被稱為幀指針(Frame Pointer)。

ebp之前首先是這個函數的返回地址,ebp-4。 ```

    • 10.2.2 調用慣例
      • 函數參數的傳遞順序和方式
      • 棧的維護方式
      • 名字修飾的策略(Name-mangling)
    • 10.2.3 函數返回值傳遞
      • C++ 返回值優化 TVO (Return Value Optimization)
  • 10.3 堆與內存管理

    • 10.3.1 什麼是堆
    • 10.3.2 Linux進程堆管理
    • 10.3.3 Windows進程堆管理
    • 10.3.4 堆分配算法
      • 空閒鏈表
      • 位圖
      • 對象池
  • 10.4 本章小結 ``` 首先回顧了i386體系結構下程序的基本內存佈局, 

並且對程序內存結構中非常重要的兩部分棧與堆進行了詳細的介紹.

在介紹棧的過程中, 學習了棧在函數調用中發揮的重要作用,

以及與之伴生的調用慣例的各方面的知識.

最後, 還了解了函數傳遞返回值的各種技術細節.

在介紹堆的過程中, 首先了解了構造堆的主要算法: 空閒鏈表和位圖.

此外, 還介紹了Windows和Linux的系統堆的管理內幕. ```

第11章 運行庫

  • 11.1 入口函數和程序初始化

    • 11.1.1 程序從main開始執行嗎
    • 11.1.2 入口函數如何實現
    • 11.1.3 運行庫與I/O
    • 11.1.4 MSVC CRT的入口函數初始化 ``` MSVC的I/O初始化主要進行了如下幾個工作:
  • 建立打開文件表.

  • 如果能夠集成自父進程, 那麼從父進程獲取繼承的句柄.

  • 初始化標準輸入輸出. ```

  • 11.2 C/C++運行庫

    • 11.2.1 C語言運行庫
    • 11.2.2 C語言標準庫
    • 11.2.3 glibc與MSVC CRT
  • 11.3 運行庫與多線程

    • 11.3.1 CRT的多線程困擾 ``` 為了解決C標準庫在多線程環境下的窘迫處境,

許多編譯器附帶了多線程版本的運行庫.

在MSVC中, 可以用/MT或/MTd等參數指定使用多線程運行庫. ``` - - 11.3.2 CRT改進

    • 11.3.3 線程局部存儲實現TLS(Thresd Local Storage)
  • 11.4 C++全局構造與析構

    • 11.4.1 glibc全局構造與析構
    • 11.4.2 MSVC CRT的全局構造和析構
  • 11.5 fread實現

    • 11.5.1 緩衝
    • 11.5.2 fread_s
    • 11.5.4 _read
    • 11.5.5 文本換行
    • 11.5.6 fread回顧 ``` 當用户調用CRT的fread時, 它到ReadFile的調用詭計如下:

fread ->

fread_s(增加緩衝溢出保護, 加鎖) ->

_fread_nolock_s(循環讀取、緩衝) ->

_read(換行符轉換) ->

ReadFile(Windows文件讀取API) - 11.6 本章小結 介紹了程序運行庫的各個方面, 

首先詳細瞭解了Glibc和MSVC CRT的程序入口點的實現,

並在此基礎上着重分析了MSVC CRT的初始化過程,

尤其是MSVC的IO初始化.

還介紹了C/C++運行庫的其他方方面面, 

包括庫函數的實現、運行庫的構造、運行庫與併發的關係,

以及最後C++運行庫實現全局構造的方法.

在介紹這些內容的過程中, 我們一改以往以Glibc的代碼為主要示例的方法,

着重以MSVC提供的運行庫源代碼為例子介紹了fread在CRT中的實現.

猶豫Glibc為了支持多平台, 它的IO部分源代碼顯得十分複雜且難懂,

不便於在本書中講解, 於是改為介紹MSVC的fread實現. ```

第12章 系統調用與API

  • 12.1 系統調用介紹

    • 12.1.1 什麼是系統調用
    • 12.1.2 Linux系統調用
    • 12.1.3 系統調用的弊端 ``` 解決這個問題, "萬能法則"又可以發揮作用了:

"解決計算機的問題可以通過增加層來實現",

於是運行庫挺身而出, 它作為系統調用與程序之間的

一個抽象層可以保持着這樣的特點:

使用便捷、形式統一. ``` - 12.2 系統調用原理

    • 12.2.1 特權級與中斷
      • User Mode 用户態
      • Kernel Mode 內核態
    • 12.2.2 基於int的Linux的經典系統調用實現 ```
  • 觸發中斷

  • 切換堆棧

在Linux中, 用户態和內核態使用的是不同的棧

兩者各自負責各自的函數調用, 互不干擾.

  1. 中斷處理程序 ```
    • 12.2.3 Linux的新型系統調用機制 ``` ldd /bin/ls 獲取一個可執行文件的共享庫的依賴情況

虛擬動態共享庫 Virtual Dynamic Shared Library

cat /proc/self/maps 查看cat自己的內存佈局

_kernel_vsyscall函數 負責進行新型的系統調用. ``` - 12.3 Windows API(Application Programming Interface 程序編程接口)

    • 12.3.1 Windows API概覽
      • MSDN是學習Win32 API極佳的工具.
    • 12.3.2 為什麼要使用Windows API
      • Windows API層就是這樣的一個"銀彈".
    • 12.3.3 API與子系統 ``` 子系統實際上有事Windows架設在API和應用程序之間的另一箇中間層.

Windows子系統在實際上已經被拋棄了. - 12.4 本章小結 回顧了進程與操作系統打交道的途徑: 系統調用和API.

介紹操作系統調用的部分中, 主要介紹了

特權級、中斷等系統調用的實現原理, 

然後還詳細介紹了Linux的系統調用的內容和實現細節.

介紹API的過程中, 回顧了API的歷史與成因、API的組織形式、實現原理.

同時還提到了與API伴生的子系統, 

介紹了子系統的存在意義、組織形式等. ```

第13章 運行庫實現

  • 13.1 C語言運行庫

    • 13.1.1 開始
    • 13.1.2 堆的實現
    • 13.1.3 IO與文件操作
    • 13.1.4 字符串相關操作
    • 13.1.5 格式化字符串
  • 13.2 如何使用Mini CRT

  • 13.3 C++運行庫實現

    • 13.3.1 new與delete
    • 13.3.2 C++全局構造與析構
      • 全局new/deete操作符重載(Global new/delete operator overloading)
    • 13.3.3 atexit實現
      • 先構造的全局對象應該後析構
    • 13.3.4 入口函數修改
    • 13.3.5 stream與string
  • 13.4 如何使用Mini CRT++

  • 13.5 本章小結 ``` 在這一章, 我們首先嚐試實現了一個支持C運行的簡易CRT: Mini CRT.

接着又為它加上了一些C++語言特性的支持, 並且將它稱為Mini CRT++.

在實現C語言運行庫的時候, 介紹了入口函數entry、堆分配算法malloc/free、IO

和文件操作fopen/fread/fwrite/fclose、

字符串函數strlen/strcmp/atoi和格式化字符串printf/fprintf.

在實現C++運行庫時, 着眼於實現C++的幾個特性:

new/delete、全局構造和析構、stream和string類.

因此在實現Mini CRT++的過程中, 我們得以詳細瞭解並親自動手

實現運行庫的各個細節, 得到一個可編譯運行的瘦身運行庫版本.

當然, Mini CRT++所包含的僅僅是真正的運行庫的一個很小子集,

它並不追求完整, 也不在運行性能上做優化,

它僅僅是一個CRT的雛形, 雖説很小, 但能夠通過

Mini CRT++窺視真正的CRT和C++運行庫的全貌,

拋磚引玉、舉一反三正式Mini CRT++的目的. ``` - 附錄A

    • A.1 字節序 ``` 通信雙方交流的信息單元應該以什麼樣的順序進行傳送.

目前在各種體系的計算機中通常採用的字節存儲機制主要有兩種:

大端(Gig-endian)

小端(Little-endian)

MSB(Most Significant Bit/Byte)最重要的位/字節

LSB(Least Significant Bit/Byte)最不重要的位/字節

一個十六進制的整數: 0x12345678, 

0x12就是MSB, 0x78就是LSB.

對於0x78這個字節而言, 它的二進制是 01111000,

那麼最左邊的那個0就是MSB, 最右邊的那個0就是LSB.

Big-endian和Little-endian的區別就是

Big-endian規定MSB在存儲時放在低地址, 在傳輸時MSB放在流的開始;

LSB存儲時放在高地址, 在傳輸時放在流的末尾.

Little_Endian主要用於我們現在的PC的CPU中,

即Intel的x86系列兼容機;

Big-Endian則主要應用在目前的Mac機器中, 一般指PowerPC系列處理器.

另外值得一提的是, 目前的TCP/IP網絡及Java虛擬機的字節序都是Big-Endian的.

這意味着如果通過網絡傳輸0x12345678這個整型變量,

首先被髮送的應該是0x12, 最後是0x78.

所以我們的程序在處理網絡流的時候, 必須注意字節序的問題.

Little-endian則相反. ```

閲讀導圖

IMG_1676.PNG

發文不易, 喜歡點讚的人更有好運氣👍 :), 定期更新+關注不迷路~

ps:歡迎加入筆者18年建立的研究iOS審核及前沿技術的三千人扣羣:662339934,坑位有限,備註“掘金網友”可被羣管通過~