Linux进程通信——内存映射mmap

news2024/11/20 16:29:12

Linux进程通信——内存映射mmap

  • 1、创建内存映射区
  • 2、进程间通信
    • 2.1 有血缘关系
    • 2.2 没有血缘关系
  • 3、拷贝文件

原文链接

1、创建内存映射区

如果想要实现进程间通信,可以通过函数创建一块内存映射区,和管道不同的是管道对应的内存空间在内核中,而内存映射区对应的内存空间在进程的用户区(用于加载动态库的那个区域),也就是说进程间通信使用的内存映射区不是一块,而是在每个进程内部都有一块。

由于每个进程的地址空间是独立的,各个进程之间也不能直接访问对方的内存映射区,需要通信的进程需要将各自的内存映射区和同一个磁盘文件进行映射,这样进程之间就可以通过磁盘文件这个唯一的桥梁完成数据的交互了。

在这里插入图片描述

如上图所示:磁盘文件数据可以完全加载到进程的内存映射区也可以部分加载到进程的内存映射区,当进程A中的内存映射区数据被修改了,数据会被自动同步到磁盘文件,同时和磁盘文件建立映射关系的其他进程内存映射区中的数据也会和磁盘文件进行数据的实时同步,这个同步机制保障了各个进程之间的数据共享。

使用内存映射区既可以进程有血缘关系的进程间通信也可以进程没有血缘关系的进程间通信。创建内存映射区的函数原型如下:

#include <sys/mman.h>
// 创建内存映射区
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • 参数:

    • addr: 从动态库加载区的什么位置开始创建内存映射区,一般指定为NULL, 委托内核分配

      • length: 创建的内存映射区的大小(单位:字节),实际上这个大小是按照4k的整数倍去分配的

      • prot: 对内存映射区的操作权限

        • PROT_READ: 读内存映射区

        • PROT_WRITE: 写内存映射区

        • 如果要对映射区有读写权限: PROT_READ | PROT_WRITE

      • flags:

        • MAP_SHARED: 多个进程可以共享数据,进行映射区数据同步
        • MAP_PRIVATE: 映射区数据是私有的,不能同步给其他进程
    • fd: 文件描述符, 对应一个打开的磁盘文件,内存映射区通过这个文件描述符和磁盘文件建立关联

    • offset: 磁盘文件的偏移量,文件从偏移到的位置开始进行数据映射,使用这个参数需要注意两个问题:

      • 偏移量必须是4k的整数倍, 写0代表不偏移
      • 这个参数必须是大于 0 的
  • 返回值:

    • 成功: 返回一个内存映射区的起始地址

    • 失败: MAP_FAILED (that is, (void *) -1)

  1. 第一个参数 addr 指定为 NULL 即可
  2. 第二个参数 length 必须要 > 0
  3. 第三个参数 prot,进程间通信需要对内存映射区有读写权限,因此需要指定为:PROT_READ | PROT_WRITE
  4. 第四个参数 flags,如果要进行进程间通信, 需要指定 MAP_SHARED
  5. 第五个参数 fd,打开的文件必须大于0,进程间通信需要文件操作权限和映射区操作权限相同
    - 内存映射区创建成功之后, 关闭这个文件描述符不会影响进程间通信
  6. 第六个参数 offset,不偏移指定为0,如果偏移必须是4k的整数倍

内存映射区使用完之后也需要释放,释放函数原型如下:

int munmap(void *addr, size_t length);
  • 参数:
    • addr: mmap()的返回值, 创建的内存映射区的起始地址
    • length: 和mmap()第二个参数相同即可
  • 返回值:函数调用成功返回 0,失败返回 -1

2、进程间通信

操作内存映射区和操作管道是不一样的,得到内存映射区之后是直接对内存地址进行操作,管道是通过文件描述符读写队列中的数据,管道的读写是阻塞的内存映射区的读写是非阻塞的。内存映射区创建成功之后,得到了映射区内存的起始地址,使用相关的内存操作函数读写数据就可以了。

2.1 有血缘关系

由于创建子进程会发生虚拟地址空间的复制,那么在父进程中创建的内存映射区也会被复制到子进程中,这样在子进程里边就可以直接使用这块内存映射区了,所以对于有血缘关系的进程,进行进程间通信是非常简单的,处理代码如下:

