Linux mmap系统调用

文章目录

  • 前言
  • 一、mmap()函数简介
  • 二、代码演示
    • 2.1 mmap使用场景
    • 2.2 私有匿名映射
    • 2.3 私有文件映射
    • 2.4 共享匿名映射
    • 2.5 共享文件映射
  • 参考

前言

NAME
       mmap, munmap - map or unmap files or devices into memory

SYNOPSIS
       #include <sys/mman.h>

       void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
       int munmap(void *addr, size_t length);

mmap函数用于将文件或设备映射到内存中。
mmap函数是一种内存映射文件的方法,它可以将一个文件或设备映射到进程的地址空间中,使得进程可以像访问内存一样访问文件或设备。
在这里插入图片描述

一、mmap()函数简介

mmap()函数在调用进程的虚拟地址空间中创建一个新的映射:

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
RETURN VALUE
       On  success,  mmap() returns a pointer to the mapped area.

通过这种方式,文件内容可以通过指针直接访问addr,就像访问普通的内存数组一样,这极大地提高了文件操作的效率和直观性。

以下是关于其行为的一些关键点:
(1)参数addr指定了新映射的起始地址。如果addr为NULL,内核会选择一个(页对齐的)地址来创建映射;这是创建新映射的最便携方法。如果addr不为NULL,内核将其作为放置映射的提示;对于Linux系统,内核会选择一个靠近的页边界(但始终大于或等于/proc/sys/vm/mmap_min_addr指定的值)并尝试在那里创建映射。如果该地址已经存在其他映射,内核会选择一个新的地址,可能与提示相关或不相关。新映射的地址将作为调用的结果返回。
(2)参数length指定映射的长度,必须大于0。
(3)对于文件映射(与匿名映射相对应,参见MAP_ANONYMOUS),映射的内容使用文件描述符fd引用的文件(或其他对象)中的从偏移量offset开始的length字节进行初始化。offset必须是sysconf(_SC_PAGE_SIZE)返回的页面大小的倍数。

总结一下,mmap()函数在调用进程的虚拟地址空间中创建一个新的映射,内核根据提供的地址或提示选择一个合适的地址来确定映射的起始位置。对于文件映射,映射的内容从文件中的指定偏移量处开始进行初始化。

在mmap()调用返回后,文件描述符fd可以立即关闭而不会使映射失效。

参数prot描述了映射的期望内存保护方式(不能与文件的打开模式冲突)。它可以是PROT_NONE,或者是以下标志位的按位或:

PROT_EXEC:页面可执行。
PROT_READ:页面可读取。
PROT_WRITE:页面可写入。
PROT_NONE:页面不可访问。

参数flags确定对映射的更新是否对其他映射同一区域的进程可见,以及是否将更新传递到底层文件。这个行为是通过在flags中包含以下值中的一个来确定的:
(1)MAP_SHARED:共享映射。对映射的更新对其他映射同一区域的进程可见,并且(对于基于文件的映射而言)会传递到底层文件。(要精确控制何时将更新传递到底层文件,需要使用msync(2)函数。)

使用MAP_SHARED标志可以实现共享内存,让多个进程可以共享同一区域的映射,并且对映射的更新可以相互可见。对于基于文件的映射,更新也会被传递到底层文件。需要注意的是,要精确控制更新何时传递到底层文件,可以使用msync(2)函数。

NAME
       msync - synchronize a file with a memory map

(2)MAP_PRIVATE:用于创建私有的写时复制(copy-on-write)映射。对映射的更新对于其他映射同一文件的进程不可见,并且不会传递到底层文件。在mmap()调用后对文件进行的更改是否在映射的区域中可见是未指定的。

使用MAP_PRIVATE标志可以创建一个独立的映射副本,对该映射的写入操作会在需要时进行写时复制,即只有在修改映射的页面时才会复制相应的页面内容,以确保每个进程都有自己的私有副本。这样,对映射的更新不会影响其他进程的映射,并且不会对底层文件进行实际的修改。

(3)MAP_ANONYMOUS:用于创建一个不由任何文件支持的映射,其内容被初始化为零。fd参数会被忽略;但是,一些实现要求如果指定了MAP_ANONYMOUS(或MAP_ANON),则fd必须为-1,因此可移植的应用程序应确保这一点。offset参数应为零。只有在Linux内核2.4及更高版本上,才支持将MAP_ANONYMOUS与MAP_SHARED结合使用。

