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,坑位有限,備註“掘金網友”可被群管通過~