使用 System V 共享記憶體進行進程間通信的標準六步驟:
shmget() 取得共享記憶體標識符shmat() 映射至進程虛擬位址shmdt() 解除進程映射shmctl(IPC_RMID) 標記核心刪除// 共享記憶體資料結構範例
typedef struct {
int flag; // 同步旗標:0=空, 1=有資料
int count; // 有效資料長度
char data[256]; // 實際資料緩衝區
} SharedData;
設計原則: 包含同步旗標、資料長度、實際負載。避免指標成員(各進程虛擬位址不同)。
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。
void *shmat(int shmid, const void *shmaddr, int shmflg);
| 參數 | 說明 |
|---|---|
shmid | shmget 回傳的標識符 |
shmaddr | 指定映射位址(通常傳 NULL 讓核心自動選擇) |
shmflg | SHM_RDONLY 唯讀,SHM_RND 位址對齊,0 可讀寫 |
回傳: 成功回傳映射起始位址,失敗回傳 (void *)-1。
int shmdt(const void *shmaddr);
shmaddr 為 shmat 回傳位址。成功回傳 0,失敗 -1。注意: 僅解除映射,不刪除核心物件。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
| 命令 | 用途 | buf 參數 |
|---|---|---|
IPC_STAT | 讀取狀態到 buf | 輸出參數 |
IPC_SET | 從 buf 設定權限/擁有者 | 輸入參數 |
IPC_RMID | 標記刪除(最後一個進程分離時實際釋放) | 忽略(傳 NULL) |
#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;
}
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())實現同步,缺點明顯:
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 系統程式開發的核心技能。