当前位置: 首页 > news >正文

#Linux内存管理#mmap函数创立私有匿名映射的工作原理

匿名私有内存映射代码案例详解(mmap)

以下是一个详细展示如何使用 mmap 创建私有匿名映射的代码案例,该技术常用于创建进程私有的内存区域(类似动态内存分配但更底层):

 

工作原理

 

1.匿名映射

 

 无需关联实际文件(区别于文件映射)

 

2.私有性(COW)

 

 MAP_PRIVATE 标志确保:

 

 修改会创建内存页的私有副本(Copy-on-Write)

 

 原始映射始终保持不变

 

初始化

 

 新分配内存默认初始化为全0(类似 calloc)

 

 

#include <stdio.h>

#include <stdlib.h>

#include <sys/mman.h>

#include <unistd.h>

#include <string.h>

#include <sys/wait.h>

 

// 内存页大小(通常4096字节)

#define PAGE_SIZE sysconf(_SC_PAGESIZE)

// 映射区域大小 (2页)

#define MEM_SIZE (2 * PAGE_SIZE)

 

int main() {

    /* ====== 步骤1: 创建私有匿名映射 ====== */

    void *mem = mmap(NULL, // 由内核选择地址

                     MEM_SIZE, // 映射大小

                     PROT_READ | PROT_WRITE, // 可读写

                     MAP_PRIVATE | MAP_ANONYMOUS, // 关键标志

                     -1, // 匿名映射忽略文件描述符

                     0); // 无文件偏移

 

    if (mem == MAP_FAILED) {

        perror("mmap 失败");

        exit(EXIT_FAILURE);

    }

 

    printf("映射地址: %p\n", mem);

    printf("页大小: %ld 字节\n", PAGE_SIZE);

 

    /* ====== 步骤2: 操作内存 (主进程) ====== */

    int *numbers = (int *)mem;

    // 初始化前10个元素 (跨越内存页边界)

    for (int i = 0; i < 10; i++) {

        numbers[i] = i * 10;

    }

 

    /* ====== 步骤3: 验证COW机制 (创建子进程) ====== */

    pid_t pid = fork();

    if (pid == -1) {

        perror("fork 失败");

        exit(EXIT_FAILURE);

    }

 

    if (pid == 0) { // 子进程

        printf("\n--- 子进程 ---\n");

        printf("子进程内存地址: %p (与父进程相同)\n", mem);

 

        // 修改数据 (触发Copy-on-Write)

        numbers[0] = 999;

        numbers[5] = 555; // 跨页修改

 

        printf("子进程修改后:\n");

        for (int i = 0; i < 10; i++) {

            printf("numbers[%d] = %d\n", i, numbers[i]);

        }

 

        // 子进程独立副本内存地址

        unsigned long page_addr = (unsigned long)mem & ~(PAGE_SIZE-1);

        printf("\n使用pmap查看内存页状态 (PID=%d):\n", getpid());

        printf("命令: pmap -X %d | grep %lx\n", getpid(), page_addr);

 

        exit(EXIT_SUCCESS);

    } 

    else { // 父进程

        wait(NULL); // 等待子进程结束

 

        printf("\n--- 父进程 ---\n");

        printf("父进程数据未改变:\n");

        for (int i = 0; i < 10; i++) {

            printf("numbers[%d] = %d\n", i, numbers[i]);

        }

 

        /* ====== 步骤4: 解除映射 ====== */

        if (munmap(mem, MEM_SIZE) == -1) {

            perror("munmap 失败");

        }

        printf("\n映射已释放\n");

    }

 

    return 0;

}

 

关键机制详解

 

1. 匿名映射创建

 

