【Linux】关于 mmap 文件映射

目录

  • [一、mmap 文件映射](#一、mmap 文件映射)
    • [1.1 基本说明](#1.1 基本说明)
    • [1.2 参数介绍](#1.2 参数介绍)
    • [1.3 写入映射](#1.3 写入映射)
    • [1.4 读取映射](#1.4 读取映射)
    • [1.5 简单实现 malloc](#1.5 简单实现 malloc)

个人主页:矢望

个人专栏:C++LinuxC语言数据结构Coze-AIMySQL

一、mmap 文件映射

1.1 基本说明

mmapmemory map,内存映射)是操作系统提供的一个系统调用,它将文件或设备的内容映射到进程的虚拟地址空间中,使进程可以像访问内存一样直接读写文件,而无需使用传统的 read / write 系统调用。

核心:让内核将文件数据页直接映射到进程的页表中,当进程访问该内存区域时,内核自动完成文件数据与物理内存之间的同步。

mmap 可以用于实现共享内存,允许不同进程间共享数据;可以用于动态内存分配等。

mmap函数:

1.2 参数介绍

void *addr : 一个提示地址,表示希望映射区域开始的地址 。然而,这个地址可能会被内核忽略,特别是当我们没有足够的权限来请求特定的地址时。如果 addrNULL ,则系统会自动选择一个合适的地址。

size_t length : 要映射到进程地址空间中的字节数 。这个长度必须是系统页面大小的整数倍(通常是 4KB ,但可能因系统而异)。如果指定的 length 不是页面大小的整数倍,系统可能会向上舍入到最近的页面大小边界(系统内存页大小为 4KB,即4096字节,而请求的内存大小为3500字节,则按照向上舍入的原则,应分配4096字节的内存)。

int prot : 指定了映射区域的内存保护属性 。可以是以下值的组合(使用按位或运算符 |):
PROT_READ :映射区域可读。
PROT_WRITE :映射区域可写。
PROT_EXEC :映射区域可执行。

int flags : 指定了映射的类型和其他选项
MAP_PRIVATE :创建一个私有映射。对映射区域的修改不会反映到底层文件中。
MAP_SHARED :创建一个共享映射。对映射区域的修改会反映到底层文件中(前提是文件是以写方式打开的,并且文件系统支持这种操作)。

其他选项(如 MAP_ANONYMOUS 、 MAP_ANONYMOUS_SHARED 等)可能也存在于某些系统上,用于创建不与文件关联的匿名映射。

int fd : 一个有效的文件描述符,指向要映射的文件或设备 。对于匿名映射,这个参数可以是 -1 (在某些系统上,也可以使用 MAP_ANONYMOUSMAP_ANON 标志来指定匿名映射,此时 fd 参数会被忽略)。

off_t offset : 文件中的起始偏移量,即映射区域的开始位置offsetlength 一起定义了映射区域在文件中的位置和大小。

返回值

mmap 成功时返回映射区域的起始地址 void* 类型,失败时返回 MAP_FAILED,通常定义为 (void*)-1

1.3 写入映射

这里我们要写的demo代码是使用 mmap 将文件映射到内存,然后直接通过内存写入来修改文件内容。

首先要做的工作就是打开目标文件

cpp 复制代码
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdlib>

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        std::cout << "Usage: " << argv[0] << " filename" << std::endl;
        exit(1);
    }

    // 1、打开目标文件
    int fd = ::open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 0666);
    if(fd < 0)
    {
        std::cerr << "open file fail" << std::endl;
        exit(2);
    }

    return 0;
}

这里有一个细节,我们使用mmap将文件映射到内存时是想要求文件可读可写的,所以我们需要以读写方式O_RDWR的方式打开文件。

打开文件之后,第二步就是要手动调整这个文件的大小 ,刚创建出来的文件大小是0,而mmap 映射时,文件大小必须 映射长度,否则会失败。这里调整大小用到了ftruncate函数。

ftruncate 是一个系统调用,用于将文件截断或扩展到指定长度。新增的区域用空字节\0填充。

成功时,返回0,失败返回-1

cpp 复制代码
#define SIZE 1024

int main(int argc, char* argv[])
{
    // 2、手动调整文件大小
    if(::ftruncate(fd, SIZE) == -1)
    {
        std::cerr << "Failed to truncate file: " << argv[1] << std::endl;
        exit(3);
    }

    return 0;
}

紧接着就是进行文件映射的工作

cpp 复制代码
// 3、进行文件映射
char* shmaddr = (char*) ::mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(shmaddr == MAP_FAILED)
{
    std::cerr << "Failed to mmap file" << argv[1] << std::endl;
    exit(4);
}
参数 含义
addr nullptr 让操作系统自动选择映射地址(最常用)
length SIZE 映射的字节数(建议是页大小的倍数,如 4096、8192
prot `PROT_READ PROT_WRITE`
flags MAP_SHARED 共享映射:修改会写回文件,其他进程可见
fd 文件描述符 指向文件(普通文件或 shm_open 对象)
offset 0 从文件开头开始映射

文件映射完成之后就是进行文件操作,我们进行写入文件

cpp 复制代码
// 4、进行文件操作
for(char c = 'a'; c <= 'z'; c++)
{
    shmaddr[c - 'a'] = c;
    sleep(1);
}

完成文件操作之后就是取消文件映射和关闭文件

munmap(Memory Unmap),是 mmap 的逆操作,用于解除内存映射。

cpp 复制代码
// 5、取消映射
::munmap(shmaddr, SIZE);

// 6、关闭文件
::close(fd);

运行测试

如上,写入成功,并且文件的大小是1024,和我们设置的一样。

1.4 读取映射

读取映射和写入映射的结构一模一样,这里就在上面的demo代码上修改了。

fstat 是一个系统调用,用于获取已打开文件的状态信息(元数据),如文件大小、权限、修改时间等。

cpp 复制代码
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdlib>
#include <unistd.h>
#include <sys/mman.h>

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        std::cout << "Usage: " << argv[0] << " filename" << std::endl;
        exit(1);
    }

    // 1、打开目标文件
    int fd = ::open(argv[1], O_RDONLY);
    if(fd < 0)
    {
        std::cerr << "open file fail" << std::endl;
        exit(2);
    }

    // 2、获取文件大小
    struct stat st;
    ::fstat(fd, &st);

    // 3、进行文件映射
    char* shmaddr = (char*) ::mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
    if(shmaddr == MAP_FAILED)
    {
        std::cerr << "Failed to mmap file" << argv[1] << std::endl;
        exit(4);
    }

    // 4、进行文件操作
    std::cout <<  "File content: " << shmaddr << std::endl;

    // 5、取消映射
    ::munmap(shmaddr, st.st_size);

    // 6、关闭文件
    ::close(fd);


    return 0;
}

修改了打开文件和映射文件时的权限以及修改了第二步,代码的核心结构没有改动。

运行测试

1.5 简单实现 malloc

上图中这个参数的选项:MAP_SHARED的修改会写回文件并对其他进程可见(多进程共享同一份物理内存),而 MAP_PRIVATE 的修改不写回文件且对其他进程不可见(写时复制机制,每个进程拥有独立副本)。MAP_ANONYMOUSmmap 的一个标志,用于创建不与任何文件关联的匿名内存映射。

MAP_SHARED 用于进程间通信、共享缓存、数据库、内存映射文件编辑器等需要多进程共享数据或持久化存储的场景;MAP_PRIVATE 用于加载动态库/可执行文件、只读分析大文件、临时修改配置等不需要保存修改或希望各进程保持独立的场景。

MAP_PRIVATE | MAP_ANONYMOUS的作用是映射出一块匿名空间,这种场景和malloc的场景很相像,事实上,大多数情境下,malloc的底层就是使用mmap实现的。当使用这个选项时,文件描述符就可以设为-1了。

demo代码

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <sys/mman.h>
#include <unistd.h>

void* my_malloc(size_t size)
{
    void* ptr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if(ptr == MAP_FAILED)
    {
        return nullptr;
    }
    return ptr;
}

void my_free(void* ptr, int size)
{
    int ret = ::munmap(ptr, size);
    if(ret != 0)
    {
        perror("munmap");
    }
}

int main()
{
    size_t size = 1024;
    char* str = (char*)my_malloc(size);
    if(str == nullptr)
    {
        perror("my_malloc");
        exit(2);
    }

    printf("Allocated memory at address: %p\n", str);

    // 使用 str 指向的内存
    memset(str, 'A', size);

    for(int i = 0; i < size; i++)
    {
        std::cout << str[i];
    }


    my_free(str, size);

    return 0;
}

运行测试

编译时加上-g选项,使用gdb进行调试时,执行info proc mapping就可以看到我们申请的空间。
info proc mappings 是一个 GDB 命令,用于查看目标进程的内存映射布局,显示进程的虚拟地址空间中各个区域的起始地址、权限、大小以及映射的文件等信息。

上图中大小是0x1000就是4096字节,即便我们使用的是1024字节,mmaplength参数也会向上取整到4096字节。上面我们也可以看到我们的objfile一栏是空的,因为是匿名映射。

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~

相关推荐
me8322 小时前
【Linux】解决Docker-Compose拉取Jenkins时失败问题。
linux·docker·jenkins
kaoa0002 小时前
Linux入门攻坚——73、运维OS Provisioning阶段工具之PXE、Cobbler
linux·运维
Lugas Luo2 小时前
SATA Port Multiplier (SATA 集线器) 原理与驱动架构深度剖析
linux·嵌入式硬件
123过去2 小时前
fcrackzip使用教程
linux·网络·测试工具·安全
水月天涯2 小时前
Mac系统下制作 Ubuntu镜像(小白教程)
linux·ubuntu·macos
A.A呐2 小时前
【Linux第二十三章】传输层
linux·运维·服务器
Yupureki2 小时前
《Linux网络编程》1.网络基础
linux·运维·服务器·c语言·网络·c++
kongba0072 小时前
复刻 Claude Code 项目御马术缰绳系统 harness engineering 落地蓝图
java·linux·服务器
RisunJan3 小时前
Linux命令-mysqldump(MySQL数据库中备份工具)
linux·数据库·mysql