在Windows环境下我知道有两种方法可以读取硬盘SMART信息:一是通过DeviceIoControl这个Win API,还有就是通过wql语句查询。这两种方法已经在网上被说烂,本文主要要记录一个找到死都找不到的东西:无论是通过上面Api还是wql查询都会返回两个512字节的数据,一个记录Attributes,还有一个是SMART信息中的THRESHOLDS(对,它们是分开存储的)。那么这两个512字节的数据结构到底是如何的呢?(我在smartctl的源码中发现了它们)。
首先是THRESHOLDS的512字节数据结构(强烈建议使用#pragma pack(push,1)
字节对齐):
#pragma pack(push,1)
/* Vendor attribute of SMART Threshold (compare to ata_smart_attribute above) */
struct ata_smart_threshold_entry {
unsigned char id;
unsigned char threshold;
unsigned char reserved[10];
};
/* Format of Read SMART THreshold Command */
struct ata_smart_thresholds_pvt {
unsigned short int revnumber;
struct ata_smart_threshold_entry thres_entries[NUMBER_ATA_SMART_ATTRIBUTES];
unsigned char reserved[149];
unsigned char chksum;
} ;
#pragma pack(pop)
紧接着是 其他Attribute的512字节数据:
#pragma pack(push,1)
struct ata_smart_attribute {
unsigned char id;
unsigned short flags;
unsigned char current;
unsigned char worst;
unsigned char raw[7];
};
struct ata_smart_values {
unsigned short int revnumber;
struct ata_smart_attribute vendor_attributes[NUMBER_ATA_SMART_ATTRIBUTES];
unsigned char offline_data_collection_status;
unsigned char self_test_exec_status; //IBM # segments for offline collection
unsigned short int total_time_to_complete_off_line; // IBM different
unsigned char vendor_specific_366; // Maxtor & IBM curent segment pointer
unsigned char offline_data_collection_capability;
unsigned short int smart_capability;
unsigned char errorlog_capability;
unsigned char vendor_specific_371; // Maxtor, IBM: self-test failure checkpoint see below!
unsigned char short_test_completion_time;
unsigned char extend_test_completion_time_b; // If 0xff, use 16-bit value below
unsigned char conveyance_test_completion_time;
unsigned short extend_test_completion_time_w; // e04130r2, added to T13/1699-D Revision 1c, April 2005
unsigned char reserved_377_385[9];
unsigned char vendor_specific_386_510[125]; // Maxtor bytes 508-509 Attribute/Threshold Revision #
unsigned char chksum;
};
#pragma pack(pop)
两种方法查询硬盘SMART信息(都要管理员权限)
1. WQL(WMI Query Language)
wmi的存在是为了让不用C++的孩子方便用它们喜欢的语言读写系统底层的东西,而避开了win api的调用(当然效率要慢一点),所以wmi的查询语言就叫wql(无论语法还是名称都类似sql),详细语法请问google,但真的值得一学。
当用wql时,用管理员权限查询root\wmi命名空间下面的MSStorageDriver_FailurePredictThresholds和MSStorageDriver_FailurePredictData两张表,里面都会有一个VendorSpecific字段记录了上面所述的512字节。
Update: 在项目中发现通过wmi获取的硬盘Interface Type是IDE,但是通过STORAGE_PROPERTY_QUERY获得的类型是SATA。实际类型是SATA,所以wim是不正确的。并不是很推荐使用wmi。建议使用win api。
2. DeviceIoControl
说实话这个代码很烦,我就直接上核心代码了。
// 这里的diskid大概长这个样子:"\\\\.\\PHYSICALDRIVE0","\\\\.\\PHYSICALDRIVE1"
bool ReadSMARTAttributes(const char* diskid, ata_smart_values& smart_value, ata_smart_thresholds_pvt& threshold_value)
{
BOOL bRet = false;
const wchar_t* physical_prefix = L"\\\\.\\PhysicalDrive";
if (diskid != NULL && wcslen(diskid) > wcslen(physical_prefix) &&
_wcsnicmp(diskid, physical_prefix, wcslen(physical_prefix)) == 0)
{
DWORD dwRet = 0;
int ucDriveIndex = wcstol(diskid + wcslen(physical_prefix), NULL, 10);
HANDLE hDevice = CreateFileW(diskid, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
if (hDevice == INVALID_HANDLE_VALUE) return false;
BYTE szAttributes[sizeof(SENDCMDOUTPARAMS) + READ_ATTRIBUTE_BUFFER_SIZE - 1];
SENDCMDINPARAMS stCIP = { 0 };
stCIP.cBufferSize = READ_ATTRIBUTE_BUFFER_SIZE;
stCIP.bDriveNumber = ucDriveIndex;
stCIP.irDriveRegs.bFeaturesReg = READ_ATTRIBUTES;
stCIP.irDriveRegs.bSectorCountReg = 1;
stCIP.irDriveRegs.bSectorNumberReg = 1;
stCIP.irDriveRegs.bCylLowReg = SMART_CYL_LOW;
stCIP.irDriveRegs.bCylHighReg = SMART_CYL_HI;
stCIP.irDriveRegs.bDriveHeadReg = 0xA0;
stCIP.irDriveRegs.bCommandReg = SMART_CMD;
bRet = DeviceIoControl(hDevice, SMART_RCV_DRIVE_DATA, &stCIP, sizeof(stCIP), szAttributes, sizeof(SENDCMDOUTPARAMS) + READ_ATTRIBUTE_BUFFER_SIZE - 1, &dwRet, NULL);
if (bRet)
{
smart_value = *(ata_smart_values*)(((SENDCMDOUTPARAMS*)szAttributes)->bBuffer);
}
stCIP.irDriveRegs.bFeaturesReg = READ_THRESHOLDS;
stCIP.cBufferSize = READ_THRESHOLD_BUFFER_SIZE;
bRet &= DeviceIoControl(hDevice, SMART_RCV_DRIVE_DATA, &stCIP, sizeof(stCIP), szAttributes, sizeof(SENDCMDOUTPARAMS) + READ_ATTRIBUTE_BUFFER_SIZE - 1, &dwRet, NULL);
if (bRet)
{
threshold_value = *(ata_smart_thresholds_pvt*)(((SENDCMDOUTPARAMS*)szAttributes)->bBuffer);
}
CloseHandle(hDevice);
}
return !!bRet;
}