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

Linux 進程間通訊實戰:共享記憶體 + 信號量實現生產者-消費者模型

     編輯:關於C語言

背景

父進程從鍵盤讀取文字行,經由**共享記憶體**傳遞給子進程,子進程寫入檔案。使用 **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
  • 信號量初值 0 ⇒ 消費者先跑會阻塞在 `sem_wait`,等待生產者 `sem_signal`。
  • 每行資料對應一次 `signal`/`wait` 配對,嚴格同步。

共享記憶體緩衝區管理

  • 單一線性緩衝區 `MAXS` bytes,游標 `write_ptr`/`read_ptr` 追蹤位置。
  • 無環形緩衝區包裝,適合「一次性傳輸已知總量」場景;長期運行服務建議改用環形緩衝區 + 兩個信號量 (空/滿)。

進程結束協議

  1. 生產者讀到 `Q` → 寫入結束標記 `-1` → `sem_signal` 喚醒消費者。
  2. 消費者讀到 `-1` → 跳出迴圈 → `waitpid` 回收子進程 → 清理 IPC。

編譯與執行


$ 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

現代替代方案 (Linux 專用)

  • POSIX 共享記憶體:`shm_open` + `mmap` + `sem_t` (具名信號量),API 更簡潔、支援線程同步。
  • 管道/Unix Domain Socket:無需手動同步,內核自動緩衝與阻塞,適合串流資料。
  • mmap 檔案映射:`mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0)` 父子進程直接共享匿名映射區,最輕量。

結語

System V 共享記憶體 + 信號量是經典 Unix IPC 基石。理解鍵值獲取、連接/分離、信號量 P/V 操作、進程同步協議、資源清理完整流程,能應對嵌入式、舊系統維護、高效能共享記憶體交換等場景。新專案優先考慮 POSIX `shm_open`/`mmap` 或管道,代碼更簡潔安全。

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