文章目录 内存映射 内存分配 内存布局 mmap() 系统调用 mmap()四种映射方式 私有文件映射 (MAP_PRIVATE) 私有匿名映射 (MAP_PRIVATE | MAP_ANONYMOUS) 共享文件映射 (MAP_SHARED) 共享匿名映射 (MAP_SHARED | MAP_ANONYMOUS) 文件映射进程通信 匿名共享内存实现通信 POSIX 共享内存进程通信 POSIX共享内存API 创建 / 打开 POSIX 共享内存对象 调整共享内存大小 删除共享内存对象 使用流程 实现代码 System V 共享内存进程通信 POSIX 共享内存API 创建 / 获取共享内存 附加到进程地址空间 分离共享内存 控制操作( 含删除、查询、修改 ) 实现代码 共享内存数据竞争问题 对比
内存映射 内存分配 Linux内核为进程分配内存空间虚拟内存管理:Linux使用虚拟内存管理,每个进程都有独立的虚拟地址空间,通常分为用户空间和内核空间。用户空间供进程使用,内核空间由内核管理 内存描述符 (mm_struct):每个进程的虚拟内存信息由mm_struct结构体管理,包含内存映射、堆、栈等信息 内存映射:进程的内存映射通过vm_area_struct结构体描述,记录虚拟内存区域的起始、结束地址、权限等,常见的内存区域包括:代码段:存放可执行代码 数据段:存放全局和静态变量 堆:动态分配的内存 栈:用于函数调用和局部变量 内存分配:内存分配主要通过以下系统调用:brk和sbrk:调整堆的大小mmap:创建新的内存映射,可用于文件映射或匿名内存分配 缺页异常处理:当进程访问未映射的虚拟内存时,触发缺页异常,内核通过以下步骤处理:检查访问权限:确认访问是否合法 分配物理内存:若合法,分配物理页并更新页表 恢复执行:进程继续执行 页面回收:当物理内存不足时,内核通过页面回收机制释放内存,包括:页面置换:将不常用的页面换出到交换空间 内存压缩:压缩内存页以减少使用 内核内存分配:内核使用kmalloc、vmalloc等函数分配内存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_t length, int prot, int flags, int fd, off_t offset) ; addr:建议映射地址(通常为NULL,由内核决定) length:映射长度 prot:保护标志(PROT_READ | PROT_WRITE | PROT_EXEC) flags:映射类型和选项 fd:文件描述符(匿名映射时为-1) offset:文件偏移量 // 配套的解除映射函数 int munmap ( void * addr, size_t length) ; 用于解除 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.6mmap()四种映射方式 类型 文件映射 匿名映射 共享 (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> struct student { char name[ 32 ] ; int age; float score; } * p; int main ( ) { int fd= open ( "stu.bin" , O_RDWR| O_CREAT, 0666 ) ; // 将文件映射到内存 p= mmap ( NULL , sizeof ( struct student ) , PROT_READ| PROT_WRITE, MAP_SHARED, fd, 0 ) ; // 持续更新分数 while ( 1 ) { scanf ( "%f" , & p-> score) ; } munmap ( p, sizeof ( struct student ) ) ; return 0 ; } # include <fcntl.h> # include <sys/mman.h> # include <stdio.h> struct student { char name[ 32 ] ; int age; float score; } * p; int main ( ) { int fd= open ( "stu.bin" , O_RDWR) ; p= mmap ( NULL , sizeof ( struct student ) , PROT_READ, MAP_SHARED, fd, 0 ) ; float last_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 ( struct student ) ) ; return 0 ; } 匿名共享内存实现通信 # include <sys/mman.h> # include <string.h> int main ( ) { // 创建匿名共享映射 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 ) ; return 0 ; } POSIX 共享内存进程通信 POSIX共享内存API 创建 / 打开 POSIX 共享内存对象 int shm_open ( const char * name, int oflag, mode_t mode) ; 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 命名格式非法) 调整共享内存大小 int ftruncate ( int fd, off_t length) ; 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>:系统无足够资源满足大小扩展需求) 删除共享内存对象 int shm_unlink ( const char * 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> int main ( ) { // 1. 创建共享内存对象 int fd= 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 ) ; return 0 ; } # include <fcntl.h> # include <sys/mman.h> # include <stdio.h> int main ( ) { // 1. 打开共享内存对象 int fd= 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" ) ; // 删除共享内存对象 return 0 ; } System V 共享内存进程通信 POSIX 共享内存API 创建 / 获取共享内存 int shmget ( key_t key, size_t size, int shmflg) ; 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 ( int shmid, const void * shmaddr, int shmflg) ; 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 指示错误原因 分离共享内存 int shmdt ( const void * shmaddr) ; shmaddr: 由 shmat() 成功返回的共享内存段映射起始地址,标识要分离的内存映射区域。必须是 shmat() 返回的原始地址,不能是偏移后的地址 返回值:成功:返回 0,表示共享内存段已成功与当前进程分离 失败:返回 -1,同时设置全局变量 errno 指示错误原因 控制操作( 含删除、查询、修改 ) int shmctl ( int shmid, int cmd, struct shmid_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> int main ( ) { // 1. 生成键值 key_t key= ftok ( "shmfile" , 65 ) ; // 2. 创建共享内存 int shmid= shmget ( key, 1024 , 0666 | IPC_CREAT) ; // 3. 附加到进程 char * shm= shmat ( shmid, NULL , 0 ) ; // 4. 写入数据 strcpy ( shm, "Hello from System V" ) ; // 5. 分离 shmdt ( shm) ; return 0 ; } # include <sys/ipc.h> # include <sys/shm.h> # include <stdio.h> int main ( ) { key_t key= ftok ( "shmfile" , 65 ) ; // 获取现有共享内存 int shmid= shmget ( key, 1024 , 0666 ) ; // 附加到进程 char * shm= shmat ( shmid, NULL , 0 ) ; // 读取数据 printf ( "Received: %s\n" , shm) ; // 分离并删除 shmdt ( shm) ; shmctl ( shmid, IPC_RMID, NULL ) ; return 0 ; } 共享内存数据竞争问题 // 进程1 int * 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> // 在共享内存中定义同步结构 struct shared_data { sem_t sem; // 信号量 int value; // 共享数据 } ; // 初始化 sem_init ( & data-> sem, 1 , 1 ) ; // 进程间共享,初始值1 // 使用 sem_wait ( & data-> sem) ; // 加锁 data-> value++ ; // 临界区操作 sem_post ( & data-> sem) ; // 解锁 对比 特性 文件映射 POSIX共享内存 System V共享内存 匿名映射 持久性 文件系统持久 系统重启消失 系统重启消失 进程结束消失 进程关系 任意进程 任意进程 任意进程 相关进程 使用难度 中等 简单 中等 简单 同步需求 需要 需要 需要 需要 可移植性 高 较高 高 高 性能 依赖文件系统 高 高 最高