使用mmap写入文件的原子性及日志文件系统下的数据恢复注意事项
用mmap配合日志型文件系统实现数据可恢复性的预防措施
这是个非常实用的问题——当你用MAP_SHARED的mmap修改文件,同时依赖ext4这类日志型文件系统(data=ordered或data=journal模式)保证可恢复性时,得注意以下几个关键预防措施:
先确保文件大小变更的持久性
示例里的ftruncate调用会调整文件大小,但这个操作本身需要被日志记录。在调用mmap之前,一定要对文件描述符执行fsync(fd),确保文件大小的变更已经被写入文件系统日志并持久化。如果跳过这一步,崩溃后文件可能会回到原来的大小,导致你后续通过mmap写入的数据完全丢失或损坏。显式同步映射区域的修改
MAP_SHARED模式下内核会异步将映射区域的修改写回磁盘,但这个时机完全由内核控制,崩溃时数据可能还停留在页缓存里。要保证修改被安全持久化,必须显式调用:msync(data, size, MS_SYNC | MS_INVALIDATE);或者也可以用
fsync(fd)(针对整个文件),但msync更精准地针对你修改的映射区域。这一步会触发日志型文件系统将数据(或元数据,取决于模式)写入journal,确保崩溃后可以恢复。处理应用层数据结构的原子性
日志型文件系统只能保证文件系统层面的一致性(比如文件不会变成损坏的状态),但无法保证你写入的struct data内部字段的一致性。比如如果你先修改data->field1,再修改data->field2,崩溃时可能只有field1被写入。解决这个问题的常见方法是:- 引入版本号字段:先写入完整的新数据副本(比如写到文件的备用区域),更新版本号,最后同步版本号;
- 使用双缓冲区映射:先写临时缓冲区,同步后再切换到主缓冲区的指针,确保每次更新都是原子的。
正确管理文件描述符与映射的生命周期
- 不要在
mmap后立刻关闭文件描述符fd——虽然关闭fd不会失效映射,但后续你需要用它来执行fsync或者做其他文件操作; - 在调用
munmap卸载映射前,务必先执行msync或fsync,确保所有未提交的修改都已经被持久化; - 如果是创建新文件(用
O_CREAT打开),要在ftruncate和mmap前执行fsync(fd),确保文件的创建元数据已经被日志记录,避免崩溃后文件消失。
- 不要在
适配不同的日志模式
- 对于
data=journal模式:内核会把数据和元数据都写入journal,安全性更高,但性能稍差。此时仍需显式同步,确保journal中的数据被提交到磁盘(内核默认的journal提交有延迟); - 对于
data=ordered模式:内核只会将元数据写入journal,数据会先被写入数据区再更新元数据。这种模式下,显式同步msync/fsync尤为重要,能避免“丢失写”问题——即元数据被日志记录但数据还没写盘的情况。
- 对于
内容的提问来源于stack exchange,提问作者Jérôme Pouiller
相关产品推荐
相关产品推荐

