【NJU-OS-JYY筆記】操作系統:設計與實現

1. 緒論

1.1. 程序的執行與狀態機

在計算機科學中,任何程序都可以被抽象為一個狀態機,無論是我們熟知的日常工具(LibreOffice,Chrome)還是開發工具(IDE,GCC,GDB),它們本質沒有任何區別。

  • 程序狀態?= 處理器狀態(寄存器值)+?內存狀態?+?操作系統狀態(文件描述符、信號處理等
  • 每次 CPU 執行一條指令 = 狀態機的一次遷移

程序的執行過程本質上是狀態的不斷變化。具體而言:

  • 狀態由以下兩部分組成:
    • 變量數值:包括全局變量和局部變量的值。
    • :程序執行過程中函數調用的上下文信息。
  • 初始狀態:當程序啟動時,初始狀態由?main?函數的第一條語句決定。此時,棧中僅包含一個?StackFrame,表示?main?函數的初始調用,全局變量也被初始化為預設的初始值。

--- 程序通過操作系統的?execve?系統調用被加載到內存中,并初始化為一個獨立的進程。

  • 狀態遷移:程序的執行可以看作是由一條條語句驅動的狀態遷移。每一步執行都會導致狀態的變化,例如變量值的更新或棧的修改。

--- 程序在內存中運行,經歷一系列的狀態遷移,完成計算任務,并通過系統調用與操作系統交互(如文件操作、進程管理、存儲管理等)。程序通過調用?exit?或?exit_group?系統調用終止執行,操作系統回收其占用的資源。

---?model checking?可以在給定一個系統和一個我們希望它擁有的性質的前提下,model checking 算法會探索這個系統的每個狀態,驗證系統是否滿足這個性質。舉個例子:如果我們希望系統滿足“無死鎖”這個性質,那么 model checking 算法就會遍歷系統的每個狀態。如果它發現存在一個從初始狀態可達的狀態沒有后繼狀態,那么它就找到了系統不滿足“無死鎖”的證據,然后給用戶報錯,否則它就可以告訴我們,這個系統確實是滿足“無死鎖”這個性質的。

一個直觀的狀態機遷移:

int main() {int sum = 0;for (int i = 0; i < 1000000; i++) {  // 用戶態循環sum += i;                       // 用戶態加法}printf(「Sum: %d\n」, sum);           // 庫函數(內部含系統調用)return 0;
}

在這個程序中:

  • 執行了約?200 萬條?用戶態指令(循環+加法)
  • 只發生了?1-2 次?系統調用(通過?printf?的?write?系統調用)
用戶態執行
┌───────────────────────┐      系統調用入口
│ 指令 1: mov eax, 0     │          │
│ 指令 2: add ebx, ecx   │          ▼
│ 指令 3: cmp edx, 100   │      ┌───────────┐
│  ... (百萬條指令)      │      │ 內核態執行 │
│                       │      │ sys_read  │
│ 指令 N: call printf    ├─────?│ sys_write │
└───────────────────────┘      └───────────┘▲       ││       ▼└───────┘系統調用返回

怎么獲得一個?Linux 進程的系統調用序列?

# 啟動新進程并跟蹤
strace <命令> [參數]
# 示例:跟蹤 `ls -l`
strace ls -l# 跟蹤已運行的進程(按 PID)
strace -p <PID>
# 示例:跟蹤 PID 為 1234 的進程
strace -p 1234# 常用選項:
strace -f         # 跟蹤子進程(適用于多進程程序)
strace -o log.txt  # 將輸出保存到文件(避免刷屏)
strace -T         # 顯示系統調用耗時
strace -s 1024    # 顯示更長的參數內容(默認只顯示 32 字節)
strace -e trace=<類別> # 只跟蹤特定系統調用(如文件操作:`-e trace=file`)系統調用指令
進程管理: fork, execve, exit, waitpid, getpid, ...
操作系統對象和訪問: open, close, read, write, pipe, mount, mkfifo, mknod, stat, socket, ...
地址空間管理: mmap, sbrk (mmap 的前身)
以及一些其他的機制: pivot_root, chmod, chown, ..

1.2. 如何證明程序的正確性

狀態機是一個具有嚴格數學定義的概念。這意味著我們可以用形式化的方法來描述和分析程序的行為。這種形式化的方法為程序的正確性驗證和優化提供了理論基礎。

Curry-Howard Correspondence:

  1. 命題即類型:邏輯中的命題 (如 A→B→AB) 對應函數類型 A→B→AB
  2. 證明即程序:命題的構造性證明過程對應該類型的一個具體程序(例如,一個實現 A→BAB?的函數即為 “AA?蘊含 BB” 的證明)。

邏輯中的蘊含消除規則對應函數調用:若有一個類型為 A→BAB?的函數 (證明),且輸入類型 AA?的值 (前提),則輸出類型 BB?的值 (結論)。而對于一階謂詞,例如 ?x.P(x)?x.P(x),就必須提供一個 xx?的實例,這構成了基于構造的 “直覺邏輯” 的基礎

1.3. 操作系統的最大職責

操作系統的最大職責是為程序提供一個透明的運行環境。從程序的角度來看,它似乎“獨占”了整個計算機的資源,并按順序執行每一條指令。然而,實際情況并非如此:操作系統在后臺持續運行,管理著硬件資源、進程調度、文件操作等復雜任務。
當程序執行系統調用(如?readwritefork?等)時,程序的執行會被暫時掛起,而操作系統接管控制權,完成相應的任務(如與硬件交互或管理進程)。完成任務后,操作系統會將控制權交還給程序,使其繼續執行。對于程序而言,這一過程是完全透明的,程序不會感知到時間的流逝或操作系統的干預。

一個最基本的 Unix 模型應該包含:

  • 進程
  • 系統調用
  • 上下文切換
  • 調度

1.4. 程序與操作系統的溝通橋梁

程序與操作系統之間的唯一通信橋梁是系統調用。系統調用是操作系統提供給程序的一組接口,允許程序請求操作系統完成特定任務(如文件讀寫、進程創建等)。例如,在 x86-64 架構中,程序可以通過?syscall?指令發起系統調用。
系統調用的重要性體現在以下幾個方面:

  • 橋梁作用:系統調用是程序與操作系統之間的接口,沒有它,程序無法與外界進行交互。
  • 安全性:系統調用提供了一種安全的機制,確保程序無法直接訪問硬件資源,從而保護系統的穩定性和安全性。

操作系統提供了許多工具(如?strace)來分析和調試程序的系統調用行為,幫助開發者理解程序的運行機制。

2. 虛擬化

2.1. 進程和程序

程序是狀態機的靜態描述,它描述了所有可能的程序狀態,程序 (動態) 運行起來,就成了進程 (進行中的程序)。

操作系統作為進程(狀態機)的管理者,一個直觀的想法就如同 Windows api 一樣,spawn 用來創建狀態機,exit 用來銷毀狀態機,但 Unix 的答案是 fork 用來復制狀態機,execve 用來復位狀態機。

fork 的行為:

立即復制狀態機,包括所有狀態的完整拷貝,寄存器 & 每一個字節的內存,新創建進程返回 0,執行 fork 的進程返回子進程的進程號——“父子關系”。

Tips:

創建一棵層的進程樹(A->B->C),并隨機退出其中的一些進程——我們可以觀察進程退出前后父子進程的關系。

----父進程(B)終止后,子進程(C)成為“孤兒”,操作系統內核(而非用戶進程)負責回收終止進程的資源,并重置其子進程的父進程。這一機制確保所有進程最終都有有效的父進程,避免資源泄露。

int pid = fork();
if (pid == -1) { // 錯誤perror(「fork」); goto fail;
} else if (pid == 0) { // 子進程execve(...);perror(「execve」); exit(EXIT_FAILURE);
} else { // 父進程...int status;waitpid(pid, &status, 0); // testkit.C 中有
}

2.2. 進程地址空間

我們可以使用?pmap?查看某個進程的地址空間,pmap?命令通過讀取?/proc/[pid]/maps?和?/proc/[pid]/smaps?文件來獲取進程的內存映射信息。Linux 內核將這些信息以文本形式暴露在?/proc?文件系統中,pmap?通過解析這些文件實現功能,而無需直接調用特定的系統調用。

進程創建開始時,有一個初始狀態,System V ABI?規定了部分寄存器和棧,Binary 中指定的 PT_LOAD 段,內存分為一段一段,每一段有訪問權限。

區域地址范圍用途內核空間?0xC0000000~0xFFFFFFFF 操作系統內核代碼和數據(所有進程共享)棧(Stack)向下增長存儲局部變量、函數調用信息(如返回地址、參數)堆(Heap)向上增長動態內存分配(malloc/free共享庫映射區固定存放動態鏈接庫(如

區域地址范圍用途
內核空間0xC0000000~0xFFFFFFFF操作系統內核代碼和數據(所有進程共享)
棧(Stack)向下增長存儲局部變量、函數調用信息(如返回地址、參數)
堆(Heap)向上增長動態內存分配(malloc/free)
共享庫映射區固定存放動態鏈接庫(如libc.so),多個進程可共享同一份代碼。文件映射,匿名映射
數據段(Data)固定存放全局變量、靜態變量
代碼段(Text)固定存放可執行指令(只讀)

libc.so),多個進程可共享同一份代碼。

現代 OS 的主流方式,將地址空間劃分為固定大小的頁(Page,通常 4KB),物理內存劃分為頁幀(Page Frame)。

  • 頁表(Page Table)?負責虛擬地址→物理地址的映射:
    • 多級頁表(如 x86-64 采用 4 級頁表:PML4→PDP→PD→PT)。
    • TLB(快表):緩存常用頁表項,加速地址轉換。
  • 缺頁異常(Page Fault)
    • 訪問未映射的頁時觸發,OS 可能從磁盤(Swap 分區)加載數據。

進程的初始狀態:

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。
  1. able data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
  2. 名稱狀態
    特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
    rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
    堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
    線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。

able data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
able data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
M,?UM,?OM,?ZM,?DM,?IM=1: 所有的浮點異常處于屏蔽 (Masked)?狀態。這意味著如果發生這些錯誤,FPU 會自動處理

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。

rFLAGS 寄存器

  • able data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
  • able data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
  • able?data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
  • ZF=0: 沒有結果為 0
    名稱狀態
    特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
    rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
    堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
    線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。
    的比較(相當于還沒開始比較)。
  • SF=0: 結果是非負數(或者理解為結果的高位不是
    名稱狀態
    特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
    rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
    堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
    線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。
    1)。
  • able data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">

le data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。

名稱狀態
特殊寄存器 (浮點單元狀態)所有錯誤狀態標志都清零控制字設置 :RC=0: 默認的舍入方式:四舍五入PC=11: 默認使用最高的 雙擴展精度M, UM, OM, ZM, DM, IM=1: 所有的浮點異常處于屏蔽 (Masked) 狀態。這意味著如果發生這些錯誤,FPU 會自動處理
rFLAGS 寄存器DF=0: 方向標志向前(很多內存操作會向高地址增長)。CF=0: 沒有進位/借位發生(就像剛重置了計算器)。PF, AF=0: 沒有特定的奇偶校驗位或半字節進位標志被設置(可以忽略)。ZF=0: 沒有結果為0的比較(相當于還沒開始比較)。SF=0: 結果是非負數(或者理解為結果的高位不是1)。OF=0: 沒有發生有符號數的溢出(計算結果沒有超出范圍)。
堆棧狀態High Addresses ... ...Auxiliary vector entries ... 0 Environment pointers ... 8+8*argc+%rsp 0 8+%rsp 參數數組 %rsp 參數個數 --- 它指向當前棧頂... ... Low Addresses
線程狀態這個新線程會繼承父線程當前的所有浮點單元狀態在此之后,新線程的浮點狀態就是其私有 (private) 的了。任何修改(例如更改舍入模式或屏蔽位)只影響該線程自身,不會影響父線程或其他線程。


?

輔助向量 (Auxiliary Vector)

操作系統通過這個表向新進程傳遞一些關鍵的系統信息或運行時參數。這些信息對于程序正常啟動、動態鏈接器 (ld-Linux) 工作等都至關重要。

類型名稱 (a_type)值 (Value)信息內容 (a_un)含義解釋
AT_NULL0ignored輔助向量的結束標記! 遇到這個類型,表示后面沒有有效條目了,輔助向量結束。
AT_IGNORE1ignored忽略此條目。 這個條目沒有意義,里面的值(a_un)也不用管。
AT_EXECFD2a_val (整數值)解釋器程序可用的文件描述符。 如果程序需要一個解釋器(如 ld-linux,負責動態鏈接),系統可能會將這個文件描述符傳給解釋器,解釋器可以用它來直接讀取真正要運行的程序文件(比如你的 ./myprogram)。
AT_PHDR3a_ptr (指針)程序在內存中的程序頭表 (Program Header Table) 位置。 如果系統已經把你的程序加載到內存里了,這個指針就指向 ELF 文件結構中程序頭表的起始地址。告訴解釋器(如果需要的話)去哪里找內存映射信息。
AT_PHENT4a_val (整數值)每個程序頭表條目的大小 (字節)。 告訴解釋器上面提到的程序頭表(AT_PHDR)中,每個條目的長度是多少字節(通常是 sizeof(Elf64_Phdr) = 56 字節)。
AT_PHNUM5a_val (整數值)程序頭表條目的個數。 告訴解釋器上面提到的程序頭表(AT_PHDR)里總共有多少個條目。
AT_PAGESZ6a_val (整數值)系統內存分頁大小 (字節)。 告訴程序操作系統管理內存的基本單位有多大(比如 4KB = 4096 字節)。很多內存操作需要對齊到這個大小。
AT_BASE7a_ptr (指針)解釋器程序自身在內存中的加載基地址。 如果程序是通過解釋器運行(如 ld-linux),這個指針指向解釋器自己代碼在內存里的起始地址。
AT_FLAGS8a_val (位掩碼)解釋器相關的標志位 (位掩碼)。 給解釋器(如 ld-linux)使用的特定標志位組合。未定義的位是0。應用程序一般不需要關心。
AT_ENTRY9a_ptr (函數指針)程序真正的入口地址 (Entry Point)。 指向程序可執行文件的入口點地址(在 ELF 文件中標記為 e_entry)。這是程序的代碼開始執行的地方(比如 _start 符號位置)。解釋器最終要把控制權交給這里。這是最重要的指針之一。
AT_NOTELF10a_val (整數值)非 ELF 文件標志。 如果值 非0,就表示當前運行的程序 不是 標準的 ELF 格式文件(可能是其他格式如 a.out, PE等)。如果是 0,就是標準的 ELF 格式。
AT_UID / AT_EUID / AT_GID / AT_EGID11 / 12 / 13 / 14a_val (整數值)用戶和組標識符。
AT_UID: 進程的實際用戶ID (啟動進程的用戶)。
AT_EUID: 進程的有效用戶ID (通常與 AT_UID 相同,或受 setuid 程序影響)。
AT_GID: 進程的實際組ID。
AT_EGID: 進程的有效組ID。
AT_PLATFORM15a_ptr (字符串指針)硬件平臺標識字符串。 指向一個描述底層硬件平臺架構的字符串(例如 "x86_64")。
AT_HWCAP16a_val (位掩碼)CPU 硬件能力位掩碼。 一個整數值,其中的位代表 CPU 支持的特定擴展指令集或功能(例如 MMX, SSE, SSE2, AVX 等)。這個值對應于 CPUID 指令(功能號1)返回的 EDX 寄存器中的一部分特征位。
AT_CLKTCK17a_val (整數值)系統時鐘節拍頻率 (clock tick)。 每秒有多少次 times() 函數會增加時鐘計數(通常是 100)。
AT_SECURE23a_val (整數值)安全模式標志。 如果值是 1,表示程序是以提升權限模式啟動的(例如用戶執行了一個設置了 suid 或 sgid 位的程序)。值是 0 則表示沒有特殊權限。安全敏感的程序需要注意這個標志。
AT_BASE_PLATFORM24a_ptr (字符串指針)基礎平臺標識字符串。 與 AT_PLATFORM 類似,但指向的字符串描述的是更基礎、更通用的平臺架構,可能與硬件平臺字符串不同(例如 AT_PLATFORM 是 "i686", AT_BASE_PLATFORM 可能是 "i386")。
AT_RANDOM25a_ptr (指針)16字節安全隨機數 (Securely Generated Random Bytes)。 指向操作系統提供的、專門用于增強安全性的 16 字節隨機數據(通常由內核的安全隨機數生成器產生)。程序可以使用這個隨機數作為密鑰種子或 ASLR 的加強。
AT_HWCAP226a_val (位掩碼)擴展 CPU 硬件能力位掩碼。 未來可用的額外 CPU 特性位掩碼(當前 AMD64 ABI 定義中該值為 0,保留給未來擴展)。可能對應 CPUID 后續指令或不同寄存器的特征位。
AT_EXECFN31a_ptr (字符串指針)被執行的程序的文件名。 指向一個字符串,該字符串保存了用戶(或 shell)傳遞給 exec() 系列函數的原始程序路徑名(例如 "/usr/bin/ls" 或 "./hello")。與 argv[0] 不同,argv[0] 可能是修改過的(例如通過符號鏈接啟動),而 AT_EXECFN 通常指向實際被執行的文件路徑。

2.3. 地址空間管理

mmap 系統調用,可以在狀態機狀態上增加/刪除/修改一段可訪問的內存:

  • MAP_ANONYMOUS: 匿名 (申請) 內存
  • fd: 把文件 “搬到” 進程地址空間中 (例子:加載器)

ptrace/process_vm_writev,可以修改另一個進程的地址空間

  1. ptrace?系統調用
    通過?PTRACE_PEEKDATA?和?PTRACE_POKEDATA?請求讀寫目標進程內存。調試工具(如 GDB)即依賴此機制,但需附加到目標進程(可能暫停其執行)。
  2. process_vm_writev?系統調用
    允許直接向目標進程寫入數據,效率高于?ptrace,且無需暫停目標進程。需要?CAP_SYS_PTRACE?能力或權限配置。

適用場景:

  1. 高效進程間通信:需要跨進程批量傳輸數據時(如共享內存的替代方案)
  2. 動態熱補丁:運行時修改目標進程的代碼/配置
  3. 調試/監控工具:注入代碼或修改內存狀態進行調試
  4. 性能敏感操作:相比?ptrace?無需頻繁暫停目標進程,延遲更低

2.4. 一切皆文件

文件描述符是指向操作系統對象的 “指針”——系統調用通過這個指針 (fd) 確定進程希望訪問操作系統中的哪個對象。我們有 open, close, read/write, lseek, dup 管理文件描述符。

//openp = malloc(sizeof(FileDescriptor));
//closedelete(p);
//read/write*(p.data++);
//lseekp.data += offset;
//dupq = p;

任何可以讀寫的東西都可以是文件,比如:

真實的設備

  • /dev/sda
  • /dev/tty

虛擬的設備 (文件)

  • /dev/urandom (隨機數), /dev/null (黑洞), ...
    • 它們并沒有實際的 “文件”
    • 操作系統為它們實現了特別的 read 和 write 操作
      • /drivers/char/mem.C
      • 甚至可以通過 /sys/class/backlight 控制屏幕亮度
  • procfs 也是用類似的方式實現的

優點

  1. 統一的編程模型
  • 開發人員只需掌握?open()/read()/write()/close()等少量 API
  • 例如:echo 「hello」 > /dev/pts/0?直接寫入終端
  • 強大的組合能力
  • 管道操作:ls | grep txt | sort | uniq
  • 流重定向:program 2>&1 > log.txt
  • 抽象層次清晰
  • VFS 虛擬文件系統(Linux 核心組件)
  • FUSE 框架允許用戶態文件系統實現
  • 透明的資源訪問
  • /proc/pid/maps?查看進程內存映射
  • /sys/class/net?管理網絡設備

缺點

  • 文件操作需要用戶/內核態切換(昂貴)
  • 網絡通信用文件 API 效率低下
    • 額外的延遲和內存拷貝
    • 單線程 I/O

解決方案

分層 API 架構,操作系統通過「內核基礎 API + 用戶態封裝」來解決這些矛盾:

Windows NT

┌───────────────────────┐

│ Win32 API │ ← 圖形/多媒體/COM 等高級接口

├───────────────────────┤

│ POSIX 子系統層 │ ← 翻譯 Unix API 調用,兼容層適配傳統抽象

├───────────────────────┤

│ NT 內核基礎 API │

│ (Objects, Handles) │ ← 微內核設計:進程/線程/虛擬內存等基本對象

└───────────────────────┘

WSL (Windows Subsystem for Linux)

┌───────────────────────┐

│ Linux 二進制 (ELF) │

├───────────────────────┤

│ lxss.sys (翻譯層) │ ← 轉換 Linux 系統調用→NT API

├───────────────────────┤

│ Windows NT 內核 │

└───────────────────────┘

甚至連我們與電腦交互的終端也都是文件,pty:

偽終端(pseudo terminal,有時也被稱為 pty)是指偽終端 master 和偽終端 slave 這一對字符設備。其中的 slave 對應 /dev/pts/ 目錄下的一個文件,而 master 則在內存中標識為一個文件描述符(fd)。偽終端由終端模擬器提供,終端模擬器是一個運行在用戶態的應用程序。

Master 端是更接近用戶顯示器、鍵盤的一端,slave 端是在虛擬終端上運行的 CLI(Command Line Interface,命令行接口)程序。Linux 的偽終端驅動程序,會把 master 端(如鍵盤)寫入的數據轉發給 slave 端供程序輸入,把程序寫入 slave 端的數據轉發給 master 端供(顯示器驅動等)讀取。請參考下面的示意圖(此圖來自互聯網):

我們打開的終端桌面程序,比如 GNOME Terminal,其實是一種終端模擬軟件。當終端模擬軟件運行時,它通過打開 /dev/ptmx 文件創建了一個偽終端的 master 和 slave 對,并讓 shell 運行在 slave 端。當用戶在終端模擬軟件中按下鍵盤按鍵時,它產生字節流并寫入 master 中,shell 進程便可從 slave 中讀取輸入;shell 和它的子程序,將輸出內容寫入 slave 中,由終端模擬軟件負責將字符打印到窗口中。

/dev/ptmx 是一個字符設備文件,當進程打開 /dev/ptmx 文件時,進程會同時獲得一個指向 pseudoterminal master(ptm)的文件描述符和一個在 /dev/pts 目錄中創建的 pseudoterminal slave(pts) 設備。通過打開 /dev/ptmx 文件獲得的每個文件描述符都是一個獨立的 ptm,它有自己關聯的 pts,ptmx(可以認為內存中有一個 ptmx 對象)在內部會維護該文件描述符和 pts 的對應關系,對這個文件描述符的讀寫都會被 ptmx 轉發到對應的 pts。我們可以通過 lsof 命令查看 ptmx 打開的文件描述符。

可以看到進程的 0u(標準輸入)、1u(標準輸出)、2u(標準錯誤輸出)都綁定到了偽終端 /dev/pts/8 上,這樣一來對這個文件描述符的讀寫都會被 ptmx 轉發到對應的 pts。

查看具體的關聯

# 查看偽終端主從設備
$ ps -ef | grep pts
user     5678  1234  0 10:00 pts/1    00:00:00 bash# 查看文件描述符指向
$ ls -l /proc/1234/fd/12
lrwx------ 1 user user 64 Jan 01 12:34 12 -> /dev/ptmx$ ls -l /dev/pts/1
crw--w---- 1 user tty 136, 1 Jan 01 10:00 /dev/pts/1

完整數據流轉示例(以執行?ls -l?為例)

當我們對當前終端進行操作,或者運行程序時:

  • 在同一個會話里,相關的進程會被放進同一個或不同的進程組。比如,你在 shell 里敲?ls | grep txt &,這個管道命令里的?ls?和?grep?通常就在同一個新的后臺進程組里。而 shell 自己在另一個進程組。
  • 為什么重要??信號(如?Ctrl-C?產生的?SIGINT)通常是發給整個前臺進程組的!這就是為什么?Ctrl-C?能殺掉一個正在運行的復雜命令(比如包含管道的命令)里的所有相關進程。Ctrl-Z(發送?SIGTSTP?暫停信號)也是發給整個前臺進程組。

為什么我們需要 sigaction 替代 Unix 的信號機制?

  1. 跨平臺兼容性
  2. 易導致信號丟失
  3. 防止嵌套信號干擾
// 傳統 signal 的脆弱實現
void handler(int sig) {signal(SIGINT, handler);  // 必須手動重新注冊// 處理邏輯
}
signal(SIGINT, handler);// 使用 sigaction 的可靠實現
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;  // 自動重啟系統調用
sigaction(SIGINT, &sa, NULL);

2.5. 加載器和鏈接器

為了這一章,我直接看了一本書,寫的很好,相見恨晚:

加載器:

  • 程序運行時激活。
  • 核心任務
    • 內存映射:解析可執行文件格式(如 ELF),將代碼/數據段加載到虛擬內存的指定位置(如.text?段只讀,.data?段可讀寫)。
    • 動態鏈接:若程序依賴動態庫(如?glibc),加載器調用動態鏈接器(ld-Linux.so)加載共享庫并解析符號地址(運行時完成)。
    • 環境初始化:設置棧/堆空間,傳遞命令行參數,跳轉到程序入口點(如_start)。

              連接器:

              • 程序構建階段(編譯后)的工具,由編譯器(如 GCC)調用。
              • 核心任務
                • 符號解析:將不同目標文件(.o/.obj)中的函數/變量引用與其定義匹配(例如,main.C?調用?printf()時,鏈接器在?libc.a?中查找其實現)。
                • 重定位:為所有代碼段(.text)、數據段(.data)分配內存地址,并修正代碼中的相對地址引用。
                • 庫整合:將靜態庫(如.a)代碼直接復制到可執行文件中,或記錄動態庫(如.so)的引用信息。
              • 輸出:生成可執行文件(如 ELF 格式),包含完整的程序邏輯和元數據(符號表、重定位表)。

              動態鏈接核心:

              1. 延遲綁定(Lazy Binding)
              • 僅在函數首次調用時解析地址,減少程序啟動時的開銷。
              • 未調用的函數不解析,節省內存和 CPU 資源。
              • 位置無關代碼(PIC)
              • GOT 存儲絕對地址,PLT 通過相對跳轉訪問 GOT,使共享庫可加載到任意內存地址。
              • 讀寫分離
              • PLT(代碼段)只讀,GOT(數據段)可寫,符合現代操作系統的內存保護機制。

              GOT(全局偏移表)

              • 定位:位于數據段(.got?或?.got.plt?節區),內容在運行時動態修改。
              • 作用
                • 存儲外部符號(如共享庫函數、全局變量)的絕對地址
                • 首次調用函數時,由動態鏈接器(ld-Linux.so)解析符號地址并填入 GOT。
                • 后續調用直接通過 GOT 跳轉,避免重復解析。
              • 結構
                • GOT[0].dynamic?段的地址(動態鏈接元數據)。
                • GOT[1]link_map?結構指針(描述已加載共享庫的鏈表)。
                • GOT[2]_dl_runtime_resolve?函數地址(符號解析入口)。
                • GOT[3..n]:存儲實際函數地址(如?printfscanf)。

              PLT(過程鏈接表)

              • 定位:位于代碼段(.plt?節區),內容在編譯后固定不變。
              • 作用
                • 作為跳轉到外部函數的代理代碼,首次調用時觸發地址解析。
                • 通過間接跳轉(jmp *GOT[n])實現延遲綁定。
              • 結構
                • PLT[0]:公共樁代碼,調用?GOT[2]_dl_runtime_resolve)。
                • PLT[1..n]:每個外部函數對應一項,包含:
                  • jmp *GOT[n](首次指向 PLT 內部的?push?指令)。
                  • push 序號(符號在重定位表中的索引)。
                  • jmp PLT[0](觸發解析流程)。

              步驟行為
              程序調用 call printf@plt(跳轉到 PLT[n])。
              PLT[n] 執行 jmp *GOT[n],此時 GOT[n] 指向 PLT[n] 的第二條指令(push 序號)。
              執行 push 序號(壓入符號索引),再 jmp PLT[0]。
              PLT[0] 將 GOT[1](link_map)壓棧,跳轉至 GOT[2](_dl_runtime_resolve)。
              _dl_runtime_resolve 解析符號地址,寫入 GOT[n],跳轉到目標函數。
              結果:符號地址被緩存至GOT,后續調用直接跳轉。call printf@plt → jmp *GOT[n] → 直接跳轉至目標函數地址(無需解析)。

              3. 并發

              我們可以很容易地把狀態機模型擴展為共享內存上的多線程模型——只是每次選擇一個狀態機執行一步,通過提供 spawn 和 join 兩個 API 來利用現有多處理器系統的共享內存能力。

              然而,由于編譯優化的 “無處不在” (處理器也是編譯器),共享內存并發的行為十分復雜。與此同時,人類又恰好是物理世界 (宏觀時間) 中的 “sequential creature”,編程語言的直覺 (順序/選擇/循環結構) 也是圍繞順序程序設計,因此共享內存上的并發編程是非常具有挑戰性的 “底層技術”。

              互斥:別的我沒學會,一把大刀保平安,我用的可熟了

              我們希望控制事件發生的先后順序,比如 A->B->C,互斥鎖只是確保分開 A,B,C,但做不到順序控制,這里邊得需要同步

              // 萬能消費者生產者公式
              // 想清楚生產/消費的條件是什么?void fish_before(char ch) {mutex_lock(&lk);while (!can_print(ch)) {cond_wait(&cv, &lk);}quota--;mutex_unlock(&lk);
              }void fish_after(char ch) {mutex_lock(&lk);quota++;current = next(ch);assert(current);cond_broadcast(&cv);mutex_unlock(&lk);
              }

              如何避免死鎖?

              Lock ordering1. 任意時刻系統中的鎖都是有限的2. 給所有鎖編號 (Lock Ordering)3. 嚴格按照從小到大的順序獲得鎖
              任意時刻,總有一個線程獲得 “編號最大” 的鎖,這個線程總是可以繼續運行,因為他已經有了所有需要的小鎖# 例子
              假設 5 個哲學家(線程)和 5 把筷子(鎖),編號為 1~5。規則要求:哲學家必須先拿編號小的筷子,再拿編號大的筷子(如必須先拿 3 號筷才能拿 4 號筷)。運行過程:哲學家 A 拿起筷子 1 和 2(鎖 1、2),哲學家 B 拿起筷子 1 和 3(鎖 1、3)。
              此時系統中最大編號為 3(被哲學家 B 持有)。
              哲學家 B 無需等待更大編號的鎖(已持有所有小于 3 的鎖),可吃完后釋放鎖 1 和 3。
              釋放后,其他哲學家(如等待鎖 3 的哲學家 C)可繼續獲取鎖 3 并推進任務。

              Do not communicate by sharing memory; instead, share memory by communicating.

              ——Effective Go

              4. 持久化

              4.1. 設備和驅動程序

              我們拿優盤插到電腦中,然后就看了這個盤符,我們可以進去查看文件和拷貝文件,這里面發生了什么?

              其實,這里面用到了?udev?技術,監聽內核發出的設備事件(如插入、拔出、狀態變化),在 Linux 系統中用戶空間(Userspace)的動態設備管理?/dev?目錄下的設備文件。

              關鍵流程如下:

              1. 當設備插入、移除或狀態變化時,比如優盤,內核識別設備然后加載對應的 USB 驅動(如?usb-storage),udev 依賴驅動已成功加載,否則無法獲取設備信息,驅動程序工作在內核空間(Kernel Space),直接與硬件交互,比如聲卡驅動將音頻數據轉換為電信號驅動揚聲器發聲
              2. 內核通過?netlink?套接字發送?uevent?事件(包含設備路徑、子系統類型、屬性等元數據)
              3. udevd?守護進程(用戶空間)監聽?uevent,解析事件內容并匹配預定義的規則
              4. udevd?讀取?/etc/udev/rules.d/?和?/lib/udev/rules.d/?下的規則文件,按文件名數字優先級升序執行匹配
              # /etc/udev/rules.d/99-usb.rules
              SUBSYSTEM==「block」, KERNEL==「sd*」, ATTRS{idVendor}==「0781」, ATTRS{idSerial}==「A1B2C3D4」, SYMLINK+=「backup_disk」, MODE=「0660」, GROUP=「storage」匹配鍵(條件):
              SUBSYSTEM(設備類型)、KERNEL(內核設備名)、ATTRS{...}(/sys 中的設備屬性,如廠商 ID)操作鍵(動作):
              SYMLINK(創建軟鏈接)、MODE(權限)、GROUP(歸屬組)、RUN(執行腳本)
              1. 根據規則匹配結果執行操作:
              • 命名控制:自定義設備名(如將 USB 磁盤固定為?/dev/backup_disk)。
              • 權限設置:通過?MODE=「0666」GROUP=「users」?修改設備文件權限。
              • 符號鏈接:創建軟鏈接(如?SYMLINK+=「cdrom」?指向?/dev/sr0)。
              • 腳本執行:設備插入時自動掛載RUN+=「/bin/mount /dev/sdb1 /mnt」
              1. 最終在?/dev?下生成設備文件,完成設備可用性配置

              這樣我們就可以以文件的形式使用設備了。

              好滴,那么為啥我們就能以文件的形式去使用這些設備的,關鍵就在于驅動程序:

              Everything Is a File, 通過執行操作系統對象的指針可以訪問一切,open/close,read/write,lseek。

              驅動程序實現了當前設備的 struct file_operations 的實現,它把系統調用翻譯成與設備能聽懂的數據,甚至包含如/dev/null 和 proc/stat 這種虛擬文件。

              • 驅動通過?ioremap()將 BAR 映射的物理地址轉為內核虛擬地址,直接讀寫設備寄存器
              • 設備直接寫內存地址(MSI Data)觸發中斷,支持多向量和定向投遞,驅動注冊中斷處理函數
              • 應用程序,通過?read()/write()或?ioctl()發送控制命令

              沒想到吧,連虛擬機都是文件,KVM 的所有關鍵操作(虛擬機生命周期管理、CPU 調度、內存映射)均通過?ioctl?實現用戶態與內核態的交互:

              kvm_fd = open(「/dev/kvm」, O_RDWR);       // 打開 KVM 設備
              vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0); // 創建虛擬機
              vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, 0); // 創建 vCPU
              ioctl(vcpu_fd, KVM_RUN, 0);              // 運行 vCPU

              當調用?KVM_RUN?后,虛擬 CPU 開始執行客戶機指令。若客戶機觸發 I/O 操作或特權指令(如訪問設備寄存器),CPU 會通過硬件虛擬化技術(Intel VT/AMD-V)退出到宿主機內核模式(VM-Exit)。KVM 內核模塊捕獲退出原因(如?KVM_EXIT_IO),并通過?ioctl?的返回值通知用戶態 QEMU 處理 I/O 模擬

              4.2. 存儲設備原理

              存儲的本質在于通過改變物理介質的狀態來記錄信息,該介質需具備至少兩種可穩定區分的狀態。

              存儲經歷了百年的發展歷史,從磁帶,磁鼓,到磁盤,軟盤,到 CD 光盤,Flash memory 閃存和 U 盤,flash memory 活到了今天,配合 FTL 技術(軟件定義磁盤)成為了存儲界的版本答案,讓我們來一一回顧,存儲的前世今生。

              4.2.1. 磁帶

              磁帶存儲的本質是磁場與磁性材料的可控相互作用:電流脈沖定向磁化介質,剩磁狀態固化信息,電磁感應逆向解碼。其高密度、低成本的特性,使其在云備份(如 AWS Glacier)、科研歸檔等場景仍不可替代。隨著 HAMR(熱輔助磁記錄)等技術的發展,磁帶存儲密度正邁向 100Gb/in2以上,持續拓展數字歷史的承載邊界。

              寫入原理:電信號→磁化狀態

              1. 輸入數據(如數字信號)轉換為電流脈沖序列。當電流通過磁頭線圈時,根據安培定律產生磁場
              2. 磁帶表面涂覆硬磁性材料(如γ-Fe?O?或 CrO?),磁頭縫隙處的磁場作用于勻速運動的磁帶,使磁性顆粒按特定方向磁化。

              讀取原理:磁化狀態→電信號

              1. 磁化區域經過讀取磁頭時,磁力線穿過磁頭鐵芯,在線圈中感應電動勢
              2. 磁通翻轉(代表 1)→ 產生正向/負向脈沖電壓,無翻轉(代表 0)→ 無顯著電壓變化
              3. 信號經放大器鑒別后還原為原始數據
              4. 讀取過程不改變磁化狀態,數據可重復讀取,磁帶壽命約 30 年

              擦除原理:退磁還原

              1. 磁帶通過消磁磁頭,施加高頻交變磁場,使磁性顆粒磁疇方向隨機化,整體剩磁歸零

              工作方式

              1. 順序存取:數據按物理順序排列,讀取需遍歷前方數據(平均延遲秒級),適合備份等低頻訪問場景
              2. 流式傳輸:現代數據流磁帶機(如 LTO-9)通過單主動輪驅動,磁帶勻速運動(4-10m/s),磁頭靜止接觸

              優劣勢:

              • 容量大:LTO-9 單卷 45TB,成本$0.01/GB;
                能耗低:脫機保存無需供電;
                壽命長:抗電磁干擾,適合冷數據歸檔
              • 順序訪問:隨機讀寫效率極低;

              4.2.2. 磁鼓

              磁帶(1928 年由 Fritz Pfleumer 發明)本質是線性一維存儲介質:數據按順序記錄在磁性涂層上,讀寫需順序掃描。其優勢是成本低、容量大,但隨機訪問效率極低(如讀取末端數據需遍歷整盤磁帶)
              1932 年奧地利工程師 Gustav Tauschek 發明的磁鼓采用圓柱形旋轉結構:數據分布在鼓面磁道上,通過固定磁頭與高速旋轉(>3000 RPM)實現毫秒級延遲訪問。以 IBM 701 計算機(1953 年)為例,磁鼓作為內存使用,容量 62.5KB,遠超同期磁帶。

              線性移動 -> 旋轉掃描,帶來革命: 錄音 -> 實時計算

              1. 并行磁頭:多磁頭同時讀寫不同磁道,提升吞吐量
              2. 旋轉尋址:數據隨鼓面旋轉周期性經過磁頭,平均延遲僅 5ms

              4.2.3. 磁盤

              磁鼓有兩個核心局限:

              1. 磁鼓僅能利用圓柱體單表面存儲數據(磁性涂層覆蓋外表面),內部空間完全浪費。而磁盤采用雙面盤片+多盤片堆疊設計(如 IBM RAMAC 350 含 50 張 24 英寸盤片),存儲密度提升數十倍
              2. 磁鼓依賴固定磁頭+鼓面旋轉實現數據定位,雖比磁帶順序讀寫快(平均延遲 5ms),但無法實現真正隨機尋址,磁盤通過可移動懸臂磁頭(如 RAMAC 的液壓驅動臂)直接定位任意磁道,尋址時間縮短至毫秒級

              磁盤有一些工程優化的突破:

              1. 磁盤引入空氣軸承技術(IBM 1301,1962 年),磁頭懸浮于盤面之上,間隙縮小至亞微米級,提升密度且降低磨損
              2. 1973 年 IBM 推出溫徹斯特架構(Winchester),盤片與磁頭密封在無塵腔體內,避免粉塵污染,磁頭停泊區設計,斷電時自動歸位,防止盤面刮傷
              3. 磁鼓表面電鍍磁介質易剝落,磁盤采用濺鍍工藝生成納米級磁性合金層(如鈷鉻鉭合金),耐磨性提升 10 倍,支持更高轉速

              4.2.4. 軟盤

              磁盤到軟盤的演進,主要是便攜性需求驅動帶來的。

              磁盤的固有限制

              • 體積與成本:1956 年 IBM 推出的首款硬盤 RAMAC 305 重達 1 噸,容量僅 5MB,價格高昂且需專用機房。
              • 不可移動性:早期硬盤為封閉式設計,數據無法跨設備傳輸,限制了個人計算機(PC)的普及。

              軟盤的技術突破

              • 便攜性設計:1967 年 IBM 開發出直徑 32 英寸軟盤,目標是為大型機提供成本低于 5 美元的可更換微代碼載體。
              • 小型化迭代
                • 1971 年:8 英寸軟盤(81KB);
                • 1976 年:5.25 英寸軟盤(360KB),成為 IBM PC 標配;
                • 1983 年:3.5 英寸軟盤(1.44MB),索尼設計保護殼提升耐用性。
              • 核心優勢:輕便、可拆卸、低成本,滿足個人數據交換需求(如軟件分發、文件傳輸)。

              市場催化

              • PC 普及浪潮:1980 年代 IBM PC 和 Apple II 興起,軟盤成為唯一通用移動存儲介質,1996 年全球用量達 50 億片

              4.2.5. CD 光盤

              軟盤到 CD 光盤:容量危機與多媒體需求升級

              1. 容量限制:1.44MB 無法滿足軟件/媒體文件增長, Windows 95 安裝需 13 張軟盤
              2. 速度低下:讀寫速率僅 100KB/s,大型文件傳輸耗時過長
              3. 易受磁場干擾、物理損壞,數據丟失風險高

              隨著激光技術的發展,我們可以利用光學反射進行相位差解碼:

              1. 激光發射:光頭發射波長 780nm 的近紅外激光束,聚焦為直徑約 1μm 的光點。
              2. 反射差異
              • 平坦區域(Land):激光垂直反射回光電探測器,光強高(代表二進制「0」序列)。
              • 凹坑區域(Pit):凹坑深度設計為激光波長的 1/4(約 0.12μm),使反射光與入射光相位差 180°,產生相消干涉,反射光強顯著減弱。
              • 信號轉換:光電探測器將反射光強差異轉換為電信號(高電平=「0」,低電平=「1」),再經解碼電路還原為數字信息。

              這就帶來了兩個技術飛躍:

              1. 容量躍遷:CD-ROM(700MB)為軟盤的 486 倍,單盤可存儲百科全書或多媒體軟件,DVD(4.7GB)進一步支持高清視頻
              2. 大規模量產:聚碳酸酯注塑成型實現大規模量產,坑點刻蝕精度達納米級,有了母盤,可以 3s 中快速注塑成型一個 CD 光盤,如果是車間流水線,可想而知數據拷貝的速率有多快。

              CD 盤片從下至上分層構成

              層級材料與功能厚度/特性聚碳酸酯基板透明塑料,承載凹坑結構 1.2mm,折射率 1.55?反射層鋁或金膜,反射激光(鋁=「銀盤」,金=「金盤」)0.05μm,反射率>70%

              層級材料與功能厚度/特性
              聚碳酸酯基板透明塑料,承載凹坑結構1.2mm,折射率1.55
              反射層鋁或金膜,反射激光(鋁="銀盤",金="金盤")0.05μm,反射率>70%
              保護層丙烯酸樹脂,防氧化與物理損傷提升盤面硬度和耐刮性
              封面層印刷標簽層不影響光學讀取

              保護層丙烯酸樹脂,防氧化與物理損傷提升盤面硬度和耐刮性封面層印刷標簽層不影響光學讀取

              4.2.6. flash memory

              開始聊聊當前的版本答案---閃存,從光學機械式固態電子式躍遷,“你還能比電快啊?”。

              CD 光盤的固有缺陷

              • 機械脆弱性:CD 依賴精密光學頭與旋轉盤片,易受劃痕、灰塵影響,誤碼率高達 10?12;
              • 讀寫性能低下:恒定線速度(CLV)設計導致內圈讀取僅 150KB/s,且不支持隨機訪問;
              • 不可動態更新:CD-RW 擦寫需全盤擦除,耗時長達 10 分鐘,擦寫壽命僅約 1000 次;
              • 容量天花板:紅光激光波長 780nm 限制最小凹坑尺寸,單碟最大 700MB,無法匹配數據爆炸需求。

              閃存技術的顛覆性創新

              • 非機械結構:浮柵晶體管(Floating Gate MOSFET)通過電子隧穿存儲數據,無移動部件,抗振動性提升 100 倍;
              • 高速讀寫:NAND 閃存隨機訪問延遲<100μs,連續讀寫速度>500MB/s(CD 的 3000 倍以上);
              • 動態擦寫能力:支持頁級寫入(4KB)和塊級擦除(256KB),擦寫壽命達 10 萬次(QLC 閃存);
              • 三維堆疊擴容:3D NAND 技術實現 200+層堆疊,單芯片容量突破 1Tb,成本降至$0.01/GB(2025 年)

              閃存的基本存儲單元是浮柵晶體管,由控制柵(Control Gate)、浮柵(Floating Gate)、源極(Source)和漏極(Drain)構成

              。浮柵被二氧化硅(SiO?)絕緣層包裹,形成電荷“陷阱”,其電荷狀態決定數據值(0 或 1):

              • 浮柵:懸浮于氧化層中,用于捕獲電子,電荷可長期保留(斷電后不丟失)。
              • 控制柵:施加電壓以操控電子運動。
              • 氧化層:隧穿氧化層(Tunnel Oxide)厚度僅 5-10nm,是電子隧穿的關鍵通道

              寫入(Program)

              • 電子注入:向控制柵施加高電壓(15-20V),電子通過?Fowler-Nordheim 隧穿效應穿越氧化層,被浮柵捕獲。
              • 電荷效應:浮柵帶負電后,晶體管的閾值電壓(Threshold Voltage)升高,讀取時被識別為“0”。

              擦除(Erase)

              • 電子釋放:在源極施加反向高壓(約 20V),浮柵電子被拉出,電荷消散,閾值電壓降低,狀態變為“1”。
              • 操作單位:擦除以塊(Block)為單位(通常 256KB-4MB),而非單個字節。

              讀取(Read)

              • 電壓檢測:向控制柵施加中等電壓,若浮柵有電荷(閾值電壓高),晶體管不導通(輸出“0”);無電荷則導通(輸出“1”)。
              • NOR/NAND 差異
                • NOR:支持隨機訪問,可直接讀取任意字節。
                • NAND:需按頁(Page,通常 4-16KB)順序讀取。

              4.2.7. FTL 技術

              閃存是有擦寫限制的,SLC 閃存約 10 萬次,QLC 閃存僅 100 次。若頻繁寫入同一物理塊,會導致局部快速損壞。

              傳統文件系統(如 EXT4、NTFS)和塊設備驅動(如 Linux 的?block_device_operations)基于扇區(512B)或塊(通常 4KB)?的覆蓋寫入設計。操作系統通過 BIO(Block I/O)接口提交讀寫請求,默認支持原地更新(in-place update)。

              FTL 的本質是在閃存物理限制與傳統系統之間構建的“翻譯層”,(操作系統->BIO->閃存)通過三大創新解決結構性矛盾:

              1. 虛擬化覆蓋寫?→ 適配操作系統;

              FTL 的映射機制

              邏輯地址(LBA)→物理地址(PBA)轉換:FTL 維護動態映射表,將操作系統的扇區請求轉換為閃存的頁操作。當數據更新時,FTL 將新數據寫入空白頁,并更新映射表指向新位置,原位置標記為“無效”(Invalid),通過“異地更新”(Out-of-Place Update)模擬磁盤的覆蓋寫行為,避免物理擦除延遲

              1. 動態磨損均衡?→ 對抗壽命短板;

              新數據優先寫入擦寫次數少的“年輕塊”(Low EC)

              1. 寫入聚合(Write Coalescing)

              緩存多個小 BIO,合并為整頁寫入

              1. 自動垃圾回收?→ 提升空間利用率。

              刪除操作是對一個塊的,而塊包含多個頁面,操作系統如果只刪除某個頁面時,就會出現一個塊部分頁面為有效,部分為無效,所以將無法刪除,因為擦除操作需作用于整個塊,且僅當塊內所有頁均為“無效”狀態時才能執行,這樣那部分無效空間就完全沒法利用了。所以回收機制就是,先遷移有效頁到新塊,再擦除原塊。

              1. 可靠性增強

              閃存出廠即含壞塊,使用中還會新增壞塊。FTL 通過映射表屏蔽壞塊,將數據重定向到備用塊

              FTL 利用 RAM 緩存映射表,并通過多通道并發訪問閃存芯片提升吞吐量

              ECC 糾錯:集成 BCH/LDPC 算法修復位翻轉(Bit Flip)

              沒有 FTL,閃存將無法被現有操作系統直接使用,其壽命和性能會因物理限制而大幅縮水。

              4.3. 目錄樹管理

              4.3.1. 隱藏文件

              Linux 系統中以.開頭的隱藏文件機制起源于 Unix 系統的設計哲學。在早期 Unix 系統中,ls?命令存在一個顯示邏輯缺陷:默認不顯示名稱以.開頭的文件。這個設計最初是為了隱藏系統關鍵文件(如...),后來逐漸演變為隱藏用戶配置文件的通用方案。

              # 顯示所有隱藏文件(含系統級)
              ls -a  # 輸出示例:.bashrc  .cache  .git/# 創建隱藏文件/目錄
              touch .secret_config
              mkdir .config# 修改現有文件為隱藏
              mv config .config

              隱藏文件有如下應用場景:

              # 典型隱藏配置文件
              .bashrc          # 用戶 Shell 配置
              .gitconfig       # Git 全局配置# 熱加載機制:修改后立即生效
              source ~/.bashrc# 隱藏 SSH 密鑰
              chmod 600 ~/.ssh/id_rsa# 防止配置泄露
              echo 「export SECRET_KEY=...」 >> .env

              內核實現邏輯如下:

              // fs/ext4/dir.C 中的關鍵函數
              int ext4_readdir(struct file *file, struct dir_context *ctx) {struct ext4_dir_entry_2 *de;while ((de = ext4_next_dir_entry(file, ctx->pos)) != NULL) {if (de->name[0] == 『.』) continue;  // 跳過隱藏文件ctx->pos = de->inode;if (!dir_emit(ctx, de->name, de->name_len, de->inode, DT_UNKNOWN))return 0;}return 0;
              }

              4.3.2. 約定俗成的目錄結構

              Linux 目錄結構遵循文件系統層次標準(FHS)

              ,其核心設計理念源于 Unix 的「一切皆文件」哲學。這種樹狀結構通過嚴格的層級劃分實現:

              • 單一根目錄:所有路徑以/為起點
              • 功能隔離:不同類別文件嚴格分區存放
              • 標準化擴展:通過/usr?/opt?實現軟件生態擴展

              目錄功能定位典型內容示例訪問權限特殊屬性/bin?基礎用戶命令存放區?ls,?cp,?mv,?rm,?chmod755 靜態鏈接二進制文件,單用戶模式可用/sbin?系統管理命令存放區?fdisk,?ifconfig,?reboot,?service750 僅 root 可執行,包含硬件管理工具/boot?系統啟動關鍵文件存儲?vmlinuz,?initrd.img,?grub.cfg,?System.map755 內核鏡像存放區,需保留足夠空間(建議≥2GB)/dev?設備文件集散地/dev/sda1,?/dev/tty,?/dev/null,?/dev/zero755 虛擬設備文件,按需動態創建/etc?系統配置中樞?passwd,?fstab,?hosts,?ssh/sshd_config644 關鍵配置文件集群,修改需謹慎/home?用戶主目錄容器/home/user1,?/home/developer755 每個用戶獨立目錄,支持配額管理/lib32 位系統庫文件存儲?libc.so.6,?libstdc++.so.6755 靜態庫與內核模塊存放區/lib6464 位系統庫文件存儲?libc-2.31.so,?libcrypto.so.3755 動態鏈接庫主要存放位置/media?可移動設備自動掛載點/media/usb,?/media/cdrom755udev 自動掛載,支持熱插拔/mnt?臨時掛載點/mnt/backup,?/mnt/data755 手動掛載專用,推薦使用?systemd-mount/opt?第三方軟件安裝區/opt/Docker,?/opt/postgresql755 商業軟件標準安裝路徑,建議保持獨立/proc?內核狀態鏡像/proc/cpuinfo,?/proc/meminfo,?/proc/net/dev?動態權限虛擬文件系統,反映實時系統狀態/root?超級用戶主目錄/root/.bashrc,?/root/scripts700 僅 root 可訪問,存放敏感配置/run?運行時數據暫存區/run/user/1000,?/run/Docker.sock1777tmpfs 文件系統,重啟數據清空/srv?服務數據存儲/srv/www,?/srv/nfs755FHS 標準服務數據存儲區/sys?內核對象樹/sys/devices,?/sys/bus,?/sys/class/net?動態權限 sysfs 文件系統,展示硬件拓撲/tmp?臨時文件回收站/tmp/cache,?/tmp/uploads1777 自動清理機制,建議配合?tmpreaper

              目錄功能定位典型內容示例訪問權限特殊屬性
              /bin基礎用戶命令存放區ls, cp, mv, rm, chmod755靜態鏈接二進制文件,單用戶模式可用
              /sbin系統管理命令存放區fdisk, ifconfig, reboot, service750僅root可執行,包含硬件管理工具
              /boot系統啟動關鍵文件存儲vmlinuz, initrd.img, grub.cfg, System.map755內核鏡像存放區,需保留足夠空間(建議≥2GB)
              /dev設備文件集散地/dev/sda1, /dev/tty, /dev/null, /dev/zero755虛擬設備文件,按需動態創建
              /etc系統配置中樞passwd, fstab, hosts, ssh/sshd_config644關鍵配置文件集群,修改需謹慎
              /home用戶主目錄容器/home/user1, /home/developer755每個用戶獨立目錄,支持配額管理
              /lib32位系統庫文件存儲libc.so.6, libstdc++.so.6755靜態庫與內核模塊存放區
              /lib6464位系統庫文件存儲libc-2.31.so, libcrypto.so.3755動態鏈接庫主要存放位置
              /media可移動設備自動掛載點/media/usb, /media/cdrom755udev自動掛載,支持熱插拔
              /mnt臨時掛載點/mnt/backup, /mnt/data755手動掛載專用,推薦使用systemd-mount
              /opt第三方軟件安裝區/opt/docker, /opt/postgresql755商業軟件標準安裝路徑,建議保持獨立
              /proc內核狀態鏡像/proc/cpuinfo, /proc/meminfo, /proc/net/dev動態權限虛擬文件系統,反映實時系統狀態
              /root超級用戶主目錄/root/.bashrc, /root/scripts700僅root可訪問,存放敏感配置
              /run運行時數據暫存區/run/user/1000, /run/docker.sock1777tmpfs文件系統,重啟數據清空
              /srv服務數據存儲/srv/www, /srv/nfs755FHS標準服務數據存儲區
              /sys內核對象樹/sys/devices, /sys/bus, /sys/class/net動態權限sysfs文件系統,展示硬件拓撲
              /tmp臨時文件回收站/tmp/cache, /tmp/uploads1777自動清理機制,建議配合tmpreaper使用

              使用

              4.3.3. 目錄樹實現

              目錄樹本質上是多叉樹(N-ary Tree)的變種,每個節點包含:

              • 數據域:存儲名稱(目錄名/文件名)、元數據(權限、時間戳等)
              • 子節點指針:指向直接子目錄或文件
              • 父節點引用:維護層級關系(非必需,常見于內存結構)
              // Linux 內核目錄項結構體示例
              struct dentry {/* RCU lookup touched fields */unsigned int d_flags;       /* protected by d_lock */seqcount_t d_seq;           /* per dentry seqlock */struct hlist_bl_node d_hash;    /* lookup hash list */struct dentry *d_parent;       /* parent directory 父目錄*/struct qstr d_name;struct inode *d_inode;      /* Where the name belongs to - NULL is* negative 與該目錄項關聯的 inode */unsigned char d_iname[DNAME_INLINE_LEN];    /* small names 短文件名*//* Ref lookup also touches following */struct lockref d_lockref;   /* per-dentry lock and refcount */const struct dentry_operations *d_op;  /* 目錄項操作 */struct super_block *d_sb;   /* The root of the dentry tree 這個目錄項所屬的文件系統的超級塊(目錄項樹的根)*/unsigned long d_time;       /* used by d_revalidate 重新生效時間*/void *d_fsdata;             /* fs-specific data 具體文件系統的數據 */struct list_head d_lru;     /* LRU list 未使用目錄以 LRU 算法鏈接的鏈表 *//** d_child and d_rcu can share memory*/union {struct list_head d_child;   /* child of parent list 目錄項通過這個加入到父目錄的 d_subdirs 中*/struct rcu_head d_rcu;} d_u;struct list_head d_subdirs; /* our children 本目錄的所有孩子目錄鏈表頭 */struct hlist_node d_alias;  /* inode alias list 索引節點別名鏈表*/
              };

              一個有效的 dentry 結構必定有一個 inode 結構,這是因為一個目錄項要么代表著一個文件,要么代表著一個目錄,而目錄實際上也是文件。所以,只要 dentry 結構是有效的,則其指針 d_inode 必定指向一個 inode 結構。但是 inode 卻可以對應多個(硬鏈接)。

              整個結構其實就是一棵樹,如果看過我的設備模型 kobject 就能知道,目錄其實就是文件(kobject、inode)再加上一層封裝,這里所謂的封裝主要就是增加兩個指針,一個是指向父目錄,一個是指向該目錄所包含的所有文件(普通文件和目錄)的鏈表頭。

              這樣才能有我們的目錄操作(比如回到上次目錄,只需要一個指針步驟【..】,而進入子目錄需要鏈表索引需要多個步驟)

              功能特性

              • 緩存機制:LRU 算法管理活躍目錄項(命中率>95%)
              • 路徑解析:通過父指針快速構建完整路徑
              • 符號鏈接:支持跨文件系統鏈接解析

              4.3.4. 硬鏈接和軟鏈接

              傳統文件系統的目錄樹本質上是帶標簽的多叉樹,但隨著現代文件系統通過引入共享機制,使目錄樹演變為有向無環圖(DAG)

              1. 多父節點支持:文件/目錄可被多個父目錄引用
              2. 共享計數器:記錄每個節點的引用數量
              3. 循環防范:通過拓撲排序確保無環路

              多叉樹適用場景

              • 傳統文件系統:如 ext4、XFS 的默認目錄結構
              • 簡單存儲需求:不需要跨用戶共享文件的場景
              • 快速遍歷需求:通過父節點快速定位子節點

              有向無環圖適用場景

              • 企業級存儲系統:需要多部門共享文檔的場景
              • 容器化環境:Docker 鏡像層間的目錄共享
              • 分布式系統:GlusterFS 等分布式文件系統

              節點與邊的數據結構定義

              typedef struct dag_inode {uint64_t cid;          // 內容尋址標識(基于 SHA-256 哈希值)vector_t parent_dirs;  // 父目錄指針數組(核心:支持多父節點)inode_meta_t meta;     // 元數據(權限、時間戳等)block_ref_t data_ref;  // 數據塊指針(指向實際文件內容)
              } dag_inode_t;
              • 節點本質:每個?dag_inode_t?結構體代表一個文件或目錄節點
              • 關鍵字段
                • cid:文件內容的唯一標識(通過哈希計算),相同內容的文件共享同一個 cid,實現數據去重。
                • parent_dirs:存儲所有父目錄的指針(傳統文件系統僅支持單父目錄,此設計實現多目錄硬鏈接)。
                • data_ref:指向文件數據塊的指針(若為目錄則可能為空)。

              邊(隱式存在于?dir_entry_t

              typedef struct {dag_inode_t* inode;  // 指向目標節點的指針(定義邊的方向)char name[256];       // 目錄項名稱(邊的標簽)
              } dir_entry_t;
              • 邊的本質
                • 目錄到文件的邊:通過?dir_entry_t?表示。
                  • inode?字段指向目標節點(文件或子目錄),形成有向邊(目錄 → 文件)。
                  • name?是邊在父目錄中的名稱(例如?libc.so)。
                • 文件到數據塊的邊:通過?block_ref_t?實現(圖中未展開)。
                • 目錄間的邊:通過?parent_dirs?中的指針連接(例如?/home/user?指向其父目錄?/home)。

              通過內容尋址建立節點間的有向關系:

              • 硬鏈接邊:共享相同數據塊(data?字段相同),多個目錄項指向同一 inode(引用計數管理)
              • 軟鏈接邊:創建特殊節點存儲目標路徑解析信息
              • 目錄邊:建立父子目錄關系

              1. 硬鏈接的 DAG 特性

              • 自動去重機制:相同內容的文件在 DAG 中僅存儲一次,所有硬鏈接指向同一 CID 節點。
              • 引用計數管理:刪除操作僅減少引用計數,數據節點在所有硬鏈接刪除后才釋放。
              • 無路徑依賴性:移動或重命名源文件不影響硬鏈接訪問(因依賴 CID 而非路徑)。

              2. 軟連接的 DAG 特性

              • 路徑解析開銷:訪問時需遞歸解析路徑字符串,可能跨越多個 DAG 子圖。
              • 失效風險:若目標路徑被刪除或移動,軟連接變為“死鏈”(Dangling Link)。
              • 元數據獨立:擁有獨立的創建時間、權限等屬性(與目標文件無關)

              組件傳統樹結構 DAG 結構優化收益元數據存儲線性 inode 表 B+樹索引查詢速度提升 5x?數據塊引用直接/間接指針內容尋址(CID)冗余數據減少 70%

              組件傳統樹結構DAG結構優化收益
              元數據存儲線性inode表B+樹索引查詢速度提升5x
              數據塊引用直接/間接指針內容尋址(CID)冗余數據減少70%
              鏈接機制僅符號鏈接硬鏈接+軟鏈接+跨卷鏈接跨設備共享實現

              鏈接機制僅符號鏈接硬鏈接+軟鏈接+跨卷鏈接跨設備共享實現

              4.3.5. 文件的元數據

              文件元數據(Metadata)是數字世界的「身份檔案」,它以結構化形式記錄著文件的全部非內容屬性。在 Linux 系統中,每個文件至少包含 46 項元數據字段,構成其數字身份的核心要素

              // ext4 文件系統 inode 結構體(簡化版)
              struct ext4_inode {__le16 i_mode;        // 文件類型與權限(0644)__le16 i_uid;         // 所有者 ID(0=root)__le32 i_size;        // 文件大小(字節)__le32 i_atime;       // 訪問時間戳__le32 i_mtime;       // 修改時間戳__le32 i_ctime;       // 狀態變更時間戳__le16 i_gid;         // 所屬組 ID__le16 i_links_count; // 硬鏈接數__le32 i_blocks;      // 占用數據塊數__le32 i_flags;       // 文件特性標志__le64 i_block[15];   // 數據塊指針數組__le32 i_extra_isize; // 擴展元數據長度// ...(其他擴展字段)
              };

              以上為傳統的元數據,現在又出現了一種很牛的設計叫 xattr。

              擴展屬性(xattr)是突破傳統文件元數據限制的革命性設計,允許用戶以鍵值對形式附加任意元數據到文件/目錄上,突破 POSIX 標準定義的 12 個基礎屬性限制。其核心特性包括:

              1. 命名空間隔離
              • user:普通用戶可讀寫(如?user.version
              • security:安全框架專用(如 SELinux 標簽)
              • system:系統級元數據(如文件編碼)
              • trusted:需 root 權限訪問
              • 跨對象支持:文件、目錄、符號鏈接均可附加屬性
              • 獨立演化:屬性值與文件內容解耦,支持獨立修改

              典型場景:在 Linux ext4 中單文件 xattr 容量達 4KB,支持數萬屬性條目;而 macOS HFS+通過 B*樹內聯存儲,限制單屬性大小但支持無限數量

              4.3.6. mount

              Linux 中的 mount 操作是連接外部存儲設備到文件系統的核心機制。其過程涉及用戶空間命令到內核的系統調用、數據結構構建和資源管理。以下是詳細解析:

              一、用戶空間:mount 命令的解析

              當執行 mount /dev/sdb1 /mnt 時:

              參數解析

              -t 指定文件系統類型(如 ext4、nfs)

              -o 定義掛載選項(如 ro 只讀、rw 讀寫、noexec 禁用執行)

              設備路徑(如 /dev/sdb1)和掛載點(如 /mnt)

              系統調用觸發

              命令通過 glibc 庫調用內核的 sys_mount() 系統調用,傳遞參數到內核空間

              二、內核空間:掛載的核心流程

              1. 系統調用入口:sys_mount()

              將用戶空間參數(設備路徑、掛載點、文件系統類型)復制到內核空間。

              2. 文件系統類型識別

              調用 get_fs_type(),根據 type 參數(如 ext4)查找已注冊的 file_system_type 結構體。該結構體包含文件系統操作函數指針(如 mount 回調)。

              3. 創建源文件系統:vfs_kern_mount()

              分配 vfsmount**:內核創建 vfsmount 結構,記錄掛載元數據(如設備名、掛載標志)。

              填充超級塊:調用文件系統特定的 get_sb() 函數(如 ext4_fill_super()),從設備讀取超級塊(super_block),初始化根目錄的 dentry(目錄項)和 inode(索引節點)。

              4. 掛載點查找與鎖定:lock_mount()

              檢查掛載點目錄(如 /mnt)是否有效,并防止并發掛載沖突。

              若掛載點已被其他文件系統覆蓋(如嵌套掛載),需找到最頂層的掛載點。

              5. 關聯源文件系統:graft_tree()

              將新創建的 vfsmount 鏈接到掛載點,更新全局掛載哈希表 mount_hashtable,建立父子關系。

              6. 命名空間集成

              將新掛載的文件系統添加到當前進程的掛載命名空間(namespace),使掛載對所有共享該命名空間的進程可見。

              掛載操作的核心是?將存儲設備關聯到文件系統樹

              1. 用戶命令?→?系統調用?→?內核數據結構構建vfsmountsuper_block)→?命名空間集成
              2. 關鍵創新點
              • 統一抽象:通過 VFS 層兼容不同文件系統(ext4/NFS/tmpfs)。
              • 動態擴展:支持物理設備、虛擬文件(ISO)、網絡存儲的透明接入。

              通過?mount,Linux 將異構存儲轉化為統一的文件樹,這正是?“一切皆文件”?哲學的基石。

              pivot_root?是 Linux 系統調用,用于將進程的根文件系統切換到新路徑,同時將舊根移動到新根下的子目錄。其核心目標是實現強隔離的根文件系統切換,避免傳統?chroot?的掛載泄漏問題。,單純的?chroot?僅重定向路徑解析,進程仍共享主機的掛載命名空間(如?/proc)。而?pivot_root?結合 Mount Namespace 可創建完全獨立的文件系統視圖--------Docker 的感覺是不是出來了

              4.3.7. overlayfs

              OverlayFS 是一種?聯合掛載文件系統,通過疊加多個目錄(稱為“層”)形成單一視圖,實現文件系統的動態合并與隔離。其核心結構包含三個目錄:

              1. Lower Directory(下層)
              • 只讀層:存儲基礎文件(如容器鏡像或系統固件),不可修改。
              • 支持多個層級(如?lowerdir=/dir1:/dir2),優先級從左到右遞減。
              • Upper Directory(上層)
              • 可讀寫層:存儲用戶新增或修改的文件,所有變更在此層生效。
              • Work Directory(工作目錄)
              • 臨時目錄:用于文件系統內部操作(如文件重命名時的原子操作),用戶無需直接訪問。
              • Merged Directory(合并視圖)
              • 最終呈現的統一目錄,合并所有層內容,用戶通過此目錄訪問文件。

              關鍵機制

              • 寫時復制(Copy-on-Write, CoW)
                當修改?lower?層文件時,OverlayFS 自動將其復制到?upper?層再修改,保持底層只讀性。
              • 文件刪除與隱藏
                • Whiteout 文件:刪除?lower?層文件時,在?upper?層創建特殊標記(字符設備文件),使文件在合并視圖中“消失”。
                • Opaque 目錄:標記目錄為不透明,隱藏下層同名目錄內容。
              • 同名文件沖突處理
                優先顯示?upper?層文件,其次是?lower?層中靠左的目錄(后寫入者優先)。

              OverlayFS 一大應用就是 Docker 鏡像層,Docker 鏡像由多層只讀文件(鏡像層)組成,容器運行時需將這些層疊加為統一視圖。聯合文件系統(如 OverlayFS)依賴底層塊設備接口實現分層管理:

              • 分層疊加:塊設備(如?/dev/loop0)將鏡像層文件虛擬化為獨立的“磁盤”,供 OverlayFS 掛載為?lowerdir(只讀層)和?upperdir(可寫層)。
              • 直接使用文件的缺陷:普通文件無法被聯合文件系統直接識別為可掛載的存儲單元,需轉換為塊設備才能被掛載。

              啟用寫時復制(CoW)機制

              • 容器內修改文件時,CoW 機制需將原始文件從只讀層復制到可寫層再修改。塊設備提供原子化的數據塊操作接口,使復制過程高效且不破壞底層鏡像。
              • 若直接操作文件:頻繁復制大文件(如數據庫)將導致 I/O 性能驟降,而塊設備通過按需復制數據塊(而非整個文件)顯著優化性能。

              4.3.8. lvm

              LVM(Logical Volume Manager,邏輯卷管理器)是 Linux 環境下對物理磁盤進行抽象化管理的核心機制,通過邏輯層動態分配存儲資源,解決傳統分區僵化問題。

              LVM 在物理磁盤和文件系統之間構建邏輯層,將存儲資源池化并動態分配,其核心組件如下:

              1. 物理卷(PV, Physical Volume)
              • 本質:物理磁盤(如?/dev/sda1)、RAID 設備或整個硬盤。
              • 管理單元:PV 被劃分為?PE(Physical Extent),默認大小?4MB,是 LVM 的最小存儲單元。
              • 初始化命令pvcreate /dev/sdb1(將分區初始化為 PV)。
              • 卷組(VG, Volume Group)
              • 本質:多個 PV 的存儲池,整合物理資源形成統一空間(如將 50GB + 60GB 磁盤合并為 110GB 的 VG)。
              • 管理機制:VG 中的 PE 可跨物理磁盤分配,打破物理邊界。
              • 邏輯卷(LV, Logical Volume)
              • 本質:從 VG 中劃分的虛擬分區(如?/dev/vg_data/lv_home)。
              • 組成單元:LV 由?LE(Logical Extent)?構成,LE 與 PE?一一對應且大小相同。
              • 創建命令lvcreate -L 20G -n lv_data vg_data
              • 文件系統(FS)
              • 建立在 LV 之上(如 ext4/XFS),通過?mount?掛載使用。

              有了 lvm,可以很輕松進行磁盤的動態管理:

              // 擴容
              lvextend -L +5G /dev/vg_data/lv_data  # 增加 5GB 空間
              resize2fs /dev/vg_data/lv_data         # 調整文件系統大小(在線生效)// 縮容
              umount /data                           # 卸載文件系統
              resize2fs /dev/vg_data/lv_data 15G     # 先縮小文件系統
              lvreduce -L 15G /dev/vg_data/lv_data   # 再縮小 LV// 快照(Snapshot)
              創建 LV 的只讀時間點副本,僅記錄變更數據(CoW 機制)
              備份數據庫時創建快照,避免服務中斷。
              lvcreate -s -n lv_snap -L 2G /dev/vg_data/lv_data

              典型應用場景

              1. 數據庫存儲:動態擴展數據庫空間(如 MySQL 數據目錄)。
              2. 云服務器:按需調整云硬盤容量,適配業務增長。
              3. 虛擬化平臺:為虛擬機提供彈性磁盤,支持快照備份。
              4. 混合磁盤管理:整合 SSD(高速緩存)與 HDD(冷數據存儲)

              4.4. 數據庫系統

              4.4.1. 文件目錄

              文件目錄其實也可以當數據庫使用:

              # 目錄結構
              db/
              ├── users/
              │   ├── .schema         # id:int, name:string
              │   ├── 1.txt           # {「id」:1, 「name」:「Alice」}
              │   └── by_role/        # 索引目錄
              │       └── 3/ → ../1.txt  # 按角色 ID 分類
              └── orders/├── 100.txt         # {「id」:100, 「user_id」:1}└── 100_user.txt → ../users/1.txt  # 關聯用戶

              表結構映射

              • 目錄 = 表:每個目錄對應一張表(如?users/?目錄即?users?表)。
              • 文件 = 記錄:目錄內的文件對應表中的一行記錄(如?users/1.txt?表示?id=1?的用戶)。
              • 文件內容 = 字段值:文件內容以鍵值對或 JSON 格式存儲字段數據,例如:
              // users/1.txt
              {「id」:1, 「name」:「Alice」, 「role_id」:3}

              軟鏈接實現表關聯:

              一對一雙向軟鏈接互指?profile/1.txt → users/1.txt(反之亦然)一對多在“多”方目錄創建指向“一”方的軟鏈接?orders/100.txt → users/1.txt?多對多建立中間目錄存儲關聯對

              一對一雙向軟鏈接互指profile/1.txt → users/1.txt(反之亦然)
              一對多在“多”方目錄創建指向“一”方的軟鏈接orders/100.txt → users/1.txt
              多對多建立中間目錄存儲關聯對user_role/1_3.txt(軟鏈接至 users/1 和 roles/3)

              user_role/1_3.txt(軟鏈接至?users/1?和?roles/3

              查詢操作的實現

              1. 條件查詢

              grep?模擬 WHERE:

              grep -l 『「role_id」:3』 users/*.txt  # 查找角色 ID 為 3 的用戶

              find?模擬 JOIN:

              find orders/ -lname 『*users/1.txt』  # 查找用戶 1 的所有訂單
              1. 聚合計算,使用?awk?統計:
              awk -F: 『{sum += $2} END {print sum}』 orders/*.txt  # 計算訂單總金額

              4.4.2. SQL

              關系型數據庫通過表結構指針機制重構目錄樹邏輯:

              1. Everything is Table
              • 對象表化:每個實體(用戶、訂單)對應一張表,每行代表一個對象實例
              • ID 即指針:外鍵(如?user_id)是跨表索引的內存地址映射
              SELECT orders.* FROM users 
              JOIN orders ON users.id = orders.user_id  -- ID 的指針跳轉
              WHERE users.name = 『Alice』;

              SQL 本質:JOIN 操作即指針配對(users.id?→?orders.user_id

              事務與 ACID:指針操作的原子保障
              ACID 確保指針跳轉的可靠性:

              ACID 屬性指針操作意義實現機制原子性指針批量更新要么全成功要么全失敗 Undo Log 回滾一致性指針始終指向有效內存地址外鍵約束+鎖機制隔離性并發指針互不干擾 MVCC 多版本控制持久性指針變更永久有效 Redo Log

              ACID屬性指針操作意義實現機制
              原子性指針批量更新要么全成功要么全失敗Undo Log回滾
              一致性指針始終指向有效內存地址外鍵約束+鎖機制
              隔離性并發指針互不干擾MVCC多版本控制
              持久性指針變更永久有效Redo Log持久化

              持久化

              START TRANSACTION; 
              UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 指針 A
              UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 指針 B
              COMMIT;  -- 原子提交兩個指針更新

              海量優化:當關系模型遇到性能瓶頸

              隨著數據量增長,傳統關系模型需針對性優化:

              1. 查詢優化策略
              • 索引即快捷指針:B+樹索引將全表掃描 O(n)降至 O(log n)
              • 覆蓋索引:在索引中內聯數據,避免回表查詢(如?SELECT id,name FROM users

              游標:精細化指針控制器
              游標(Cursor)實現逐行精準控制,避免內存溢出

              DECLARE user_cursor CURSOR FOR 
              SELECT id FROM users WHERE status = 『active』;  -- 定義結果集指針
              OPEN user_cursor;
              FETCH NEXT FROM user_cursor;  -- 指針向前移動
              WHILE @@FETCH_STATUS = 0 DO-- 處理當前行數據FETCH NEXT FROM user_cursor; 
              END WHILE;
              CLOSE user_cursor;
              1. 分布式改造方案

              優化方向實現方式典型案例水平分片按 ID 哈希分布到不同物理節點 MySQL 分庫分表讀寫分離寫主庫→讀從庫(異步指針同步)AWS RDS Proxy?列式存儲按列組織數據,提升聚合查詢速度

              優化方向實現方式典型案例
              水平分片按ID哈希分布到不同物理節點MySQL分庫分表
              讀寫分離寫主庫→讀從庫(異步指針同步)AWS RDS Proxy
              列式存儲按列組織數據,提升聚合查詢速度Google BigQuery

              Google BigQuery

              4.4.3. NoSQL

              關系型數據庫的瓶頸無法滿足 Web 2.0 需求

              1. 海量數據處理能力不足
                互聯網應用(如社交網絡、電商平臺)產生的數據量呈指數級增長,關系型數據庫在單機或集群模式下難以高效存儲和查詢 PB 級數據。例如,頻繁的多表關聯查詢或復雜事務操作在高并發場景下性能急劇下降
              2. 擴展性受限
                關系型數據庫依賴縱向擴展(升級硬件),但單臺服務器的性能存在物理上限。橫向擴展(增加節點)則因事務一致性(ACID)和復雜關聯查詢的約束難以實現,導致成本高昂且運維復雜
              3. 高并發讀寫延遲
                如電商秒殺或社交平臺熱點事件,關系型數據庫的鎖機制和事務管理易引發死鎖,導致響應延遲,無法支撐百萬級并發請求

              數據模型的革新需求

              1. 非結構化/半結構化數據的爆發
                用戶生成內容(圖片、日志、地理位置)、物聯網傳感器數據等多為動態、無固定結構。關系型數據庫的嚴格模式(Schema)需預先定義表結構,無法靈活適應數據類型的變化
              2. 靈活性與開發效率
                NoSQL 的文檔型(如 MongoDB)、鍵值對(如 Redis)等數據模型允許動態增減字段,無需停機修改表結構,加速了迭代開發周期

              當數據模型突破二維結構,NoSQL 提供新思路:

              1. 數據模型對比

              類型數據結構指針邏輯適用場景鍵值數據庫?Key-ValueKey 即數據地址指針緩存(Redis)文檔數據庫?JSON

              類型數據結構指針邏輯適用場景
              鍵值數據庫Key-ValueKey即數據地址指針緩存(Redis)
              文檔數據庫JSON文檔文檔內嵌指針(_id)內容管理(MongoDB)
              列族數據庫列簇+行鍵行鍵定位列簇指針日志分析(HBase)
              圖數據庫節點+邊邊即關系指針社交網絡(Neo4j)

              文檔文檔內嵌指針(_id)內容管理(MongoDB)列族數據庫列簇+行鍵行鍵定位列簇指針日志分析(HBase)圖數據庫節點+邊邊即關系指針社交網絡(Neo4j)

              1. ACID 與 BASE 的權衡
              • 關系型數據庫:強一致性保障指針精確(ACID)
              • NoSQL:最終一致性換取吞吐量(BASE 原則)
              BASE: Basically Available(基本可用)Soft-state(軟狀態)Eventually consistent(最終一致)

              例:微信朋友圈使用 HBase 實現寫擴散,異步同步到好友時間線

              4.5. 計算機系統安全

              4.5.1. 訪問控制

              UID(用戶標識符)

              • UID 是內核中用戶的唯一整數標識(0~65535),用于判定進程對文件、設備等資源的訪問權限。
                • 根用戶(UID 0):擁有系統最高權限,可繞過所有權限檢查。
                • 系統用戶(UID 1–499):供守護進程使用,權限受限。
                • 普通用戶(UID ≥500):常規用戶權限范圍。
              • 生成方式
                • 本地系統:通過?/etc/passwd?靜態分配,或 LDAP 動態映射。
                • 分布式系統:采用 Snowflake、號段模式、Redis 自增等算法生成全局唯一 UID

              GID(組標識符)

              • 定義與作用
                將多個 UID 歸為一組,統一分配權限(如共享目錄的讀寫權限)。
              • 附屬組
                用戶可屬于多個組(通過?/etc/group?配置),進程的權限取所有所屬組的并集

              Linux 進程實際運行時涉及四類身份標識:

              標識類型縮寫作用實際用戶 IDruid 進程啟動者的原始 UID 有效用戶 IDeuid 權限檢查的依據,決定文件訪問能力保存用戶 IDsuid 臨時存儲 euid,用于權限恢復文件系統用戶 IDfuid

              標識類型縮寫作用
              實際用戶IDruid進程啟動者的原始 UID
              有效用戶IDeuid權限檢查的依據,決定文件訪問能力
              保存用戶IDsuid臨時存儲 euid,用于權限恢復
              文件系統用戶IDfuid文件操作時的最終權限標識(通常等于 euid)

              文件操作時的最終權限標識(通常等于 euid)

              當進程嘗試訪問文件時,內核比較?euid?和文件屬主的 UID,并檢查文件模式(mode)中的權限位

              文件模式(mode):權限規則的載體

              文件模式是一個 12 位整數(如?0o40755),分為兩部分:

              1. 基礎權限(9 位)
              • rwxr-xr-x:分別定義屬主、屬組、其他用戶的讀/寫/執行權限。
              • 特殊權限位(3 位)
              • SetUID(4):以文件屬主身份運行進程(euid = 文件屬主 UID)。
              • SetGID(2):對文件以屬組身份運行;對目錄則新建文件繼承目錄屬組。
              • Sticky Bit(1):目錄內文件僅屬主可刪除(如?/tmp
              chmod u+s /usr/bin/passwd  此時普通用戶執行 passwd 時,進程 euid 臨時變為 0(root),從而修改 /etc/shadow

              程序權限需求 SetUID 作用?passwd?修改 /etc/shadow 臨時賦予 root 寫權限?ping?發送 ICMP 包獲取 RAW Socket 權限?sudo

              程序權限需求SetUID 作用
              passwd修改 /etc/shadow臨時賦予 root 寫權限
              ping發送 ICMP 包獲取 RAW Socket 權限
              sudo切換用戶以目標用戶身份執行命令

              切換用戶以目標用戶身份執行命令

              4.5.2. 攻破一個進程

              緩沖區溢出

              高地址
              ┌─────────────────┐
              │     參數        │ ← 調用者壓入的參數(若參數過多)
              ├─────────────────┤
              │   返回地址(EIP)│ ← 函數執行完后跳轉的位置
              ├─────────────────┤
              │   舊 EBP         │ ← 保存調用者的棧幀基址
              ├─────────────────┤
              │   局部變量       │ ← 當前函數的變量(如 buffer[64])
              └─────────────────┘
              低地址(棧頂) → ESPESP(棧指針):始終指向當前棧頂(最低地址)
              EBP(基址指針):指向當前棧幀的基地址,用于定位參數和局部變量(如[EBP-4]表示第一個局部變量)
              EIP(指令指針):存儲下一條待執行指令的地址,被覆蓋后程序將跳轉到攻擊者指定的地址#include <stdio.h>
              #include <string.h>void vulnerable() {char buffer[64];  // 局部變量,位于棧幀底部gets(buffer);     // 無邊界檢查的輸入函數
              }int main() {vulnerable();return 0;
              }

              溢出過程分析

              1. 棧幀初始化
              • 調用?vulnerable()時,棧中壓入?main?的返回地址(EIP)和舊 EBP。
              • 局部變量?buffer?分配在舊 EBP 下方(如地址?0xbffff2c0)。
              • 輸入超量數據
              • 若用戶輸入 70 字節(如「A」*68 + 「\xef\xbe\xad\xde」):
                • 前 64 字節填滿?buffer
                • 后續 4 字節覆蓋舊 EBP(破壞棧幀鏈)。
                • 最后 4 字節覆蓋返回地址(EIP)(如?0xdeadbeef)。
              • 函數返回時的劫持
              • vulnerable()執行?RET?指令時,從棧頂彈出覆蓋后的 EIP?到指令寄存器。
              • CPU 跳轉到?0xdeadbeef?執行(可能是攻擊代碼入口)。

              攻擊者如何利用此漏洞

              攻擊載荷構造

              plaintext復制| 填充數據(64 字節) | 覆蓋的 EBP(4 字節) | 惡意代碼地址(4 字節) | Shellcode(惡意指令) |
              • Shellcode:一段機器碼(如啟動/bin/sh),通常注入到?buffer?起始位置。
              • 覆蓋的 EIP:指向?buffer?起始地址(需預測地址,或通過 NOP 雪橙增加命中率)。

              實際攻擊步驟

              1. 定位偏移量
              • 通過調試器(如 GDB)確定?buffer?到 EIP 的偏移(如 72 字節)。
              • 繞過保護機制
              • 若系統啟用 ASLR,需結合內存泄露漏洞獲取地址。
              • 若啟用棧不可執行(NX),需改用 ROP 鏈(復用已有代碼片段)

              防御機制與最佳實踐

              編程層防護

              • 替換危險函數
                • 用?fgets(buffer, sizeof(buffer), stdin)替代?gets(),限制輸入長度。
              • 編譯器增強
                • GCC 的-fstack-protector:插入金絲雀值(Canary)于 EBP 和局部變量之間,函數返回前校驗其完整性。

              操作系統級防護

              機制原理效果?ASLR?隨機化棧/堆/庫的基址,增加預測 EIP 的難度迫使攻擊者需地址泄露漏洞?DEP/NX?標記棧內存為“不可執行”,阻止 Shellcode 運行需結合 ROP 繞過?KPTI?隔離用戶/內核頁表,阻止 Meltdown

              機制原理效果
              ASLR隨機化棧/堆/庫的基址,增加預測EIP的難度迫使攻擊者需地址泄露漏洞
              DEP/NX標記棧內存為“不可執行”,阻止Shellcode運行需結合ROP繞過
              KPTI隔離用戶/內核頁表,阻止Meltdown類攻擊竊取內核數據緩解旁路攻擊

              類攻擊竊取內核數據緩解旁路攻擊

              硬件漏洞 meltdown

              利用 CPU?亂序執行推測執行的優化缺陷:

              1. 權限繞過:用戶態程序訪問內核內存(如?0xffffffffc0000000)觸發異常,但亂序執行已讀取數據。
              2. 旁路泄露:通過緩存時序攻擊(Cache Timing)推斷內核數據值。例如,根據數組?array[data*4096]的加載時間差異泄露字節內容

              KPTI(內核頁表隔離)防御機制

              • 地址空間分割
                • 舊模式:用戶態與內核態共享頁表,用戶可映射內核地址。
                • KPTI 模式:用戶進程維護兩份頁表:
                  • 用戶頁表:僅含用戶空間地址,無內核映射。
                  • 內核頁表:完整映射內核,僅在陷入內核時切換。
              • 性能代價:每次系統調用/中斷需切換頁表(TLB 刷新),導致性能下降 10–20%。

              4.6. 虛擬機,容器,微服務

              之前 nemu 運行程序只有原來本機性能的 1/10,vmware 做到了什么?使虛擬機成為了商用的產品,即 1 臺 pc 可以虛擬化出 100 臺賣給你,ISP 廠商狂喜。

              guest ring3 -> host ring3

              1. 指令捕獲
              • Guest OS 的內核代碼(原需 Ring0)被置于?Host Ring3?執行。
              • 當 Guest 執行敏感指令時,CPU 觸發異常(如#GP),控制權移交 VMware Hypervisor。
              • 指令翻譯與模擬
              • Hypervisor 截獲異常,解析觸發異常的指令。
              • 通過二進制翻譯引擎動態重組代碼塊:
                • 將敏感指令(如?IN/OUT?端口操作)替換為調用 Hypervisor 模擬函數的跳轉指令。
                • 生成安全的“重組指令塊”(Translated Code Block),在 Host Ring3 執行模擬邏輯。
              • 執行環境隔離
              • Guest Ring3 應用:直接運行于 Host Ring3(無需翻譯),性能接近原生。
              • Guest Ring0 代碼:翻譯后在 Host Ring3 模擬執行,實現“Ring0 行為”的軟模擬

              x86 硬件虛擬化,8 級頁表

              影子頁表(Shadow Page Table)的復雜化處理

              • 傳統挑戰
                在 4 級頁表時代,VMware 的影子頁表技術已需維護兩套頁表(Guest 頁表 + 影子頁表),并通過缺頁異常同步映射關系。8 級頁表使頁表項數量指數級增長,同步開銷劇增。
              • VMware 的優化
                • 選擇性同步機制:僅監控 Guest 修改頁表的行為(如?mov cr3?指令),而非全量同步,減少 VM Exit 次數。
                • 跳轉緩存(Jump Cache):緩存已翻譯的頁表項,避免重復處理相同路徑的頁表遍歷。

              硬件輔助內存虛擬化(EPT/NPT)的深度整合

              • 技術原理
                Intel EPT(Extended Page Tables)和 AMD NPT(Nested Page Tables)允許 CPU 硬件直接處理“Guest 虛擬地址 → Host 物理地址”的兩級轉換,無需 Hypervisor 介入。
              • VMware 的創新應用
                • 動態切換機制:默認啟用 EPT/NPT,但在嵌套虛擬化或特殊指令(如?invvpid)場景下動態切換回影子頁表,兼顧性能與兼容性。
                • 大頁(Huge Page)支持:通過 EPT/NPT 直接映射 2MB/1GB 大頁,減少 8 級頁表的遍歷層級,降低 TLB 未命中率

              操作系統虛擬化---namespace+cggroups - Docker

              云時代-微服務-- k8s

              serverless--容器的概念也不要了-faas

              oversubscribed -》 流量計費 qps

              cicd

              5. Reference

              Model checking 簡述

              操作系統原理 (2025 春季學期)

              HTTPS://jyywiki.cn/OS/manuals/sysv-abi.pdf

              HTTPS://www.cnblogs.com/yubo-guan/p/18804432

              HTTPS://www.cnblogs.com/sparkdev/p/11605804.HTML

              深入了解之鏈接器與加載器_加載器_邱學喆_InfoQ 寫作社區

              HTTPS://book.douban.com/subject/3652388/、

              HTTPS://www.cnblogs.com/yikoulinux/p/14470713.HTML

              本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
              如若轉載,請注明出處:http://www.pswp.cn/news/920481.shtml
              繁體地址,請注明出處:http://hk.pswp.cn/news/920481.shtml
              英文地址,請注明出處:http://en.pswp.cn/news/920481.shtml

              如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

              相關文章

              GaussDB 修改schema屬主時報:must be member of role “dtest“

              1 問題現象schema的屬主為root&#xff0c;客戶需要修改對應的業務用戶&#xff0c;在使用root用戶登入postgres庫時修改schema屬主時報&#xff1a;ERROR:dn_6007_6008_6009:must be member of role "dtest"執行命令為&#xff1a;alter schema dtest owner to dtes…

              好?真題資源+專業練習平臺=高效備賽2025年初中古詩文大會(0829)

              2025年初中生古詩文大會的初選11月2日-9日正式開賽&#xff0c;還有兩個多月。快來做真題&#xff0c;吃透題目背后的知識點&#xff0c;舉一反三不但對比賽有用&#xff0c;對于課內的語文學習也有很大促進。【好消息】2025年古詩文大會閱讀專輯的模擬題好真題獨家超詳細完整解…

              Pointer--Learing MOOC-C語言第九周指針

              2、指針運算1.指針運算&#xff08;本節內容詳細請登錄中國大學MOOC官網查詢&#xff09;指針是可計算的112&#xff1f;指針計算*p指針比較0地址指針的類型用指針來做什么2.動態內存分配輸入數據&#xff1a;1.如果輸入數據時候&#xff0c;先告訴你個數&#xff0c;然后再輸入…

              升級DrRacket8.10到8.18版本@Ubuntu24.04

              升級DrRacket8.10到8.18版本 安裝參考&#xff1a;在FreeBSD、Windows、Ubuntu24三種平臺下安裝Racket多范式編程語言_racket安裝-CSDN博客 Ubuntu24.04里面的版本是8.10,所以無法使用apt upgrade升級&#xff0c;最終是使用下載升級軟件&#xff0c;手工升級完成&#xff01…

              亞馬遜季節性產品運營策略:從傳統到智能化的演進

              "季節性產品如何在有限銷售窗口內實現收益最大化&#xff1f;" "面對劇烈波動的市場需求&#xff0c;廣告投放該如何靈活應對&#xff1f;" "如何避免旺季斷貨或淡季資源浪費的庫存難題&#xff1f;" "傳統人工運營方式能否跟上季節性產品的…

              解析xml文件并錄入數據庫

              主函數&#xff1a;參數處理、信號處理、打開日志、解析參數到結構體、添加進程心跳、處理業務函數業務處理函數&#xff1a;將規則xml加載到結構體&#xff08;xml文件名、對應表名、更新標志、預先執行語句&#xff09;、打開源文件夾并匹配10000個xml文件、判斷數據庫是否開…

              mongoDB學習(docker)

              docker 命令創建mongoDBdocker pull mongo docker run -d --name my-mongo \-e MONGO_INITDB_ROOT_USERNAMEroot \-e MONGO_INITDB_ROOT_PASSWORD123456 \-v /my/data/mongo:/data/db \-p 27017:27017 \mongodocker run -d --name my-mongo -e MONGO_INITDB_ROOT_USERNAMEroot…

              軟件測試(四):等價類和判定表

              1.等價類劃分表例&#xff1a;qq號等價類測試用例&#xff1a;無論有效無效&#xff0c;對應的用例都只舉一個數據例子&#xff08;例子在其對應的用例情況區間任選一個即可&#xff09;自測案例寫完測試用例后執行測試用例驗證&#xff08;借助工具DDSP&#xff09;實際結果與…

              week5-[二維數組]翻轉

              week5-[二維數組]翻轉 題目描述 給定一個 nnn\times nnn 的正方形二維數組&#xff0c;將它旋轉 180180180 度后輸出。 輸入格式 輸入共 n1n 1n1 行。 第 111 行 111 個正整數 nnn。 接下來 nnn 行&#xff0c;每行 nnn 個正整數 aija_{ij}aij? 表示這個二維數組。 輸出格式 …

              微調大模型并部署服務提供外部調用

              微調大模型并部署服務提供外部調用1.背景知識介紹說明LoRA 微調算法LoRA原理&#xff1a;微調常見框架2. 環境搭建下載并使用docker compose部署 LLaMA-Factory3. 微調微調結束之后導出模型4. 本地運行模型5. 服務http調用驗證應用到的技術 微調框架&#xff08; LLama-Factory…

              命令行操作:邏輯運算符、重定向與管道

              命令行操作&#xff1a;邏輯運算符、重定向與管道前言一、邏輯運算符1.1. 邏輯運算符 && (AND)1.2. 邏輯運算符 || (OR)1.3. 標準文件描述符 (FD)二、重定向2.1 重定向: > 與 >>2.2 重定向錯誤輸出: 2>/ 與 2>>2.3 POSIX 推薦(經常使用)三、管道 (順…

              IDA Pro 逆向安卓 SO 庫實戰指南:從靜態分析到動態調試

              IDA Pro 逆向安卓 SO 庫是一個系統性的工程。下面我將為你提供一個從環境準備、基礎靜態分析到高級動態調試的完整實戰指南。一、 準備工作與環境搭建 所需工具IDA Pro: 主力逆向工具&#xff0c;建議使用 7.7 或更高版本&#xff0c;對 ARM/ARM64 架構支持更好。目標 APK:…

              Python爬蟲(47)Python異步爬蟲與K8S彈性伸縮:構建百萬級并發數據采集引擎

              目錄一、背景與行業痛點二、核心技術架構解析2.1 異步爬蟲引擎設計2.2 K8S彈性伸縮架構三、生產環境實踐數據3.1 性能基準測試3.2 成本優化效果四、高級優化技巧4.1 協程級熔斷降級4.2 預測式擴容五、總結&#x1f308;Python爬蟲相關文章&#xff08;推薦&#xff09;一、背景…

              處理器的雙發射是什么?

              處理器的雙發射是什么? 這是一個處理器微架構層面的概念,對于理解現代高性能CPU(包括一些Cortex-M7/M55/M85等高端MCU內核)如何提升性能至關重要。 核心摘要 雙發射 是一種處理器設計技術,允許CPU的譯碼器在一個時鐘周期內,同時解碼并派發兩條指令到不同的執行單元中去…

              麒麟操作系統掛載NAS服務器

              前言&#xff1a;因信創整改&#xff0c;需將原服務器的服務全部遷移到信創服務器&#xff0c;在部署完應用后&#xff0c;發現外掛了NAS服務&#xff08;可用df -h查看掛載文件&#xff09;&#xff0c;于是在信創服務器上需要掛載NAS服務器。在Linux上掛載NAS服務器可以通過多…

              qt配置ros2環境,簡單版本

              因為不同的系統環境會有差異&#xff0c;先把我的環境介紹如下&#xff1a; 系統&#xff1a;Ubuntu22.04 ROS版本&#xff1a;ros2 humble Qt版本&#xff1a;qt-5.14.2 要配置Qt中的ros環境&#xff0c;需要在Qt的系統環境中添加2個變量&#xff0c;一個是編譯環境變量、一個…

              【基于C# + HALCON的工業視覺系統開發實戰】三十六、PCB焊點缺陷檢測:0漏檢的局部變形匹配技術

              摘要:針對PCB焊點檢測中虛焊、錫珠、偏移三大核心缺陷,本文提出基于局部變形匹配與黃金模板的工業級解決方案。系統采用"同軸光源+四向可調支架"的硬件布局消除器件陰影,結合HALCON 24.11的局部變形匹配算法適應PCB熱膨脹形變。通過多尺度模板庫自學習機制實現8秒…

              諾基亞無人機網絡(NDN-Nokia Drone Networks):面向工業運營的全自動無人機解決方案

              諾基亞無人機網絡(NDN-Nokia Drone Networks)&#xff1a;面向工業運營的全自動無人機解決方案諾基亞無人機網絡 (NDN) 是一款先進的全自動無人機一體化解決方案&#xff0c;旨在提升和數字化各種工業和公共安全用例。這款多功能 BVLOS 解決方案基于先進的蜂窩連接&#xff0c;…

              OpenTelemetry 在 Spring Boot 項目中的3種集成方式

              目錄 1. 自動埋點&#xff08;Java Agent&#xff09; 2. 注解驅動&#xff08;WithSpan&#xff09; 3. 手動埋點&#xff08;SDK 編程&#xff09; 配置關鍵點 方案選擇建議 OpenTelemetry 在 Spring Boot 項目中的應用主要有以下三種方式&#xff0c;按實現復雜度由低到…

              nvue文件text標簽 不同樣式的文本在同一段落顯示

              參考鏈接&#xff1a;nvue中處理text為塊級元素的替代解決方法_nvue 塊級元素-CSDN博客 實現效果&#xff1a; nvue的text是塊級元素&#xff0c;用上層加粗的絕對定位覆蓋底層的文本實現 <view class"cus-text-area"><!-- nvue的text是塊級元素&#xff0…