商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > c++如何获取磁盘的序列号和硬件信息_ioctl用法【进阶】

c++如何获取磁盘的序列号和硬件信息_ioctl用法【进阶】

  发布于2026-07-18 阅读(0)

扫一扫,手机访问

Windows下最可靠的磁盘序列号获取方式是调用DeviceIoControl发送IOCTL_STORAGE_QUERY_PROPERTY,需以管理员权限打开.PhysicalDrive0句柄,使用STORAGE_DEVICE_PROPERTY查询,返回UTF-16LE编码序列号并正确处理空格与截断。

c++如何获取磁盘的序列号和硬件信息_ioctl用法【进阶】

先说一下核心结论:在Windows平台上,直接调用DeviceIoControl发送IOCTL_STORAGE_QUERY_PROPERTY,是目前获取真实硬件序列号(注意,不是卷标,也不是驱动器号)最稳定、最可靠的方式。很多程序误用了GetVolumeInformation或者WMI,结果拿到的其实是卷序列号(volume serial number),跟硬盘物理标签完全是两码事。

这里的关键在于:必须以管理员权限打开物理设备句柄(比如\\.\PhysicalDrive0),并且需要结合STORAGE_PROPERTY_ID.StorageDevicePropertyStorageAdapterProtocolSpecificProperty,才能绕过某些NVMe设备设置的访问限制。具体操作中有几个地方需要特别注意:

  • CreateFile必须带上GENERIC_READFILE_SHARE_READ | FILE_SHARE_WRITE,否则绝大多数SSD或NVMe设备会直接返回ERROR_ACCESS_DENIED,让你毫无办法。
  • 结构体STORAGE_PROPERTY_QUERYPropertyId要设为StorageDevicePropertyQueryType则设为PropertyStandardQuery,这是标准配置。
  • 返回的缓冲区需要足够大,建议至少预留1024字节。如果DeviceIoControl返回ERROR_INSUFFICIENT_BUFFER,那就先调用一次,拿到实际所需的大小再说。

Linux下用ioctl(fd, HDIO_GET_IDENTITY, ...)获取ATA硬盘信息失败怎么办

到了Linux这边,情况就有点不一样了。直接在/dev/sda上发HDIO_GET_IDENTITY,常常会失败,报错Operation not permittedInappropriate 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编码,而且末尾不保证有\0终止符。Linux的hd_driveid_t.serial_no是ASCII字符数组,但厂商常常填满20字节,中间混入空格或0x00/0x20交替填充。如果直接用std::string((char*)ptr, len),很可能截断或引入乱码。

  • Windows:用WideCharToMultiByte(CP_UTF8, ...)转换,并且必须根据StringLength字段来处理,而不是简单搜索\0
  • Linux:用strnchr(serial_no, ' ', sizeof(serial_no))截掉开头和中间的空格,再用std::string_view(serial_no, sizeof(serial_no)).find_last_not_of(" ")去掉尾部空白。
  • NVMe设备的序列号,可能藏在Identify Controller数据结构的第20–39字节,而且是左对齐的ASCII,需要手动trim。

最后需要提醒一句:硬件序列号本身其实并不可靠。部分OEM盘(比如戴尔、惠普定制的SSD)会返回全0或固定字符串,这时候只能fallback到WWN或NVMe EUI-64,不过那已经不属于“序列号”的范畴了。

本文转载于:https://www.php.cn/faq/2343882.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注