正在检测Windows中的SSD
Detecting SSD in Windows
我想根据系统驱动器是否为SSD来更改C++应用程序的性能和行为。示例:
- 有了SSD,我希望我的游戏服务器应用程序能够完全加载每个地图和所有对象,以最大限度地提高性能
- 使用HDD,我希望我的游戏服务器应用程序只加载每个地图中的基本对象和实体,而不加载外部对象
我见过http://msdn.microsoft.com/en-gb/library/windows/desktop/aa364939(v=vs.85).aspx,这是一种确定某个驱动器是否为HDD、CD ROM、DVD ROM、可移动介质等的方法,但它仍然无法检测主系统驱动器是否为SSD。我也看过有没有办法检测驱动器是否是SSD?,但该解决方案仅适用于Linux。
我认为我可以以某种方式生成一个大的罚款(500MB),然后计算写文件所需的时间,但其他系统变量很容易影响结果。
在Windows中,使用C++,有没有办法确定主系统驱动器是否是SSD?
经过一些研究并使用本页答案中的信息,以下是我使用Windows 7及更高版本的C WinAPI实现的:
//Open drive as such: "\?PhysicalDriveX" where X is the drive number
//INFO: To get drive number from a logical drive letter, check this method:
// (But keep in mind that a single logical drive, or a volume,
// can span across several physical drives, as a "spanned volume.")
// http://stackoverflow.com/a/11683906/843732
#include <WinIoCtl.h>
#include <Ntddscsi.h>
DWORD bytesReturned;
//As an example, let's test 1st physical drive
HANDLE hDevice = ::CreateFile(L"\\?\PhysicalDrive0",
GENERIC_READ | GENERIC_WRITE, //We need write access to send ATA command to read RPMs
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
if(hDevice != INVALID_HANDLE_VALUE)
{
//Check TRIM -- should be Y for SSD
_tprintf(L"TRIM=");
STORAGE_PROPERTY_QUERY spqTrim;
spqTrim.PropertyId = (STORAGE_PROPERTY_ID)StorageDeviceTrimProperty;
spqTrim.QueryType = PropertyStandardQuery;
bytesReturned = 0;
DEVICE_TRIM_DESCRIPTOR dtd = {0};
if(::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
&spqTrim, sizeof(spqTrim), &dtd, sizeof(dtd), &bytesReturned, NULL) &&
bytesReturned == sizeof(dtd))
{
//Got it
_tprintf(L"%s", dtd.TrimEnabled ? L"Y" : L"N");
}
else
{
//Failed
int err = ::GetLastError();
_tprintf(L"?");
}
//Check the seek-penalty value -- should be N for SSD
_tprintf(L", seekPenalty=");
STORAGE_PROPERTY_QUERY spqSeekP;
spqSeekP.PropertyId = (STORAGE_PROPERTY_ID)StorageDeviceSeekPenaltyProperty;
spqSeekP.QueryType = PropertyStandardQuery;
bytesReturned = 0;
DEVICE_SEEK_PENALTY_DESCRIPTOR dspd = {0};
if(::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
&spqSeekP, sizeof(spqSeekP), &dspd, sizeof(dspd), &bytesReturned, NULL) &&
bytesReturned == sizeof(dspd))
{
//Got it
_tprintf(L"%s", dspd.IncursSeekPenalty ? L"Y" : L"N");
}
else
{
//Failed
int err = ::GetLastError();
_tprintf(L"?");
}
//Get drive's RPMs reading -- should be 1 for SSD
//CODE SOURCE: https://emoacht.wordpress.com/2012/11/06/csharp-ssd/
_tprintf(L", RPM=");
ATAIdentifyDeviceQuery id_query;
memset(&id_query, 0, sizeof(id_query));
id_query.header.Length = sizeof(id_query.header);
id_query.header.AtaFlags = ATA_FLAGS_DATA_IN;
id_query.header.DataTransferLength = sizeof(id_query.data);
id_query.header.TimeOutValue = 5; //Timeout in seconds
id_query.header.DataBufferOffset = offsetof(ATAIdentifyDeviceQuery, data[0]);
id_query.header.CurrentTaskFile[6] = 0xec; // ATA IDENTIFY DEVICE
bytesReturned = 0;
if(::DeviceIoControl(hDevice, IOCTL_ATA_PASS_THROUGH,
&id_query, sizeof(id_query), &id_query, sizeof(id_query), &bytesReturned, NULL) &&
bytesReturned == sizeof(id_query))
{
//Got it
//Index of nominal media rotation rate
//SOURCE: http://www.t13.org/documents/UploadedDocuments/docs2009/d2015r1a-ATAATAPI_Command_Set_-_2_ACS-2.pdf
// 7.18.7.81 Word 217
//QUOTE: Word 217 indicates the nominal media rotation rate of the device and is defined in table:
// Value Description
// --------------------------------
// 0000h Rate not reported
// 0001h Non-rotating media (e.g., solid state device)
// 0002h-0400h Reserved
// 0401h-FFFEh Nominal media rotation rate in rotations per minute (rpm)
// (e.g., 7 200 rpm = 1C20h)
// FFFFh Reserved
#define kNominalMediaRotRateWordIndex 217
_tprintf(L"%d", (UINT)id_query.data[kNominalMediaRotRateWordIndex]);
}
else
{
//Failed
int err = ::GetLastError();
_tprintf(L"?");
}
_tprintf(L"n");
::CloseHandle(hDevice);
}
如果你没有包含驱动程序DDK,这里有一些定义:
#ifndef StorageDeviceTrimProperty
#define StorageDeviceTrimProperty 8
#endif
#ifndef DEVICE_TRIM_DESCRIPTOR
typedef struct _DEVICE_TRIM_DESCRIPTOR {
DWORD Version;
DWORD Size;
BOOLEAN TrimEnabled;
} DEVICE_TRIM_DESCRIPTOR, *PDEVICE_TRIM_DESCRIPTOR;
#endif
#ifndef StorageDeviceSeekPenaltyProperty
#define StorageDeviceSeekPenaltyProperty 7
#endif
#ifndef DEVICE_SEEK_PENALTY_DESCRIPTOR
typedef struct _DEVICE_SEEK_PENALTY_DESCRIPTOR {
DWORD Version;
DWORD Size;
BOOLEAN IncursSeekPenalty;
} DEVICE_SEEK_PENALTY_DESCRIPTOR, *PDEVICE_SEEK_PENALTY_DESCRIPTOR;
#endif
struct ATAIdentifyDeviceQuery
{
ATA_PASS_THROUGH_EX header;
WORD data[256];
};
最后,我的测试结论。
我有几个通过SATA电缆连接的三星SSD,还有一个使用PCIe插槽直接连接到逻辑板的PCIe SSD驱动器。我还有一个大的内部西部数字硬盘(旋转驱动器),也通过SATA电缆连接,还有几个外部旋转硬盘。
这是我为他们得到的:
Samsung SSD 256GB: TRIM=Y, seekPenalty=N, RPM=1
Samsung SSD 500GB: TRIM=Y, seekPenalty=N, RPM=1
PCIs SSD: TRIM=Y, seekPenalty=?, RPM=0
Internal WD HDD: TRIM=N, seekPenalty=?, RPM=0
External WD HDD: TRIM=?, seekPenalty=?, RPM=?
External Cavalry HDD: TRIM=?, seekPenalty=Y, RPM=?
因此,正如您所看到的,在我的情况下,对于所有6个驱动器来说,唯一正确的参数是TRIM。我并不是说你也会这样。这只是我对自己拥有的硬盘的发现。
我认为您使用了错误的工具。与其根据驱动器是SSD来进行假设,不如让代码在慢速和快速驱动器上都能很好地工作,例如,先加载基本对象,然后再加载其余对象。三年后,[…]的发明可能会使普通硬盘驱动器比SSD更快,这会破坏你的代码。纯粹基于速度也适用于RAM光盘、NFS、USB3.0棒和其他你不关心或不能关心的东西。
编辑:硬盘驱动器实际上与慢速SSD不同。虽然它们读写速度都很快,但HDD需要大量的时间来寻找。因此,使用两种不同的访问策略是有意义的:通过对SSD的随机访问来挑选重要数据,并顺序读取HDD。您可能只需要实现顺序策略就可以了,因为这在固态硬盘上仍然可以正常工作。不过,检查HDD而不是SSD更有意义,因为您需要对HDD进行特殊处理,而SSD、RAMdisk、NFS等不应受到寻道时间的影响,因此可以进行相同处理。
您可以使用Microsoft WMI类MSFT_PhysicalDisk
。4的中介类型为SSD,SpindleSpeed将为0。
是的,很有可能确定驱动器是否为SSD。SSD通常支持TRIM命令,所以我会检查一下驱动器是否支持TRIM指令。
在Windows中,您可以使用IOCTL_STORAGE_QUERY_PROPERTY来获取DEVICE_TRIM_DESCRIPTOR结构,该结构将告诉您是否启用了TRIM。
如果你真的知道自己在做什么,你可以获得原始的IDENTIFY DEVICE包,并自己解释数据。对于SATA驱动器,它将是字169位0。
无需考虑驱动器类型。通过读取一些无论如何都加载的游戏数据来进行测量,并决定使用哪种策略。(别忘了做一个配置选项:)
我的直觉告诉我这种方法是错误的。如果某人的磁盘速度较慢,那么预加载应该更重要,因为动态加载会导致卡顿。另一方面,如果驱动器足够快,我不需要浪费内存,因为我可以快速加载数据。
我发现的最好的方法是在ROOT\microsoft\windows\storage命名空间中使用带有WMI 的MSFT_PhysicalDisk
这给了你两个属性
- Spindle速度
- 媒体类型
介质类型为您提供值
- 0未指定
- 3 HDD
- 4 SSD
- 5 SCM
主轴速度为0的非常不言自明
Main.cpp
#include <iostream>
#include <windows.h>;
#include <Wbemidl.h>
#include <comdef.h>
#include "StorageDevice.h"
#include <vector>
#pragma comment(lib, "wbemuuid.lib")
using namespace::std;
void IntializeCOM()
{
HRESULT hres;
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl;
// Program has failed.
}
// Step 2: --------------------------------------------------
// Set general COM security levels --------------------------
hres = CoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
if (FAILED(hres))
{
cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
CoUninitialize(); // Program has failed.
}
}
void SetupWBEM(IWbemLocator*& pLoc, IWbemServices*& pSvc)
{
// Step 3: ---------------------------------------------------
// Obtain the initial locator to WMI -------------------------
HRESULT hres;
//IWbemLocator *pLoc = NULL;
hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&pLoc);
if (FAILED(hres))
{
cout << "Failed to create IWbemLocator object." << " Err code = 0x" << hex << hres << endl;
CoUninitialize();
}
// Step 4: -----------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServer method
//IWbemServices *pSvc = NULL;
// Connect to the ROOT\microsoft\windows\storage namespace with
// the current user and obtain pointer pSvc
// to make IWbemServices calls.
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\microsoft\windows\storage"), // Object path of WMI namespace
NULL, // User name. NULL = current user
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (for example, Kerberos)
0, // Context object
&pSvc // pointer to IWbemServices proxy
);
if (FAILED(hres))
{
cout << "Could not connect. Error code = 0x" << hex << hres << endl;
pLoc->Release();
CoUninitialize();
}
// Step 5: --------------------------------------------------
// Set security levels on the proxy -------------------------
hres = CoSetProxyBlanket(
pSvc, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hres))
{
cout << "Could not set proxy blanket. Error code = 0x" << hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
}
}
int main()
{
IWbemLocator *wbemLocator = NULL;
IWbemServices *wbemServices = NULL;
IntializeCOM();
SetupWBEM(wbemLocator, wbemServices);
IEnumWbemClassObject* storageEnumerator = NULL;
HRESULT hres = wbemServices->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM MSFT_PhysicalDisk"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&storageEnumerator);
if (FAILED(hres))
{
cout << "Query for MSFT_PhysicalDisk. Error code = 0x" << hex << hres << endl;
wbemServices->Release();
wbemLocator->Release();
CoUninitialize();
}
IWbemClassObject *storageWbemObject = NULL;
ULONG uReturn = 0;
vector<StorageDevice> storageDevices;
while (storageEnumerator)
{
HRESULT hr = storageEnumerator->Next(WBEM_INFINITE, 1, &storageWbemObject, &uReturn);
if (0 == uReturn || hr != S_OK)
{
break;
}
StorageDevice storageDevice;
VARIANT deviceId;
VARIANT busType;
VARIANT healthStatus;
VARIANT spindleSpeed;
VARIANT mediaType;
storageWbemObject->Get(L"DeviceId", 0, &deviceId, 0, 0);
storageWbemObject->Get(L"BusType", 0, &busType, 0, 0);
storageWbemObject->Get(L"HealthStatus", 0, &healthStatus, 0, 0);
storageWbemObject->Get(L"SpindleSpeed", 0, &spindleSpeed, 0, 0);
storageWbemObject->Get(L"MediaType", 0, &mediaType, 0, 0);
storageDevice.DeviceId = deviceId.bstrVal == NULL ? "" : _bstr_t(deviceId.bstrVal);
storageDevice.BusType = busType.uintVal;
storageDevice.HealthStatus = healthStatus.uintVal;
storageDevice.SpindleSpeed = spindleSpeed.uintVal;
storageDevice.MediaType = mediaType.uintVal;
storageDevices.push_back(storageDevice);
storageWbemObject->Release();
}
}
程序将磁盘属性存储在此处的强类型对象"storageDevice"中,并将其推送到向量上,以便我们稍后使用
StorageDevice.h
#pragma once
#include <iostream>
using namespace::std;
class StorageDevice
{
public:
StorageDevice();
~StorageDevice();
string DeviceId;
int BusType;
int HealthStatus;
int SpindleSpeed;
int MediaType;
};
StorageDevice.cpp
#include "StorageDevice.h"
StorageDevice::StorageDevice()
{
}
StorageDevice::~StorageDevice()
{
}
视频教程和源代码c++在这里下载
- 将成员变量添加到共享库中的类中,不会破坏二进制兼容性吗
- .cpp和.h文件中的模板专用化声明
- 反向给定链表中的K节点
- 正在查找文档以获得PS4平台的C++中的设备信息
- enum是C++中的宏变量还是整数变量
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 将字符串存储在c++中的稳定内存中
- 文本文件中的单词链表
- 递归函数计算序列中的平方和(并输出过程)
- 如何从C++中的依赖类型中获得它所依赖的类型
- C++中的"inline"关键字
- 如何运行位于boost/libs/python/example/tutorial目录中的hello.cpp和Jamfil
- 如何使用 < 和 > 命令获取 c++ 中的输入和输出?
- 用C++中的一个变量定义一个常量
- vector.resize()中的分配错误
- 使用指针从C++中的数组中获取最大值
- arr[-1]在c++中的奇怪行为
- 如何改善Linux中的SSD I/O吞吐量并发
- 以编程方式清除 Linux 中挂载 (SSD) 磁盘上的缓存
- 正在检测Windows中的SSD