Linux mmap系统调用

文章目录

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

前言

NAMEmmap, munmap - map or unmap files or devices into memorySYNOPSIS#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 VALUEOn  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)函数。

NAMEmsync - 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 4096int 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 4096int 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:111Content 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 4096int 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

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

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

相关文章

跨境电商趋势解析:社交电商携手私域流量运营,精准触达与转化

随着全球化的深入发展&#xff0c;跨境电商逐渐成为全球贸易的重要组成部分。在这一背景下&#xff0c;社交电商作为一种新兴的商业模式&#xff0c;正逐渐在跨境电商领域崭露头角&#xff0c;并对私域流量的运营产生了深远的影响。本文Nox聚星将和大家分析社交电商在跨境电商中…

浏览器一键重新发起请求

一、需求场景 在前端开发过程中&#xff0c;经常会需要重新请求后台进行代码调试&#xff0c;之前的常规方法是刷新浏览器页面或者点击页面进行交互&#xff0c;这样对多个请求的场景就很方便&#xff0c;但是往往很多时候我们只是单纯的想重新发起一个请求&#xff08;多个请求…

力扣---接雨水---单调队列

题目&#xff1a; 单调队列思想&#xff1a; 没有思路的小伙伴可以先把这个想清楚哦&#xff1a;力扣hot10---大根堆双端队列-CSDN博客 从上面的图就可以发现&#xff0c;如果柱子呈递减序列&#xff0c;那么不会接到雨水&#xff0c;只要有一个小凸起柱子&#xff0c;那么这个…

PubMedQA数据集分享

来源: AINLPer公众号&#xff08;每日干货分享&#xff01;&#xff01;&#xff09; 编辑: ShuYini 校稿: ShuYini 时间: 2024-2-28 该数据集由匹兹堡、卡内基梅隆等大学提出&#xff0c;它是第一个需要对生物医学研究文本进行推理&#xff0c;特别是其定量内容的问答数据集。…

【前端】-初始前端以及html的学习

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

Vue开发实例(八)Vuex状态管理store

Vuex状态管理store 一、Vuex的安装与配置二、store使用方法1、基础使用2、提交变更3、getters使用4、在其他页面&#xff08;组件&#xff09;中显示5、modules多模块 做vue项目的时候&#xff0c; store状态管理器可以帮助我们完成一些数据的存储和管理&#xff0c;通俗理解是…

如何将任何文本转换为概念图(GC)

原文地址&#xff1a;how-to-convert-any-text-into-a-graph-of-concepts 使用 Mistral 7B 将任何文本语料库转换为知识图的方法 2023 年 11 月 10 日 使用递归 RAG 方法来实现具有多跳推理的 QnA&#xff0c;以回答基于大型文本语料库的复杂查询。 知识图增强生成与递归 R…

unity-urp:视野雾

问题背景 恐怖游戏在黑夜或者某些场景下&#xff0c;需要用雾或者黑暗遮盖视野&#xff0c;搭建游戏氛围 效果 场景中&#xff0c;雾会遮挡场景和怪物&#xff0c;但是在玩家视野内雾会消散&#xff0c;距离玩家越近雾越薄。 当前是第三人称视角&#xff0c;但是可以轻松的…

【C++】十大排序算法之 归并排序 快速排序

本次介绍内容参考自&#xff1a;十大经典排序算法&#xff08;C实现&#xff09; - fengMisaka - 博客园 (cnblogs.com) 排序算法是《数据结构与算法》中最基本的算法之一。 十种常见排序算法可以分为两大类&#xff1a; 比较类排序&#xff1a;通过比较来决定元素间的相对次序…

力扣--滑动窗口438.找到字符串中所有字母异位词

思路分析&#xff1a; 使用两个数组snum和pnum分别记录字符串s和p中各字符出现的次数。遍历字符串p&#xff0c;统计其中各字符的出现次数&#xff0c;存储在pnum数组中。初始化snum数组&#xff0c;统计s的前m-1个字符的出现次数。从第m个字符开始遍历s&#xff0c;通过滑动窗…