/*
    1. 先创建内存映射区, 得到一个起始地址, 假设使用ptr指针保存这个地址
    2. 通过fork() 创建子进程 -> 子进程中也就有一个内存映射区, 子进程中也有一个ptr指针指向这个地址
    3. 父进程往自己的内存映射区写数据, 数据同步到了磁盘文件中, 磁盘文件数据又同步到子进程的映射区中
       子进程从自己的映射区往外读数据, 这个数据就是父进程写的
*/
#include <sys/mman.h>
#include <fcntl.h>

int main()
{
    // 1. 打开一个磁盘文件
    int fd = open("./english.txt", O_RDWR);
    // 2. 创建内存映射区
    void* ptr = mmap(NULL, 4000, PROT_READ|PROT_WRITE,
                     MAP_SHARED, fd, 0);
    if(ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(0);
    }

    // 3. 创建子进程
    pid_t pid = fork();
    if(pid > 0)
    {
        // 父进程, 写数据
        const char* pt = "我是你爹, 你是我儿子吗???";
        memcpy(ptr, pt, strlen(pt)+1);
    }
    else if(pid == 0)
    {
        // 子进程, 读数据
        usleep(1);	// 内存映射区不阻塞, 为了让子进程读出数据
        printf("从映射区读出的数据: %s\n", (char*)ptr);
    }

    // 释放内存映射区
    munmap(ptr, 4000);

    return 0;
}

2.2 没有血缘关系

对于没有血缘关系的进程间通信,需要在每个进程中分别创建内存映射区,但是这些进程的内存映射区必须要关联相同的磁盘文件,这样才能实现进程间的数据同步。

进程A的测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>

int main()
{
    // 1. 打开一个磁盘文件
    int fd = open("./english.txt", O_RDWR);
    // 2. 创建内存映射区
    void* ptr = mmap(NULL, 4000, PROT_READ|PROT_WRITE,
                     MAP_SHARED, fd, 0);
    if(ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(0);
    }
    
    const char* pt = "==================我是你爹, 你是我儿子吗???****************";
    memcpy(ptr, pt, strlen(pt)+1);

    // 释放内存映射区
    munmap(ptr, 4000);

    return 0;
}

进程B的测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>

int main()
{
    // 1. 打开一个磁盘文件
    int fd = open("./english.txt", O_RDWR);
    // 2. 创建内存映射区
    void* ptr = mmap(NULL, 4000, PROT_READ|PROT_WRITE,
                     MAP_SHARED, fd, 0);
    if(ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(0);
    }

    // 读内存映射区
    printf("从映射区读出的数据: %s\n", (char*)ptr);

    // 释放内存映射区
    munmap(ptr, 4000);

    return 0;
}

3、拷贝文件

使用内存映射区除了可以实现进程间通信,也可以进行文件的拷贝,使用这种方式拷贝文件可以减少程序猿的工作量,我们只需要负责创建内存映射区和打开磁盘文件,关于文件中的数据读写就无需关心了。

使用内存映射区拷贝文件思路:

1 打开被拷贝文件,得到文件描述符 fd1,并计算出这个文件的大小 size
2 创建内存映射区A并且和被拷贝文件关联,也就是和fd1关联起来,得到映射区地址 ptrA
3 创建新文件,得到文件描述符 fd2,用于存储被拷贝的数据,并且将这个文件大小拓展为 size
4 创建内存映射区B并且和新创建的文件关联,也就是和fd2关联起来,得到映射区地址 ptrB
5 进程地址空间之间的数据拷贝,memcpy(ptrB, ptrA,size),数据自动同步到新建文件中
6 关闭内存映射区

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>

int main()
{
    // 1. 打开一个操盘文件english.txt得到文件描述符
    int fd = open("./english.txt", O_RDWR);
    // 计算文件大小
    int size = lseek(fd, 0, SEEK_END);

    // 2. 创建内存映射区和english.txt进行关联, 得到映射区起始地址
    void* ptrA = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if(ptrA == MAP_FAILED)
    {
        perror("mmap");
        exit(0);
    }

    // 3. 创建一个新文件, 存储拷贝的数据
    int fd1 = open("./copy.txt", O_RDWR|O_CREAT, 0664);
    // 拓展这个新文件
    ftruncate(fd1, size);

    // 4. 创建一个映射区和新文件进行关联, 得到映射区的起始地址second
    void* ptrB = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, 0);
    if(ptrB == MAP_FAILED)
    {
        perror("mmap----");
        exit(0);
    }
    // 5. 使用memcpy拷贝映射区数据
    // 这两个指针指向两块内存, 都是内存映射区
    // 指针指向有效的内存, 拷贝的是内存中的数据
    memcpy(ptrB, ptrA, size);

    // 6. 释放内存映射区
    munmap(ptrA, size);
    munmap(ptrB, size);
    close(fd);
    close(fd1);

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1282620.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

