初始mmap
一、核心定义
mmap() 是 Linux/Unix 系统调用,全称 memory map(内存映射),把文件 / 设备空间直接映射到进程虚拟地址空间,进程可以像读写内存指针一样读写文件,无需传统 read/write 的内核缓冲区中转。也就说命令该操作的效率的高。
二、主要作用
1. 高效读写文件(最常用)
传统文件读写流程: 应用缓冲区 → 内核页缓存 → 磁盘(两次 数据拷贝:用户态 ↔ 内核态)
mmap 映射后: 磁盘文件直接映射到进程虚拟内存,省去一次内核缓冲区拷贝
读文件:直接访问内存地址,系统缺页异常时内核加载磁盘数据
写文件:修改内存数据,内核定时自动刷回磁盘,也可手动调用 msync() 强制落盘 优势:大文件读写性能大幅提升。
2. 进程间通信(IPC:共享内存)
创建一块匿名映射 / 映射同一个文件,多个进程绑定同一片虚拟内存:
一个进程修改内存内容,其他进程立刻可见也就是我们之前共享内存的概念。
速度远快于管道、消息队列(无内核缓冲区复制) 适用场景:大数据、视频流、缓存数据多进程共享。
3. 加载可执行程序、动态库
操作系统运行程序时:
用 mmap 将可执行文件、.so 动态库映射进进程地址空间
不会一次性全部载入内存,采用按需分页加载
节省物理内存,多个进程共用同一份库文件物理内存(共享文本段)
4. 硬件设备内存映射(嵌入式常用)
驱动将硬件寄存器、外设物理内存通过 mmap 暴露给用户态: 用户程序无需调用 ioctl,直接指针读写硬件寄存器,嵌入式开发高频使用。 Linux与 Unix
5. 匿名内存分配
创建无文件关联的匿名映射,替代 malloc 申请大块内存:
适合超大内存申请
可用于父子进程间内存共享(fork 后共享映射区)
三、关键优缺点
优点
数据拷贝少,IO 效率更高;
内存访问方式操作文件,编码简洁;
多进程共享内存,IPC 性能顶尖;
懒加载,节省物理内存占用。
缺点
映射长度必须按内存页(4KB)对齐,小文件会有内存浪费;这时候我们可以调用ftruncate来进行文件大小的扩容。
进程崩溃时,未刷盘的数据容易丢失;
内存越界会污染映射文件,风险更高;
小文件读写场景优势不明显。
四、基础使用流程
open() 打开文件
mmap() 建立映射,返回内存首地址 Linux与 Unix
指针直接读写这片内存
munmap() 解除映射
close() 关闭文件
五、典型应用场景
数据库(Redis、MySQL):日志、数据文件高速读写;
音视频播放器:超大媒体文件流式读取;
多进程服务器:缓存数据共享;
嵌入式:操控外设寄存器;
系统底层:程序加载器、动态链接器。
主要就是我们的文件打开后获得文件描述符,然后通过mmap将这个文件映射到我们的进程地址空间中, 然后我们后续的操作直接在堆栈之间进行操作即可。
详解mmap的接口
我们关于mmap的接口这里我们查寻一下我们对应的手册。 Linux与 Unix
man mmap
函数原型:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
我们所包含的头文件:#include <sys/mman.h> 计算机驱动器和存储设备
看这个参数之前我们可以类比一下我们的共享内存的参数。
这里我们拿这个进行类比
void *shmat(int shmid, const void *shmaddr, int shmflg);
我们的第二个参数
所以这里的第一个参数和他是一样的, 我们指定映射到我们进程地址空间的哪个地方。
参数的介绍:
addr: 我们将文件映射到虚拟地址空间的哪个地方。
length:表示我们这个文件映射的长度, 一般是4096一个页的大小。
prot:输入型参数,指定我们映射区域的内存保护属性
PROT_READ:映射区域可读。
PROT_WRITE:映射区域可写。
PROT_EXEC:映射区域可执行。
PROT_NONE : 页面可能无法访问
这里我们要注意!注意!!注意!!!
我们要将我们对应的文件读取到我们的虚拟空间的页缓存中,我们这里必须要求读权限,但是我们这里如果不写的情况下我们是可以不使用我们的写权限的。 计算机驱动器和存储设备
open 打开模式 mmap prot 是否成功
O_RDONLY PROT_READ ✅成功
O_RDONLY PROT_READ|PROT_WRITE ❌失败,fd 没有写权限
O_WRONLY PROT_READ|PROT_WRITE ❌失败,fd 没有读权限
O_RDWR PROT_READ ✅成功
O_RDWR PROT_READ|PROT_WRITE ✅成功(最常用)
flags:我们对应的标记位,输出型参数。
这里的flags有许多种的参数。
MAP_PRIVATE:创建一个私有映射,对映射区的修改不会反应到底层的文件中。
MAP_SHARED:创建一个共享映射,对映射区域的修改会反应底层的文件中(前提是文件是以写方式打开的,并且文件系统支持这样的操作)。
标志 修改内存会写到磁盘 用于 IPC 进程通信
MAP_PRIVATE ❌不会写回文件 ❌不可以
MAP_SHARED ✅内存改动同步磁盘 ✅可以
其他选项(如MAP_ANONYMOUS、MAPANONYMOUS等)可能会存在于某些系统中,用于创建不与文件关联的匿名映射。
fd:要映射文件的文件描述符。 计算机驱动器和存储设备
offset:文件中的起始偏移量,及映射区域开始的位置。offset和lenth一起定义了映射区域在文件中的位置和大小。
返回值:
成功返回指向这个空间的虚拟地址, 失败返回-1 ((void*)-1)
使用mmap
在使用之前我们要先了解两个有用的系统调用的接口。
ftruncate
这个参数就是将我们的文件截到给定的长度。
man ftruncate
函数原型:
SYNOPSIS
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
参数的介绍:
fd:我们要扩大文件的文件描述符。 计算机驱动器和存储设备
lenth: 要将这个文件扩大到多少。我们是以4096进行内存的划分的一般扩展到我们对应的4096。
返回值:
成功返回0 , 失败返回-1 , 错误码被设置。
fstat
获取我们当前文件的大小。
man fstat
函数原型:
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
参数的介绍
fd: 获取文件的文件描述符。 计算机驱动器和存储设备
statbuf:我们的输出型参数。取出一个结构体。里面有size会返回对应文件的大小。
返回值:
成功返回0,失败返回-1.错误码被设置。
我们是用mmap映射到进程地址空间
我们的commit文件: 计算机驱动器和存储设备
#pragma once
#include<cstdio>
#include<iostream>
#include<sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define LEN_MAX 4096
#include "commit.hpp"
int main()
{
//先打开文件
int fd = open("test.txt", O_RDWR| O_CREAT | O_TRUNC,0666);
ftruncate(fd,LEN_MAX); //使用他的目的是我们是以4kb为单位进行开辟空间的,避免空间被浪费。
void* address = mmap(nullptr,LEN_MAX, PROT_WRITE|PROT_READ , MAP_SHARED,fd, 0);
if(address == (void*)-1)
{
perror("mmap fail!");
exit(1);
}
return 0;
}
我们这个文件进行写操作,我们这里要写入A-Z的字母吧。
#include "commit.hpp"
#define LEN_MAX 4096
int main()
{
int fd = open("test.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
// 重点:用 LEN_MAX(4096),严禁SIZE_MAX
ftruncate(fd, LEN_MAX);
void* address = mmap(nullptr,LEN_MAX, PROT_WRITE|PROT_READ, MAP_SHARED,fd, 0);
if(address == (void*)-1)
{
perror("mmap fail!");
exit(1);
}
for(char c='A';c<='Z';c++)
{
((char*)address)[c - 'A'] = c;
}
close(fd);
return 0;
}
munmap
当然这里会有些安全方面的问题,我们使用完我们的mmap要进行我们mmap的回收,这里我们使用我们的munmap Linux与 Unix
man munmap
函数原型
int munmap(void *addr, size_t length);
参数介绍
addr: 我们刚开始返回的虚拟地址的起始地址。
length:我们该文件的长度。当然这里我们是配合我们对应的fstat使用的。 计算机驱动器和存储设备
我们知道我们对应的长度的情况:
#include "commit.hpp"
#define LEN_MAX 4096
int main()
{
int fd = open("test.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
// 重点:用 LEN_MAX(4096),严禁SIZE_MAX
ftruncate(fd, LEN_MAX);
void* address = mmap(nullptr,LEN_MAX, PROT_WRITE|PROT_READ, MAP_SHARED,fd, 0);
if(address == (void*)-1)
{
perror("mmap fail!");
exit(1);
}
for(char c='A';c<='Z';c++)
{
((char*)address)[c - 'A'] = c;
}
munmap(address, LEN_MAX);
close(fd);
return 0;
}
我们使用我们的fstat
#include "commit.hpp"
#define LEN_MAX 4096
int main()
{
int fd = open("test.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
// 重点:用 LEN_MAX(4096),严禁SIZE_MAX
ftruncate(fd, LEN_MAX);
void* address = mmap(nullptr,LEN_MAX, PROT_WRITE|PROT_READ, MAP_SHARED,fd, 0);
if(address == (void*)-1)
{
perror("mmap fail!");
exit(1);
}
for(char c='A';c<='Z';c++)
{
((char*)address)[c - 'A'] = c;
}
struct stat statbuf;
fstat(fd,&statbuf);
munmap(address, statbuf.st_size);
close(fd);
return 0;
}
使用我们的mmap模拟实现我们malloc
模拟实现我们的malloc
mymalloc.hpp
#pragma once
#include<cstdio>
#include<iostream>
#include<sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define SPA_SIZE_MAX 4096
class my_malloc
{
public:
my_malloc(int size):_spa_size(size),_addr(nullptr),_fd(-1)
{
_fd = open("malloc.txt", O_RDWR|O_CREAT, 0666);
if(_fd<0)
{
perror("open fail");
exit(1);
}
if(ftruncate(_fd, _spa_size)==-1)
{
perror("fteuncate fail");
exit(2);
}
_addr=mmap(nullptr,size, PROT_WRITE| PROT_READ,MAP_SHARED,_fd,0);
if(_addr==(void*)-1)
{
perror("mmap fail");
exit(3);
}
}
void* get_addr()
{
return _addr;
}
~my_malloc()
{
struct stat statbuf;
if(fstat(_fd,&statbuf)==-1)
{
perror("fstat fail");
exit(4);
}
if(munmap(_addr,statbuf.st_size)==-1)
{
perror("mummap fail");
exit(5);
}
}
private:
int _spa_size;
void* _addr;
int _fd;
};
mymalloc.cc
#include "mymalloc.hpp"
int main()
{
my_malloc mc(1024);
void* add = mc.get_addr();
for(char c= 'A'; c<='Z';c++)
{
((char*)add)[c-'A'] =c;
}
return 0;
}
我们运行查看
我们发现我们果然模拟实现了我们的malloc
————————————————
版权声明:本文为CSDN博主「于此止?」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/buhuizhiyuci/article/details/163638785