news 2026/4/15 18:51:53

进程通信之共享内存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
进程通信之共享内存

文章目录

  • 内存映射
    • 内存分配
    • 内存布局
    • mmap() 系统调用
      • 查看进程内存映射
    • mmap()四种映射方式
      • 私有文件映射 (MAP_PRIVATE)
      • 私有匿名映射 (MAP_PRIVATE | MAP_ANONYMOUS)
      • 共享文件映射 (MAP_SHARED)
      • 共享匿名映射 (MAP_SHARED | MAP_ANONYMOUS)
  • 文件映射进程通信
    • 实现原理
    • 实现代码
  • 匿名共享内存实现通信
  • POSIX 共享内存进程通信
    • POSIX共享内存API
      • 创建 / 打开 POSIX 共享内存对象
      • 调整共享内存大小
      • 删除共享内存对象
    • 使用流程
    • 实现代码
  • System V 共享内存进程通信
    • POSIX 共享内存API
      • 创建 / 获取共享内存
      • 附加到进程地址空间
      • 分离共享内存
      • 控制操作( 含删除、查询、修改 )
    • 实现代码
  • 共享内存数据竞争问题
    • 解决方案
  • 对比
  • C语言当中的内存分区

内存映射

内存分配

  • Linux内核为进程分配内存空间
    • 虚拟内存管理:Linux使用虚拟内存管理,每个进程都有独立的虚拟地址空间,通常分为用户空间和内核空间。用户空间供进程使用,内核空间由内核管理
    • 内存描述符 (mm_struct):每个进程的虚拟内存信息由mm_struct结构体管理,包含内存映射、堆、栈等信息
    • 内存映射:进程的内存映射通过vm_area_struct结构体描述,记录虚拟内存区域的起始、结束地址、权限等,常见的内存区域包括:
      • 代码段:存放可执行代码
      • 数据段:存放全局和静态变量
      • 堆:动态分配的内存
      • 栈:用于函数调用和局部变量
    • 内存分配:内存分配主要通过以下系统调用:
      • brksbrk:调整堆的大小
      • mmap:创建新的内存映射,可用于文件映射或匿名内存分配
    • 缺页异常处理:当进程访问未映射的虚拟内存时,触发缺页异常,内核通过以下步骤处理:
      • 检查访问权限:确认访问是否合法
      • 分配物理内存:若合法,分配物理页并更新页表
      • 恢复执行:进程继续执行
    • 页面回收:当物理内存不足时,内核通过页面回收机制释放内存,包括:
      • 页面置换:将不常用的页面换出到交换空间
      • 内存压缩:压缩内存页以减少使用
    • 内核内存分配:内核使用kmallocvmalloc等函数分配内存
      • kmalloc用于小块连续内存
      • vmalloc用于大块不连续内存

内存布局

  • Linux 进程内存布局
    • 代码段 (Text):存放可执行代码
    • 数据段 (Data):全局变量和静态变量
    • BSS段:未初始化的全局变量
    • 堆 (Heap):动态分配的内存(malloc/free)
    • 栈 (Stack):函数调用、局部变量
    • 共享库:动态链接库映射区域

mmap() 系统调用

  • mmap() 是 Linux/Unix 下核心的系统调用,用于将文件或共享内存对象映射到当前进程的虚拟地址空间,映射完成后,进程可通过直接操作虚拟内存的方式读写文件 / 共享内存,无需调用 read()/write() 等传统 I/O 函数,效率更高
#include<sys/mman.h>void*mmap(void*addr,size_tlength,intprot,intflags,intfd,off_toffset);
  • addr:建议映射地址(通常为NULL,由内核决定)
  • length:映射长度
  • prot:保护标志(PROT_READ | PROT_WRITE | PROT_EXEC)
  • flags:映射类型和选项
  • fd:文件描述符(匿名映射时为-1)
  • offset:文件偏移量
// 配套的解除映射函数intmunmap(void*addr,size_tlength);
  • 用于解除 mmap() 建立的内存映射,释放进程虚拟地址空间