知识点滴 - 什么是半透膜和渗透压

半透膜和渗透作用 1748年的一天&#xff0c;法国物理学家诺勒为了改进酒的制作水平&#xff0c;设计了这样一个试验&#xff1a;在一个玻璃圆筒中装满酒精&#xff0c;用猪膀胱封住&#xff0c;然后把圆筒全部浸在水中。当他正要做下一步的工作时&#xff0c;突然发现&#xff…

MySQL 的 NULL 是怎么存储的?

目录 一、MySQL介绍 二、什么是NULL 三、MySQL 的 NULL 是怎么存储的 一、MySQL介绍 MySQL是一种关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它是一种开源软件&#xff0c;由瑞典MySQL AB公司开发&#xff0c;后被Sun Microsystems收购&#xff0c;现在…

【电机控制】PMSM无感foc控制(六)相电流检测及重构 — 双电阻采样、三电阻采样

0. 前言 目前&#xff0c;永磁同步电机的电流信号采样方法应用较多的是分流电阻采样&#xff0c;包括单电阻、双电阻以及三电阻采样法。其中&#xff0c;单电阻采样上一章节已经讲解&#xff0c;这章讲双电阻以及三电阻电流采样法。 1. 双电阻采样 1.1 双电阻采样原理 双电阻采…

CRM管理系统:让你的业务运行更流畅

是客户关系管理系统的缩写。现代企业利用网络技术协调企业和客户之间在销售和服务上的对接&#xff0c;提升企业核心竞争力的一种手段。那么CRM管理系统一般包含哪些功能模块呢&#xff1f;今天小编为大家介绍一下。 一、好的CRM管理系统有哪些功能特性 1、有全方位的客户视图…

【开源】基于Vue.js的河南软件客服系统

文末获取源码&#xff0c;项目编号&#xff1a; S 067 。 \color{red}{文末获取源码&#xff0c;项目编号&#xff1a;S067。} 文末获取源码&#xff0c;项目编号&#xff1a;S067。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统管理人员2.2 业务操作人员 三、…

Elasticsearch高级

文章目录 一.数据聚合二.RestAPI实现聚合三.ES自动补全(联想)四.数据同步五.elasticsearch集群 一.数据聚合 在ES中的数据聚合&#xff08;aggregations&#xff09;可以近似看做成mysql中的groupby分组,聚合可以实现对文档数据的统计、分析、运算,常见的聚合的分类有以下几种…

ArcGIS提示当前许可不支持影像服务器

1、问题&#xff1a; 在用ArcGIS上处理影像栅格数据时&#xff08;比如栅格数据集裁剪、镶嵌数据集构建镶嵌线等&#xff09;经常会出现。 无法启动配置 RasterComander.ImageServer <详信息 在计算机XXXXX上创建服务器对象实例失败 当前许可不支持影像服务器。 ArcGIS提示当…

计算机组成学习-指令系统总结

复习本章时&#xff0c;思考以下问题&#xff1a; 1)什么是指令&#xff1f;什么是指令系统&#xff1f;为什么要引入指令系统&#xff1f;2)一般来说&#xff0c;指令分为哪些部分&#xff1f;每部分有什么用处&#xff1f;3)对于一个指令系统来说&#xff0c;寻址方式多和少…

CDA level-2 备考经验分享 转数据分析师CDA证书备考 考试相关说明

抓住了23年的尾巴&#xff0c;正好给我考过了CDA level-2 &#xff0c;虽然今年只有这几个小收获&#xff0c;但是还是很开心了&#xff0c;毕竟知足常乐嘛。 由于工作原因&#xff0c;因此复习都是间断性的&#xff0c;勉勉强强给通过了&#xff0c;只得了个C。 考试注册报名与…

