news 2026/3/22 2:07:50

Linux进程间通信之内存映射

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux进程间通信之内存映射

内存映射(mmap)

概述

内存映射(mmap)是一种将文件或设备映射到进程地址空间的机制。通过内存映射,进程可以像访问普通内存一样访问文件,也可以用于进程间通信。mmap提供了高效的文件I/O和进程间通信方式。

通信原理

基本概念

内存映射的特点:

  1. 文件映射:将文件映射到进程地址空间
  2. 共享映射:多个进程可以共享同一映射区域
  3. 零拷贝:直接内存访问,减少数据拷贝
  4. 虚拟内存:利用虚拟内存机制,按需加载
  5. 同步机制:可以自动同步到文件

实现机制

文件映射
  1. 创建映射

    • 使用mmap()将文件映射到进程地址空间
    • 返回映射区域的虚拟地址
    • 可以指定映射大小、权限、标志等
  2. 访问数据

    • 进程通过指针直接访问映射区域
    • 访问时触发页错误,内核按需加载文件内容
    • 修改数据时,根据标志决定是否写回文件
  3. 同步

    • msync():手动同步映射区域到文件
    • munmap():取消映射,自动同步
  4. 数据流向

    进程A ──┐ ├──> [共享映射区域] <──> [文件/设备] 进程B ──┤ 进程C ──┘
匿名映射(进程间通信)
  1. 创建匿名映射

    • 使用mmap()创建匿名映射(不关联文件)
    • 使用MAP_SHARED标志,多个进程可以共享
    • 通过fork()或显式共享实现进程间通信
  2. 共享机制

    • 父子进程通过fork()共享映射区域
    • 任意进程可以通过文件描述符共享(需要特殊处理)

API说明

mmap()

#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:映射标志(MAP_SHARED共享,MAP_PRIVATE私有,MAP_ANONYMOUS匿名等)
    • fd:文件描述符(匿名映射时为-1)
    • offset:文件偏移量(通常为0)
  • 返回值:成功返回映射地址,失败返回MAP_FAILED

munmap()

intmunmap(void*addr,size_tlength);
  • 功能:取消内存映射
  • 参数
    • addr:映射地址
    • length:映射长度
  • 返回值:成功返回0,失败返回-1

msync()

intmsync(void*addr,size_tlength,intflags);
  • 功能:同步映射区域到文件
  • 参数
    • addr:映射地址
    • length:同步长度
    • flags:同步标志(MS_SYNC同步写,MS_ASYNC异步写,MS_INVALIDATE使缓存无效)
  • 返回值:成功返回0,失败返回-1

示例代码

示例: 使用 mmap 读写文件

#include<stdio.h>#include<sys/mman.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>intmain(){constchar*path="demo.txt";constchar*msg="hello mmap\n";intfd=open(path,O_RDWR|O_CREAT,0666);if(fd<0){perror("open");return1;}// 确保文件大小 >= 要写入的长度size_tlen=strlen(msg);if(ftruncate(fd,len)==-1){perror("ftruncate");close(fd);return1;}// 建立映射: 读写, 共享写回文件void*addr=mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(addr==MAP_FAILED){perror("mmap");close(fd);return1;}// 写入数据memcpy(addr,msg,len);// 同步到文件if(msync(addr,len,MS_SYNC)==-1){perror("msync");}// 读取验证write(STDOUT_FILENO,addr,len);// 清理munmap(addr,len);close(fd);return0;}

示例: 多线程共享 mmap 区域并安全更新

#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<sys/mman.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>#defineTHREADS4#definePER_THREAD100000structshared{pthread_mutex_tlock;intcounter;};void*worker(void*arg){structshared*sh=(structshared*)arg;for(inti=0;i<PER_THREAD;i++){pthread_mutex_lock(&sh->lock);sh->counter++;pthread_mutex_unlock(&sh->lock);}returnNULL;}intmain(){constchar*path="shared.dat";intfd=open(path,O_RDWR|O_CREAT,0666);if(fd<0){perror("open");return1;}// 准备文件长度if(ftruncate(fd,sizeof(structshared))==-1){perror("ftruncate");close(fd);return1;}// 建立映射structshared*sh=mmap(NULL,sizeof(structshared),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(sh==MAP_FAILED){perror("mmap");close(fd);return1;}// 初始化锁和计数器pthread_mutexattr_tattr;pthread_mutexattr_init(&attr);pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_PRIVATE);// 同一进程线程间共享pthread_mutex_init(&sh->lock,&attr);sh->counter=0;// 创建线程pthread_tth[THREADS];for(inti=0;i<THREADS;i++){pthread_create(&th[i],NULL,worker,sh);}for(inti=0;i<THREADS;i++){pthread_join(th[i],NULL);}printf("Final counter = %d (expect %d)\n",sh->counter,THREADS*PER_THREAD);// 清理pthread_mutex_destroy(&sh->lock);munmap(sh,sizeof(structshared));close(fd);return0;}

