<摘要>
mmap(内存映射)是Unix/Linux系统中一个强大的系统调用,它像一座神奇的桥梁,将磁盘文件直接映射到进程的虚拟地址空间。通过mmap,程序可以像访问普通内存一样读写文件,避免了繁琐的read/write系统调用,极大地提升了I/O性能。mmap不仅用于文件操作,还在进程间通信、内存管理等领域发挥着重要作用。本文将用生动的比喻和实际案例,带你深入理解这个强大的系统工具。
<正文>
1. 基本概念与用途:内存与文件的完美融合
想象一下,你正在阅读一本厚重的百科全书。传统的方式是:每次需要查阅某个条目时,你都要翻到对应的页码,阅读内容,然后合上书。这种方式就像传统的文件I/O——每次都需要显式地读取数据。
而现在,有一种魔法:你可以把整本书"映射"到你的大脑中,随时直接访问任何页面的内容,就像这些知识已经存在于你的记忆中一样。这就是mmap的魔力!
生动比喻:图书馆与个人书房
传统文件I/O(read/write):
- 就像在公共图书馆看书
- 每次想看书都要去图书馆借阅(read)
- 看完后要归还(文件位置管理)
- 修改内容需要重新抄写(write)
内存映射(mmap):
- 就像把图书馆的书复制到你的个人书房
- 书一直在你手边,随时可以翻阅
- 可以在书上直接做笔记(修改文件)
- 当你决定保存时,书房的书会自动同步回图书馆
常见使用场景
- 高性能文件I/O:处理大文件时提供接近内存的访问速度
- 进程间通信:多个进程共享同一内存映射区域
- 动态链接库加载:系统加载共享库的主要机制
- 内存数据库:Redis等数据库的核心技术
- 零拷贝网络传输:高性能网络编程
- 大数据处理:处理超出物理内存的大型文件
2. 函数声明与来源
mmap函数定义在sys/mman.h头文件中,属于POSIX标准的一部分。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
这个函数调用建立了一个内存映射,让我们用mermaid图来理解它的核心机制:
3. 参数详解:构建映射的六个关键要素
参数1:addr - 期望的映射地址
- 类型:
void* - 含义:建议的映射起始地址
- 常见取值:通常设为
NULL(由系统自动选择) - 实际意义:“我希望映射到这个地址,但你可以调整”
参数2:length - 映射长度
- 类型:
size_t - 含义:要映射的字节数
- 实际意义:需要多大的"书房空间"
- 重要提示:长度必须是系统页大小的整数倍
参数3:prot - 保护权限
- 类型:
int - 含义:内存区域的访问权限
- 常见取值:
PROT_READ:可读PROT_WRITE:可写PROT_EXEC:可执行PROT_NONE:不可访问
参数4:flags - 映射类型和选项
- 类型:
int - 含义:控制映射行为的标志
- 关键选项:
MAP_SHARED:修改会写回文件,进程间共享MAP_PRIVATE:写时复制,修改不影响原文件MAP_ANONYMOUS:匿名映射(不基于文件)MAP_FIXED:强制使用指定地址
参数5:fd - 文件描述符
- 类型:
int - 含义:要映射的文件描述符
- 特殊值:对于匿名映射,设为-1
- 实际意义:要映射的"书"的借阅证
参数6:offset - 文件偏移量
- 类型:
off_t - 含义:文件中映射起始位置的偏移
- 重要要求:必须是系统页大小的整数倍
- 实际意义:从书的第几页开始映射
4. 返回值的含义:映射成功的钥匙
成功情况
返回映射区域的起始地址,这个指针可以像普通内存指针一样使用。
失败情况
返回MAP_FAILED(通常是(void*)-1),并设置errno指示错误原因。
常见错误码
EACCES:文件不可访问EAGAIN:文件已被锁定EBADF:无效的文件描述符EINVAL:无效的参数ENOMEM:没有足够的内存
让我们通过一个更详细的流程图来理解mmap的完整生命周期:
5. 实例与应用场景:让理论落地生根
案例1:高性能文件编辑器
应用场景:开发一个需要快速处理大文件的文本编辑器,传统逐行读取的方式在处理大文件时性能较差。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
/**
* @brief 使用mmap快速搜索文件内容
*
* 该函数演示如何使用mmap将整个文件映射到内存,
* 然后在内存中快速搜索目标字符串,避免多次read调用。
*
* @in:
* - filename: 要搜索的文件名
* - search_str: 要搜索的字符串
*
* @out:
* - 返回匹配位置的指针,未找到返回NULL
*
* 返回值说明:
* 成功找到返回位置指针,失败返回NULL
*/
char* mmap_search_file(const char* filename, const char* search_str) {
int fd = -1;
void* mapped = NULL;
struct stat sb;
// 打开文件
fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("open");
return NULL;
}
// 获取文件信息
if (fstat(fd, &sb) == -1) {
perror("fstat");
close(fd);
return NULL;
}
// 检查文件大小
if (sb.st_size == 0) {
printf("文件为空\n");
close(fd);
return NULL;
}
// 创建内存映射
mapped = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap");
close(fd);
return NULL;
}
printf("成功映射文件 %s, 大小: %ld bytes\n", filename, sb.st_size);
// 在映射的内存中搜索字符串
char* found = memmem(mapped, sb.st_size, search_str, strlen(search_str));
if (found) {
printf("找到字符串 '%s' 在偏移量 %ld 处\n",
search_str, (found - (char*)mapped));
} else {
printf("未找到字符串 '%s'\n", search_str);
}
// 清理资源
munmap(mapped, sb.st_size);
close(fd);
return found;
}
/**
* @brief 使用mmap实现文件拷贝
*
* 通过内存映射实现高效的文件拷贝,特别适合大文件。
* 避免了在用户态和内核态之间多次拷贝数据。
*/
int mmap_copy_file(const char* src_file, const char* dst_file) {
int src_fd = -1, dst_fd = -1;
void *src_map = NULL, *dst_map = NULL;
struct stat sb;
// 打开源文件
src_fd = open(src_file, O_RDONLY);
if (src_fd == -1) {
perror("open source");
return -1;
}
// 获取源文件大小
if (fstat(src_fd, &sb) == -1) {
perror("fstat source");
close(src_fd);
return -1;
}
if (sb.st_size == 0) {
printf("源文件为空\n");
close(src_fd);
return 0;
}
// 创建目标文件并设置大小
dst_fd = open(dst_file, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (dst_fd == -1) {
perror("open destination");
close(src_fd);
return -1;
}
// 设置目标文件大小
if (ftruncate(dst_fd, sb.st_size) == -1) {
perror("ftruncate");
close(src_fd);
close(dst_fd);
return -1;
}
// 映射源文件
src_map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0);
if (src_map == MAP_FAILED) {
perror("mmap source");
close(src_fd);
close(dst_fd);
return -1;
}
// 映射目标文件
dst_map = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, dst_fd, 0);
if (dst_map == MAP_FAILED) {
perror("mmap destination");
munmap(src_map, sb.st_size);
close(src_fd);
close(dst_fd);
return -1;
}
// 拷贝数据(内存到内存,非常快!)
memcpy(dst_map, src_map, sb.st_size);
// 确保数据写入磁盘
if (msync(dst_map, sb.st_size, MS_SYNC) == -1) {
perror("msync");
}
printf("成功拷贝文件 %s -> %s, 大小: %ld bytes\n",
src_file, dst_file, sb.st_size);
// 清理资源
munmap(src_map, sb.st_size);
munmap(dst_map, sb.st_size);
close(src_fd);
close(dst_fd);
return 0;
}
int main() {
printf("mmap文件操作演示\n");
printf("================\n\n");
// 创建测试文件
FILE* test_file = fopen("test_data.txt", "w");
if (test_file) {
for (int i = 0; i < 100; i++) {
fprintf(test_file, "这是第%d行数据,包含一些测试内容\n", i);
}
fprintf(test_file, "特殊标记: HELLO_MMAP\n");
fclose(test_file);
printf("创建测试文件 test_data.txt\n");
}
// 演示文件搜索
printf("\n1. 文件搜索演示:\n");
mmap_search_file("test_data.txt", "HELLO_MMAP");
mmap_search_file("test_data.txt", "不存在的字符串");
// 演示文件拷贝
printf("\n2. 文件拷贝演示:\n");
mmap_copy_file("test_data.txt", "copy_of_test_data.txt");
// 验证拷贝结果
printf("\n3. 验证拷贝结果:\n");
mmap_search_file("copy_of_test_data.txt", "HELLO_MMAP");
return 0;
}
编译与运行:
创建Makefile:
CC = gcc
CFLAGS = -Wall -g -O2
TARGET = mmap_demo
SOURCES = mmap_demo.c
all: $(TARGET)
$(TARGET): $(SOURCES)
$(CC) $(CFLAGS) -o $(TARGET) $(SOURCES)
clean:
rm -f $(TARGET) test_data.txt copy_of_test_data.txt
run: $(TARGET)
./$(TARGET)
.PHONY: all clean run
编译方法:
make
运行程序:
./mmap_demo
运行结果解读:
mmap文件操作演示
================
创建测试文件 test_data.txt
1. 文件搜索演示:
成功映射文件 test_data.txt, 大小: 3500 bytes
找到字符串 'HELLO_MMAP' 在偏移量 3400 处
成功映射文件 test_data.txt, 大小: 3500 bytes
未找到字符串 '不存在的字符串'
2. 文件拷贝演示:
成功拷贝文件 test_data.txt -> copy_of_test_data.txt, 大小: 3500 bytes
3. 验证拷贝结果:
成功映射文件 copy_of_test_data.txt, 大小: 3500 bytes
找到字符串 'HELLO_MMAP' 在偏移量 3400 处
案例2:进程间共享内存通信
应用场景:开发一个需要高性能进程间通信的系统,多个进程需要共享大量数据。传统的管道或消息队列在数据量大时性能较差。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
/**
* @brief 共享内存数据结构
*
* 定义共享内存区域的数据结构,包含通信协议和实际数据。
* 多个进程通过访问这个结构来实现数据共享。
*/
typedef struct {
int data_ready; // 数据就绪标志
int counter; // 计数器
char message[256]; // 消息缓冲区
} shared_data_t;
volatile sig_atomic_t keep_running = 1;
void signal_handler(int sig) {
keep_running = 0;
}
/**
* @brief 创建共享内存的写入进程
*
* 该进程负责创建共享内存区域并定期更新数据。
* 演示了MAP_SHARED和MAP_ANONYMOUS的使用。
*/
void writer_process() {
printf("写入进程启动 (PID: %d)\n", getpid());
// 创建共享内存映射(匿名映射,不基于文件)
shared_data_t* shared = mmap(NULL, sizeof(shared_data_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
if (shared == MAP_FAILED) {
perror("mmap");
exit(1);
}
// 初始化共享数据
memset(shared, 0, sizeof(shared_data_t));
shared->data_ready = 0;
shared->counter = 0;
printf("共享内存创建成功,开始写入数据...\n");
printf("按Ctrl+C停止写入进程\n\n");
// 注册信号处理
signal(SIGINT, signal_handler);
// 定期更新共享数据
while (keep_running) {
shared->counter++;
snprintf(shared->message, sizeof(shared->message),
"消息 #%d from PID %d", shared->counter, getpid());
// 设置数据就绪标志
shared->data_ready = 1;
printf("写入: %s\n", shared->message);
// 等待读取进程处理
sleep(2);
// 重置标志,等待下一次写入
shared->data_ready = 0;
sleep(1);
}
printf("\n写入进程退出\n");
// 清理资源
munmap(shared, sizeof(shared_data_t));
}
/**
* @brief 读取共享内存的进程
*
* 该进程通过fork创建,与写入进程共享内存区域。
* 演示了进程间通过mmap共享内存的机制。
*/
void reader_process() {
printf("读取进程启动 (PID: %d)\n", getpid());
// 使用相同的映射参数访问共享内存
shared_data_t* shared = mmap(NULL, sizeof(shared_data_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
if (shared == MAP_FAILED) {
perror("mmap");
exit(1);
}
printf("成功连接到共享内存,等待数据...\n\n");
int last_counter = -1;
while (keep_running) {
if (shared->data_ready) {
if (shared->counter != last_counter) {
printf("读取: %s (计数器: %d)\n",
shared->message, shared->counter);
last_counter = shared->counter;
}
}
usleep(100000); // 100ms
}
printf("读取进程退出\n");
munmap(shared, sizeof(shared_data_t));
}
/**
* @brief 基于文件的共享内存演示
*
* 使用实际文件作为共享内存的备份,即使进程完全退出,
* 其他进程仍然可以通过文件访问共享数据。
*/
void file_backed_shared_memory() {
const char* shm_file = "/tmp/shm_demo";
int fd;
shared_data_t* shared;
printf("\n基于文件的共享内存演示:\n");
// 创建或打开共享文件
fd = open(shm_file, O_RDWR | O_CREAT, 0644);
if (fd == -1) {
perror("open");
return;
}
// 设置文件大小
if (ftruncate(fd, sizeof(shared_data_t)) == -1) {
perror("ftruncate");
close(fd);
return;
}
// 映射文件到内存
shared = mmap(NULL, sizeof(shared_data_t),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (shared == MAP_FAILED) {
perror("mmap");
close(fd);
return;
}
// 初始化或使用现有数据
if (shared->counter == 0) {
// 首次初始化
strcpy(shared->message, "初始消息");
shared->counter = 1;
printf("初始化共享数据\n");
} else {
// 使用现有数据
shared->counter++;
printf("读取现有数据并更新: %s (计数器: %d)\n",
shared->message, shared->counter);
}
// 更新消息
snprintf(shared->message, sizeof(shared->message),
"文件备份共享内存 #%d", shared->counter);
printf("当前数据: %s\n", shared->message);
// 确保数据写入文件
msync(shared, sizeof(shared_data_t), MS_SYNC);
// 清理
munmap(shared, sizeof(shared_data_t));
close(fd);
}
int main() {
printf("mmap进程间通信演示\n");
printf("==================\n\n");
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
// 子进程 - 读取者
reader_process();
} else {
// 父进程 - 写入者
printf("父进程PID: %d, 子进程PID: %d\n", getpid(), pid);
sleep(1); // 确保读取进程先启动
writer_process();
// 等待子进程结束
wait(NULL);
}
// 演示文件备份的共享内存
file_backed_shared_memory();
return 0;
}
程序流程图:
案例3:内存池分配器
应用场景:开发高性能服务器,需要频繁分配和释放小内存块。传统malloc/free在频繁操作时性能较差,且容易产生内存碎片。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
/**
* @brief 内存块头结构
*
* 每个分配的内存块都包含这个头结构,用于管理内存块的状态。
*/
typedef struct mem_block {
size_t size; // 块大小(不包括头)
int free; // 是否空闲
struct mem_block* next; // 下一个块
} mem_block_t;
#define POOL_SIZE (1024 * 1024) // 1MB内存池
#define BLOCK_MIN_SIZE 16 // 最小分配块大小
/**
* @brief 内存池分配器
*
* 使用mmap创建大块内存,然后在其上实现自定义的内存分配管理。
* 减少系统调用次数,提高内存分配性能。
*/
typedef struct {
void* pool; // 内存池起始地址
size_t pool_size; // 内存池总大小
mem_block_t* free_list; // 空闲链表
} mem_pool_t;
/**
* @brief 初始化内存池
*
* 使用mmap分配一大块内存作为内存池,然后初始化管理结构。
*
* @param size 内存池大小
* @return mem_pool_t* 内存池指针
*/
mem_pool_t* mem_pool_create(size_t size) {
mem_pool_t* pool = malloc(sizeof(mem_pool_t));
if (!pool) {
return NULL;
}
// 使用mmap分配内存池
pool->pool = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (pool->pool == MAP_FAILED) {
free(pool);
perror("mmap");
return NULL;
}
pool->pool_size = size;
// 初始化第一个内存块
mem_block_t* first_block = (mem_block_t*)pool->pool;
first_block->size = size - sizeof(mem_block_t);
first_block->free = 1;
first_block->next = NULL;
pool->free_list = first_block;
printf("创建内存池: %p, 大小: %zu bytes\n", pool->pool, size);
return pool;
}
/**
* @brief 从内存池分配内存
*
* 实现首次适应算法,在内存池中寻找合适的空闲块进行分配。
*
* @param pool 内存池
* @param size 请求的字节数
* @return void* 分配的内存地址
*/
void* mem_pool_alloc(mem_pool_t* pool, size_t size) {
if (!pool || size == 0) {
return NULL;
}
// 对齐到8字节边界
size = (size + 7) & ~7;
mem_block_t* current = pool->free_list;
mem_block_t* prev = NULL;
// 首次适应算法
while (current) {
if (current->free && current->size >= size) {
// 找到合适的块
if (current->size >= size + sizeof(mem_block_t) + BLOCK_MIN_SIZE) {
// 分割块
mem_block_t* new_block = (mem_block_t*)((char*)current + sizeof(mem_block_t) + size);
new_block->size = current->size - size - sizeof(mem_block_t);
new_block->free = 1;
new_block->next = current->next;
current->size = size;
current->next = new_block;
}
current->free = 0;
// 从空闲链表移除
if (prev) {
prev->next = current->next;
} else {
pool->free_list = current->next;
}
printf("分配内存: %p, 大小: %zu bytes\n",
(void*)(current + 1), current->size);
return (void*)(current + 1);
}
prev = current;
current = current->next;
}
printf("内存分配失败: 请求大小 %zu bytes\n", size);
return NULL;
}
/**
* @brief 释放内存回内存池
*
* 释放内存块并尝试合并相邻的空闲块,减少内存碎片。
*
* @param pool 内存池
* @param ptr 要释放的内存指针
*/
void mem_pool_free(mem_pool_t* pool, void* ptr) {
if (!pool || !ptr) {
return;
}
mem_block_t* block = (mem_block_t*)ptr - 1;
printf("释放内存: %p, 大小: %zu bytes\n", ptr, block->size);
block->free = 1;
// 合并相邻的空闲块
mem_block_t* current = (mem_block_t*)pool->pool;
while (current) {
if (current->free && current->next && current->next->free) {
// 合并当前块和下一个块
current->size += sizeof(mem_block_t) + current->next->size;
current->next = current->next->next;
} else {
current = current->next;
}
}
// 简化:将释放的块添加到空闲链表头部
block->next = pool->free_list;
pool->free_list = block;
}
/**
* @brief 销毁内存池
*
* 释放mmap分配的内存和内存池管理结构。
*
* @param pool 内存池指针
*/
void mem_pool_destroy(mem_pool_t* pool) {
if (!pool) {
return;
}
if (pool->pool && pool->pool != MAP_FAILED) {
munmap(pool->pool, pool->pool_size);
printf("释放内存池: %p\n", pool->pool);
}
free(pool);
}
/**
* @brief 显示内存池状态
*
* 遍历内存池中的所有块,显示每个块的状态信息。
*
* @param pool 内存池
*/
void mem_pool_status(const mem_pool_t* pool) {
if (!pool) {
return;
}
printf("\n内存池状态:\n");
printf("池地址: %p, 总大小: %zu bytes\n", pool->pool, pool->pool_size);
mem_block_t* current = (mem_block_t*)pool->pool;
size_t total_used = 0;
size_t total_free = 0;
int block_count = 0;
while (current) {
printf("块 %d: 地址 %p, 大小 %zu, 状态 %s\n",
block_count, (void*)current, current->size,
current->free ? "空闲" : "使用中");
if (current->free) {
total_free += current->size;
} else {
total_used += current->size;
}
block_count++;
current = current->next;
}
printf("统计: 总块数 %d, 使用中 %zu bytes, 空闲 %zu bytes, 元数据 %zu bytes\n",
block_count, total_used, total_free,
block_count * sizeof(mem_block_t));
}
// 性能测试
void performance_test() {
printf("\n性能测试:\n");
printf("==========\n");
mem_pool_t* pool = mem_pool_create(1024 * 1024); // 1MB
if (!pool) {
return;
}
// 测试多次分配和释放
void* pointers[100];
// 分配测试
clock_t start = clock();
for (int i = 0; i < 100; i++) {
pointers[i] = mem_pool_alloc(pool, (i + 1) * 16);
}
clock_t alloc_time = clock() - start;
// 释放测试
start = clock();
for (int i = 0; i < 100; i++) {
if (pointers[i]) {
mem_pool_free(pool, pointers[i]);
}
}
clock_t free_time = clock() - start;
printf("分配100个块时间: %ld 微秒\n", alloc_time);
printf("释放100个块时间: %ld 微秒\n", free_time);
mem_pool_status(pool);
mem_pool_destroy(pool);
}
int main() {
printf("mmap内存池分配器演示\n");
printf("====================\n\n");
// 创建内存池
mem_pool_t* pool = mem_pool_create(POOL_SIZE);
if (!pool) {
return 1;
}
// 演示分配和释放
printf("\n1. 基本分配演示:\n");
void* ptr1 = mem_pool_alloc(pool, 100);
void* ptr2 = mem_pool_alloc(pool, 200);
void* ptr3 = mem_pool_alloc(pool, 50);
if (ptr1) memset(ptr1, 'A', 100);
if (ptr2) memset(ptr2, 'B', 200);
if (ptr3) memset(ptr3, 'C', 50);
mem_pool_status(pool);
printf("\n2. 释放和合并演示:\n");
mem_pool_free(pool, ptr2);
mem_pool_status(pool);
mem_pool_free(pool, ptr1);
mem_pool_status(pool);
printf("\n3. 重新分配演示:\n");
void* ptr4 = mem_pool_alloc(pool, 150);
if (ptr4) memset(ptr4, 'D', 150);
mem_pool_status(pool);
// 清理
mem_pool_free(pool, ptr3);
mem_pool_free(pool, ptr4);
// 性能测试
performance_test();
mem_pool_destroy(pool);
return 0;
}
时序图展示内存池操作:
6. 进阶技巧与最佳实践
性能优化技巧
- 正确设置映射标志:
// 对于只读文件,使用PRIVATE映射避免不必要的写回
void* map = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
// 对于需要写入的共享文件,使用SHARED映射
void* map = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- 使用大页(Huge Pages):
// 在支持大页的系统上,可以使用大页提高性能
void* map = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
- 对齐和大小调整:
// 确保映射大小和偏移是页大小的倍数
size_t page_size = getpagesize();
size_t map_size = (file_size + page_size - 1) & ~(page_size - 1);
off_t map_offset = offset & ~(page_size - 1);
错误处理最佳实践
void* create_mapping(const char* filename, size_t* mapped_size) {
int fd = open(filename, O_RDWR);
if (fd == -1) {
perror("open");
return MAP_FAILED;
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("fstat");
close(fd);
return MAP_FAILED;
}
if (sb.st_size == 0) {
fprintf(stderr, "文件为空\n");
close(fd);
return MAP_FAILED;
}
void* mapped = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap");
close(fd);
return MAP_FAILED;
}
*mapped_size = sb.st_size;
close(fd); // 映射建立后可以关闭文件描述符
return mapped;
}
安全考虑
- 边界检查:
// 总是检查访问是否在映射范围内
void safe_mem_access(void* mapped, size_t mapped_size, size_t offset, size_t len) {
if (offset + len > mapped_size) {
fprintf(stderr, "访问越界: offset=%zu, len=%zu, mapped_size=%zu\n",
offset, len, mapped_size);
return;
}
// 安全访问
char* data = (char*)mapped + offset;
// ... 使用data
}
- 权限管理:
// 使用mprotect动态更改权限
if (mprotect(mapped, size, PROT_READ) == -1) {
perror("mprotect");
}
7. 现代应用与发展
与相关技术的比较
| 技术 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| mmap | 零拷贝、高性能、易用 | 内存压力、truncate问题 | 大文件处理、IPC |
| read/write | 简单、可控 | 多次系统调用、数据拷贝 | 小文件、流式数据 |
| sendfile | 内核级零拷贝 | 只能文件到socket | 静态文件服务 |
| splice | 管道零拷贝 | 复杂的使用方式 | 网络代理、数据管道 |
C++现代封装
#include <memory>
#include <system_error>
class MappedFile {
private:
void* data_ = nullptr;
size_t size_ = 0;
public:
MappedFile(const std::string& filename, bool writable = false) {
// RAII封装,自动管理资源
int fd = open(filename.c_str(), writable ? O_RDWR : O_RDONLY);
if (fd == -1) {
throw std::system_error(errno, std::system_category());
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
close(fd);
throw std::system_error(errno, std::system_category());
}
size_ = sb.st_size;
int prot = writable ? (PROT_READ | PROT_WRITE) : PROT_READ;
int flags = writable ? MAP_SHARED : MAP_PRIVATE;
data_ = mmap(nullptr, size_, prot, flags, fd, 0);
close(fd);
if (data_ == MAP_FAILED) {
throw std::system_error(errno, std::system_category());
}
}
~MappedFile() {
if (data_ && data_ != MAP_FAILED) {
munmap(data_, size_);
}
}
// 禁止拷贝
MappedFile(const MappedFile&) = delete;
MappedFile& operator=(const MappedFile&) = delete;
// 允许移动
MappedFile(MappedFile&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
void* data() const { return data_; }
size_t size() const { return size_; }
// 同步数据到磁盘
void sync() {
if (msync(data_, size_, MS_SYNC) == -1) {
throw std::system_error(errno, std::system_category());
}
}
};
8. 总结
通过本文的详细讲解,相信你已经对mmap有了全面的理解。让我们用最后一个总结图来回顾mmap的核心特性:

mmap是Unix/Linux系统中一个极其强大的工具,它巧妙地利用了虚拟内存机制,在用户空间和内核空间之间架起了一座高效的桥梁。通过理解mmap的工作原理和最佳实践,你可以在适当的场景中充分发挥其性能优势。
记住,技术工具没有绝对的优劣,只有适用与否。mmap在处理大文件、需要频繁随机访问、或者进行进程间通信时表现出色,但在处理小文件或需要严格内存控制的场景中,传统的read/write可能更为合适。
掌握mmap,意味着你掌握了现代系统编程中的重要利器,能够在性能关键的应用中游刃有余。希望本文能帮助你在实际项目中更好地运用这个强大的工具!

1574

被折叠的 条评论
为什么被折叠?