HT78621 3.5A开关限流降压变换器基本参数信息

HT78621是一款高压降压开关稳压器&#xff0c;可向负载提供高达2A的连续电流。 HT78621 特性&#xff1a; ・宽输入电压: 5V – 60V ・峰值开关电流限值典型3.5A ・Z高1MHz开关频率 ・支持PWM调光控制输入&#xff0c;应用于LED ・集成G端MOSFET的短路保护 ・200μA静态电…

【眼界 | 每日技术】日常生活中的那些技术,增长眼界系列(一)

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

备忘录怎么传到电脑?备忘录手机电脑互传方法

对于那些记性不好的人来说&#xff0c;手机上的备忘录简直是个不可或缺的好帮手。可是有时候&#xff0c;我们在手机上记录的内容需要在电脑上查看&#xff0c;这时候该怎么办呢&#xff1f; 曾经&#xff0c;我也为备忘录的手机电脑互传问题头疼不已。手机上记录的事项&#…

智能锁-SI522TORC522方案资料

南京中科微这款SI522目前完全PinTOPin兼容的NXP&#xff1a;RC522、CV520 复旦微&#xff1a;FM17520、FM17522/FM17550 瑞盟&#xff1a;MS520、MS522 国民技术:NZ3801、NZ3802 SI522 是应用于13.56MHz 非接触式通信中高集成度读写卡系列芯片中的一员。是NXP 公司针对&quo…

涵盖多种功能,龙讯旷腾Module第三期:光、磁、力学和极化性质

Module是什么 在PWmat的基础功能上&#xff0c;我们针对用户的使用需求开发了一些顶层模块&#xff08;Module&#xff09;。这些Module中的一部分是与已有的优秀工具的接口&#xff0c;一部分是以PWmat的计算结果为基础得到实际需要的物理量&#xff0c;一部分则是为特定的计…

OpenCV介绍及安装

目录 1.OpenCV简介 2.OpenCV安装 3.检查OpenCV是否安装成功 4.OpenCV模块 5.学习技巧 1.OpenCV简介 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个基于开源发行的跨平台计算机视觉库&#xff0c;主要用于图像处理、计算机视觉和机器学习等领域…

OpenGL 和 OpenGL ES 2.0/3.X 一致性测试说明(CTS)

本文档介绍如何构建、移植和运行 OpenGL 和 OpenGL ES 2.0/3.X 一致性测试&#xff0c;以及如何验证和提交测试结果。 [TOC]目录 测试环境要求 一致性测试需要文件系统。文件系统需要支持长文件名&#xff08;即 > 8.3 名称格式&#xff09;。一致性测试中的源文件使用大…

全部免费!6个宝藏级别的AI网站!

专注AIGC领域的专业社区&#xff0c;关注百度文心一言等大语言模型&#xff08;LLM&#xff09;的发展和应用落地&#xff0c;关注LLM的基准评测和市场研究&#xff0c;欢迎关注&#xff01; 前两天给大家介绍了一款AI绘图工具fooocus&#xff0c;帮大家把AI绘画的使用门槛大幅…

MQ - KAFKA 高级篇

kafak是一个分布式流处理平台,提供消息持久化,基于发布-订阅的方式的消息中间件&#xff0c;同时通过消费端配置相同的groupId支持点对点通信。 ##适用场景&#xff1a; 构造实时流数据管道,用于系统或应用之间可靠的消息传输.数据采集及处理,例如连接到一个数据库系统,捕捉表…

12-2 Mybatis-Plus与Spring整合

user-springboot programming 实体类 ## 链接数据源 C3p0&#xff08;原始化&#xff09; 连接池的数据源 引入mysql 自动配置类DataSource会生效 需要你去配置相关的数据库参数 需要用到连接池 数据源的配置类 SpringBoot的测试类 SpringBootTest 原先是RunWith和Conf…

JAVA高级-1

常用API 第一章 API 产品说明书 第二章 Scanner类&#xff08;输入&#xff09; 功能&#xff1a;获取键盘输入 package day7_12.demo01_Scanner;import java.util.Scanner; //1、导包 /* 功能&#xff1a;获取键盘输入引用类型一般使用步骤1、导包&#xff1a;impo…