mmap不能直接防止断电丢失日志,因其仅映射文件到内存,写入操作修改页缓存,依赖内核writeback刷盘;断电时未刷盘的页缓存会丢失,即使调用msync(ms_sync),若存储设备未启用flush/barrier或禁用写缓存,仍可能丢数据。

为什么 mmap 不能直接防止断电丢失日志
mmap 本身不保证数据落盘,它只是把文件映射到内存地址空间;写入 mmap 区域相当于修改页缓存(page cache),仍依赖内核后续调用 writeback 刷盘。断电时未刷入磁盘的页缓存会丢失——哪怕你调用了 msync(MS_SYNC),如果底层存储设备禁用写缓存(或没启用 barrier / FLUSH 指令),依然可能丢最后几批数据。
所以关键不是“用不用 mmap”,而是:如何让 mmap + 文件 + 存储栈协同做到可恢复、不覆盖、有持久化语义。
必须配合 O_DSYNC 或 O_SYNC 打开日志文件
仅靠 mmap + msync 不够,因为 msync 只保证页缓存写入块设备队列,不等物理刷写完成。真正能约束落盘时机的是打开文件时的标志:
-
O_DSYNC:每次msync(MS_SYNC)返回前,确保本次写入的数据和元数据(如 mtime)已物理写入磁盘 -
O_SYNC:比O_DSYNC更严,还要求文件大小等元数据也落盘(对追加日志略重,但更稳妥)
示例打开方式:
int fd = open("/var/log/app.log", O_RDWR | O_CREAT | O_APPEND | O_DSYNC, 0644);
注意:O_APPEND 和 mmap 不能直接共用——mmap 写位置由指针偏移控制,append 模式下 write() 自动寻位,二者语义冲突。因此实际中要放弃 O_APPEND,改用显式维护写偏移(log_offset 变量)并原子更新。
如何用 mmap 实现环形缓冲区 + 偏移持久化
核心思路:把 mmap 区域当作固定大小的环形缓冲区,每次写日志先填 buffer,再用 msync 刷当前段,并用独立小文件(或 mmap 映射的 header 页)持久化最新写入位置。
常见错误是只 mmap 日志主体,却把 offset 存在普通变量里——断电后 offset 丢失,重启时无法知道从哪继续写,导致覆盖或跳空。
正确做法:
- 分配一块额外的 mmap 区域(例如 4KB)专用于存储结构体:
struct { uint64_t head; uint64_t tail; uint32_t magic; } - 每次提交一批日志后,先更新该结构体中的
tail,再msync这个 header 页(带MS_SYNC) - 启动时先读 header 页校验
magic,再用tail定位日志末尾
header mmap 示例:
int hdr_fd = open("/var/log/app.log.hdr", O_RDWR | O_CREAT, 0644);
ftruncate(hdr_fd, 4096);
void* hdr_ptr = mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, hdr_fd, 0);
// 更新后:
((log_hdr_t*)hdr_ptr)->tail = new_tail;
msync(hdr_ptr, 4096, MS_SYNC);
必须禁用 CPU/编译器重排序 + 使用 memory barrier
即使 header 更新了,编译器或 CPU 可能重排指令顺序,导致 msync 在结构体字段写入前就返回。C++ 中需显式插入屏障:
- 写 header 字段后,调用
std::atomic_thread_fence(std::memory_order_release) - 或者用
std::atomic<uint64_t></uint64_t>包裹tail,store 时指定memory_order_release - 对应地,启动时读取用
memory_order_acquire
否则,即使硬件刷盘成功,CPU 可能还没把 store 指令的结果写到 cache line,msync 就刷了个“旧值”。
另外,mmap 必须用 MAP_SHARED,且所有写操作都应通过指针而非 memcpy 等间接方式——后者可能绕过内存映射的写跟踪逻辑,导致 msync 失效。
最易被忽略的一点:SSD/NVMe 的写缓存默认开启,需确认设备支持 FLUSH 并启用(如 Linux 下 hdparm --flushcache /dev/sdX 不适用,应查 /sys/block/nvme0n1/queue/dax 和 rotational,并确保文件系统挂载含 barrier=1 或使用 XFS + logdev)。没有这层保障,前面所有软件层努力都可能白费。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











