news 2026/5/3 6:55:30

Linux进程间通信之共享内存实现篇

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux进程间通信之共享内存实现篇

Linux 内核中共享内存的实现(基于 2.6.12)

核心文件与路径

  • ipc/shm.c: System V 共享内存系统调用与核心逻辑
  • include/linux/shm.h: 结构体与常量定义(shmid_kernel#ifdef __KERNEL__块内, 仅内核可见)
  • ipc/util.c: IPC 公共 ID/权限/锁管理
  • mm/: 通用内存管理接口(页分配、映射)

核心数据结构

shmid_kernel(位于include/linux/shm.h

// include/linux/shm.h (在 #ifdef __KERNEL__ 块内, 仅内核可见)structshmid_kernel/* private to the kernel */{structkern_ipc_permshm_perm;// IPC 权限/键值/锁structfile*shm_file;// 关联的 shmem 文件, 承载页缓存intid;// 内部 ID (用于 IPC 表索引)unsignedlongshm_nattch;// 当前附加次数(映射该段的进程数)unsignedlongshm_segsz;// 段大小(字节)time_tshm_atim;// 最后 attach 时间time_tshm_dtim;// 最后 detach 时间time_tshm_ctim;// 最后变更时间pid_tshm_cprid;// 创建者 PIDpid_tshm_lprid;// 最后操作 PIDstructuser_struct*mlock_user;// 锁定内存的用户结构(用于 SHM_LOCK)};

shmem_inode_info

  • 来自 tmpfs/shmem,作为shm_file的支撑,负责页缓存与换入换出(除非 SHM_LOCK 锁定)。

系统调用路径(x86_64 类推)

  • sys_shmgetipc/shm.c:sys_shmgetnewseg()创建段(分配 shmid_kernel + shmem 文件)并通过ipc_addid安装到 IPC 表
  • sys_shmatipc/shm.c:sys_shmatdo_shmat()检查权限/地址 → 通过do_mmap_pgoff()shm_file映射到进程用户空间
  • sys_shmdtipc/shm.c:sys_shmdtdo_shmdt()卸载映射,递减shm_nattch
  • sys_shmctlipc/shm.c:sys_shmctl处理IPC_RMID/IPC_SET/IPC_STAT/SHM_LOCK/SHM_UNLOCK

关键流程

创建段(shmget → newseg)

  1. 校验大小不超过shm_ctlmax,对齐页(向上按页或 SHMLBA)。
  2. 分配shmid_kernelipc_rcu_alloc),初始化shm_perm、大小、时间戳、计数。
  3. 创建支撑文件:调用shmem_file_setup()创建匿名 shmem 文件,关联到shm_file,用于后续映射与页缓存管理。
  4. 通过ipc_addid(&shm_ids, …)安装到 IPC ID 表,返回 shmid(序号+序列)。

简要实现(ipc/shm.c:sys_shmget/newseg

asmlinkagelongsys_shmget(key_tkey,size_tsize,intshmflg){structshmid_kernel*shp;interr,id;// 尺寸检查: 不得超过 shm_ctlmaxif(size>shm_ctlmax)return-EINVAL;// 对齐到页面/SHMLBA (VIPT/TLB 需求)size=ALIGN(size,SHMLBA);// 如果指定 IPC_PRIVATE 或不存在且 IPC_CREAT,则创建id=newseg(key,shmflg,size);returnid;// 返回 shmid (序号+序列)}staticintnewseg(key_tkey,intshmflg,size_tsize){structshmid_kernel*shp;intid,err;// 分配内核描述符shp=ipc_rcu_alloc(sizeof(*shp));if(!shp)return-ENOMEM;shp->shm_perm.key=key;shp->shm_perm.mode=(shmflg&S_IRWXUGO);shp->shm_perm.security=NULL;shp->shm_segsz=size;shp->shm_atim=shp->shm_dtim=0;// 初始未映射/未解绑shp->shm_ctim=get_seconds();// 创建时间shp->shm_cprid=shp->shm_lprid=current->pid;shp->shm_nattch=0;// 尚无进程映射// 创建支撑文件 (基于 shmem)shp->shm_file=shmem_file_setup("SYSV0000",size,0);if(IS_ERR(shp->shm_file)){err=PTR_ERR(shp->shm_file);ipc_rcu_putref(shp);returnerr;}// 安装到 IPC ID 表id=ipc_addid(&shm_ids,&shp->shm_perm,shmmni);if(id==-1){fput(shp->shm_file);ipc_rcu_putref(shp);return-ENOSPC;}returnshm_buildid(id,shp->shm_perm.seq);// 返回 shmid}

映射(shmat → do_shmat)

  1. 权限检查与 ID 校验 (ipc_lock/ipc_checkid/ipcperms)。
  2. 解析shmaddr/shmflg
    • SHM_RDONLY只读映射
    • SHM_RND地址向下按SHMLBA对齐
    • 若指定地址且违反对齐/冲突,返回EINVAL
  3. 增加shm_nattch,更新时间shm_atimshm_lprid
  4. 通过do_mmap_pgoff()shm_file映射到进程用户空间(用户态虚拟地址因进程不同而异,但指向同一物理页集合)。

简要实现(ipc/shm.c:sys_shmat/do_shmat

asmlinkagelongsys_shmat(intshmid,char__user*shmaddr,intshmflg){unsignedlongaddr;interr;// do_shmat 负责权限/地址检查与实际映射err=do_shmat(shmid,shmaddr,shmflg,&addr);if(err)returnerr;returnaddr;// 用户态虚拟地址}staticintdo_shmat(intshmid,char__user*shmaddr,intshmflg,unsignedlong*raddr){structshmid_kernel*shp;structfile*file;unsignedlongaddr,flags;// 获取并锁定段shp=shm_lock(shmid);if(!shp)return-EINVAL;if(ipc_checkid(&shp->shm_perm,shmid)){shm_unlock(shmid);return-EIDRM;}if(ipcperms(&shp->shm_perm,(shmflg&SHM_RDONLY)?S_IRUGO:S_IRUGO|S_IWUGO)){shm_unlock(shmid);return-EACCES;}file=shp->shm_file;get_file(file);// 引用计数+1, 防止映射期间文件被释放// 处理地址/对齐addr=(unsignedlong)shmaddr;if(shmflg&SHM_RND)addr&=~(SHMLBA-1);flags=MAP_SHARED;if(shmflg&SHM_RDONLY)flags|=PROT_READ;elseflags|=PROT_READ|PROT_WRITE;// 实际映射: do_mmap_pgoff 返回用户态虚拟地址addr=do_mmap_pgoff(file,addr,shp->shm_segsz,flags,0,0);if(IS_ERR_VALUE(addr)){fput(file);shm_unlock(shmid);returnaddr;}// 更新状态shp->shm_nattch++;// 映射计数+1shp->shm_atim=get_seconds();// 记录最后 attach 时间shp->shm_lprid=current->pid;// 记录最后操作 PIDshm_unlock(shmid);*raddr=addr;return0;}

解除映射(shmdt → do_shmdt)

  1. 查找并拆除进程中对应的 VMA 区域(按基地址与长度)。
  2. 递减shm_nattch,更新时间shm_dtimshm_lprid
  3. 如果段已被标记删除且shm_nattch==0,释放资源。

简要实现(ipc/shm.c:sys_shmdt/do_shmdt

asmlinkagelongsys_shmdt(char__user*shmaddr){returndo_shmdt((unsignedlong)shmaddr);}staticintdo_shmdt(unsignedlongaddr){structshmid_kernel*shp;intretval;// 通过 VMA 找到对应的 shm 段, 执行 unmapretval=shm_unmap(addr);// 内部执行 find_vma + do_munmapif(retval)returnretval;// shm_unmap 中会递减 shm_nattch,并在必要时触发销毁return0;}

控制(shmctl)

  • IPC_RMID: 标记段删除,若shm_nattch==0立即释放(shm_destroy),否则等待最后一个 detach 后释放。
  • IPC_SET: 更新权限和shm_perm.uid/gid/mode,更新时间戳。
  • IPC_STAT: 填充shmid_ds返回用户态。
  • SHM_LOCK/SHM_UNLOCK: 锁定/解锁页以防换出(需要 CAP_IPC_LOCK),内部通过shm_lock/shm_unlock配合 shmem。

简要实现(ipc/shm.c:sys_shmctl,聚焦 IPC_RMID)

asmlinkagelongsys_shmctl(intshmid,intcmd,structshmid_ds__user*buf){structshmid_kernel*shp;interr;down(&shm_ids.sem);// IPC 表级锁shp=shm_lock(shmid);// 对象锁 + 获取if(!shp){err=-EINVAL;gotoout_up;}if(ipc_checkid(&shp->shm_perm,shmid)){err=-EIDRM;gotoout_unlock_up;}switch(cmd){caseIPC_RMID:// 从 IPC 表移除,标记删除shm_destroy(shp);err=0;break;caseIPC_SET:// 更新权限/属主/模式err=shmctl_down(shp,cmd,buf);break;caseIPC_STAT:// 拷贝状态给用户err=shmctl_stat(shp,buf);break;caseSHM_LOCK:caseSHM_UNLOCK:err=shmctl_do_lock(shp,cmd);break;default:err=-EINVAL;break;}out_unlock_up:shm_unlock(shmid);out_up:up(&shm_ids.sem);returnerr;}

释放与回收

  • shm_destroy:从 IPC 表移除 (ipc_rmid),关闭shm_file,释放shmid_kernel
  • 如果段被 IPC_RMID 标记,最后一个shmdt完成后触发真正释放。

删除与回收(ipc/shm.c:shm_destroy

staticvoidshm_destroy(structshmid_kernel*shp){// 从 IPC ID 表移除并标记删除ipc_rmid(&shm_ids,&shp->shm_perm);// 关闭支撑文件,释放引用fput(shp->shm_file);// 释放描述符 (RCU)ipc_rcu_putref(shp);}

并发与锁

  • IPC 表锁:shm_ids.sem序列化添加/删除。
  • 对象锁:ipc_lock保护单个段的元数据(权限、计数、状态)。
  • RCU/引用计数:ipc_rcu_alloc/putref管理对象生命周期;shm_nattch计数配合删除判定。

相关限制(2.6.12)

  • shmmax:单段最大字节数 (/proc/sys/kernel/shmmax)
  • shmmni:系统最大段数 (/proc/sys/kernel/shmmni)
  • shmall:可用页框总数上限 (/proc/sys/kernel/shmall)
  • SHMLBA:映射对齐粒度(通常为页或更大,对齐到大页边界以兼容 VIPT/TLB 需求)

重要特性/行为

  • 段由 shmem(tmpfs)文件承载页缓存,支持按需分配与换出(除非 SHM_LOCK)。
  • shmat映射地址位于用户态虚拟地址空间,各进程地址可不同但物理页共享。
  • shmdt仅拆映射并递减计数,不一定释放物理页;IPC_RMID+shm_nattch==0才真正销毁。
  • SEM_UNDO不涉及共享内存;共享内存的同步需用户态自管(信号量/互斥锁等)。

参考函数/符号(2.6.12)

  • sys_shmget/sys_shmat/sys_shmdt/sys_shmctl
  • newseg/do_shmat/do_shmdt/shm_destroy
  • shmem_file_setup(支撑文件)/do_mmap_pgoff(映射)
  • ipc_addid/ipc_rmid/ipc_lock/ipc_checkid
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/30 23:40:32

MATLAB高质量图像导出完整指南:从新手到专家的快速进阶

MATLAB高质量图像导出完整指南:从新手到专家的快速进阶 【免费下载链接】export_fig A MATLAB toolbox for exporting publication quality figures 项目地址: https://gitcode.com/gh_mirrors/ex/export_fig 还在为MATLAB图像导出效果不佳而困扰吗&#xff…

作者头像 李华
网站建设 2026/4/30 23:40:27

Groove音乐播放器:重新定义你的数字音乐生活体验

Groove音乐播放器:重新定义你的数字音乐生活体验 【免费下载链接】Groove 项目地址: https://gitcode.com/gh_mirrors/gr/Groove 在数字音乐时代,寻找一款既能满足专业需求又具备优雅界面的播放器并非易事。Groove音乐播放器以其独特的架构设计和…

作者头像 李华
网站建设 2026/4/30 23:40:28

高级排产系统如何提升汽车零部件生产效率?

在当今高度复杂且动态变化的制造业环境中,汽车零部件企业正面临着前所未有的挑战。随着订单多样化、交期缩短以及全球供应链的不确定性加剧,传统的生产计划方式已经难以满足现代制造业的需求。过去,许多企业依赖经验判断或基于无限产能假定的…

作者头像 李华
网站建设 2026/4/30 23:40:30

PHP 8.3.0 Windows终极下载指南:快速上手指南

PHP 8.3.0 Windows终极下载指南:快速上手指南 【免费下载链接】PHP8.3.0Windows64位版本下载 PHP 8.3.0 Windows 64位版本下载 项目地址: https://gitcode.com/open-source-toolkit/2d0fb 欢迎来到PHP 8.3.0 Windows版本的完整下载指南!无论您是P…

作者头像 李华
网站建设 2026/4/30 17:08:53

基于SpringBoot框架的兼职平台的设计与实现

兼职平台的设计与实现背景随着互联网技术的快速发展和移动设备的普及,兼职市场逐渐从传统的线下中介模式转向线上平台化运营。这种转变不仅提高了信息传递的效率,还降低了求职者与雇主之间的沟通成本。然而,现有的兼职平台仍存在信息不对称、…

作者头像 李华