使用MAP_ANONYMOUS标志创建的映射不与任何文件相关联,其内容被初始化为零。这种映射通常用于实现匿名内存,用于共享数据或作为临时存储。由于没有与文件的关联,对映射的更改不会影响任何文件,并且不需要指定文件描述符(fd参数被忽略)。

如下图所示:
在这里插入图片描述
Memory mmaping segment 就属于内存映射区。

二、代码演示

2.1 mmap使用场景

物理内存页主要分为两种:一种是匿名页,另一种是文件页。
根据物理内存页的类型分类,内存映射自然也分为两种:一种是虚拟内存对匿名物理内存页的映射,另一种是虚拟内存对文件页的映射。

(1)匿名页(Anonymous Pages):匿名页是一种没有与之关联的文件的内存页。它们通常用于存储进程的堆栈、堆分配的内存以及共享内存等。匿名页的内容在映射时可以初始化为零或未初始化状态,不会与任何文件进行关联。

(2)文件页(File Pages):文件页是与文件关联的内存页。它们用于将文件的内容映射到进程的地址空间,允许进程通过内存访问文件的内容,而无需直接进行读取和写入操作。文件页可以用于读取文件的内容,也可以用于将修改的数据写回文件。

(1)私有匿名映射:malloc分配大内存在glibc中对应的mmap()实现,以及BSS 段,堆,栈。
(2)私有文件映射:映射动态库,文件的text、data段。
(3)共享匿名映射:用于进程间(父子进程)共享内存。
(4)共享文件映射:用于进程间(不同的进程)共享内存,通信。
(5)其他,比如大页内存。

2.2 私有匿名映射

私有匿名映射使用一下标志位:

MAP_PRIVATE | MAP_ANONYMOUS

其中fd = -1,与文件没有关联。

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

#define SIZE 4096

int main() {
    // 创建一个私有匿名映射
    void* addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap failed");
        exit(1);
    }

    // 在映射的内存中进行读写操作
    int* data = (int*)addr;
    *data = 42;

    printf("Value at mapped memory: %d\n", *data);

    // 取消映射
    if (munmap(addr, SIZE) == -1) {
        perror("munmap failed");
        exit(1);
    }

    return 0;
}
# ./a.out
Value at mapped memory: 42

私有匿名映射(mmap/brk/malloc)申请的内存是一段虚拟地址空间,当没有在这段虚拟地址空间写入的时候,没有对应的物理内存,只有在这段虚拟地址空间写入的时候,就会发生缺页异常,然后分配对应的物理地址,建立虚拟地址空间和物理地址的影映射关系。
在这里插入图片描述
从上图我们可以看到进程虚拟内存空间中的 BSS 段,堆,栈这些虚拟内存区域都是私有匿名映射区域,glibc 中的 malloc函数当申请比较大的内存时,也使用私有匿名映射区域。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>