查看进程内存映射

# 查看进程的内存映射cat/proc/<PID>/maps# 示例输出00400000-00401000 r-xp 000060000 08:01123456/bin/program# 代码段00600000-00601000 rw-p 00000000 08:01123456/bin/program# 数据段7ffff7a00000-7ffff7bc1000 r-xp 00000000 08:01789012/lib/libc.so.6

mmap()四种映射方式

  • 按数据来源分类:
类型文件映射匿名映射
共享 (SHARED)共享文件映射共享匿名映射
私有 (PRIVATE)私有文件映射私有匿名映射

私有文件映射 (MAP_PRIVATE)

  • 用途:用文件内容初始化内存(如可执行文件加载)
  • 变更对其他进程不可见,不会写回文件
  • 常用于只读数据共享

私有匿名映射 (MAP_PRIVATE | MAP_ANONYMOUS)

  • 用途:分配新内存(malloc() 大块内存时使用)
  • 初始化为0,变更私有
  • 代替 brk() 分配大内存

共享文件映射 (MAP_SHARED)

  • 用途:内存映射I/O、进程间通信
  • 变更对其他进程可见,会写回文件
  • 高效的文件读写方式

共享匿名映射 (MAP_SHARED | MAP_ANONYMOUS)

  • 用途:相关进程间共享内存
  • 无文件支持,纯内存共享
  • 必须相关进程(如父子进程)

文件映射进程通信

实现原理

  • 多个进程映射同一文件的同一区域
  • 使用 MAP_SHARED 标志
  • 对映射区域的修改会同步到文件和其他进程

实现代码

  • 发送端
#include<fcntl.h>#include<sys/mman.h>#include<stdio.h>structstudent{charname[32];intage;floatscore;}*p;intmain(){intfd=open("stu.bin",O_RDWR|O_CREAT,0666);// 将文件映射到内存p=mmap(NULL,sizeof(structstudent),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);// 持续更新分数while(1){scanf("%f",&p->score);}munmap(p,sizeof(structstudent));return0;}
  • 接收端
#include<fcntl.h>#include<sys/mman.h>#include<stdio.h>structstudent{charname[32];intage;floatscore;}*p;intmain(){intfd=open("stu.bin",O_RDWR);p=mmap(NULL,sizeof(structstudent),PROT_READ,MAP_SHARED,fd,0);floatlast_score=p->score;printf("当前分数:%.2f\n",p->score);while(1){if(p->score!=last_score){printf("分数变更为:%.2f\n",p->score);last_score=p->score;}sleep(1);}munmap(p,sizeof(structstudent));return0;}

匿名共享内存实现通信

#include<sys/mman.h>#include<string.h>intmain(){// 创建匿名共享映射char*shm=mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);strcpy(shm,"Hello from parent");if(fork()==0){// 子进程读取printf("Child: %s\n",shm);exit(0);}wait(NULL);munmap(shm,4096);return0;}

POSIX 共享内存进程通信

POSIX共享内存API

创建 / 打开 POSIX 共享内存对象

intshm_open(constchar*name,intoflag,mode_tmode);
  • name:系统级唯一标识名,命名规则严格:必须以 / 开头,且后续不能再包含其他 /(例如 /my_shm 合法,/my/shm 非法)
  • oflag:打开 / 创建标志,可通过 按位或组合多个标志,O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL、O_TRUNC
  • mode:仅当 oflag 包含 O_CREAT 时有效,用于指定新共享内存对象的访问权限,取值与 open()、mq_open() 权限一致,最终实际权限会被进程的 umask 掩码修正(实际权限 = mode & ~umask)、若不使用 O_CREAT,该参数可传入 0(仅为占位,无实际意义)
  • 返回值:
    • 成功:返回一个有效的文件描述符(非负整数),后续对共享内存的操作(ftruncate、mmap 等)均依赖该文件描述符
    • 失败:返回 -1,同时设置全局变量 errno 指示错误原因(例如 EEXIST:O_CREAT|O_EXCL 时共享内存已存在;ENOENT:对象不存在且未指定 O_CREAT;EINVAL:name 命名格式非法)

