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

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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nat easy IP ACL

0表示匹配&#xff0c;1表示任意&#xff08;主机位0.0.0.255&#xff08;255主机位&#xff09;&#xff09; rule deny source 192.168.2.1 0 设置拒绝192.168.2.1的主机通过 记住将其应用到接口上 [AR2]acl 2000 //创建基本ACL [AR2-acl-basic-2000]rule deny source 192…

网络安全(二)-- Linux 基本安全防护技术

4.1. 概述 安全防护基础主要是会用Linux系统&#xff0c; 熟悉Linux基本操作命令。 在这个章节中&#xff0c;我们主要探讨自主访问控制&#xff08;许可位、ACL&#xff09;、文件属性、 PAM技术、能力机制等。 4.1.1. 补充命令 本章节中&#xff0c;涉及一些新的命令&#…

NPS内网穿透教程

1.简介 nps是一款轻量级、高性能、功能强大的内网穿透代理服务器。目前支持tcp、udp流量转发&#xff0c;可支持任何tcp、udp上层协议&#xff08;访问内网网站、本地支付接口调试、ssh访问、远程桌面&#xff0c;内网dns解析等等……&#xff09;&#xff0c;此外还支持内网ht…

python socket编程6 - 使用PyQt6 开发UI界面实现TCP server和TCP client单机通讯的例子

使用PyQt6 开发UI界面实现TCP server和TCP client单机通讯的示例。 一、PyQt6 实现的界面 二、TCP server代码的修改示意 界面提供网络参数的配置&#xff0c;以及提供人机交互过程中的数据获取和显示。 1、把上面的server代码封装成两个部分 A、class Server 负责接受UI界面…

JS浮点数精度问题及解决方案

前端面试大全JS浮点数精度问题及解决方案 &#x1f31f;经典真题 &#x1f31f;浮点数精度常见问题 &#x1f31f;为什么会有这样的问题 &#x1f31f;真题解答 &#x1f31f;总结 &#x1f31f;经典真题 为什么 console.log(0.20.10.3) 得到的值为 false &#x1f31f;…

Spring Security 6.x 系列(9)—— 基于过滤器链的源码分析(二)

一、前言 在本系列文章&#xff1a; Spring Security 6.x 系列&#xff08;4&#xff09;—— 基于过滤器链的源码分析&#xff08;一&#xff09;中着重分析了Spring Security在Spring Boot 的自动配置、 DefaultSecurityFilterChain 的构造流程、FilterChainProxy 的构造流…

12.4 C++ 作业

完成沙发床的多继承 #include <iostream>using namespace std;//封装 沙发 类 class Sofa { private:string *sitting; public://无参构造函数Sofa(){cout << "Sofa::无参构造函数" << endl;}//有参构造函数Sofa(string s):sitting(new string(s)…

手机升级到iOS15.8后无法在xcode(14.2)上真机调试

之前手机是iOS14.2的系统,在xcode上进行真机测试运行良好&#xff0c;因为想要使用Xcode的Instruments功能&#xff0c;今天将系统更新到了iOS15.8 &#xff0c;结果崩了 说是Xcode和手机系统不兼容不能进行真机测试。在网上查不好些方法&#xff0c;靠谱的就是下载相关版本的…

C语言小游戏:三子棋

目录 &#x1f30d;前言 &#x1f685;目录设计 &#x1f48e;游戏逻辑设置 ⚔三子棋棋盘设计 ⚔三子棋运行逻辑 &#x1f440;怎么设置人下棋 &#x1f440;怎么设置电脑下棋 ✈如何判断输赢 ✍结语 &#x1f30d;前言 Hello,csdn的各位小伙伴你们好啊!这次小赵给大…

ArcGIS平滑处理栅格数据

一、实验背景 基于栅格数据的空间分析&#xff0c;常常需要根据特定的分析场景对栅格数据进行处理&#xff0c;如栅格数据的噪声处理。噪声是属性值具有突跃特征的像元位置&#xff0c;直接对带有噪声的栅格数据进行分析会对结果造成较大的影响。而降噪的主要方法之一是平滑&a…

