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. ❌ 让共享内存一直存在(内存泄漏)
相关推荐
魔云连洲3 小时前
深入解析:Object.prototype.toString.call() 的工作原理与实战应用
前端·javascript·原型模式
第四维度43 小时前
【全志V821_FoxPi】6-2 IMX219 MIPI摄像头适配
linux·ipc·tina·v821·imx219
杜子不疼.3 小时前
【Linux】进程的初步探险:基本概念与基本操作
linux·人工智能·ai
JinSo3 小时前
alien-signals 系列 —— 认识下一代响应式框架
前端·javascript·github
开心不就得了3 小时前
Glup 和 Vite
前端·javascript
szial3 小时前
React 快速入门:菜谱应用实战教程
前端·react.js·前端框架
西洼工作室3 小时前
Vue CLI为何不显示webpack配置
前端·vue.js·webpack
de之梦-御风3 小时前
【Linux】 开启关闭MediaMTX服务
linux·运维·服务器
Morphlng3 小时前
wstunnel 实现ssh跳板连接
linux·服务器·网络·ssh