mmap与LRU职责不同:mmap负责整块文件页映射,不支持key粒度卸载;LRU管理逻辑缓存项生命周期,不能直接缓存mmap裸指针,否则引发悬垂指针或页表泄漏,须分层设计并引入引用计数式mmap管理器。

为什么 mmap + LRU 组合不是“天然好配”
mmap 本身不管理缓存生命周期,它只是把文件页映射进虚拟地址空间;LRU 管理的是“哪些键值该留、哪些该踢”,但 mmap 映射的是整块文件区域,没法按 key 粒度卸载。两者职责不同:mmap 负责“怎么读得快”,LRU 负责“读哪些、留多久”。硬凑容易出问题——比如用 mmap 映射整个 GB 日志,再套个 LRU 去“淘汰某几行”,实际根本删不掉物理映射页,只会漏掉逻辑引用。
真正可行的 mmap + LRU 分层结构
必须分清楚哪层用 mmap、哪层用 LRU:
-
mmap仅用于**只读大文件的底层数据加载**:例如将一个 512MB 的索引文件一次性mmap,返回const char*地址供后续随机查找 -
LRU仅作用于**上层逻辑缓存项**:比如缓存的是std::string pattern → std::vector<off_t> positions</off_t>这类搜索结果,而不是原始文件内容本身 - 绝不把
mmap返回的指针直接塞进 LRU 缓存容器——那会引发悬垂指针(munmap 后还拿着地址)或页表泄漏(重复 mmap 不释放旧映射)
关键实操:如何安全复用 mmap 区域而不崩
多个 LRU 缓存项可能查同一份 mmap 数据,但不能各自 mmap/munmap。正确做法是引入引用计数式 mmap 管理器:
- 用
std::unordered_map<:string mappedfile></:string>全局缓存文件路径 → 映射信息(含addr、size、fd、引用计数) - 每次 get 操作先查路径是否存在;存在则引用计数 +1,直接复用
addr - put 或析构时引用计数 -1,归零才调
munmap(addr, size)+close(fd) - 映射时必须检查
mmap返回值是否为MAP_FAILED,否则后续解引用必Segmentation fault
LRU 缓存值里存什么?绝对别存裸指针
如果你的 LRU 缓存 value 类型是 std::string 或 std::vector,没问题;但如果想存“某段 mmap 区域里的子串”,必须拷贝出来:
- 错误写法:
cache.put("key", (char*)mapped_addr + offset)—— 这个char*没所有权,LRU 释放后指针就野了 - 正确写法:
cache.put("key", std::string((char*)mapped_addr + offset, len)),或用std::string_view(但仅限该 mmap 生命周期内有效,且需确保 LRU 不跨线程持有) - 若坚持零拷贝,value 类型应封装为
struct MappedSlice { const char* ptr; size_t len; std::string file_path; },并在析构时触发引用计数减一
最易被忽略的一点:mmap 映射的文件如果被外部进程截断或删除,Linux 下映射区域仍可读(页已加载),但新访问可能触发 SIGBUS;LRU 层完全感知不到——你得在 mmap 封装层加 stat() 校验或监听 inotify 事件,而不是指望 LRU 自己发现。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