mmap(NULL, MEM_SIZE, PROT_READ|PROT_WRITE, 

     MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

  

 MAP_ANONYMOUS:创建不关联文件的内存

 

 MAP_PRIVATE:保证修改不会影响其他进程

 

    内存初始化为全0(如未初始化变量读取为0)

 

2. Copy-on-Write 机制演示

 

  父进程初始化数据:

  numbers[0] = 0

  numbers[5] = 50

 

  子进程修改后:

  numbers[0] = 999 // 触发COW

  numbers[5] = 555 // 另一页也触发COW

 

  父进程数据保持:

  numbers[0] = 0

  numbers[5] = 50

  

3. 内存页状态检查(使用pmap)

 

 执行程序后运行(替换PID和地址):  

 

 pmap -X [PID] | grep [page_addr]

 

输出示例:

 

7f8d4f600000 8K rw--- [ anon ] // 父进程

7f8d4f600000 8K rw--- [ anon ] // 子进程(不同物理页)

 

尽管虚拟地址相同,但修改后子进程使用独立的物理内存页

 

编译运行

 

gcc mmap_anonymous.c -o mmap_demo

./mmap_demo

 

 

映射地址: 0x7f5d08cbb000

页大小: 4096 字节

 

--- 子进程 ---

子进程内存地址: 0x7f5d08cbb000 (与父进程相同)

子进程修改后:

numbers[0] = 999

numbers[1] = 10

numbers[2] = 20

numbers[3] = 30

numbers[4] = 40

numbers[5] = 555

...

 

使用pmap查看内存页状态 (PID=12345):

命令: pmap -X 12345 | grep 7f5d08cbb000

 

--- 父进程 ---

父进程数据未改变:

numbers[0] = 0

numbers[1] = 10

numbers[2] = 20

...

 

映射已释放

 

应用场景

 

1.安全内存分配

 

替代malloc分配敏感数据(密钥等),进程结束自动清零释放

 

2.大内存分配优化

 

减少malloc频繁调用的开销(glibc malloc内部使用mmap分配大内存块)

 

3.进程间通信基础

 

共享内存需配合MAP_SHARED(此案例为私有)

 

重要提示:实际应用中,匿名映射常用于需要显式控制内存生命周期或性能关键场景,常规内存分配建议优先使用标准库函数(malloc/calloc)

 

主要作用:

 

1. 私有性(Copy-on-Write)

- 作用:映射的内存区域对每个进程是私有的。当进程尝试修改映射区域内的内存页时,内核会为该进程创建一个新的物理页副本(Copy-on-Write机制),修改不会影响其他进程(即使其他进程映射了相同的物理内存)。

- 应用场景:适用于进程需要独立修改数据,同时初始数据相同的场景。例如,在fork子进程后,父子进程需要独立的内存空间。

2. 匿名性

- 作用:映射的内存区域不与任何文件关联,完全由内核在物理内存中分配。这意味着:

- 内存内容初始化为全零(自动清零)。

- 无需打开文件或设备。

- 应用场景:需要分配大块内存作为数据缓冲区、堆空间或进程内部数据结构。

3. 替代 malloc 的底层机制

   - 作用:glibc 中的 malloc 在分配大块内存(通常大于128KB)时会自动使用 mmap(MAP_ANONYMOUS | MAP_PRIVATE)来分配。相比通过brk/sbrk扩展堆内存:

- 避免内存碎片(映射的内存块独立,可单独释放)

- 减少锁争用(每个mmap分配独立,多线程下竞争小)

- 应用场景:自定义内存分配器、高性能内存池。

4. 安全性

   - 作用:私有匿名映射的内存不会被其他进程访问(因为没有关联文件描述符和MAP_SHARED),适合存储敏感数据(如密钥)。

- 应用场景:安全敏感的数据存储,例如加密密钥管理。

5. 进程间通信的基础组件

   - 作用:虽然私有匿名映射本身不直接共享,但结合fork使用,可以在父子进程间共享初始数据(在写操作前实际使用同一物理内存,写操作后分离)。但真正的共享需使用MAP_SHARED。

- 应用场景:fork后父子进程需要高效传递初始化数据(如大型配置数据)。

6. 高效的大内存管理

   - 作用:

- 避免用户态与内核态之间的数据复制(相比read/write)。

- 允许直接访问物理内存(通过页表映射),操作如同普通数组。

- 应用场景:大规模数据处理(如图像处理、科学计算)。

7. 内存回收

   - 作用:当进程结束(或被munmap)时,映射的内存会被自动释放归还给操作系统,不会造成内存泄漏。

- 应用场景:需要临时使用大块内存的场景,如临时数据缓冲。

http://www.lryc.cn/news/595791.html

相关文章:

  • 在 Ubuntu 22.04 上安装并优化 Nginx nginx入门操作 稍难,需要有一定理论 多理解 多实践
  • Debug 与 Release 版本构建详解
  • 嵌入式学习-土堆目标检测(2)-day26
  • 【AI时代速通QT】第五节:Qt Creator如何引入第三方库,以OpenCV为例
  • [2025CVPR]ViKIENet:通过虚拟密钥实例增强网络实现高效的 3D 对象检测
  • STM32-SPI全双工同步通信
  • Kotlin 作用域函数 let 的实现原理
  • 替代Oracle?金仓数据库用「敢替力」重新定义国产数据库
  • 【Android】xml和Java两种方式实现发送邮件页面
  • Windows PE文件内未用空间学习
  • 17-VRRP
  • 基于 Vue,SPringBoot开发的新能源充电桩的系统
  • ospf技术
  • 【机器学习】第四章 回归算法
  • 高层功能架构详解 - openExo
  • Flutter基础(前端教程①⑧-Text文本-Icon图标-Image图片)
  • C语言符号可见性控制与工程实践——深入理解 __attribute__((visibility)) 和 -fvisibility=hidden
  • 跨服务调用中,直接使用 MDC的上下文无法自动传递
  • Oracle 12c 创建数据库初级教程
  • 从FDTD仿真到光学神经网络:机器学习在光子器件设计中的前沿应用工坊
  • 从RAG到Agentic RAG
  • 无人机吊舱与遥控器匹配技术解析
  • 一文读懂深度模型优化器,掌握炼丹工具
  • MySQL 学习二 MVCC
  • IBGP互联(ensp)
  • 【nginx】隐藏服务器指纹:Nginx隐藏版本号配置修改与重启全攻略
  • Unity中,Panel和 Canvas的区别
  • 数字签名(Digital Signature)
  • VR技术在元宇宙游戏中的作用及发展前景深度分析
  • A316-V71-Game-V1:虚拟7.1游戏声卡评估板技术解析