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

System V 共享記憶體進程間通信實戰(二):完整流程與同步機制

     編輯:關於C語言

共享記憶體 IPC 完整操作流程

使用 System V 共享記憶體進行進程間通信的標準六步驟:

  1. 定義資料結構: 設計共享記憶體映射的結構體型別
  2. 獲取/建立區段: shmget() 取得共享記憶體標識符
  3. 附掛到位址空間: shmat() 映射至進程虛擬位址
  4. 資料讀寫操作: 透過指標直接存取共享記憶體
  5. 分離映射關係: shmdt() 解除進程映射
  6. 刪除共享記憶體: shmctl(IPC_RMID) 標記核心刪除

關鍵資料結構設計

// 共享記憶體資料結構範例
typedef struct {
    int flag;           // 同步旗標:0=空, 1=有資料
    int count;          // 有效資料長度
    char data[256];     // 實際資料緩衝區
} SharedData;

設計原則: 包含同步旗標、資料長度、實際負載。避免指標成員(各進程虛擬位址不同)。

API 詳細參數說明

shmget:獲取共享記憶體

int shmget(key_t key, size_t size, int shmflg);
參數說明
key鍵值(ftok() 產生或 IPC_PRIVATE)
size區段大小(bytes),建立時需指定,存取時填 0
shmflg權限 + 旗標:IPC_CREAT | IPC_EXCL | 0666

回傳: 成功回傳 shmid (非負整數),失敗回傳 -1。

shmat:附掛共享記憶體

void *shmat(int shmid, const void *shmaddr, int shmflg);
參數說明
shmidshmget 回傳的標識符
shmaddr指定映射位址(通常傳 NULL 讓核心自動選擇)
shmflgSHM_RDONLY 唯讀,SHM_RND 位址對齊,0 可讀寫

回傳: 成功回傳映射起始位址,失敗回傳 (void *)-1。

shmdt:分離共享記憶體

int shmdt(const void *shmaddr);

shmaddr 為 shmat 回傳位址。成功回傳 0,失敗 -1。注意: 僅解除映射,不刪除核心物件。

shmctl:控制共享記憶體

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
命令用途buf 參數
IPC_STAT讀取狀態到 buf輸出參數
IPC_SET從 buf 設定權限/擁有者輸入參數
IPC_RMID標記刪除(最後一個進程分離時實際釋放)忽略(傳 NULL)

完整雙進程通信範例

寫入進程(writer.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define SHM_KEY 0x1234
#define SHM_SIZE sizeof(SharedData)

typedef struct {
    int flag;
    int count;
    char data[256];
} SharedData;

int main() {
    // 1. 獲取/建立共享記憶體
    int shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) { perror("shmget"); exit(1); }
    
    // 2. 附掛
    SharedData *shm = (SharedData *)shmat(shmid, NULL, 0);
    if (shm == (void *)-1) { perror("shmat"); exit(1); }
    
    // 3. 寫入資料(簡單輪詢同步,實務用信號量)
    while (shm->flag == 1) { usleep(10000); }  // 等待讀取端取走
    
    strcpy(shm->data, "Hello Shared Memory!");
    shm->count = strlen(shm->data);
    shm->flag = 1;  // 標記有資料
    
    printf("寫入完成:%s\n", shm->data);
    
    // 4. 分離
    shmdt(shm);
    return 0;
}

讀取進程(reader.c)

int main() {
    int shmid = shmget(SHM_KEY, SHM_SIZE, 0666);  // 不加 IPC_CREAT,僅存取
    if (shmid == -1) { perror("shmget"); exit(1); }
    
    SharedData *shm = (SharedData *)shmat(shmid, NULL, 0);
    if (shm == (void *)-1) { perror("shmat"); exit(1); }
    
    // 輪詢等待資料
    while (shm->flag == 0) { usleep(10000); }
    
    printf("讀取到:%.*s (長度=%d)\n", shm->count, shm->data, shm->count);
    
    shm->flag = 0;  // 標記已取走,允許寫入端繼續
    shmdt(shm);
    return 0;
}

執行結果演示

$ ./writer &
[1] 12345
$ ./reader
讀取到:Hello Shared Memory! (長度=20)
$ 
[1]+  Done                    ./writer

關鍵問題:同步機制缺失

上例使用忙等待輪詢(while(flag) usleep())實現同步,缺點明顯:

  • CPU 浪費:空轉消耗核心資源
  • 延遲不可控:usleep 精度有限,響應抖動大
  • 競態風險:旗標檢查與修改非原子,極端情況下仍可能衝突

正確解法:信號量同步

// 建立兩個信號量:empty(初始=1), full(初始=0)
int semid = semget(SEM_KEY, 2, IPC_CREAT | 0666);
semctl(semid, 0, SETVAL, 1);  // empty = 1
semctl(semid, 1, SETVAL, 0);  // full = 0

// 寫入端
struct sembuf p_empty = {0, -1, 0};  // P(empty)
struct sembuf v_full  = {1, +1, 0};  // V(full)
semop(semid, &p_empty, 1);  // 等待空槽
// 寫入資料...
semop(semid, &v_full, 1);   // 通知有資料

// 讀取端
struct sembuf p_full = {1, -1, 0};  // P(full)
struct sembuf v_empty = {0, +1, 0}; // V(empty)
semop(semid, &p_full, 1);   // 等待資料
// 讀取資料...
semop(semid, &v_empty, 1);  // 釋放空槽

共享記憶體優缺點總結

優點缺點
最高效 IPC(零拷貝,直接映射)需額外同步機制(信號量/互斥鎖)
適合大量資料高頻交換進程崩潰可能留下髒資料
支援任意資料結構跨網路/機器不支援(僅本機)
核心持久化,進程結束資料不丟需手動清理,否則洩漏核心資源

最佳實踐清單

  • ✅ 定義結構體含同步欄位(旗標/版本號/時間戳)
  • ✅ 必搭配信號量實現生產者-消費者同步
  • ✅ 使用 IPC_EXCL 檢測重複建立
  • ✅ 進程退出前必呼叫 shmdt()
  • ✅ 專用清理程式或 atexit() 註冊 shmctl(IPC_RMID)
  • ✅ 監控 ipcs -m 定期檢查殘留區段

總結

System V 共享記憶體是最高效的本機 IPC 方式,但「共享記憶體不提供同步」是核心特性。實務中必須搭配信號量(或 POSIX 互斥鎖放入共享區)實現正確的生產者-消費者模型。掌握 shmget/shmat/shmdt/shmctl 四大 API 與同步模式,是 Linux 系統程式開發的核心技能。

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