说明:

  • 使用 MAP_SHARED 将文件映射为共享区域
  • 通过 pthread_mutex_t 保证多线程更新的原子性
  • 如果要在多进程间共享, 将PTHREAD_PROCESS_PRIVATE改为PTHREAD_PROCESS_SHARED, 并确保映射为 MAP_SHARED 且 mutex 存放于共享内存中

示例: 多进程(父子进程)共享 mmap 区域并安全更新

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/mman.h>#include<sys/stat.h>#include<sys/wait.h>#include<fcntl.h>#include<pthread.h>#include<string.h>#include<errno.h>#definePROCESSES4#definePER_PROCESS100000// 共享数据结构, 包含互斥锁和计数器structshared{pthread_mutex_tlock;// 进程间共享的互斥锁intcounter;// 共享计数器intinitialized;// 初始化标志, 确保只初始化一次};intmain(){constchar*path="shared_mmap.dat";intfd;structshared*sh;pid_tpids[PROCESSES];inti;// 创建或打开共享文件fd=open(path,O_RDWR|O_CREAT,0666);if(fd<0){perror("open");return1;}// 调整文件大小以容纳共享数据结构if(ftruncate(fd,sizeof(structshared))==-1){perror("ftruncate");close(fd);return1;}// 建立共享内存映射// MAP_SHARED 确保多个进程共享同一块物理内存sh=(structshared*)mmap(NULL,sizeof(structshared),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(sh==MAP_FAILED){perror("mmap");close(fd);return1;}// 父进程负责初始化共享数据结构if(sh->initialized==0){// 初始化进程间共享的互斥锁属性pthread_mutexattr_tattr;pthread_mutexattr_init(&attr);// 关键: 设置为进程间共享, 这样多个进程可以使用同一个互斥锁pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);// 初始化互斥锁if(pthread_mutex_init(&sh->lock,&attr)!=0){perror("pthread_mutex_init");munmap(sh,sizeof(structshared));close(fd);return1;}pthread_mutexattr_destroy(&attr);// 初始化计数器sh->counter=0;// 设置初始化标志, 防止子进程重复初始化sh->initialized=1;printf("Parent: Initialized shared memory\n");}// 创建多个子进程for(i=0;i<PROCESSES;i++){pids[i]=fork();if(pids[i]<0){perror("fork");// 清理已创建的子进程for(intj=0;j<i;j++){kill(pids[j],SIGTERM);}munmap(sh,sizeof(structshared));close(fd);return1;}elseif(pids[i]==0){// 子进程: 执行计数器递增操作printf("Child %d (PID %d): Starting increments\n",i,getpid());for(intj=0;j<PER_PROCESS;j++){// 加锁保护临界区pthread_mutex_lock(&sh->lock);sh->counter++;pthread_mutex_unlock(&sh->lock);}printf("Child %d (PID %d): Completed increments\n",i,getpid());// 子进程退出, munmap 会自动清理映射exit(0);}}// 父进程等待所有子进程完成printf("Parent: Waiting for all children to complete...\n");for(i=0;i<PROCESSES;i++){intstatus;waitpid(pids[i],&status,0);if(WIFEXITED(status)){printf("Parent: Child %d (PID %d) exited with status %d\n",i,pids[i],WEXITSTATUS(status));}}// 验证最终结果printf("Parent: Final counter = %d (expected %d)\n",sh->counter,PROCESSES*PER_PROCESS);if(sh->counter==PROCESSES*PER_PROCESS){printf("Parent: Success! Counter is correct.\n");}else{printf("Parent: Error! Counter mismatch (race condition detected).\n");}// 清理资源pthread_mutex_destroy(&sh->lock);munmap(sh,sizeof(structshared));close(fd);// 可选: 删除共享文件// unlink(path);return0;}

说明:

  • 使用MAP_SHARED标志创建共享内存映射, 多个进程可以访问同一块物理内存
  • 使用PTHREAD_PROCESS_SHARED属性的互斥锁, 确保多个进程可以正确同步
  • 父进程负责初始化共享数据结构(互斥锁和计数器)
  • 多个子进程并发更新共享计数器, 通过互斥锁保证原子性
  • 所有进程通过fork()共享父进程的映射区域, 无需额外文件操作

示例: 多个独立进程通过文件共享 mmap 区域