12.4_黑马MybatisPlus笔记(下)

目录 11 12 thinking&#xff1a;关于Mybatis Plus中BaseMapper和IService&#xff1f; 13 ​编辑 thinking&#xff1a;CollUtil.isNotEmpty? 14 thinking&#xff1a;Collection、Collections、Collector、Collectors&#xff1f; thinking&#xff1a;groupBy&#…

风格迁移网络修改流程(自用版)

一. AdaAttN-Revisit Attention Mechanism in Arbitrary Neural Style Transfer&#xff08;ICCV2021&#xff09; 下载vgg_normalised.pth打开visdom python -m visdom.server在 train_adaattn.sh 中配置 content_path、style_path 和 image_encoder_path&#xff0c;分别表…

FFmpeg在Centos服务器上离线安装(包含所需依赖)并实现拉取rtsp流与推送至rtmp服务器

场景 Windows上使用FFmpeg实现rtsp视频流推流到RTMP流媒体服务器(EasyCVR流媒体服务器)&#xff1a; Windows上使用FFmpeg实现rtsp视频流推流到RTMP流媒体服务器(EasyCVR流媒体服务器)_rtsp 转流-CSDN博客 上面讲了在windows上ffmpeg的应用示例&#xff0c;如果是在centos服…

Hadoop进阶学习---HDFS分布式文件存储系统

1.hdfs分布式文件存储的特点 分布式存储:一次写入,多次读取 HDFS文件系统可存储超大文件,时效性较差. HDFS基友硬件故障检测和自动快速恢复功能. HDFS为数据存储提供很强的扩展能力. HDFS存储一般为一次写入,多次读取,只支持追加写入,不支持随机修改. HDFS可以在普通廉价的机器…

canvas绘制小丑

说明&#xff1a; 借鉴博主基于canvas绘制一个爱心(10行代码就够了) - 掘金 (juejin.cn) 代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content&quo…

震坤行2023安全月活动顺利收官

震坤行2023安全月活动顺利收官 2023年6月&#xff0c;是第22个全国“安全生产月”&#xff0c;主题为 “人人讲安全、个个会应急”。震坤行工业超市(上海)有限公司基于国家 “安全生产月”的主题要求&#xff0c;以及公司具体的安全形势&#xff0c;于6月1日在全公司范围内正式…

EM32DX-E4【C#】

1外观&#xff1a; ecat总线&#xff0c;分布式io 2电源&#xff1a; 靠近SW拨码&#xff1a;24V 中间&#xff1a;0V 靠近面板&#xff1a;PE接地 3DI&#xff1a; 6000H DI输入寄存器 16-bit &#xff08;16位输入&#xff09; 00H U8 子索引总数 01H Unsigned16 IN1…

TCP 半连接队列和全连接队列

在 TCP 三次握手的时候&#xff0c;Linux 内核会维护两个队列&#xff0c;分别是&#xff1a; 半连接队列&#xff0c;也称 SYN 队列&#xff1b; 全连接队列&#xff0c;也称 accept 队列&#xff1b; 服务端收到客户端发起的 SYN 请求后&#xff0c;内核会把该连接存储到半连…

数据库系统概论期末经典大题讲解(用关系代数进行查询)

今天也是结束的最为密集的考试周&#xff0c;在分析过程中自己也有些许解题的感悟&#xff0c;在此分享出来&#xff0c;希望能帮到大家期末取得好成绩。 一.专门的关系运算 1.选择&#xff08;σ&#xff09; 选择操作符用于从关系中选择满足特定条件的元组 例如&#xff0c;…

计算机组成原理学习-总线总结

复习本章时&#xff0c;思考以下问题&#xff1a; 1)引入总线结构有什么好处&#xff1f;2)引入总线结构会导致什么问题&#xff1f;如何解决&#xff1f;