Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 共享內存通信實戰:shmget/shmat/fork 父子進程零拷貝數據交換

編輯:Linux 教學

前言

共享內存(Shared Memory, shm)是最快的進程間通信(IPC)方式:映射同一物理頁到多個進程虛擬地址空間,實現零拷貝數據直讀寫。本文以完整父子進程示例,演示 shmget/shmat/shmdt/shmctl 全生命週期,並解析地址映射關係、同步機制缺失風險、持久化特性與 ipcs 監控。

一、完整示例代碼

#include "apue.h"
#include 

#define SHM_SIZE 100000
#define SHM_MODE 0600

int main(void) {
    int shmid, pid;
    char *shmptr;
    key_t key;

    // 1. 生成 IPC key
    if ((key = ftok("sharem.c", 1)) == -1)
        err_sys("ftok error");

    // 2. 創建/獲取共享內存段
    if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT | SHM_MODE)) == -1)
        err_sys("shmget error");

    // 3. fork 子進程
    if ((pid = fork()) < 0)
        err_sys("fork error");

    if (pid == 0) {                 // ========== 子進程:寫入 ==========
        if ((shmptr = shmat(shmid, 0, 0)) == (void *)-1)
            err_sys("child shmat error");
        printf("child shm attached: %lx ~ %lx\n",
               (unsigned long)shmptr, (unsigned long)shmptr + SHM_SIZE);

        printf("child input: ");
        scanf("%s", shmptr);        // 從標準輸入讀字符串寫入共享內存
        _exit(0);
    } else {                        // ========== 父進程:讀取 ==========
        sleep(5);                   // 簡易同步:等子進程寫完(實際應用需信號量/記錄鎖)
        if ((shmptr = shmat(shmid, 0, 0)) == (void *)-1)
            err_sys("parent shmat error");
        printf("parent shm attached: %lx ~ %lx\n",
               (unsigned long)shmptr, (unsigned long)shmptr + SHM_SIZE);

        printf("parent output: %s\n", shmptr);
        // 父進程結束,共享內存仍保留(持久化)
    }
    return 0;
}

二、運行結果與地址映射分析

child shm attached: 7f8b9c000000 ~ 7f8b9c0186a0
parent shm attached: 7f8b9c000000 ~ 7f8b9c0186a0
child input: hello_world
parent output: hello_world

關鍵觀察

  • 父子進程虛擬地址區間完全相同(本例中均為 0x7f8b9c000000 起)。
  • 這得益於匿名內存映射機制:內核將同一物理頁框映射到兩進程相同虛擬地址(非必然,僅父子進程常見)。
  • 跨進程指針陷阱:若在共享內存存儲指針,絕不可存物理/虛擬地址,應存偏移量(目標地址 - 共享內存基址),所有進程按基址+偏移量計算。

三、API 速查表

函數作用關鍵參數
ftok(path, id)生成 IPC key路徑需存在,id 為 0-255
shmget(key, size, flags)創建/獲取段IPC_CREAT|0600,返回 shmid
shmat(shmid, addr, flag)掛接到進程空間addr=0 由內核選址,返回虛擬地址
shmdt(shmptr)分離映射不刪除段,僅解除映射
shmctl(shmid, IPC_RMID, 0)標記刪除段最後一個分離進程退出時真正釋放物理頁

四、同步機制:睡眠不是正解

示例中 sleep(5) 純屬演示簡化。生產環境必須使用:

  • 信號量(semget/semop 或 POSIX sem_t):PV 操作互斥/同步。
  • 記錄鎖(fcntl F_SETLKW):對共享內存文件描述符加鎖(需 shm_open 路徑)。
  • 事件fd / futex:高性能用戶態同步。

五、持久化與 ipcs 監控

  • 共享內存隨內核生命週期持久化:進程結束、甚至用戶登出,段仍存在,直到 shmctl IPC_RMID 或重啟。
  • ipcs -m 查看所有段:shmid、perms、bytes、nattch(掛接進程數)、pid(創建者/最後操作者)。
  • ipcrm -m shmid 手動刪除。

六、進階:POSIX 共享內存 (shm_open + mmap)

int fd = shm_open("/my_shm", O_CREAT|O_RDWR, 0600);
ftruncate(fd, SHM_SIZE);
void *ptr = mmap(NULL, SHM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 使用 ptr 讀寫
munmap(ptr, SHM_SIZE);
shm_unlink("/my_shm");

優勢:路徑名命名、支援 fcntl 鎖、統一 mmap 接口、可用 ls /dev/shm/ 直觀查看。

總結

共享內存以物理頁多映射實現零拷貝高速 IPC,核心 API 四部曲:ftok → shmget → shmat → shmdt/shmctl。
必記三點:
1. 跨進程存偏移量,不存指針。
2. 必須額外加同步(信號量/鎖),否則數據競爭。
3. 段持久化,及時 IPC_RMID 或 shm_unlink 避免洩漏。
掌握此機制,是高性能多進程架構(如 Nginx 共享字典、Redis 共享內存緩存、數據庫共享緩衝池)的基石。

Copyright © Windows教程網 All Rights Reserved