调整共享内存大小

intftruncate(intfd,off_tlength);
  • fd:有效的文件描述符,此处必须是由 shm_open() 成功返回的共享内存对象描述符(且需拥有可写权限,即 shm_open() 时指定了 O_WRONLY 或 O_RDWR)
  • length:共享内存对象调整后的目标字节长度(非负整数)
    • 若 length 小于当前共享内存的大小:超出 length 的部分数据会被丢弃,内存空间被回收
    • 若 length 大于当前共享内存的大小:扩展的部分会被初始化为 0,新增的内存空间可被后续 mmap 映射使用
    • 共享内存创建后默认长度为 0,必须通过 ftruncate() 设定有效大小,否则无法正常映射使用
  • 返回值:
    • 成功:返回 0,表示共享内存对象的大小已成功调整为 length 字节
    • 失败:返回 -1,同时设置全局变量 errno 指示错误原因 (例如<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">EBADF</font>:无效的文件描述符或无写权限;<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">EINVAL</font><font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">length</font>为负数,或描述符对应对象不支持大小调整;<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">ENOSPC</font>:系统无足够资源满足大小扩展需求)

删除共享内存对象

intshm_unlink(constchar*name);
  • name:与 shm_open() 中一致的共享内存对象唯一标识名,标识要删除的目标共享内存对象,名称必须对应系统中已存在的共享内存
  • 返回值:
    • 成功:返回 0,表示共享内存对象的「链接」已被成功移除
    • 失败:返回 -1,同时设置全局变量 errno 指示错误原因 (例如 ENOENT:指定名称的共享内存对象不存在;EINVAL:name 命名格式非法)

使用流程

  • shm_open() 创建/打开共享内存对象
  • ftruncate() 设置大小
  • mmap() 映射到进程地址空间
  • 读写操作
  • munmap() 取消映射
  • shm_unlink() 删除对象(可选)

实现代码

  • 发送端
#include<fcntl.h>#include<sys/mman.h>#include<stdio.h>intmain(){// 1. 创建共享内存对象intfd=shm_open("/myshm",O_RDWR|O_CREAT,0666);// 2. 设置大小ftruncate(fd,4096);// 3. 映射到内存char*str=mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);close(fd);// 关闭文件描述符// 4. 写入数据sprintf(str,"Hello from sender\n");sleep(10);// 保持映射// 5. 清理munmap(str,4096);return0;}
  • 接收端
#include<fcntl.h>#include<sys/mman.h>#include<stdio.h>intmain(){// 1. 打开共享内存对象intfd=shm_open("/myshm",O_RDONLY,0666);// 2. 映射到内存char*str=mmap(NULL,4096,PROT_READ,MAP_SHARED,fd,0);close(fd);// 3. 读取数据printf("Received: %s",str);// 4. 清理munmap(str,4096);shm_unlink("/myshm");// 删除共享内存对象return0;}

System V 共享内存进程通信

POSIX 共享内存API

创建 / 获取共享内存

intshmget(key_tkey,size_tsize,intshmflg);
  • key:用于标识系统级唯一共享内存段的键值,手动指定一个非负整数 ,不同进程使用相同 key 可访问同一个共享内存段。用 IPC_PRIVATE(值为 0)创建一个仅当前进程可见的私有共享内存段(通常用于父子进程间通信),可通过 ftok() 函数生成基于文件路径和项目 ID 的唯一 key,避免手动指定冲突
  • size:共享内存段的目标字节大小(非负整数)。若为创建新共享内存段,必须指定有效的 size,例如 4096;若为获取已存在的共享内存段(仅用 key 匹配现有段),size 可指定为 0(无需匹配大小,仅用于标识获取操作)
  • shmflg:打开 / 创建标志,可通过 按位或组合多个标志,O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL、O_TRUNC
  • 返回值:
    • 成功:返回一个有效的 共享内存段 ID(shmid,非负整数),后续所有对该共享内存的操作(shmat/shmdt/shmctl)均依赖该 ID
    • 失败:返回 -1,同时设置全局变量 errno 指示错误原因(例如 EEXIST:IPC_CREAT|IPC_EXCL 时内存段已存在;ENOENT:无对应 key 的内存段且未指定 IPC_CREAT;ENOMEM:系统无足够内存创建新段;EINVAL:size 无效或超出系统限制)