// writer.c: 写入进程#include<stdio.h>#include<stdlib.h>#include<sys/mman.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<pthread.h>#include<string.h>structshared{pthread_mutex_tlock;charmessage[256];intready;};intmain(){constchar*path="shared_mmap.dat";intfd=open(path,O_RDWR|O_CREAT,0666);if(fd<0){perror("open");return1;}if(ftruncate(fd,sizeof(structshared))==-1){perror("ftruncate");close(fd);return1;}structshared*sh=(structshared*)mmap(NULL,sizeof(structshared),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(sh==MAP_FAILED){perror("mmap");close(fd);return1;}// 初始化互斥锁(仅第一次)if(sh->ready==0){pthread_mutexattr_tattr;pthread_mutexattr_init(&attr);pthread_mutexattr_setpshared(&attr,PTHREAD_PROCESS_SHARED);pthread_mutex_init(&sh->lock,&attr);pthread_mutexattr_destroy(&attr);sh->ready=1;}// 写入数据pthread_mutex_lock(&sh->lock);snprintf(sh->message,sizeof(sh->message),"Hello from writer (PID %d)",getpid());printf("Writer: Wrote message\n");pthread_mutex_unlock(&sh->lock);sleep(2);// 等待读取进程读取munmap(sh,sizeof(structshared));close(fd);return0;}
// reader.c: 读取进程#include<stdio.h>#include<stdlib.h>#include<sys/mman.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<pthread.h>structshared{pthread_mutex_tlock;charmessage[256];intready;};intmain(){constchar*path="shared_mmap.dat";intfd=open(path,O_RDWR,0666);if(fd<0){perror("open");return1;}structshared*sh=(structshared*)mmap(NULL,sizeof(structshared),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(sh==MAP_FAILED){perror("mmap");close(fd);return1;}// 等待写入进程初始化while(sh->ready==0){usleep(100000);// 100ms}// 读取数据pthread_mutex_lock(&sh->lock);printf("Reader: Read message: %s\n",sh->message);pthread_mutex_unlock(&sh->lock);munmap(sh,sizeof(structshared));close(fd);return0;}

说明:

  • 两个独立的程序通过同一个文件共享 mmap 映射区域
  • 写入进程负责初始化互斥锁和数据结构
  • 读取进程等待初始化完成后读取数据
  • 两个进程可以独立编译和运行, 通过文件系统协调

性能评价

优点

  1. 高效:零拷贝,直接内存访问
  2. 虚拟内存:利用虚拟内存机制,按需加载
  3. 大文件处理:适合处理大文件,无需一次性加载
  4. 进程间通信:可以用于进程间共享数据
  5. 灵活性:可以映射文件或创建匿名映射

缺点

  1. 需要同步:多进程访问需要同步机制
  2. 复杂性:API相对复杂,需要理解虚拟内存
  3. 系统限制:受虚拟内存大小限制
  4. 文件依赖:文件映射依赖文件系统

性能特点

  • 延迟:低(直接内存访问)
  • 吞吐量:高(零拷贝,适合大文件)
  • CPU占用:低(利用虚拟内存机制)
  • 内存占用:按需加载,节省内存

适用场景

  • ✅ 大文件处理
  • ✅ 需要高效文件I/O的场景
  • ✅ 进程间共享数据
  • ✅ 需要零拷贝的场景
  • ✅ 数据库、缓存等应用
  • ❌ 小文件简单读写(普通I/O更简单)
  • ❌ 不需要共享的场景

注意事项

  1. 同步机制:多进程访问共享映射时,需要同步机制(如信号量)
  2. 页大小对齐:注意系统页大小,某些操作需要页对齐
  3. 文件大小:映射大小不能超过文件大小(除非扩展文件)
  4. 权限设置:注意prot和flags的权限设置
  5. 错误处理:注意处理MAP_FAILED返回值
  6. 资源清理:使用完毕后调用munmap()取消映射
  7. 同步时机:根据需要使用msync()同步数据到文件
  8. 匿名映射:匿名映射用于进程间通信时,需要特殊机制共享

扩展阅读

  • man 2 mmap
  • man 2 munmap
  • man 2 msync
  • man 7 shm_overview
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/19 12:51:26

k8s-应用部署和组件及常用命令(2)

、相关组件介绍&#xff1a;node是k8s的集群节点&#xff0c;和实际的机器对应pod是应用容器&#xff0c;不同的应用部署在不同的pod中&#xff0c;k8s协调资源&#xff0c;将pod部署到集群中的node节点上&#xff08;非master节点&#xff09;。同时pod又是挂在namespace下面的…

作者头像 李华
网站建设 2026/3/20 3:28:36

【后端】【Java】一文深入理解 Spring Boot RESTful 风格接口开发

深入理解 Spring Boot RESTful 风格接口开发一、什么是 RESTful&#xff1f;RESTful 是一种基于 REST&#xff08;Representational State Transfer&#xff0c;表述性状态转移&#xff09; 架构风格的 Web 接口设计规范。在 RESTful 风格中&#xff1a;一切皆资源通过 URL 表示…

作者头像 李华
网站建设 2026/3/13 23:33:19

52、Samba与分布式文件系统(DFS)全解析

Samba与分布式文件系统(DFS)全解析 1. Samba连接与文件操作 Samba是一种强大的工具,可让Linux服务器与Windows网络集成。我们可以尝试连接之前创建的共享(samba - share),以clientB主机为例,使用smbclient工具连接到serverA上的共享。 - 连接共享 :使用 smbclien…

作者头像 李华
网站建设 2026/3/20 13:29:50

Cesium快速入门19:Entity折线材质

上一节课我们把“面”的材质讲完了&#xff0c;今天换“线”——折线&#xff08;Polyline&#xff09;。 不管是道路、航线还是飞线特效&#xff0c;全靠下面几种现成材质&#xff0c;一句代码就能换皮肤。一、最朴素的红线先画两个点&#xff0c;宽度 5 像素&#xff0c;纯红…

作者头像 李华