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

#Linux内存管理# 详细介绍使用mmap函数创立共享匿名映射的工作原理

使用 mmap 创建共享匿名映射 - 详细解析与代码示例

 

共享匿名映射的工作原理

 

共享匿名映射 (MAP_SHARED | MAP_ANONYMOUS) 允许不相关进程间建立高效的共享内存通信机制:

 

1.零文件依赖

 

 不涉及磁盘文件,纯内存通信

 

 使用 MAP_ANONYMOUS 标志,忽略文件描述符

 

2.内核分配机制

 

 内核直接分配匿名物理内存页

 

 页内容初始化为零 

 

3.共享内存原理

 

 不同进程的虚拟地址映射到相同物理内存页

 

 映射地址在不同进程中通常不同 

 

4.高速通信特性

 

 进程间通信绕过内核缓冲

 

 比管道/套接字快10-100倍

 

 适合高频、低延迟场景 

 

5.进程终止处理

 

 映射自动释放

 

 内存回收由内核管理 

 

完整代码示例(父子进程通信)

 

#include <stdio.h>

#include <stdlib.h>

#include <sys/mman.h>

#include <sys/wait.h>

#include <unistd.h>

#include <string.h>

#include <time.h>

#include <fcntl.h>

 

#define SHM_SIZE 4096 // 共享内存大小(页大小倍数)

#define DATA_SIZE 256

 

// 共享数据结构

struct shared_data {

    pid_t owner;

    int counter;

    char timestamp[DATA_SIZE];

    char message[DATA_SIZE];

};

 

void child_process(void *shm_ptr) {

    struct shared_data *data = (struct shared_data *)shm_ptr;

    

    // 子进程等待父进程初始化

    while (data->owner == 0) {

        usleep(100000); // 100ms等待

    }

    

    printf("\n子进程 [%d] 开始访问共享内存\n", getpid());

    

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

        // 读取父进程写入的数据

        printf("子进程读取: 计数器=%d | %s | 时间: %s\n", 

               data->counter, data->message, data->timestamp);

        

        // 更新计数器(可能产生竞争条件)

        data->counter++;

        

        // 写入子进程消息

        time_t now = time(NULL);

        strftime(data->timestamp, DATA_SIZE, "%Y-%m-%d %H:%M:%S", localtime(&now));

        snprintf(data->message, DATA_SIZE, "子进程 %d 写入第 %d 条", getpid(), i+1);

        

        usleep(300000); // 300ms

    }

}

 

int main() {

    // 创建共享匿名映射

    void *shm_ptr = mmap(NULL, SHM_SIZE, 

                       PROT_READ | PROT_WRITE,

                       MAP_SHARED | MAP_ANONYMOUS, // 关键参数

                       -1, 0);

    

    if (shm_ptr == MAP_FAILED) {

        perror("共享内存映射失败");

        exit(EXIT_FAILURE);

    }

    

    struct shared_data *data = (struct shared_data *)shm_ptr;

    

    // 初始化共享区域

    memset(data, 0, sizeof(struct shared_data));

    data->owner = getpid();

    data->counter = 0;

    strcpy(data->timestamp, "未初始化");

    strcpy(data->message, "等待数据传输...");

    

    printf("=============================================\n");

    printf("共享匿名映射创建成功!\n");

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

    printf("内存大小: %d 字节\n", SHM_SIZE);

    printf("父进程 PID: %d\n", getpid());

    printf("=============================================\n");

    

    // 创建子进程

    pid_t pid = fork();

    

    if (pid == 0) {

        // 子进程分支

        child_process(shm_ptr);

        exit(EXIT_SUCCESS);

    } else if (pid > 0) {

        // 父进程分支

        printf("\n父进程 [%d] 开始写入共享内存\n", getpid());

        

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

            // 更新父进程数据

            data->counter += 10;

            

            // 更新时间戳

            time_t now = time(NULL);

            strftime(data->timestamp, DATA_SIZE, "%Y-%m-%d %H:%M:%S", localtime(&now));

            

            // 更新消息

            snprintf(data->message, DATA_SIZE, "父进程 %d 写入第 %d 条", getpid(), i+1);

            

            printf("父进程写入: 计数器=%d | %s\n", data->counter, data->message);

            

            usleep(500000); // 500ms

        }

        

        // 等待子进程结束

        int status;

        wait(&status);

        

        printf("\n最终共享状态:\n");

        printf(" 最终计数器: %d\n", data->counter);

        printf(" 最终消息: %s\n", data->message);

        printf(" 最终时间: %s\n", data->timestamp);

        

        // 解除映射

        if (munmap(shm_ptr, SHM_SIZE) == -1) {

            perror("取消映射失败");

        }

        

        printf("共享内存已释放\n");

    } else {

        perror("fork 失败");

        // 解除映射

        munmap(shm_ptr, SHM_SIZE);

        exit(EXIT_FAILURE);

    }

    

    return EXIT_SUCCESS;

 

关键代码解析

 

1. 创建共享匿名映射

 

void *shm_ptr = mmap(NULL, SHM_SIZE, 

                   PROT_READ | PROT_WRITE,

                   MAP_SHARED | MAP_ANONYMOUS, 

                   -1, 0);

       

 MAP_ANONYMOUS:创建基于内存的映射(不依赖文件)

 

 MAP_SHARED:使映射可在进程间共享

 

 fd = -1:文件描述符被忽略

 

 内存自动初始化为零 

 

2. 共享数据结构设计

 

struct shared_data {

    pid_t owner; // 标识数据创建者

    int counter; // 共享计数器

    char timestamp[256]; // 时间戳

