windows下最可靠的磁盘序列号获取方式是调用deviceiocontrol发送ioctl_storage_query_property,需以管理员权限打开.physicaldrive0句柄,使用storage_device_property查询,返回utf-16le编码序列号并正确处理空格与截断。

GetDriveGeometry + IOCTL_STORAGE_QUERY_PROPERTY 是 Windows 上最可靠的磁盘序列号获取方式
直接调用 DeviceIoControl 发送 IOCTL_STORAGE_QUERY_PROPERTY 是目前 Windows 下获取真实硬件序列号(非卷标、非驱动器号)的唯一稳定途径。很多程序误用 GetVolumeInformation 或 WMI,结果拿到的是卷序列号(volume serial number),和硬盘物理标签完全无关。
关键点在于:必须以管理员权限打开物理设备句柄(如 \\.\PhysicalDrive0),且需使用 STORAGE_PROPERTY_ID.StorageDeviceProperty 配合 StorageAdapterProtocolSpecificProperty 才能绕过某些 NVMe 设备的访问限制。
-
CreateFile必须带GENERIC_READ和FILE_SHARE_READ | FILE_SHARE_WRITE,否则多数 SSD/NVMe 会返回ERROR_ACCESS_DENIED - 结构体
STORAGE_PROPERTY_QUERY的PropertyId设为StorageDeviceProperty,QueryType设为PropertyStandardQuery - 返回缓冲区需足够大(建议至少 1024 字节),否则
DeviceIoControl返回ERROR_INSUFFICIENT_BUFFER,需先调用一次获取所需大小
Linux 下用 ioctl(fd, HDIO_GET_IDENTITY, ...) 获取 ATA 硬盘信息失败怎么办
在 Linux 中对 /dev/sda 直接发 HDIO_GET_IDENTITY 往往失败,报错 Operation not permitted 或 Inappropriate ioctl for device —— 这不是权限问题,而是内核策略限制:从 2.6.35+ 起,默认禁止普通进程对块设备执行该 ioctl,尤其对 SATA/NVMe 设备无效。
真正可行的方式是读取 sysfs 接口:/sys/block/sda/device/model、/sys/block/sda/device/serial,或使用 smartctl -i /dev/sda(依赖 smartmontools)。若坚持用 ioctl,必须满足两个条件:
- 打开的是 ATA 设备原始节点(如
/dev/hdX,现代系统基本已废弃) - 进程需有
CAP_SYS_RAWIO能力(sudo setcap cap_sys_rawio+ep ./your_program),且内核未禁用CONFIG_ATA - NVMe 设备必须换用
nvme_ioctl_admin_cmd(需libnvme或自定义ioctl调用NVME_IOCTL_ADMIN_CMD)
跨平台 C++ 封装要注意 struct packing 和 ioctl 编号差异
Windows 的 IOCTL_STORAGE_QUERY_PROPERTY 和 Linux 的 HDIO_GET_IDENTITY 不仅语义不同,底层 ioctl 编号构造规则也完全不同:Windows 是 CTL_CODE(...) 宏生成的 32 位值,Linux 是通过 _IOR/_IOW 宏按 bit 位域拼出来的。硬写宏常量会导致编译或运行时静默失败。
更隐蔽的问题是结构体内存布局:hd_driveid_t 在 x86/x86_64 下默认对齐为 2 字节,而 Windows 的 STORAGE_DEVICE_DESCRIPTOR 要求 1 字节 packed。不加 #pragma pack(1) 或 __attribute__((packed)),DeviceIoControl 可能返回 ERROR_INVALID_PARAMETER,Linux 则可能读到错位字段。
- Windows 侧必须用
#pragma pack(push, 1)包裹所有 STORAGE_* 结构体声明 - Linux 侧若手动构造
hd_driveid_t,需确认gcc版本是否默认启用-mno-align-double(旧版本影响大) - 不要试图用同一段 C++ 代码同时编译 Windows/Linux ioctl 调用;应严格分离 #ifdef WIN32 / __linux__ 分支
序列号里混入空格、不可见字符、UTF-16 BOM 是常见陷阱
Windows 返回的 STORAGE_DEVICE_DESCRIPTOR 中 SerialNumberOffset 指向的字符串是 UTF-16LE 编码,且末尾不保证有