int main() {
    size_t size = 256 * 1024; // 128K内存的大小

    getpid();
    // 使用malloc分配内存
    char* buffer = (char*)malloc(size);
    if (buffer == NULL) {
        perror("malloc");
        exit(1);
    }
    getpid();

    // 内存分配成功,可以使用buffer指针访问分配的内存
    // 这里可以进行读取、写入或处理数据的操作

    // 释放内存
    free(buffer);

    return 0;
}
# strace ./a.out
......
getpid()                                = 101479
brk(NULL)                               = 0x55a399f12000
brk(0x55a399f33000)                     = 0x55a399f33000
mmap(NULL, 266240, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fec8b5ea000
getpid()                                = 101479
munmap(0x7fec8b5ea000, 266240)          = 0
mmap(NULL, 266240, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fec8b5ea000

2.3 私有文件映射

私有文件映射标志位:

MAP_PRIVATE

其中fd与文件有关联。

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

#define FILE_PATH "example.txt"
#define SIZE 4096

int main() {
    // 打开文件
    int fd = open(FILE_PATH, O_RDWR);
    if (fd == -1) {
        perror("open failed");
        exit(1);
    }

    // 获取文件大小
    struct stat st;
    if (fstat(fd, &st) == -1) {
        perror("fstat failed");
        exit(1);
    }
    off_t file_size = st.st_size;

    // 创建私有文件映射
    void* addr = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap failed");
        exit(1);
    }

    // 在映射的内存中进行读取操作
    char* data = (char*)addr;
    printf("Content of the file:%s\n", data);

    // 在映射的内存中进行写入操作
    sprintf(data, "Hello, World!");

    printf("Content of the file:%s\n", data);

    // 取消映射
    if (munmap(addr, file_size) == -1) {
        perror("munmap failed");
        exit(1);
    }

    // 关闭文件
    if (close(fd) == -1) {
        perror("close failed");
        exit(1);
    }

    return 0;
}

example.txt 文件的内容是 111。
读取其内容,然后写入:

# cat example.txt
111
# ./a.out
Content of the file:111

Content of the file:Hello, World!
# cat example.txt
111

可以看到对私有文件映射区域的修改不会修改实际的文件。

私有文件映射允许多个进程将文件的内容映射到各自的虚拟内存空间中,但对映射的修改只反映到各自的文件页上,而不会影响其他进程的文件页。这种方式可以用于加载二进制可执行文件的代码段和数据段到进程的虚拟内存空间中以及加载动态库。
在这里插入图片描述
从上图我们可以看到进程虚拟内存空间中的 text 段,data 段和.so动态库这些虚拟内存区域都是私有文件映射区域。

2.4 共享匿名映射

私有匿名映射使用一下标志位:

MAP_SHARED | MAP_ANONYMOUS

其中fd = -1,与文件没有关联。

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

#define SIZE 4096

int main() {
    // 创建共享匿名映射
    void* addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap failed");
        exit(1);
    }

    // 创建子进程
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork failed");
        exit(1);
    } else if (pid == 0) {
        // 子进程写入数据到共享内存
        char* data = (char*)addr;
        sprintf(data, "Hello from the child process!");

        // 子进程结束
        exit(0);
    } else {
        // 等待子进程结束
        wait(NULL);

        // 父进程读取共享内存中的数据
        char* data = (char*)addr;
        printf("Content of shared memory: %s\n", data);

        // 解除映射
        if (munmap(addr, SIZE) == -1) {
            perror("munmap failed");
            exit(1);
        }
    }

    return 0;
}
# ./a.out
Content of shared memory: Hello from the child process!

共享匿名映射在父子进程之间共享内存和实现进程间通信时非常有用。它是一种特殊的共享文件映射,不需要依赖具体的文件,而是将映射的内存区域与进程间共享。
父子进程通信:父进程可以创建一个共享匿名映射,并将其传递给子进程。子进程可以访问并修改映射的内存区域,从而与父进程进行通信。这种方法常用于进程间共享数据或传递消息。

父进程和子进程其页表项是相同的。只要父子进程中的一个发生了缺页中断,就给分配物理内存,建立其虚拟内存和物理内存之间的映射,由于父子进程的页表项是相同的,且共享内存,那么另一个发生缺页中断时,对应页表项已经建立了到物理地址的映射关系。

2.5 共享文件映射

共享文件映射标志位:

MAP_SHARED

其中fd与文件有关联。

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

#define SIZE 4096
#define FILE_NAME "shared_memory"

int main() {
    // 创建共享文件
    int fd = open(FILE_NAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open failed");
        exit(1);
    }

    // 设置共享文件大小
    if (ftruncate(fd, SIZE) == -1) {
        perror("ftruncate failed");
        exit(1);
    }

    // 创建共享文件映射
    void* addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap failed");
        exit(1);
    }

    // 创建子进程
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork failed");
        exit(1);
    } else if (pid == 0) {
        // 子进程写入数据到共享内存
        char* data = (char*)addr;
        sprintf(data, "Hello from the child process!");

        // 将修改刷新到文件
        if (msync(addr, SIZE, MS_SYNC) == -1) {
            perror("msync failed");
            exit(1);
        }

        // 子进程结束
        exit(0);
    } else {
        // 等待子进程结束
        wait(NULL);

        // 父进程读取共享内存中的数据
        char* data = (char*)addr;
        printf("Content of shared memory: %s\n", data);

        // 解除映射
        if (munmap(addr, SIZE) == -1) {
            perror("munmap failed");
            exit(1);
        }

        // 关闭文件
        if (close(fd) == -1) {
            perror("close failed");
            exit(1);
        }

        // 删除共享文件
        if (unlink(FILE_NAME) == -1) {
            perror("unlink failed");
            exit(1);
        }
    }

    return 0;
}
# ./a.out
Content of shared memory: Hello from the child process!

