多進程加載同一動態庫(.so)時,全局變量是共享還是獨立?答案:**物理內存共享(只讀段),寫時複製(COW)實現邏輯隔離**。本文從 ELF 加載、頁表映射、COW 觸發、編程影響四個維度徹底講透此機制。
| 層面 | 表現 |
|---|---|
| 物理內存 | 初始共享同一物理頁框(只讀映射) |
| 虛擬地址 | 各進程獨立虛擬地址空間,映射同一物理頁 |
| 讀操作 | 所有進程讀到相同值,零拷貝共享 |
| 寫操作 | 触發頁錯誤 → 內核複製物理頁 → 修改進程獨享新頁 → 其他進程不受影響 |
| 程序員感知 | 「像是不共享」,各進程邏輯獨立,互不干擾 |
進程 A 寫全局變量 g_var
↓
CPU 檢測頁表項 (PTE) 的 Write 比特為 0 → 頁錯誤 #PF
↓
內核 do_wp_page()
↓
檢測 VMA 標誌 VM_WRITE | VM_SHARED? → 是私有寫保護頁
↓
分配新物理頁框 → 複製原頁內容 → 更新 PTE 指向新頁、置 Write=1
↓
進程 A 繼續寫入新頁
↓
進程 B 仍映射舊物理頁,讀寫互不影響
// libshared.c
int global_var = 42;
// main.c
#include
#include
#include
extern int global_var;
int main() {
pid_t pid = fork();
if (pid == 0) { // 子進程
sleep(1);
printf("Child before write: global_var=%d, addr=%p\n", global_var, &global_var);
global_var = 100; // 觸發 COW
printf("Child after write: global_var=%d, addr=%p\n", global_var, &global_var);
} else { // 父進程
printf("Parent before: global_var=%d, addr=%p\n", global_var, &global_var);
sleep(2);
printf("Parent after: global_var=%d, addr=%p\n", global_var, &global_var);
wait(NULL);
}
return 0;
}
配合 `/proc/PID/pagemap` 或 `pmap -X PID` 可見:寫入前父子進程該虛擬頁映射同一 PFN;寫入後 PFN 分離。
| 特性 | 靜態鏈接 | 動態鏈接 |
|---|---|---|
| 全局變量實例 | 每個可執行文件獨立一份 | 進程間共享物理頁(COW 隔離) |
| 內存佔用 | 每進程獨佔 | 只讀段共享,寫時才分離 |
| 更新部署 | 需重新鏈接所有程序 | 替換 .so 即可(兼容性許可下) |
Linux 動態庫全局變量**物理共享、邏輯隔離**,核心機制是**頁級寫時複製(COW)**。程序員應視其為「進程私有」,若需進程間共享數據,請顯式使用共享內存等 IPC 機制。理解 COW 不僅能避坑,更是掌握現代操作系統內存管理的核心鑰匙。