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) 删除
相关推荐
坚持就完事了1 小时前
Ubuntu和Centos中安装软件的命令
linux·ubuntu·centos
程序猿编码1 小时前
Linux 高负载场景下 Web 服务访问日志极速定位工具实现解析(C/C++代码实现)
linux·服务器·c语言·前端·c++
CC城子1 小时前
嵌入式Linux宕机问题GDB调试(二)
linux·gdb
lifewange1 小时前
VMware如何安装并配置CentOs镜像
linux·运维·centos
j_xxx404_1 小时前
【Linux进程间通信】硬核剖析:消息队列、信号量、内核IPC资源统一管理与mmap加餐
linux·运维·开发语言·c++·人工智能·ai
keyipatience1 小时前
14.Linux进程状态:从运行到僵尸的奥秘
linux
SilentSamsara2 小时前
生成器完全指南:`yield` 与惰性求值的工程价值
linux·开发语言·python·算法·机器学习·青少年编程
不怕犯错,就怕不做13 小时前
RK3562的CPU如何降频及关闭硬件编解码
linux·驱动开发·嵌入式硬件
CoderMeijun13 小时前
Linux 文件操作详解:open/read/write/lseek 系统调用
linux·文件操作·系统调用·open·文件描述符