附加到进程地址空间

void*shmat(intshmid,constvoid*shmaddr,intshmflg);
  • shmid: 由<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">shmget()</font>成功返回的**共享内存段 ID**,标识要附加的目标共享内存段(需有效且当前进程有访问权限)
  • shmaddr: 指定共享内存段映射到当前进程地址空间的起始地址。常用取值NULL(推荐),由系统自动选择合适的空闲地址进行映射,避免手动指定地址导致冲突或无效;若手动指定非 NULL 地址:需保证地址对齐(符合内存页要求)
  • shmflg: 附加模式标志
    • 0(默认:以读写模式附加共享内存段(需当前进程拥有该内存段的读写权限),常传入 0 即可满足大部分需求
    • SHM_RDONLY:以只读模式附加共享内存段(需当前进程拥有该内存段的读权限),此时进程无法修改共享内存中的数据
    • SHM_REMAP:仅当 shmaddr 非 NULL 时有效,替换当前进程 shmaddr 地址处已存在的映射(覆盖原有映射)
  • 返回值:
    • 成功:返回共享内存段映射到当前进程地址空间的起始虚拟地址(void * 类型),进程可通过该地址访问共享内存数据
    • 失败:返回 -1,同时设置全局变量 errno 指示错误原因

分离共享内存

intshmdt(constvoid*shmaddr);
  • shmaddr: 由 shmat() 成功返回的共享内存段映射起始地址,标识要分离的内存映射区域。必须是 shmat() 返回的原始地址,不能是偏移后的地址
  • 返回值:
    • 成功:返回 0,表示共享内存段已成功与当前进程分离
    • 失败:返回 -1,同时设置全局变量 errno 指示错误原因

控制操作( 含删除、查询、修改 )

intshmctl(intshmid,intcmd,structshmid_ds*buf);
  • shmaddr: 由 shmget() 成功返回的共享内存段 ID,标识要进行控制操作的目标共享内存段(需有效且当前进程有对应操作权限)
  • cmd:要执行的控制命令:
    • IPC_STAT:查询共享内存段的属性信息,将属性写入 buf 指向的 struct shmid_ds 结构体中(buf 不能为 NULL)
    • IPC_SET:修改共享内存段的属性,仅能修改 struct shmid_ds 中的 shm_perm.uid、shm_perm.gid、shm_perm.mode 字段(需进程拥有对应权限,buf 不能为 NULL)
    • IPC_RMID:删除共享内存段,此时 buf 可指定为 NULL(无意义,无需传入)
  • buf:指向 struct shmid_ds 结构体的指针,用于存储或修改共享内存段的属性
  • 返回值:
    • 成功:返回 0
    • 失败:返回 -1,同时设置全局变量 errno 指示错误原因

实现代码

  • 发送端
#include<sys/ipc.h>#include<sys/shm.h>#include<string.h>intmain(){// 1. 生成键值key_tkey=ftok("shmfile",65);// 2. 创建共享内存intshmid=shmget(key,1024,0666|IPC_CREAT);// 3. 附加到进程char*shm=shmat(shmid,NULL,0);// 4. 写入数据strcpy(shm,"Hello from System V");// 5. 分离shmdt(shm);return0;}
  • 接收端
#include<sys/ipc.h>#include<sys/shm.h>#include<stdio.h>intmain(){key_tkey=ftok("shmfile",65);// 获取现有共享内存intshmid=shmget(key,1024,0666);// 附加到进程char*shm=shmat(shmid,NULL,0);// 读取数据printf("Received: %s\n",shm);// 分离并删除shmdt(shm);shmctl(shmid,IPC_RMID,NULL);return0;}

共享内存数据竞争问题

  • 多个进程同时读写共享内存会导致数据不一致:
// 进程1int*data=shmat(shm_id,NULL,0);*data=10;while(*data!=0);// 等待进程2// 此时进程2可能也在修改data

解决方案

  • 信号量 (Semaphore)
  • 互斥锁 (Mutex) - 配合共享内存使用
  • 条件变量 (Condition Variable)
  • 文件锁 (fcntl)
#include<semaphore.h>#include<sys/mman.h>// 在共享内存中定义同步结构structshared_data{sem_tsem;// 信号量intvalue;// 共享数据};// 初始化sem_init(&data->sem,1,1);// 进程间共享,初始值1// 使用sem_wait(&data->sem);// 加锁data->value++;// 临界区操作sem_post(&data->sem);// 解锁

对比

特性文件映射POSIX共享内存System V共享内存匿名映射
持久性文件系统持久系统重启消失系统重启消失进程结束消失
进程关系任意进程任意进程任意进程相关进程
使用难度中等简单中等简单
同步需求需要需要需要需要
可移植性较高
性能依赖文件系统最高
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/3 1:55:35

苹果电脑NTFS读写全攻略:零成本实现外接硬盘自由传输

苹果电脑NTFS读写全攻略&#xff1a;零成本实现外接硬盘自由传输 【免费下载链接】Free-NTFS-for-Mac Nigate&#xff0c;一款支持苹果芯片的Free NTFS for Mac小工具软件。NTFS R/W for macOS. Support Intel/Apple Silicon now. 项目地址: https://gitcode.com/gh_mirrors/…

作者头像 李华
网站建设 2026/4/10 10:14:07

RPG Maker MV/MZ文件解密工具终极指南:从新手到高手的完整教程

RPG Maker MV/MZ文件解密工具终极指南&#xff1a;从新手到高手的完整教程 【免费下载链接】RPG-Maker-MV-Decrypter You can decrypt RPG-Maker-MV Resource Files with this project ~ If you dont wanna download it, you can use the Script on my HP: 项目地址: https:/…

作者头像 李华
网站建设 2026/4/15 17:36:33

Mac终极NTFS读写方案:Nigate免费工具完全指南

Mac终极NTFS读写方案&#xff1a;Nigate免费工具完全指南 【免费下载链接】Free-NTFS-for-Mac Nigate&#xff0c;一款支持苹果芯片的Free NTFS for Mac小工具软件。NTFS R/W for macOS. Support Intel/Apple Silicon now. 项目地址: https://gitcode.com/gh_mirrors/fr/Free…

作者头像 李华
网站建设 2026/4/13 14:39:22

76 最小覆盖子串【滑动窗口】

给定两个字符串 s 和 t&#xff0c;长度分别是 m 和 n&#xff0c;返回 s 中的 最短窗口 子串&#xff0c;使得该子串包含 t 中的每一个字符&#xff08;包括重复字符&#xff09;。如果没有这样的子串&#xff0c;返回空字符串 ""。测试用例保证答案唯一。示例 1&am…

作者头像 李华
网站建设 2026/4/13 9:12:42

如何快速获取抖音评论数据:零基础实战指南

如何快速获取抖音评论数据&#xff1a;零基础实战指南 【免费下载链接】TikTokCommentScraper 项目地址: https://gitcode.com/gh_mirrors/ti/TikTokCommentScraper 你是否曾经为了分析抖音视频的用户反馈而苦恼&#xff1f;面对成百上千条评论&#xff0c;手动整理不仅…

作者头像 李华
网站建设 2026/3/28 5:49:39

WorkshopDL终极突破:解锁跨平台Steam创意工坊下载新纪元

WorkshopDL终极突破&#xff1a;解锁跨平台Steam创意工坊下载新纪元 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 在游戏模组的世界里&#xff0c;无数精彩内容被平台界限所分…

作者头像 李华