Linux 进程间通信:共享内存详解

一、通信方式回顾

在理解共享内存之前,先回顾三种通信方式:

通信方式 特点 举例
单工 单向,方向固定 广播、电视
半双工 双向,同一时刻只能一方发 对讲机、管道
全双工 双向,同一时刻双方可互发 电话、共享内存

共享内存是全双工通信------双方可以同时读写(需自行处理同步,通常配合信号量使用)。

常见 IPC(进程间通信)方式:管道、共享内存、消息队列、信号量、套接字。

共享内存是效率最高的 IPC 方式,因为数据不需要在内核态和用户态之间拷贝。

二、shmget - 创建/获取共享内存

cpp 复制代码
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

参数说明

参数 含义 取值
key 共享内存唯一标识 IPC_PRIVATE(私有) 或 ftok() 生成
size 共享内存大小(字节) 创建时填正整数,获取已有填 0
shmflg 标志位 + 权限 IPC_CREATIPC_EXCL、权限位(如 0666

shmflg 标志位

标志 作用
IPC_CREAT 如果 key 不存在则创建
IPC_EXCL IPC_CREAT 配合,key 已存在则报错
0666 权限(rw-rw-rw-)

返回值

  • 成功:返回共享内存标识符(正数)

  • 失败:返回 -1 ,设置 errno

不同场景的 shmget 调用

场景 代码 说明
创建新共享内存 `shmget(key, 1024, IPC_CREAT 0666)`
创建(独占) `shmget(key, 1024, IPC_CREAT IPC_EXCL
获取已有 shmget(key, 0, 0) size 填 0,flags 填 0
cpp 复制代码
// 示例:创建 1024 字节的共享内存
int shmid = shmget(1234, 1024, IPC_CREAT | 0666);
if (shmid == -1) {
    perror("shmget");
    exit(1);
}

三、shmat - 映射共享内存到进程地址空间

创建的共享内存需要映射到进程的地址空间才能使用。

cpp 复制代码
#include <sys/shm.h>

void* shmat(int shm_id, const void* shm_addr, int shmflg);

参数说明

参数 含义 取值
shm_id 共享内存标识符 shmget() 的返回值
shm_addr 附加地址 NULL(系统自动选择)或指定地址
shmflg 附加标志 0(读写)、SHM_RDONLY(只读)

shmflg 标志

标志 作用
0 默认,可读写
SHM_RDONLY 只读,写入会段错误
SHM_EXEC 允许执行代码

返回值

  • 成功:返回映射后的地址指针

  • 失败:返回 (void*)-1

cpp 复制代码
// 示例:映射共享内存
char* shmaddr = (char*)shmat(shmid, NULL, 0);
if (shmaddr == (void*)-1) {
    perror("shmat");
    exit(1);
}
// 现在可以像普通内存一样读写
strcpy(shmaddr, "Hello");

四、shmdt - 分离共享内存

分离操作不会删除共享内存,只是解除进程与共享内存的映射关系。

cpp 复制代码
#include <sys/shm.h>

int shmdt(const void* shm_addr);
参数 含义
shm_addr shmat() 返回的地址

返回值

  • 成功:返回 0

  • 失败:返回 -1

cpp 复制代码
// 示例:分离共享内存
if (shmdt(shmaddr) == -1) {
    perror("shmdt");
    exit(1);
}

注意:进程退出时会自动分离,但显式调用是良好习惯。

五、shmctl - 删除/控制共享内存

cpp 复制代码
#include <sys/shm.h>

int shmctl(int shm_id, int cmd, struct shmid_ds* buf);

常用 cmd

cmd 作用
IPC_RMID 删除共享内存
IPC_STAT 获取共享内存状态
IPC_SET 设置共享内存属性
cpp 复制代码
// 示例:删除共享内存
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
    perror("shmctl");
    exit(1);
}

六、完整示例:两个进程通信

需求

  • 写进程 :持续向共享内存写入数据,输入 end 则退出

  • 读进程:持续读取共享内存的数据并打印,写进程退出后自动结束

写进程(write.c)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>

int main() {
    key_t key = 1234;
    int shmid;
    char* shmaddr;
    
    // 1. 创建共享内存
    shmid = shmget(key, 1024, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }
    
    // 2. 映射共享内存
    shmaddr = (char*)shmat(shmid, NULL, 0);
    if (shmaddr == (void*)-1) {
        perror("shmat");
        exit(1);
    }
    
    // 3. 写入数据
    char buf[256];
    while (1) {
        printf("请输入:");
        fgets(buf, sizeof(buf), stdin);
        buf[strcspn(buf, "\n")] = '\0';  // 去掉换行符
        
        strcpy(shmaddr, buf);
        
        if (strcmp(buf, "end") == 0) {
            break;
        }
    }
    
    // 4. 分离共享内存
    shmdt(shmaddr);
    
    // 5. 删除共享内存(由写进程负责删除)
    shmctl(shmid, IPC_RMID, NULL);
    
    return 0;
}

读进程(read.c)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>

int main() {
    key_t key = 1234;
    int shmid;
    char* shmaddr;
    char last[256] = "";
    
    // 1. 获取已存在的共享内存
    shmid = shmget(key, 0, 0);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }
    
    // 2. 映射共享内存
    shmaddr = (char*)shmat(shmid, NULL, 0);
    if (shmaddr == (void*)-1) {
        perror("shmat");
        exit(1);
    }
    
    // 3. 持续读取数据
    while (1) {
        if (strcmp(shmaddr, last) != 0) {
            printf("读取到:%s\n", shmaddr);
            strcpy(last, shmaddr);
            
            if (strcmp(shmaddr, "end") == 0) {
                break;
            }
        }
        usleep(100000);  // 睡眠 0.1 秒,避免忙等待
    }
    
    // 4. 分离共享内存
    shmdt(shmaddr);
    
    return 0;
}

编译运行

bash 复制代码
# 编译
gcc write.c -o write
gcc read.c -o read

# 终端1:运行读进程
./read

# 终端2:运行写进程
./write

# 写进程输入数据,读进程会实时显示
# 输入 end 后,写进程退出,读进程也随之退出

七、常用命令

命令 作用
ipcs -m 查看系统中的共享内存
ipcrm -m <shmid> 删除指定的共享内存
bash 复制代码
# 查看所有共享内存
ipcs -m

# 删除共享内存(如果程序没删干净)
ipcrm -m 12345

八、共享内存 vs 管道

对比项 管道 共享内存
通信方向 半双工 全双工
数据拷贝 有(内核态↔用户态) 无(直接访问)
速度 快(最快 IPC)
同步机制 内核自动阻塞 需自行处理(如信号量)
使用场景 简单数据流 大量数据、频繁通信

九、注意事项

注意事项 说明
同步问题 共享内存本身无同步机制,多进程同时读写需配合信号量
资源释放 写进程负责 shmctl(IPC_RMID) 删除,否则内存泄漏
key 冲突 多个进程使用相同的 key 才能共享同一块内存
权限控制 shmflg 中设置权限,如 0666
分离 vs 删除 shmdt 只解除映射,shmctl(IPC_RMID) 才真正删除

十、总结

知识点 核心要点
共享内存特点 全双工、无数据拷贝、效率最高
shmget 创建/获取共享内存,返回 shmid
shmat 映射到进程地址空间,返回指针
shmdt 分离共享内存(不删除)
shmctl 删除/控制共享内存
key 标识 多个进程用相同 key 共享同一块内存
同步问题 需自行处理(信号量),内核不负责
资源释放 必须调用 shmctl(IPC_RMID) 删除
相关推荐
zzzzzz31018 小时前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
XIAOHEZIcode19 小时前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
A小辣椒3 天前
TShark:Wireshark CLI 功能
linux
A小辣椒3 天前
TShark:基础知识
linux
AlfredZhao3 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao3 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334664 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪4 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠4 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush44 天前
嵌入式linux学习记录十四、术语
linux·嵌入式