父進程從鍵盤讀取文字行,經由**共享記憶體**傳遞給子進程,子進程寫入檔案。使用 **System V 信號量** 同步雙向存取,確保「寫一行、通知一次、讀一行」嚴格交替。
#define MAXS 1025
#define BUF_SIZE 200
#define SEM_KEY 251
#define SHM_KEY 241
#define SHM_SIZE MAXS
#define FILENAME "abc.txt"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 信號量操作封裝 */
union semun { int val; struct semid_ds *buf; unsigned short *array; };
int sem_wait(int semid) {
struct sembuf op = {0, -1, 0};
return semop(semid, &op, 1);
}
int sem_signal(int semid) {
struct sembuf op = {0, +1, 0};
return semop(semid, &op, 1);
}
int main(void) {
/* 1. 建立/取得信號量 (初始值 0) */
int semid = semget(SEM_KEY, 1, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); return 1; }
union semun su = {.val = 0};
if (semctl(semid, 0, SETVAL, su) == -1) { perror("semctl"); return 1; }
/* 2. 建立/取得共享記憶體 */
int shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0600);
if (shmid == -1) { perror("shmget"); return 1; }
/* 3. 連接共享記憶體到進程位址空間 */
char *shm = shmat(shmid, NULL, 0);
if (shm == (void*)-1) { perror("shmat"); return 1; }
memset(shm, 0, SHM_SIZE);
char *write_ptr = shm; // 寫入位置游標
/* 4. fork 子進程 */
pid_t pid = fork();
if (pid == -1) { perror("fork"); return 1; }
if (pid == 0) { /* ========== 子進程:生產者 (讀鍵盤 → 共享記憶體) ========== */
char line[BUF_SIZE];
int line_no = 1;
printf("deepfuture.javeye.com#line %d$ ", line_no);
while (fgets(line, BUF_SIZE, stdin)) {
if (line[0] == 'Q' && strlen(line) <= 2) { /* 單獨 Q 結束 */
printf("\n退出....\n");
break;
}
/* 寫入共享記憶體 */
size_t len = strlen(line);
memcpy(write_ptr, line, len);
write_ptr += len;
/* 通知消費者有一行新資料 */
sem_signal(semid);
printf("deepfuture.javeye.com#line %d$ ", ++line_no);
}
*write_ptr = -1; // 結束標記
sem_signal(semid); // 喚醒消費者處理結束標記
shmdt(shm);
return 0;
}
else { /* ========== 父進程:消費者 (共享記憶體 → 檔案) ========== */
FILE *fp = fopen(FILENAME, "ab");
if (!fp) { perror("fopen"); return 1; }
char read_buf[BUF_SIZE];
char *read_ptr = shm;
while (1) {
sem_wait(semid); // 等待生產者寫入
if (*read_ptr == (char)-1) break; // 結束標記
/* 讀取一行 (至換行符) */
size_t i = 0;
while (*read_ptr != '\n' && *read_ptr != (char)-1) {
read_buf[i++] = *read_ptr++;
}
if (*read_ptr == '\n') {
read_buf[i++] = '\n';
++read_ptr;
}
read_buf[i] = '\0';
/* 寫入檔案 */
fwrite(read_buf, 1, i, fp);
fflush(fp);
}
fclose(fp);
waitpid(pid, NULL, 0); // 等待子進程結束
/* 清理 IPC 資源 */
shmdt(shm);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
return 0;
}
}
sequenceDiagram
participant Producer as 子進程(生產者)
participant SHM as 共享記憶體
participant Sem as 信號量(初始0)
participant Consumer as 父進程(消費者)
Producer->>SHM: memcpy 一行資料
Producer->>Sem: sem_signal (+1)
Consumer->>Sem: sem_wait (-1) 阻塞直到有資料
Consumer->>SHM: 讀取一行
Consumer->>File: fwrite
$ gcc -std=gnu99 -Wall -O2 -o shm_ipc shm_ipc.c
$ ./shm_ipc
deepfuture.javeye.com#line 1$ hello world
deepfuture.javeye.com#line 2$ this is a test
deepfuture.javeye.com#line 3$ Q
退出....
$ cat abc.txt
hello world
this is a test
| 現象 | 原因 | 解決 |
|---|---|---|
| 編譯警告 `implicit declaration of function semget` | 缺少 ` | 補全頭文件 |
| 執行 `semget` 失敗 `EACCES` | 舊 IPC 物件權限不符 | `ipcrm -s |
| 共享記憶體內容亂碼 | 未 `memset` 初始化或游標邏輯錯誤 | 檢查 `write_ptr`/`read_ptr` 推進邏輯 |
| 程式掛住不退出 | 信號量配對失衡 (少 signal 或多 wait) | 每行嚴格一對一,結束標記也要 signal |
System V 共享記憶體 + 信號量是經典 Unix IPC 基石。理解鍵值獲取、連接/分離、信號量 P/V 操作、進程同步協議、資源清理完整流程,能應對嵌入式、舊系統維護、高效能共享記憶體交換等場景。新專案優先考慮 POSIX `shm_open`/`mmap` 或管道,代碼更簡潔安全。