共享文件映射在多进程之间共享内存、实现进程间通信,并且避免写时复制的场景中非常常见。

在这种情况下,多个进程可以通过将同一个文件映射到它们的地址空间来实现共享内存。这意味着它们可以直接读取和写入映射的内存区域,而无需进行复制操作。

共享文件映射的优势在于,多个进程可以通过将同一个文件映射到它们的地址空间来共享数据,而无需进行复制。这对于需要频繁读写共享数据的场景非常有用,因为它避免了写时复制带来的性能开销。

需要注意的是,共享文件映射使用文件作为底层存储介质,因此对于共享内存的读写操作会反映到文件中。这也意味着共享文件映射在进程终止后依然存在,并且可以被其他进程访问。因此,需要小心处理共享文件映射的生命周期和访问权限,以确保数据的一致性和安全性。

参考

https://mp.weixin.qq.com/s/AUsgFOaePwVsPozC3F6Wjw

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

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

相关文章

如何使用 ArcGIS Pro 制作三维地形图

伴随硬件性能的提高和软件算法的优化&#xff0c;三维地图的应用场景会越来越多&#xff0c;这里为大家介绍一下在ArcGIS Pro怎么制作三维地形图&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的DEM和影像数据&#xff0c;除了DEM和影像数据…

Python数据处理实战(4)-上万行log数据提取并作图进阶版

系列文章&#xff1a; 0、基本常用功能及其操作 1&#xff0c;20G文件&#xff0c;分类&#xff0c;放入不同文件&#xff0c;每个单独处理 2&#xff0c;数据的归类并处理 3&#xff0c;txt文件指定的数据处理并可视化作图 4&#xff0c;上万行log数据提取并作图进阶版&a…

vue3的开发小技巧

「总之岁月漫长&#xff0c;然而值得等待。」 目录 父组件调用子组件函数如何访问全局api 父组件调用子组件函数 ref, defineExpose //父组件 代码 <child ref"ch">this.$refs.ch.fn();//子组件 函数抛出 const fn () > { }; defineExpose({ fn });如何…

Type-C接口PD协议统一:引领电子科技新纪元的优势解析

在电子科技日新月异的今天&#xff0c;充电接口的统一化已经成为了业界的一大趋势。其中&#xff0c;Type-C接口凭借其传输速度快、使用便捷等优点&#xff0c;迅速成为了市场上的主流选择。而PD&#xff08;Power Delivery&#xff09;协议的统一&#xff0c;更是为Type-C接口…

投标中项目组织结构的设置以及调整(样式表,多级列表)

投标中项目组织结构的设置以及调整&#xff08;样式表&#xff0c;多级列表&#xff09;&#xff1a; 投标项目中需要处理大规模的文字排版&#xff0c;就是需要用到样式表&#xff08;解决层级关系&#xff09;&#xff0c;多级列表&#xff08;解决自动编号的问题&#xff0…

CSP-J 2021 T2 插入排序

文章目录 题目传送门算法解析总代码提交记录尾声 题目传送门 洛谷 P7910 [CSP-J 2021] 插入排序 算法解析 千万不要题目让你插入排序你就插入排序 首先可以用 p a i r pair pair 来存储元素的值&#xff08; f i r s t first first&#xff09;和原来的下标&#xff08; s…

Vue组件中的scoped属性

Vue组件中的scoped属性的作用是&#xff1a;当前的单文件组件的css样式只用于当前组件的template模板&#xff0c;在Vue脚手架汇总组件间关系时避免样式命名重复的情况。 原理&#xff1a;使用data-*属性在template模板中使用样式的HTML元素上添加额外属性&#xff0c;再利用标…

【UE5】游戏框架GamePlay

游戏框架 游戏 由 游戏模式(GameMode) 和 游戏状态(GameState) 所组成 加入游戏的 人类玩家 与 玩家控制器(PlayerController) 相关联 玩家控制器允许玩家在游戏中拥有 HUD&#xff0c;这样他们就能在关卡中拥有物理代表 玩家控制器还向玩家提供 输入控制&#xff08;Input…

【Qt】四种绘图设备详细使用

绘图设备有4个: **绘图设备是指继承QPainterDevice的子类————**QPixmap QImage QPicture QBitmap(黑白图片) QBitmap——父类QPixmapQPixmap图片类&#xff0c;主要用来显示&#xff0c;它针对于显示器显示做了特殊优化&#xff0c;依赖于平台的&#xff0c;只能在主线程…

