pmem需配置为appdirect模式并用memorymappedfile+file_flag_no_buffering直写,写后必须调用flushfilebuffers;refs+dax下还需setfileintegritylevel启用原子写。

PMEM 设备必须先配置为 AppDirect 模式才能被 C# 用作文件存储
Windows 下直接把 NVDIMM 当成普通 SSD 用,FileStream 写进去照样慢——因为默认是 Memory Mode,整块内存被当成易失性 RAM,断电即丢,且不走持久化路径。AppDirect 模式才是关键,它让系统暴露一块“字节寻址、持久化、可 mmap”的裸地址空间。
实操建议:
- 用
ipmctl命令确认模式:ipmctl show -memoryresources,输出中需含AppDirectCapacity且值 > 0 - 若为 Memory Mode,需清空配置并重设:
ipmctl delete -f -dimm→ipmctl create -f -goal PersistentMemoryType=AppDirect→ 重启 - Windows 中会识别为一个或多个“非易失性内存”磁盘(如
D:),格式化为 NTFS 或 ReFS(推荐 ReFS,对持久内存元数据更友好)
用 MemoryMappedFile + FILE_FLAG_NO_BUFFERING 绕过页缓存直写 PMEM
普通 FileStream 默认走系统页缓存,写入 PMEM 时多了一层拷贝和延迟,无法发挥字节寻址优势。真正低延迟持久写必须绕过缓存,并保证对齐。
实操建议:
- 创建映射文件时指定
PageReadAccess或PageReadWrite,但底层文件需以FILE_FLAG_NO_BUFFERING打开(C# 中需 P/InvokeCreateFile获取句柄传给MemoryMappedFile.CreateFromFile) - 所有读写偏移和长度必须是磁盘扇区大小(通常 512 字节)的整数倍;缓冲区地址也需按 512 字节对齐(用
Marshal.AllocHGlobal+VirtualAlloc更稳妥) - 写完后调用
FlushViewOfFile+FlushFileBuffers(后者在 AppDirect 卷上才真正触发持久化到 NAND)
Write-Through 不等于“立即落盘”,C# 需显式调用 FlushFileBuffers
即使开了 FILE_FLAG_WRITE_THROUGH,Windows 仍可能将数据暂存在设备内部缓存(如 NVDIMM 的 DRAM 缓存区)。断电前未刷出,数据就丢了——这不是 C# 的 bug,而是硬件行为。
常见错误现象:
- 进程退出前没调
FlushFileBuffers,关机后文件内容回退到上次刷盘状态 - 用
FileStream.Flush()无效:它只刷 .NET 缓冲区,不触达内核/硬件 - 在非 AppDirect 卷(如 Memory Mode 或普通 SSD)上调用
FlushFileBuffers无实际持久效果
正确做法:每次关键写入后,用 P/Invoke 调 FlushFileBuffers;或封装成 IDisposable 在 Dispose 中强制刷一次。
ReFS 卷 + SetFileIntegrityLevel 可启用硬件级原子写,但 C# 无直接封装
Windows 10/Server 2016+ 的 ReFS 支持“原子写”(Atomic Write),配合支持 DAX(Direct Access)的 NVDIMM,能实现单指令级的持久化,避免传统 write+flush 的两步开销。但 .NET Runtime 完全没暴露这个能力。
使用场景与限制:
- 仅限 ReFS 格式化、挂载为 DAX 模式(
fsutil behavior set disablelastaccess 1+format /fs:refs /q /dax)的卷 - 必须用
SetFileIntegrityLevel(kernel32.dll)设置文件为INTEGRITY_LEVEL_SIGNED或更高,否则原子写不生效 - C# 需 P/Invoke
CreateFile、SetFileIntegrityLevel、WriteFile全链路控制,FileStream和MemoryMappedFile均不支持该语义
这一步最容易被跳过:没设 integrity level,哪怕硬件和文件系统都支持,写的仍是普通非原子流。










