绕过文件系统直接读写NVMe盘的方法
绕过文件系统直接访问NVMe存储设备可以消除文件系统开销,实现最高性能的裸设备访问。以下是不同操作系统下的实现方法。
Linux系统下的直接访问
1. 通过设备文件直接访问
Linux将NVMe设备表示为块设备文件(如/dev/nvme0n1
),可以直接读写:
#include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #define BLOCK_SIZE 4096 #define BLOCK_COUNT 1024 int main() { int fd = open("/dev/nvme0n1", O_RDWR | O_DIRECT); if (fd < 0) { perror("无法打开NVMe设备"); return 1; } // 分配对齐的内存缓冲区(O_DIRECT要求) void *buffer; posix_memalign(&buffer, BLOCK_SIZE, BLOCK_SIZE * BLOCK_COUNT); // 从设备读取数据 ssize_t ret = read(fd, buffer, BLOCK_SIZE * BLOCK_COUNT); if (ret < 0) { perror("读取失败"); } // 向设备写入数据 ret = write(fd, buffer, BLOCK_SIZE * BLOCK_COUNT); if (ret < 0) { perror("写入失败"); } free(buffer); close(fd); return 0; }
2. 使用Linux NVMe ioctl接口
Linux内核提供了专门的NVMe ioctl接口进行更底层的控制:
#include <linux/nvme_ioctl.h> #include <sys/ioctl.h> struct nvme_user_io { __u8 opcode; __u8 flags; __u16 control; __u16 nblocks; __u16 rsvd; __u64 metadata; __u64 addr; __u64 slba; __u32 dsmgmt; __u32 reftag; __u16 apptag; __u16 appmask; }; int submit_nvme_io(int fd, struct nvme_user_io *io) { return ioctl(fd, NVME_IOCTL_SUBMIT_IO, io); }
3. 使用SPDK (Storage Performance Development Kit)
SPDK是Intel开发的高性能存储开发工具包,完全绕过内核:
#include "spdk/nvme.h" #include "spdk/env.h" static void read_complete(void *arg, const struct spdk_nvme_cpl *completion) { printf("读取操作完成\n"); } int main() { struct spdk_env_opts opts; spdk_env_opts_init(&opts); spdk_env_init(&opts); struct spdk_nvme_transport_id trid = {}; struct spdk_nvme_ctrlr *ctrlr; struct spdk_nvme_ns *ns; struct spdk_nvme_qpair *qpair; // 初始化NVMe控制器 trid.trtype = SPDK_NVME_TRANSPORT_PCIE; snprintf(trid.subnqn, sizeof(trid.subnqn), "%s", SPDK_NVMF_DISCOVERY_NQN); ctrlr = spdk_nvme_connect(&trid, NULL, 0); ns = spdk_nvme_ctrlr_get_ns(ctrlr, 1); qpair = spdk_nvme_ctrlr_alloc_io_qpair(ctrlr, NULL, 0); // 分配DMA兼容内存 void *buffer = spdk_dma_zmalloc(0x1000, 0x1000, NULL); // 提交读取命令 spdk_nvme_ns_cmd_read(ns, qpair, buffer, 0, // LBA起始地址 1, // 扇区数 read_complete, NULL, 0); // 处理完成事件 while (spdk_nvme_qpair_process_completions(qpair, 0) >= 0); spdk_dma_free(buffer); spdk_nvme_ctrlr_free_io_qpair(qpair); spdk_nvme_detach(ctrlr); return 0; }
Windows系统下的直接访问
1. 使用CreateFile API直接访问物理驱动器
#include <windows.h> #include <stdio.h> #define SECTOR_SIZE 512 int main() { HANDLE hDevice = CreateFileW(L"\\\\.\\PhysicalDrive0", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hDevice == INVALID_HANDLE_VALUE) { printf("无法打开设备: %d\n", GetLastError()); return 1; } // 设置对齐的缓冲区 DWORD bytesReturned; BOOL result; char *buffer = (char*)VirtualAlloc(NULL, SECTOR_SIZE * 8, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // 读取第一个扇区 LARGE_INTEGER li; li.QuadPart = 0; SetFilePointerEx(hDevice, li, NULL, FILE_BEGIN); result = ReadFile(hDevice, buffer, SECTOR_SIZE * 8, &bytesReturned, NULL); if (!result) { printf("读取失败: %d\n", GetLastError()); } VirtualFree(buffer, 0, MEM_RELEASE); CloseHandle(hDevice); return 0; }
2. 使用Windows NVMe驱动接口
通过DeviceIoControl与NVMe驱动通信:
#include <windows.h> #include <winioctl.h> #include <ntddscsi.h> #define NVME_PASS_THROUGH_IOCTL CTL_CODE(FILE_DEVICE_NVME, 0x801, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA) int main() { HANDLE hDevice = CreateFile(L"\\\\.\\PhysicalDrive0", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); NVME_PASS_THROUGH_COMMAND cmd = {0}; cmd.SRB.IOCTL = NVME_PASS_THROUGH_IOCTL; cmd.DataBufferLen = 4096; cmd.NVMeCmd[0] = 0x02; // 读取命令 DWORD bytesReturned; DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH, &cmd, sizeof(cmd), &cmd, sizeof(cmd), &bytesReturned, NULL); CloseHandle(hDevice); return 0; }
注意事项
-
权限要求:需要root/管理员权限
-
数据安全:直接操作可能破坏文件系统结构
-
内存对齐:直接I/O要求内存缓冲区对齐(通常4KB)
-
性能考虑:
-
使用多队列提高并行性
-
考虑NUMA亲和性
-
使用轮询模式避免中断开销
-
-
高级工具:
-
nvme-cli (Linux): 提供命令行接口直接与NVMe设备交互
-
WinNVMe (Windows): 提供NVMe设备管理功能
-
nvme read /dev/nvme0n1 -s 0 -z 4096 -c 1 -d output.bin
-
绕过文件系统直接访问NVMe设备可以获得最高性能,但需要谨慎操作以避免数据损坏。对于生产环境,建议使用成熟的框架如SPDK而不是直接裸设备操作。