掘根教你拿捏C++异常(try,catch,throw,栈解退,异常规范,异常的重新抛出)

在介绍异常之前&#xff0c;我觉得很有必要带大家了解一下运行时错误和c异常出现之前的处理运行时错误的方式。这样子能更深入的了解异常的作用和工作原理 运行阶段错误 我们知道&#xff0c;程序有时候会遇到运行阶段错误&#xff0c;导致程序无法正常运行下去 C在运行时可…

【Springer出版 · EI检索】| 第二届先进无人飞行系统国际会议(ICAUAS 2024)

会议简介 Brief Introduction 2024年第二届先进无人飞行系统国际会议(ICAUAS 2024) 会议时间&#xff1a;2024年6月14日-16日 召开地点&#xff1a;中国南昌 大会官网&#xff1a;ICAUAS 2024-2024 2nd International Conference on Advanced Unmanned Aerial Systems2024 2nd …

即插即用篇 | YOLOv8 引入 ParNetAttention 注意力机制 | 《NON-DEEP NETWORKS》

论文名称:《NON-DEEP NETWORKS》 论文地址:https://arxiv.org/pdf/2110.07641.pdf 代码地址:https://github.com/imankgoyal/NonDeepNetworks 文章目录 1 原理2 源代码3 添加方式4 模型 yaml 文件template-backbone.yamltemplate-small.yamltemplate-large.yaml

视频监控平台EasyCVR+4G/5G应急布控球远程视频监控方案

随着科技的不断发展&#xff0c;应急布控球远程视频监控方案在公共安全、交通管理、城市管理等领域的应用越来越广泛。这种方案通过在现场部署应急布控球&#xff0c;实现对特定区域的实时监控&#xff0c;有助于及时发现问题、快速响应&#xff0c;提高管理效率。 智慧安防视…

vite+vue3门户网站菜单栏动态路由控制

门户网站用户端需要分板块展示&#xff0c;板块内容由管理端配置&#xff0c;包括板块名称&#xff0c;访问路径&#xff0c;路由组件&#xff0c;展示顺序&#xff0c;是否展示。如下图所示&#xff1a; 用户访问门户网站时&#xff0c;展示菜单跳转通过板块配置&#xff0c;动…

Python笔记(四)—— Python函数

4.1 函数的初体验 函数 函数&#xff1a;是组织好的&#xff0c;可重复使用的&#xff0c;用来实现特定功能的代码段 name "itheima" length len(name) print(length) 运行结果&#xff1a; 思考&#xff1a;为什么随时都可以使用len()统计长度 因为&#xff…

c++ 开发环境 LNK1104: 无法打开文件“avcodec.lib”

别人分享&#xff0c; 和自己最近遇到问题一摸一样。以为没什么用的静态资源&#xff0c;结果 无法编译。 昨天安装配置了&#xff0c;结果今天早上打开电脑&#xff0c;所以dll的工程全部报错&#xff1a; 1>------ 已启动全部重新生成: 项目: Dll_test, 配置: Debug x64…

使用插件vue-seamless-scroll 完成内容持续动态

1、安装插件 npm install vue-seamless-scroll --save 2、项目中引入 //单独引入import vueSeamlessScroll from vue-seamless-scrollexport default {components: { vueSeamlessScroll},}//或者在main.js引入import scroll from vue-seamless-scrollVue.use(scroll)3、页面使…

导游百度百科创建词条的条件?如何才更有效的通过审核

导游在如今的社会中扮演着重要的角色&#xff0c;他们不仅仅是旅行中的引路人&#xff0c;更是旅客们了解目的地地域文化和历史的窗口。因此&#xff0c;作为导游&#xff0c;有必要在百度百科上建立自己的词条&#xff0c;以展现自身的专业知识和经验。那导游百度百科词条创建…

[C++]类和对象,explicit,static,友元,构造函数——喵喵要吃C嘎嘎4

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;大大会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

uniapp实现---类似购物车全选

目录 一、实现思路 二、实现步骤 ①view部分展示 ②JavaScript 内容 ③css中样式展示 三、效果展示 四、小结 注意事项 一、实现思路 点击商家复选框&#xff0c;可选中当前商家下的所有商品。点击全选&#xff0c;选中全部商家的商品 添加单个多选框&#xff0c;在将多选…