首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 進程間通信之共享內存完整指南:shmget/shmat/shmdt/shmctl、信號量同步、父子進程實戰

     編輯:關於C語言

Linux 進程間通信之共享內存完整指南

共享內存是 最快的 IPC 方式:多進程映射同一物理頁面,用戶態直接讀寫,無內核拷貝開銷。但無內建同步,必須配合信號量/互斥鎖。本文系統講解 API、同步模式、完整雙進程示例、父子進程繼承映射、安全性陷阱。

核心 API 速查表

函數原型關鍵參數返回值
shmgetint shmget(key_t key, size_t size, int shmflg);key: 命名標識 (ftok 生成)
size: 字節數
shmflg: 權限 | IPC_CREAT | IPC_EXCL
shmid (≥0) 或 -1
shmatvoid *shmat(int shmid, const void *shmaddr, int shmflg);shmaddr: 指定映射地址 (通常 NULL 由內核選擇)
shmflg: SHM_RDONLY 只讀
映射地址 或 (void*)-1
shmdtint shmdt(const void *shmaddr);shmaddr: shmat 返回地址0 成功 / -1 失敗
shmctlint shmctl(int shmid, int cmd, struct shmid_ds *buf);cmd: IPC_STAT/IPC_SET/IPC_RMID
buf: 權限/狀態結構體
0 成功 / -1 失敗

權限位說明 (與 open mode 一致)

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" 退出

父子進程共享內存 (fork 後繼承映射)

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 實現文件映射調試困難 (無內核中介)

Linux IPC 方式對比

方式速度同步親緣適用場景
管道/命名管道中內建父子/無小數據流、Shell 管線
信號量快核心無計數同步、資源保護
消息隊列中內建無結構化消息、異步通知
信號慢異步無事件通知、進程控制
共享內存最快無無大數據、高吞吐、實時
Socket中內建跨機網絡通信、本地 Unix Domain

小結

共享內存 = shmget → shmat → 信號量保護讀寫 → shmdt → shmctl(IPC_RMID)。
掌握 ftok 生成穩定 key、SEM_UNDO 防死鎖、fork 繼承映射、IPC_RMID 及時清理 五大工程鐵律,即可構建高性能、健壯的共享內存 IPC 系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved