mmap函数的详细讲解

mmap()函数是一个用于在用户空间和内核空间之间进行文件映射的系统调用。它允许文件在物理内存中的特定区域被映射到进程的地址空间中,从而允许进程通过内存访问操作来读取和写入文件。

函数原型:
#include <sys/mman.h>
​
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

参数说明:

  • addr:映射的起始地址,一般为NULL,让内核自动选择。

  • length:映射的长度,可以是文件的长度或者是页的整数倍。

  • prot:内存保护标志,用于指定内存的保护方式,如读、写、执行等。

  • flags:控制映射的标志,如映射是共享的还是私有的。

  • fd:打开的文件描述符,指定要映射的文件。

  • offset:文件映射的偏移量,指定映射文件开始的位置。

参数的重点介绍:
  1. addr

  • 如果addrNULL,内核会自动选择一个起始地址作为映射的起点。

  • 如果addr不为NULL,则内核会尝试将映射放置在指定的起始地址处。如果指定的地址无效或与已有映射冲突,则mmap()调用会失败。

  • 通常情况下,最常用的做法是将addr设置为NULL,由内核选择一个适当的地址。

  1. length

  • length参数指定要映射的长度,可以是文件的长度或者是页的整数倍。对于文件映射,length一般为文件大小。

  • 对齐到页面大小可以获得最佳性能,因为页面是内存管理的最小单位。

  1. prot

  • prot参数指定内存的保护标志,用于指定内存的访问权限。常见的标志有PROT_READ(可读)、PROT_WRITE(可写)和PROT_EXEC(可执行)。

  • 这些标志可以按位或运算进行组合,以指定多个访问权限。

  1. flags

  • flags参数用于控制映射的标志,用于指定映射是共享的还是私有的,以及一些其他的映射特性。

  • 常见的标志有MAP_SHARED(共享映射)和MAP_PRIVATE(私有映射)。

  • 共享映射允许多个进程对映射进行读写操作,而私有映射则会对映射的页进行复制,每个进程都有自己的私有拷贝。

  1. fd

  • fd参数是打开的文件描述符,用于指定要映射的文件。

  • 如果fd为-1,则表示创建一个匿名映射(anonymous mapping),不与任何文件关联,而是在内存中分配一段连续的空间。

  • 匿名映射通常用于进程间的共享内存或者用作临时存储空间。

  1. offset

  • offset参数指定从文件的哪个位置开始进行映射。一般情况下,可以将offset设置为0,表示从文件的开头位置开始映射。

  • offset必须是页面大小的整数倍。

返回值:

mmap()函数返回一个指向映射区域的指针,如果映射失败则返回MAP_FAILED。

文件映射的类型(flags参数):
  • 私有映射(Private Mapping):对映射的修改不会影响到原始文件,映射的页会被复制到进程的私有页中。

  • 共享映射(Shared Mapping):多个进程可以对映射进行读写操作,任何一个进程对映射的修改都会影响到其他进程。

文件映射的特性:

文件映射是一种将文件内容直接映射到进程地址空间的技术,它允许进程通过对内存的直接操作来读取或写入文件。文件映射具有以下特性:

  1. 零拷贝(Zero-Copy): 文件映射可以实现零拷贝操作,即数据可以直接从文件系统读取到映射的内存区域,而无需在用户空间和内核空间之间进行数据复制。这可以显著提高I/O操作的效率。

  2. 内存操作: 通过文件映射,进程可以使用内存访问指令(如读写操作)来直接访问文件内容,而不是使用传统的文件I/O(如read()write())。这使得文件操作看起来像是对内存的直接操作,简化了编程模型。

  3. 共享访问: 当使用共享映射(MAP_SHARED标志)时,多个进程可以访问同一文件映射区域。这意味着任何进程对映射区域的修改都会立即对其他所有共享该映射的进程可见,反之亦然。

  4. 私有访问: 私有映射(MAP_IVATE标志)则不允许其他进程访问该映射区域。进程对私有映射的修改不会影响其他进程,也不会影响原始文件。

  5. 懒惰写入(Lazy Write): 默认情况下,文件映射采用懒惰写入策略。这意味着对映射区域的修改不会立即写入文件系统,而是在映射区域被回收时或者显式调用msync()函数时才进行写入。这可以减少不必要的磁盘I/O操作,提高性能。

  6. 同步机制: 文件映射提供了同步机制,例如msync()函数可以用来强制将映射区域的修改写入文件系统,或者将文件系统中的修改同步到映射区域。

  7. 内存保护: 文件映射允许通过prot参数来设置内存保护标志,如PROT_READPROT_WRITEPROT_EXEC,以确保映射区域的安全性。

  8. 映射类型: 文件映射可以是匿名映射(不与任何文件关联)或文件映射(与特定文件关联)。匿名映射通常用于进程间通信,而文件映射则用于访问文件系统中的数据。

  9. 页对齐: 文件映射的起始地址和长度通常需要对页面大小进行对齐。页面大小通常是4KB,这取决于系统的内存管理配置。

mmap内存映射原理(摘自大佬博客)

mmap内存映射的实现过程,总的来说可以分为三个阶段:

(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

1、进程在用户空间调用库函数mmap

2、在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址

3、为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化

4、将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中

(二)调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系

5、为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。

6、通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。

7、内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。

8、通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

(三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。

9、进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。

10、缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。

11、调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。

12、之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟(写回策略中有介绍),可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

注意点:
  1. 对齐要求: mmap()函数通常会要求映射区域的起始地址和长度是页面大小(通常为4KB)的整数倍。因此,在使用mmap()函数时,需要确保起始地址和长度符合对齐要求,否则可能导致映射失败。

  2. 长度与文件大小的关系: 如果将文件映射到内存中,通常需要保证映射的长度与文件的大小相匹配,以确保可以访问到文件的所有内容。否则,如果访问超出映射长度范围的数据,将会引发段错误(Segmentation Fault)。

  3. 文件描述符的打开方式: 在创建文件映射时,需要使用正确的文件描述符打开方式。如果需要对映射进行读写操作,应使用O_RDWR标志;如果只需要读取文件内容,应使用O_RDONLY标志;如果只需要写入文件内容,应搭配O_CREAT | O_TRUNC等标志使用。

  4. 内存保护标志的设置: 需要根据需求正确设置内存保护标志(prot参数),以决定映射区域是否可以读取、写入或执行。不正确的设置可能导致访问权限不足的问题。

  5. 共享映射的同步: 如果使用共享映射(MAP_SHARED标志),需要注意在多个进程之间对映射区域的同步操作。在多进程并发访问映射区域时,需要使用其他机制(如信号量、文件锁等)来保证数据的一致性和互斥访问。

  6. 文件的改变对映射的影响: 如果映射的文件发生变化(如被其他进程修改、截断等),映射区域的行为是未定义的。为了避免这种情况,可以在映射区域上使用madvise()函数,并使用MADV_DONTNEED标志来告知系统底层页不再需要,从而避免无效访问。

  7. 取消映射和关闭文件描述符: 在不再使用映射区域后,需要使用munmap()函数释放映射内存,同时关闭相关的文件描述符。如果忘记取消映射和关闭文件描述符,可能会导致内存泄漏和资源泄漏。

拓展函数
munmap()

munmap()函数是用于取消映射区域的系统调用,它将释放由mmap()函数创建的内存映射。

以下是munmap()函数的重点介绍:

  1. 函数原型:

#include <sys/mman.h>
int munmap(void *addr, size_t length);

参数说明:

  • addr:指向要取消映射的起始地址。

  • length:映射区域的长度。

  1. 函数返回值: munmap()函数返回0表示成功,返回-1表示失败。失败的原因可能是无效的起始地址或长度,或者由于权限不足。

  2. 功能: munmap()函数用于取消映射区域,将内核中的虚拟内存释放回操作系统。取消映射后,该区域不再可访问。

  3. 注意事项:

  • munmap()函数必须以mmap()函数返回的起始地址和长度作为参数进行调用,否则会出现未定义的行为。

  • 取消映射后,该区域的访问将引发段错误(Segmentation Fault)。

  • 取消映射并不会自动关闭与映射区域关联的文件描述符,需要通过调用close()函数来显式关闭。

msync()

msync()函数是用于将内存中的改动同步到文件系统中的函数,确保数据的持久化。它可以用于确保对映射区域的修改写入文件系统,或者将文件系统中的修改同步到映射区域。

以下是msync()函数的重点介绍:

  1. 函数原型:

#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);

参数说明:

  • addr:指向映射区域的起始地址。

  • length:映射区域的长度。

  • flags:用于指定同步操作的标志。常见的标志有MS_ASYNC(异步写入)、MS_SYNC(同步写入)和MS_INVALIDATE(使缓存无效)。

  1. 函数返回值: msync()函数返回0表示成功,返回-1表示失败。失败的原因可能是无效的起始地址或长度,或者由于权限不足。

  2. 功能: msync()函数用于将进程内存中对映射区域的修改写入到文件系统中,或者将文件系统中的修改同步到映射区域。这样可以确保数据的持久化,保证文件的一致性。

  3. 同步模式msync()函数提供了两种同步模式:

  • MS_ASYNC:异步写入模式。这种模式下,msync()函数会立即返回,而不会等待写入操作的完成。这样可以提高性能,但不能保证写入的数据立即持久化到磁盘上。

  • MS_SYNC:同步写入模式。这种模式下,msync()函数会等待所有的写入操作完成,然后返回。这确保了对映射区域的修改已经持久化到磁盘上。

  1. 缓存处理: msync()函数的flags参数中的MS_INVALIDATE标志可用于使缓存无效,即强制从文件系统重新读取映射区域的数据。这样可以确保映射区域的数据与文件系统的数据一致。

例子
1.读取文件:

#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
​
int main() {int fd = open("example.txt", O_RDONLY);if (fd == -1) {perror("open");return 1;}
​struct stat sb;if (fstat(fd, &sb) == -1) {perror("fstat");close(fd);return 1;}
​char *addr = (char *)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (addr == MAP_FAILED) {perror("mmap");close(fd);return 1;}
​for (off_t i = 0; i < sb.st_size; ++i) {putchar(addr[i]);}
​munmap(addr, sb.st_size);close(fd);
​return 0;
}
​

在这个示例中,我们打开一个名为example.txt的文件并获取它的文件描述符。然后,使用fstat()函数获取文件的元数据信息,包括文件大小。

接下来,我们使用mmap()函数将文件映射到内存中。PROT_READ标志表示映射的内存可读。MAP_PRIVATE标志表示创建一个私有映射,对映射的修改不会影响原始文件。

然后,我们可以通过访问addr指针来读取映射的文件内容,并使用sb.st_size来确定要读取的字节数。

最后,使用munmap()函数取消映射,并关闭文件描述符。

2.写入文件
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
​
int main() {const char *data = "Hello, World!";size_t length = strlen(data);
​int fd = open("output.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd == -1) {perror("open");return 1;}
​if (ftruncate(fd, length) == -1) {perror("ftruncate");close(fd);return 1;}
​char *addr = (char*)mmap(NULL, length, PROT_WRITE, MAP_SHARED, fd, 0);if (addr == MAP_FAILED) {perror("mmap");close(fd);return 1;}
​memcpy(addr, data, length);
​if (msync(addr, length, MS_SYNC) == -1) {perror("msync");close(fd);return 1;}
​munmap(addr, length);close(fd);
​return 0;
}

在这个示例中,我们创建了一个名为output.txt的文件,并将字符串"Hello, World!"写入到文件中。

首先,我们打开文件并使用ftruncate()函数设置文件大小为要写入的数据的长度。然后,使用mmap()函数将文件映射到内存中。PROT_WRITE标志表示映射的内存可写入。MAP_SHARED标志表示创建一个共享映射,对映射的修改将影响到原始文件。

接下来,我们使用memcpy()函数将数据从源data复制到映射的内存区域。

然后,我们使用msync()函数将对映射区域的修改同步到文件系统,并确保写入了磁盘。

最后,我们取消映射,并关闭文件描述符。

3.共享内存

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h> 
​
int main()
{const int SIZE = 4096;const char *name = "shared_memory";
​int fd = shm_open(name, O_CREAT | O_RDWR, 0666);ftruncate(fd, SIZE);
​void *addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (addr == MAP_FAILED){perror("mmap");return 1;}
​pid_t pid = fork();
​if (pid < 0){perror("fork");return 1;}else if (pid == 0){strncpy((char*)addr, "Hello from child", strlen("Hello from child"));return 0;}else{wait(NULL);printf("Parent received: %s\n", (char *)addr);shm_unlink(name);return 0;}
}

这个示例展示了如何使用mmap函数进行进程间共享内存。父进程创建了一个共享内存区域,然后fork出一个子进程。子进程写入数据到共享内存,父进程读取并打印共享内存中的数据

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

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

相关文章

笔记---dp---最长上升子序列模型

模型原始题目&#xff1a;AcWing.895.最长上升子序列 题目关系如下&#xff1a; 转化一 AcWing.1017.怪盗基德的滑翔翼 怪盗基德是一个充满传奇色彩的怪盗&#xff0c;专门以珠宝为目标的超级盗窃犯。 而他最为突出的地方&#xff0c;就是他每次都能逃脱中村警部的重重围堵…

ZigBee学习——在官方例程实现组网

✨Z-Stack版本&#xff1a;3.0.2 ✨IAR版本&#xff1a;10.10.1 ✨这篇博客是在善学坊BDB组网实验的基础上进行完善&#xff0c;并指出实现的过程中会出现的各种各样的问题&#xff01; 善学坊教程地址&#xff1a; ZigBee3.0 BDB组网实验 文章目录 一、基础工程选择二、可能遇…

Linux(Ubuntu) 环境搭建:MySQL

注&#xff1a;服务器默认以root用户登录 服务器的终端中输入以下指令&#xff1a; # 安装 MySQL apt install mysql-server # 查看版本 mysql -V # 查看 MySQL 服务状态 systemctl status mysql # 安装完成后&#xff0c;MySQL 服务将自动启动 # MySQL 服务在系统启动时自动…

spring 入门 一

文章目录 Spring简介Spring的优势Spring的体系结构 Spring快速入门Spring程序开发步骤导入Spring开发的基本包坐标编写Dao接口和实现创建Spring核心配置文件在Spring配置文件中配置UserDaoImpl使用Spring的API获得Bean实例 Spring配置文件Bean标签基本配置Bean标签范围配置Bean…

2.10

头文件&#xff1a; #include <sqlite3.h> 编译时候要加上-lsqlite3 gcc a.c -lsqlite3 1&#xff09;sqlite3_open 打开一个数据库&#xff0c;如果数据库不存在&#xff0c;则创建一个数据库 2&#xff09;sqlite3_close 关闭数据库&#xff0c;断开句柄所拥有的资…

HarmonyOS 横屏调试与真机横屏运行

我们有些程序 需要横屏才能执行出效果 我们在预览器上 点击如下图指向出 就进入一个横屏调试了 但 我们真机运行 依旧是竖着的 我们如下图 找到 module.json5 在 abilities 下面 第一个对象 最下面 加上 "orientation": "landscape"然后 我们再真机运…

Oracle表结构转成MySQL表结构

在将Oracle数据库表结构转换为MySQL数据库表结构时&#xff0c;需要考虑两大数据库系统之间的差异。以下是一些基本步骤和注意事项&#xff0c;帮助您进行转换&#xff1a;1、字符集和排序规则&#xff1a; Oracle使用的是固定的字符集和排序规则&#xff0c;而MySQL使用的是可…

Rust变量与常量介绍

Rust是一门注重安全性和性能的系统编程语言&#xff0c;其中变量和常量的概念有着独特的设计和特性。在本文中&#xff0c;我们将深入了解Rust中的变量和常量&#xff0c;并解释它们之间的区别&#xff0c;同时通过多个例子进行说明。 Rust常量 在Rust中&#xff0c;常量是不…

第三百二十一回

文章目录 1. 概念介绍2. 使用方法2.1 基本用法2.2 缓冲原理 3. 示例代码4. 内容总结 我们在上一章回中介绍了"FadeInImage组件"相关的内容&#xff0c;本章回中将介绍CachedNetworkImage组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

搜索专项---最短路模型

文章目录 迷宫问题武士风度的牛抓住那头牛 一、迷宫问题OJ链接 本题思路:只需要记录各个点是有哪个点走过来的&#xff0c;就能递推得出路径。记录前驱假设从 1,1 这个点向下走到了2, 1&#xff0c;则将2,1这个点的前驱记为1,1。这样&#xff0c;将整张地图 bfs 后&#xff0c…

vue3学习——封装菜单栏

/Layout/Sidebar/index.vue <script setup lang"ts"> import Sidebar from ./Sidebar.vue // 在下面的代码里 import { useRoute } from vue-router import useUserStore from /store/modules/user.ts // state中存放菜单数据 import useLayoutSetting from /…

数据结构:并查集讲解

并查集 1.并查集原理2.并查集实现3.并查集应用4.并查集的路径压缩 1.并查集原理 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在此过程中…

C语言之动态内存管理

目录 1. 为什么要有动态内存分配2. malloc和freemallocfree 3. calloc和realloccallocrealloc 4. 常见的动态内存的错误对NULL直接的解引用操作对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放一块动态开辟内存的一部分对同一块动态内存多次释放动态开辟内存…

Github 2024-02-06 开源项目日报Top9

根据Github Trendings的统计&#xff0c;今日(2024-02-06统计)共有9个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目4TypeScript项目2C项目1Ruby项目1HTML项目1Go项目1Rust项目1C项目1Kotlin项目1 Magic Mask for And…

如何从 iPhone 恢复已删除的视频:简单有效方法

无论您是在尝试释放空间时不小心删除了 iPhone 上的视频&#xff0c;还是在出厂时清空了手机&#xff0c;现在所有数据都消失了&#xff0c;都不要放弃。有一些方法可以恢复这些视频。 在本文中&#xff0c;我们将向您展示六种最有效的数据恢复方法&#xff0c;可以帮助您从 i…

图形系统开发实战课程:进阶篇(上)——3.图层类(Layer)

图形开发学院&#xff5c;GraphAnyWhere 课程名称&#xff1a;图形系统开发实战课程&#xff1a;进阶篇(上)课程章节&#xff1a;“图层类&#xff08;Layer&#xff09;”原文地址&#xff1a;https://graphanywhere.com/graph/advanced/2-3.html 第三章&#xff1a;图层类&am…

代码随想录算法训练营Day55|392.判断子序列、115.不同的子序列

目录 392.判断子序列 思路 ​算法实现 115.不同的子序列 思路 算法实现 总结 392.判断子序列 题目链接 文章链接 思路 利用动规五部曲进行分析&#xff1a; 1.确定dp数组及其下标含义&#xff1a; dp[i][j] 表示以下标i-1为结尾的字符串s&#xff0c;和以下标j-1为结尾的…

寒假作业:2024/2/11

作业1&#xff1a;使用递归实现n! 代码&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> int fun(int n) {if(0n){return 1;}else{return n*fun(n-1);} } int main(int argc, const char *argv[]) {int n;printf("please en…

深入浅出CChart 每日一课——红花当然配绿叶,CChart辅助图形绘制

各位同学&#xff0c;好久不见&#xff0c;我可想死你们了&#xff01;&#xff01;&#xff01;咦&#xff0c;那位不是巩叔吗&#xff1f;不好意思&#xff0c;侵权了&#xff0c;请多担待_。 前面的课程呢&#xff0c;拓展的内容比较多&#xff0c;最近笨笨想聚焦在CChart本…

车载电子电器架构 —— 电子电气系统功能开发

车载电子电器架构 —— 电子电气系统功能开发 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,喝完再挣扎,出门靠自己,四海皆…