共享內存是 最快的 IPC 方式:多進程映射同一物理頁面,用戶態直接讀寫,無內核拷貝開銷。但無內建同步,必須配合信號量/互斥鎖。本文系統講解 API、同步模式、完整雙進程示例、父子進程繼承映射、安全性陷阱。
| 函數 | 原型 | 關鍵參數 | 返回值 |
|---|---|---|---|
shmget | int shmget(key_t key, size_t size, int shmflg); | key: 命名標識 (ftok 生成) size: 字節數 shmflg: 權限 | IPC_CREAT | IPC_EXCL | shmid (≥0) 或 -1 |
shmat | void *shmat(int shmid, const void *shmaddr, int shmflg); | shmaddr: 指定映射地址 (通常 NULL 由內核選擇) shmflg: SHM_RDONLY 只讀 | 映射地址 或 (void*)-1 |
shmdt | int shmdt(const void *shmaddr); | shmaddr: shmat 返回地址 | 0 成功 / -1 失敗 |
shmctl | int shmctl(int shmid, int cmd, struct shmid_ds *buf); | cmd: IPC_STAT/IPC_SET/IPC_RMID buf: 權限/狀態結構體 | 0 成功 / -1 失敗 |
0644 = 用戶讀寫(6) | 組讀(4) | 其他讀(4)
0666 = 所有讀寫
IPC_CREAT: 不存在則創建
IPC_EXCL: 與 CREAT 同用,存在則報錯 (確保獨佔創建)
共享內存本身無原子性、無互斥。典型模式:
// 初始化
semid = semget(key, 1, 0666 | IPC_CREAT);
semctl(semid, 0, SETVAL, 1); // 信號量初值 1 (互斥鎖)
// P 操作 (加鎖)
struct sembuf p = {0, -1, SEM_UNDO};
semop(semid, &p, 1);
// 訪問共享內存...
// V 操作 (解鎖)
struct sembuf v = {0, +1, SEM_UNDO};
semop(semid, &v, 1);
SEM_UNDO 確保進程異常終止時內核自動釋放鎖,防死鎖。
共享結構體 (shmdata.h):
#define TEXT_SZ 2048
struct shared_use_st {
int written; // 0=可寫, 1=可讀
char text[TEXT_SZ];
};
消費者 (shmread.c):
// 創建/獲取共享內存 + 信號量
// 循環:P(sem) → 檢查 written → 讀取 → written=0 → V(sem)
// 輸入 "end" 退出,清理 shmctl(IPC_RMID) + semctl(IPC_RMID)
生產者 (shmwrite.c):
// 同鍵獲取共享內存 + 信號量
// 循環:P(sem) → 檢查 written==0 → fgets → 複製 → written=1 → V(sem)
// 輸入 "end" 退出
int main() {
int shmid = shmget(0x33, sizeof(int), IPC_CREAT | 0600);
int *ptr = shmat(shmid, NULL, 0);
*ptr = 0;
int semid = semget(0x44, 1, IPC_CREAT | 0600);
semctl(semid, 0, SETVAL, 1);
pid_t pid = fork();
if (pid == 0) { // 子進程:+1
for (int i=0;i<100;++i) { P(semid); (*ptr)++; V(semid); }
} else { // 父進程:-1
for (int i=0;i<100;++i) { P(semid); (*ptr)--; V(semid); }
waitpid(pid, NULL, 0);
printf("Final: %d\n", *ptr); // 應為 0
}
shmdt(ptr);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, IPC_RMID, 0);
}
關鍵點:fork 後子進程自動繼承父進程的共享內存映射與信號量描述符,無需重新 shmat/semget。
| 陷阱 | 後果 | 修正 |
|---|---|---|
| 僅用 written 旗標無信號量 | 競爭條件:雙方同時讀寫、旗標翻轉錯誤 | 必須用信號量/互斥鎖保護臨界區 |
| written++/-- 非原子 | 多消費者導致計數錯誤 | 信號量 P/V 原子操作替代旗標計數 |
| 未清理 IPC 對象 | 系統資源洩漏,重啟前殘留 | 進程退出前 shmctl/semctl IPC_RMID |
| shmat 指定固定地址 | 地址衝突導致映射失敗 | 傳 NULL 讓內核自動選擇 |
| ftok 路徑文件被刪除 | key 變化,無法獲取同一 IPC | 使用穩定存在的文件路徑 (如 /etc/passwd) |
| 優點 | 缺點 |
|---|---|
| 零拷貝,最高吞吐 | 無內建同步,需額外機制 |
| 無親緣關系限制 | 進程崩潰可能留下髒數據 |
| 支持大數據量共享 | 需手動管理生命周期 |
| 可配合 mmap 實現文件映射 | 調試困難 (無內核中介) |
| 方式 | 速度 | 同步 | 親緣 | 適用場景 |
|---|---|---|---|---|
| 管道/命名管道 | 中 | 內建 | 父子/無 | 小數據流、Shell 管線 |
| 信號量 | 快 | 核心 | 無 | 計數同步、資源保護 |
| 消息隊列 | 中 | 內建 | 無 | 結構化消息、異步通知 |
| 信號 | 慢 | 異步 | 無 | 事件通知、進程控制 |
| 共享內存 | 最快 | 無 | 無 | 大數據、高吞吐、實時 |
| Socket | 中 | 內建 | 跨機 | 網絡通信、本地 Unix Domain |
共享內存 = shmget → shmat → 信號量保護讀寫 → shmdt → shmctl(IPC_RMID)。
掌握 ftok 生成穩定 key、SEM_UNDO 防死鎖、fork 繼承映射、IPC_RMID 及時清理 五大工程鐵律,即可構建高性能、健壯的共享內存 IPC 系統。