    char message[256]; // 消息内容

};

 

 定义清晰的通信协议

 

 可扩展为复杂数据结构      

 

3. 父子进程通信模式

 

// 父进程写入数据

data->counter += 10;

snprintf(data->message, ...);

 

// 子进程读取数据

printf("子进程读取: %s", data->message);

 

 内存访问完全透明,如同访问常规内存

 

 修改立即可见(无需显式同步) 

 

4. 资源管理

 

// 解除映射

munmap(shm_ptr, SHM_SIZE); 

 

 父子进程都应解映射

 

 最后一个解映射的进程会触发内存回收

 

5. 编译运行与输出示例 

 

# 编译程序

gcc anon_shared_mmap.c -o anon_shared_mmap

 

# 运行程序

./anon_shared_mmap

 

示例输出:

 

=============================================

共享匿名映射创建成功!

映射地址: 0x7fd94ed1a000

内存大小: 4096 字节

父进程 PID: 31245

=============================================

 

父进程 [31245] 开始写入共享内存

父进程写入: 计数器=10 | 父进程 31245 写入第 1 条

 

子进程 [31246] 开始访问共享内存

子进程读取: 计数器=10 | 父进程 31245 写入第 1 条 | 时间: 2023-08-05 15:45:22

父进程写入: 计数器=20 | 父进程 31245 写入第 2 条

子进程读取: 计数器=21 | 子进程 31246 写入第 1 条 | 时间: 2023-08-05 15:45:23

子进程读取: 计数器=21 | 子进程 31246 写入第 1 条 | 时间: 2023-08-05 15:45:23

父进程写入: 计数器=31 | 父进程 31245 写入第 3 条

...

父进程写入: 计数器=50 | 父进程 31245 写入第 5 条

子进程读取: 计数器=51 | 子进程 31246 写入第 4 条 | 时间: 2023-08-05 15:45:25

 

最终共享状态:

  最终计数器: 51

  最终消息: 子进程 31246 写入第 4 条

  最终时间: 2023-08-05 15:45:25

共享内存已释放

 

非父子进程通信方案

 

非相关进程可通过路径名共享匿名内存(Linux特有方法):

 

#define SHM_NAME "/my_shm"

 

// 创建进程

int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);

ftruncate(fd, SHM_SIZE);

void *ptr = mmap(NULL, SHM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

 

// 访问进程

int fd = shm_open(SHM_NAME, O_RDWR, 0);

void *ptr = mmap(NULL, SHM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

 

// 最后清理

shm_unlink(SHM_NAME);

 

优势与适用场景

 

1.极低延迟通信

 

 微秒级进程间通信

 

 高频交易系统核心

 

2.零拷贝架构

 

 避免数据复制开销

 

 大数据处理系统

 

3.实时系统

 

 工业控制系统

 

 多媒体处理管道

 

4.安全敏感场景

 

 敏感数据不落盘

 

 安全算法实现

 

注意事项

 

1.同步机制 

 

// 在共享数据中加入互斥锁

pthread_mutex_t lock;

pthread_mutexattr_t attr;

pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);

pthread_mutex_init(&lock, &attr);

 

2.内存对齐

 

// 避免错误共享(false sharing)

__attribute__((aligned(64))) int counter;

 

3.资源清理

 

// 确保所有进程解除映射

atexit(cleanup_handler);

 

4.内存可见性

 

// 使用内存屏障保证写入可见

__sync_synchronize();

 

此代码展示了 mmap 共享匿名映射的核心机制。通过结构化的共享内存设计,进程可以高效交换复杂数据结构,适用于从简单计数器到复杂数据库的各种高性能场景。

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

相关文章:

  • 力扣146:LRU缓存
  • 单片机-----基础知识整合
  • Win10_Qt6_C++_YOLO推理 -(1)MingW-opencv编译
  • Linux dd命令 数据备份、转换与磁盘操作的终极工具
  • LinkedList的模拟实现+LinkedList和ArrayList的区别
  • 低代码中的统计模型是什么?有什么作用?
  • 海外短剧系统全栈开发指南:从视频编解码到全球CDN架构实战
  • 什么是5G-A三防平板?有什么特点?哪些领域能用到?
  • SpringBoot 内嵌 Tomcat 的相关配置
  • 上网行为管理之身份认证实验
  • 纯CPU场景下C++的分布式模型训练框架设计思路
  • 18.设备虚拟化
  • LeetCode 407:接雨水 II
  • SQL 中 CASE WHEN 及 SELECT CASE WHEN 的用法
  • SparkSQL 聚合函数 MAX 对 NULL 值的处理
  • 基于多种机器学习的水质污染及安全预测分析系统的设计与实现【随机森林、XGBoost、LightGBM、SMOTE、贝叶斯优化】
  • 小白做投资测算,如何快速上手?
  • 网安-SQL注入-sqli-labs
  • OpenLayers 快速入门(七)矢量数据
  • Centos7.9多网卡绑定做链路聚合
  • 回顾 Palantir:八年之旅的反思
  • 《P4092 [HEOI2016/TJOI2016] 树》
  • 线段树学习笔记 - 练习题(1)
  • UniApp X 网络请求避坑指南:从 JS 到 UTS 的 JSON 数据处理全解析
  • Neo4j 框架 初步简单使用(基础增删改查)
  • OpenEuler系统架构下编译redis的RPM包
  • 【基于OpenCV的图像处理】图像预处理之图像色彩空间转换以及图像灰度化处理
  • 【web页面接入Apple/google/facebook三方登录】
  • SQL基础⑥ | 聚合函数
  • Java项目中定时任务三方工具和技术的深度应用指南