史上最细,接口自动化测试用例设计编写总结,一篇带你打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 说到自动化测试&a…

亚信安慧AntDB:“融合+实时”引领数据库创新

在当今多变的数据应用场景中&#xff0c;AntDB作为行业领先的超融合流式实时数仓&#xff0c;秉承着“融合实时”的研发理念&#xff0c;全面应对企业日益复杂的数据处理需求。通过SQL接口访问多种执行引擎&#xff0c;AntDB在实现交易、分析等多重能力的“超融合”方面取得了显…

SQL设计时增加说明列

后关闭sql Studio,然后打开注册表,注册表地址: 计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\SQL Server Management Studio\18.0_IsoShell\DataProject 如有版本不同,红色内容有所变化,修改内容如下: SSVPropViewColumnsSQL70,SSVPropViewColumnsSQL80 全修改为 1,2,6,7…

魔方,3循环是你的秘密[嗑瓜子]。​

引理1.任意Sn中的元素&#xff1a;(N_1N_2N_3...N_m) 证明&#xff1a; (N_1N_2N_3...N_m) (N_1N_m)(N_1N_m-1)...(N_1N_2) 举例&#xff1a; 比如(1234) (14)(13)(12) (3214) (34)(31)(32) 2.任意An可以表示成3循环的乘积&#xff0c; 证明&#xff1a; 1.An中的元素属于…

抖店无货源模式,采购商品、平台渠道正规吗?相关基础问题解答

我是王路飞。 无货源模式&#xff0c;相信你们也都不陌生了。 每个电商平台都存在这种模式&#xff0c;且我以为&#xff0c;每个电商平台的发展壮大&#xff0c;最应该感谢的就是这些无货源商家了。 而现在说到无货源&#xff0c;最适合普通人的无疑就是抖音小店了。 今天…

[云原生] k8s之存储卷

一、emptyDir存储卷 当Pod被分配给节点时&#xff0c;首先创建emptyDir卷&#xff0c;并且只要该Pod在该节点上运行&#xff0c;该卷就会存在。正如卷的名字所述&#xff0c;它最初是空的。Pod 中的容器可以读取和写入emptyDir卷中的相同文件&#xff0c;尽管该卷可以挂载到每…

【Redis】redis持久化

redis 持久化 所谓的持久化&#xff0c;就是把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。 redis 开始是将所有数据保持在内存中&#xff0c;对数据的更新将根据策略配置异步地保存在磁盘中。 持久化的方式 快照方式 快照是某时某刻对数据的完整备份。在以…

3dmax画图卡顿解决方法---模大狮模型网

当你在使用3D Max进行画图时遇到卡顿问题&#xff0c;可以尝试以下方法来解决&#xff1a; 减少模型复杂度&#xff1a;如果你的场景中有过多的高细节模型&#xff0c;可能会导致卡顿。尝试减少模型的复杂度&#xff0c;合并或简化多边形数量过多的模型。这将减轻计算机的负担&…

【UE 材质 Niagara】爆炸效果

目录 效果 步骤 一、材质部分 二、Niagara部分 效果 步骤 一、材质部分 1. 创建一个材质&#xff0c;这里命名为“M_Burst” 打开“M_Burst”&#xff0c;设置混合模式为半透明&#xff0c;设置着色模型为无光照&#xff0c;勾选双面显示 在材质图表中首先创建扰动效果 其…

智能指针基础知识【C++】【RAII思想 || unique_ptr || shared_ptrweak_ptr || 循环引用问题】

目录 一&#xff0c;为什么需要智能指针 二&#xff0c;内存泄露的基本认识 1. 内存泄露分类 2. 常见的内存检测工具 3&#xff0c;如何避免内存泄露 三&#xff0c;智能指针的使用与原理 1. RAII思想 2. 智能指针 &#xff08;1. unique_ptr &#xff08;2. shared_…