news 2026/5/1 11:43:38

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/5/1 7:22:52

使用 Docker 快速搭建 MinIO 文件存储服务

在日常开发中,我们经常需要处理文件上传、下载、存储与访问的场景,比如图片、视频、日志文件、模型数据集等。传统的文件系统难以满足高并发、分布式部署以及云原生化的需求。而 MinIO 作为一款高性能、轻量级、兼容 Amazon S3 协议的对象存储服务&#…

作者头像 李华
网站建设 2026/5/1 8:44:54

C语言goto语句深入解析:合理使用规避陷阱,让代码更高效

掌握goto的正确使用场景,避免滥用导致的代码混乱在C语言编程中,"goto"语句是最具争议性却又无法被完全替代的特性之一。本文将全面介绍"goto"语句的定义、应用场景、常见错误及解决方法,帮助初学者正确理解并合理使用这一…

作者头像 李华
网站建设 2026/5/1 7:32:02

SpringBoot3高效数据权限控制实战

前言你是否苦恼于在 Spring Boot3 项目中,数据权限控制难以高效实现?在当今互联网软件开发领域,数据安全至关重要,而高效的数据权限控制则是保障数据安全的关键环节。对于从事互联网软件开发的人员来说,在 Spring Boot…

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

Windows安全组件终极管理指南:2025年系统性能优化完整方案

Windows安全组件终极管理指南:2025年系统性能优化完整方案 【免费下载链接】windows-defender-remover 项目地址: https://gitcode.com/gh_mirrors/win/windows-defender-remover 在Windows系统日常使用中,许多用户面临着安全组件过度占用系统资…

作者头像 李华
网站建设 2026/5/1 6:53:05

LFM2-700M:Liquid AI重新定义边缘智能终端部署标准

导语 【免费下载链接】LFM2-700M-GGUF 项目地址: https://ai.gitcode.com/hf_mirrors/LiquidAI/LFM2-700M-GGUF Liquid AI推出新一代边缘智能模型LFM2-700M,通过混合架构设计实现180ms超低延迟与800MB内存控制,为智能手表、智能家居等终端设备提…

作者头像 李华
网站建设 2026/5/1 6:25:29

Lottie-Web性能优化实战:告别内存泄漏的完整策略

Lottie-Web性能优化实战:告别内存泄漏的完整策略 【免费下载链接】lottie-web Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/ 项目地址: https://gitcode.com/gh_mirrors/lo/lottie-web 你…

作者头像 李华