Linux 提供豐富的進程間通訊(IPC)與執行緒同步機制。本文系統整理 8 大 IPC 方式,並提供 生產者-消費者模型(pthread 條件變數)、命名管道、System V 信號量、訊息佇列、共享記憶體、信號 六大完整可編譯執行範例。
| 機制 | 適用場景 | 資料流向 | 同步支援 | 跨主機 | 效能 |
|---|---|---|---|---|---|
| 管道 | 親緣關係進程 | 單向 | 內建阻塞 | 否 | 中 |
| 命名管道 (FIFO) | 無親緣進程 | 單向 | 內建阻塞 | 否 | 中 |
| 信號 | 異步事件通知 | 單向 | 信號處理函數 | 否 | 低(資料量極小) |
| 訊息佇列 | 結構化資料交換 | 雙向 | 阻塞收發 | 否 | 中 |
| 共享記憶體 | 大量資料高速共享 | 隨機存取 | 需配合信號量/互斥鎖 | 否 | 最高 |
| 記憶體映射 (mmap) | 檔案/裝置映射共享 | 隨機存取 | 需額外同步 | 否 | 高 |
| 信號量 | 同步/互斥控制 | 控制訊號 | P/V 原語 | 否 | 高(核心態) |
| Socket | 網路/跨主機通訊 | 雙向串流/資料包 | 阻塞/非阻塞/epoll | 是 | 視網路 |
環狀緩衝區,notempty/notfull 兩條件變數實現無忙等待同步。
// 關鍵結構
struct prodcons {
int buffer[16];
pthread_mutex_t lock;
pthread_cond_t notempty, notfull;
int readpos, writepos;
};
// 放入資料
void put(struct prodcons *b, int data) {
pthread_mutex_lock(&b->lock);
while ((b->writepos + 1) % 16 == b->readpos)
pthread_cond_wait(&b->notfull, &b->lock); // 等待非滿
b->buffer[b->writepos++] = data;
b->writepos %= 16;
pthread_cond_signal(&b->notempty); // 通知非空
pthread_mutex_unlock(&b->lock);
}
// 取出資料
int get(struct prodcons *b) {
pthread_mutex_lock(&b->lock);
while (b->writepos == b->readpos)
pthread_cond_wait(&b->notempty, &b->lock); // 等待非空
int data = b->buffer[b->readpos++];
b->readpos %= 16;
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
return data;
}
關鍵點:while 而非 if 檢查條件(防止虛假喚醒);pthread_cond_wait 原子釋放鎖並休眠,喚醒後重新獲取鎖。
// 建立 FIFO
mkfifo("fifo.demo", 0666);
// 寫入端 (writefifo.c)
int fd = open("fifo.demo", O_WRONLY); // 讀端未開啟時阻塞
while (fgets(buf, N, stdin)) {
write(fd, buf, strlen(buf));
if (strncmp(buf, "exit", 4) == 0) break;
}
// 讀取端 (readfifo.c)
int fd = open("fifo.demo", O_RDONLY); // 寫端未開啟時阻塞
while ((n = read(fd, buf, N)) > 0)
write(STDOUT_FILENO, buf, n);
非阻塞模式:open(..., O_RDONLY | O_NONBLOCK)。
// P 操作 (等待/鎖定)
struct sembuf sem_b = {0, -1, SEM_UNDO};
semop(sem_id, &sem_b, 1);
// V 操作 (釋放/解鎖)
sem_b.sem_op = 1;
semop(sem_id, &sem_b, 1);
兩進程交替列印 X、Y,展示關鍵區保護。SEM_UNDO 確保進程異常終止時自動釋放信號量。
key_t key = ftok(".", 'a');
int qid = msgget(key, IPC_CREAT | 0666);
struct msgbuf { long mtype; char mtext[512]; } msg;
msgsnd(qid, &msg, len, 0); // 發送
msgrcv(qid, &msg, 512, 0, 0); // 接收 (mtype=0 取首個)
msgctl(qid, IPC_RMID, NULL); // 刪除
結構體包含 pid_t pid 交換進程 ID,透過 SIGUSR1 通知對方資料就緒。
// 關鍵流程
shmid = shmget(key, sizeof(SHM), IPC_CREAT | IPC_EXCL | 0666);
shmaddr = shmat(shmid, NULL, 0);
shmaddr->pid = getpid();
pause(); // 等待對方就緒
// 之後雙方透過 kill(peerpid, SIGUSR1) 互相通知
IPC_EXCL 判斷誰是第一個建立者,避免競爭條件。
// 司機進程
signal(SIGTSTP, driver_handler); // Ctrl+Z: 發車/到站
signal(SIGUSR1, driver_handler); // 售票員通知發車
signal(SIGUSR2, driver_handler); // 售票員通知停車
// 售票員進程
signal(SIGINT, conductor_handler); // Ctrl+C: 發車
signal(SIGQUIT, conductor_handler); // Ctrl+\: 停車
signal(SIGUSR1, conductor_handler); // 司機通知到站
eventfd/signalfd (Linux 專用) 比 System V IPC 更現代、介面更簡潔。IPC_RMID、shmctl、semctl、msgctl 及時釋放核心資源,避免洩漏。errno 區分 EINTR (被信號中斷) 需重試與真正錯誤。--ipc=host 或適當 namespace 設定。掌握這些 IPC 原語與同步模式,是開發高效能、高可靠度 Linux 系統軟體(資料庫、訊息中介軟體、即時通訊伺服器)的核心技能。