linux学习笔记(18)进程间通讯——共享内存

共享内存 ( 最快 的进程间通信方式)

公享内存为多个进程之间共享和传递数据提供了一种有效的方式。
共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。
所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。
如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。
由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问

共享内存的四个核心函数

|----------|-----------|--------|
| 函数 | 作用 | 比喻 |
| shmget() | 创建/获取共享内存 | 租一块地 |
| shmat() | 连接到进程地址空间 | 拿到钥匙进门 |
| shmdt() | 断开连接 | 离开房子 |
| shmctl() | 控制(删除等) | 退租 |

int shmid = shmget(IPC_PRIVATE, 1024, 0666);
// IPC_PRIVATE: 创建新的共享内存
// 1024: 大小(字节)
// 0666: 读写权限
char *ptr = shmat(shmid, NULL, 0);
// shmid: 共享内存ID
// NULL: 让系统自动选择地址
// 0: 读写模式
// 返回值: 共享内存的首地址
shmdt(shared_memory);
// shared_memory: shmat()返回的地址
shmctl(shmid, IPC_RMID, NULL);
// IPC_RMID: 删除命令
例子:

cpp 复制代码
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
    int shmid;
    char *shared_memory;
    // 1. 创建共享内存(大小1KB)
    shmid = shmget(IPC_PRIVATE, 1024, 0666);
    printf("1. 创建共享内存,ID = %d\n", shmid);
    // 2. 连接到当前进程
    shared_memory = shmat(shmid, NULL, 0);
    printf("2. 连接到共享内存,地址 = %p\n", shared_memory);
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程:写入数据
        printf("3. 子进程:写入数据\n");
        strcpy(shared_memory, "Hello from Child!");
        printf("   子进程写了: %s\n", shared_memory);
    } else {
        // 父进程:读取数据
        sleep(1);  // 等子进程先写        
        printf("4. 父进程:读取数据\n");
        printf("   父进程读到: %s\n", shared_memory);
        wait(NULL);
        // 3. 断开连接
        shmdt(shared_memory);
        printf("5. 断开共享内存连接\n");
        // 4. 删除共享内存
        shmctl(shmid, IPC_RMID, NULL);
        printf("6. 删除共享内存\n");
    }
    return 0;
}

优点:

  • ✅ 速度最快(直接内存访问)
  • ✅ 大数据量友好(可分配很大内存)
  • ✅ 使用简单(像普通内存一样操作)

缺点:

  • ❌ 需要同步(多个进程同时写会冲突)
  • ❌ 不会自动释放(要手动管理生命周期)

重要提醒:共享内存需要同步!

解决方案:

  • 用信号量保护共享内存
  • 用互斥锁同步访问

共享内存 + 信号量 = 完美组合

问题:多个进程同时写会冲突

解决方案:用信号量保护共享内存

cpp 复制代码
#include <stdio.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
// 必须的联合体
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};
// P/V操作
void P(int semid) { struct sembuf op = {0, -1, 0}; semop(semid, &op, 1); }
void V(int semid) { struct sembuf op = {0, 1, 0}; semop(semid, &op, 1); }
// 在共享内存中定义数据结构
struct shared_data {
    int counter;
    char message[100];
};
int main() {
    int shmid, semid;
    struct shared_data *shared;
    union semun sem_arg;    
    // 1. 创建共享内存(包含我们的数据结构)
    shmid = shmget(IPC_PRIVATE, sizeof(struct shared_data), 0666);
    shared = (struct shared_data*)shmat(shmid, NULL, 0);
    // 2. 创建信号量保护共享内存
    semid = semget(IPC_PRIVATE, 1, 0666);
    sem_arg.val = 1;
    semctl(semid, 0, SETVAL, sem_arg);
    // 初始化共享数据
    shared->counter = 0;
    strcpy(shared->message, "Initial");
    printf("初始状态: counter=%d, message=%s\n", shared->counter, shared->message);
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程:修改共享数据
        for (int i = 0; i < 3; i++) {
            P(semid);  // 获取锁            
            // 安全地修改共享数据
            shared->counter++;
            sprintf(shared->message, "Child modified %d", i);
            printf("子进程: counter=%d, message=%s\n", shared->counter, shared->message);
            V(semid);  // 释放锁
            sleep(1);
        }
    } else {
        // 父进程:修改共享数据
        for (int i = 0; i < 3; i++) {
            P(semid);  // 获取锁
            // 安全地修改共享数据
            shared->counter++;
            sprintf(shared->message, "Parent modified %d", i);
            printf("父进程: counter=%d, message=%s\n", shared->counter, shared->message);
            V(semid);  // 释放锁
            sleep(1);
        }
        wait(NULL);
        // 清理
        shmdt(shared);
        shmctl(shmid, IPC_RMID, NULL);
        semctl(semid, 0, IPC_RMID);
    }
    return 0;
}

共享内存的持久化特性

共享内存会一直存在,直到:

  1. 被显式删除 (shmctl(IPC_RMID))
  2. 系统重启
  3. 使用 ipcrm 命令删除

共享内存的最佳实践

一定要做的:

  1. 总是用信号量同步
  2. 及时释放连接 (shmdt)
  3. 程序退出前清理 (shmctl(IPC_RMID))

要避免的:

  1. ❌ 直接操作共享内存不加锁
  2. ❌ 忘记断开连接
  3. ❌ 让共享内存一直存在(内存泄漏)
相关推荐
geovindu4 小时前
css: Timeline scribble
前端·css·iphone
ai小陈10 小时前
GPU服务器租用存储验收:检查点写入与磁盘吞吐实战
运维·服务器·人工智能·ai·ssh·gpu算力
Ivanqhz11 小时前
激活函数在 Transformer 中的作用及各种变体简述
java·linux·数据库·人工智能·深度学习
沙漠之主12 小时前
C++编程教学设计资料:从入门到实战的完整课程方案
java·前端·c++
hasty12 小时前
不上传新包,也能改变用户拿到的版本:npm dist-tag 的 OIDC 权限治理
前端·npm·node.js
FPGA小徐12 小时前
PA1 基础设施(2)深度解析:NEMU 中 ITRACE、MTRACE、FTRACE、IRINGBUF、ETRACE、DIFFTEST 全解析
java·服务器·网络
Csvn12 小时前
diff 算法(虚拟 DOM Reconciliation)
前端
知行EDI13 小时前
知行之桥 MaBang 端口使用指南——Create Order 订单创建篇
java·服务器·数据库
成旭先生13 小时前
【2026】企业信息模糊查询 API 实战:名称、注册号、统一社会信用代码、企业类型与法人一次查全
服务器·数据库·数据服务
前端snow13 小时前
ai agent --- 文件存储
前端