简体   繁体   English

在 Windows 中检测 SSD

[英]Detecting SSD in Windows

I would like to change the performance and behaviour of my C++ application, according to whether the system drive is an SSD or not.我想根据系统驱动器是否为 SSD 来更改我的 C++ 应用程序的性能和行为。 Example:例子:

  • With SSD, I want my gameserver application to load each map fully, with all objects in order to maximize performance.使用 SSD,我希望我的游戏服务器应用程序能够完全加载每个地图以及所有对象,以最大限度地提高性能。
  • With HDD, I want my gameserver application to load only the essential objects and entities in each map, with no external objects loaded.使用 HDD,我希望我的游戏服务器应用程序只加载每个地图中的基本对象和实体,而不加载任何外部对象。

I've seen http://msdn.microsoft.com/en-gb/library/windows/desktop/aa364939(v=vs.85).aspx , which is a way of determining if a certain drive is a HDD, CD ROM, DVD ROM, Removable Media, etc, but it STILL can't detect whether the main system drive is an SSD.我见过http://msdn.microsoft.com/en-gb/library/windows/desktop/aa364939(v=vs.85).aspx ,这是一种确定某个驱动器是否是 HDD、CD 的方法ROM、DVD ROM、可移动媒体等,但仍无法检测主系统驱动器是否为 SSD。 I've also seen Is there any way of detecting if a drive is a SSD?我还看到有没有办法检测驱动器是否是 SSD? , but the solution only applies to Linux. ,但该解决方案仅适用于 Linux。

I thought that I could somehow generate a large fine (500MB), and then time how long it takes to write the file, but however other system variables can easily influence the result.我以为我可以以某种方式生成一个大罚款(500MB),然后计算写入文件所需的时间,但是其他系统变量很容易影响结果。

In Windows, using C++, is there any way to get whether the main system drive is an SSD or not?在Windows下,使用C++,有什么办法可以判断主系统盘是否是SSD?

Having done some research and using the info from the answers on this page, here's my implementation using C WinAPIs for Windows 7 and later:做了一些研究并使用了本页答案中的信息后,这是我使用 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);
}

In case you don't have driver DDK includes, here're some definitions:如果您没有包含驱动程序 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];
};

Lastly, conclusion of my tests.最后,总结我的测试。

I have several Samsung SSDs connected via a SATA cable, and one PCIe SSD drive that is connected directly to the logic board using PCIe slot.我有几个通过 SATA 电缆连接的三星 SSD,以及一个使用 PCIe 插槽直接连接到逻辑板的 PCIe SSD 驱动器。 I also have one large internal Western Digital HDD (spinning drive) that is also connected via a SATA cable, and a couple of external spinning HDDs.我还有一个大型内部西部数据硬盘(旋转驱动器),它也通过 SATA 电缆连接,以及几个外部旋转硬盘。

Here's what I get for them:这是我为他们得到的:

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=?

So as you see, in my case, the only parameter that is correct for all 6 drives is TRIM.如您所见,就我而言,唯一对所有 6 个驱动器都正确的参数是 TRIM。 I'm not saying that it will be in your case as well.我并不是说你的情况也会如此。 It's just my finding with the drives that I own.这只是我对我拥有的驱动器的发现。

I believe you are using the wrong tool.我相信您使用了错误的工具。 Instead of making assumptions based on a drive being an SSD you should make your code work well with slow and fast drives, for example by loading the essential objects first and the rest later.与其假设驱动器是 SSD,您应该让代码在慢速驱动器和快速驱动器上都能很好地工作,例如先加载基本对象,然后再加载其他对象。 In three years the invention of [...] may make regular hard drives faster than SSDs which would break your code.在三年内,[...] 的发明可能会使普通硬盘驱动器比 SSD 更快,这会破坏您的代码。 Going purely based on speed will also work for RAM discs, NFS, USB3.0-sticks and other stuff you didn't or cannot thing about.纯粹基于速度也适用于 RAM 磁盘、NFS、USB3.0 记忆棒和其他您没有或不能使用的东西。

EDIT: A HDD is not actually the same as a slow SSD.编辑:HDD 实际上与慢速 SSD 不同。 While they are both fast at reading and writing a HDD needs significant time for seeking.虽然它们在读取和写入 HDD 方面都很快,但需要大量时间进行查找。 It thus makes sense to use two different access strategies: picking the important data via random access for the SSD and sequentially reading for the HDD.因此,使用两种不同的访问策略是有意义的:通过随机访问 SSD 和顺序读取 HDD 来选择重要数据。 You will probably get away with only implementing the sequential strategy as that should still work ok with SSDs.您可能只会实施顺序策略,因为它仍然适用于 SSD。 It makes more sense to check for a HDD instead of a SSD though, because you need to treat the HDD special while SSD, RAMdisc, NFS and so on should not suffer from seek times and can thus be treated the same.不过,检查 HDD 而不是 SSD 更有意义,因为您需要对 HDD 进行特殊处理,而 SSD、RAMdisc、NFS 等不应受到寻道时间的影响,因此可以相同对待。

You can use the Microsoft WMI Class MSFT_PhysicalDisk .您可以使用 Microsoft WMI 类MSFT_PhysicalDisk The mediatype of 4 is SSD and SpindleSpeed will be 0. 4 的媒体类型是 SSD,SpindleSpeed 将为 0。

Yes, there is a high chance of determining whether a drive is an SSD.是的,很有可能确定驱动器是否为 SSD。 SSD typically support the TRIM command, so I would check to see if the drive supports the TRIM command. SSD 通常支持 TRIM 命令,所以我会检查驱动器是否支持 TRIM 命令。

In Windows, you can use IOCTL_STORAGE_QUERY_PROPERTY to get the DEVICE_TRIM_DESCRIPTOR structure which will tell you if TRIM is enabled.在 Windows 中,您可以使用IOCTL_STORAGE_QUERY_PROPERTY来获取DEVICE_TRIM_DESCRIPTOR结构,它会告诉您是否启用了 TRIM。

If you really know what you're doing, you can get the raw IDENTIFY DEVICE package, and interpret the data yourself.如果您真的知道自己在做什么,则可以获得原始 IDENTIFY DEVICE 包,并自己解释数据。 For SATA drives it would be word 169 bit 0.对于 SATA 驱动器,它将是字 169 位 0。

do not bother of drive type.不要打扰驱动器类型。 make a measurement by reading some of your game data that is loaded anyways and decide which strategy to use.通过读取一些无论如何加载的游戏数据进行测量,并决定使用哪种策略。 (do not forget to make an configuration option :) (不要忘记做一个配置选项:)

nether the less my gut instinct tells me that the approach is wrong.我的直觉告诉我这种方法是错误的。 if someone has a slow disk then preloading should be more important since on the fly loading will cause stuttering.如果有人的磁盘速度较慢,那么预加载应该更重要,因为即时加载会导致卡顿。 On the other side if the drive is fast enough i do not need to waste memory because i can load data on the fly fast enough.另一方面,如果驱动器足够快,我不需要浪费内存,因为我可以足够快地动态加载数据。

The best way I found was using the MSFT_PhysicalDisk in the ROOT\\microsoft\\windows\\storage namespace with WMI我发现的最好方法是将 ROOT\\microsoft\\windows\\storage 命名空间中的MSFT_PhysicalDisk与 WMI 一起使用

This gives you two properties这为您提供了两个属性

  • SpindleSpeed主轴转速
  • MediaType媒体类型

The Media Type gives you values媒体类型为您提供价值

  • 0 Unspecified 0 未指定
  • 3 HDD 3 硬盘
  • 4 SSD 4 固态硬盘
  • 5 SCM 5 单片机

And a spindle speed of 0 is pretty self-explanatory主轴速度为 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();
    }


}

The programs stores the disk properties in a strongly typed object "storageDevice" here, and pushed it onto a vector so we can use it later程序将磁盘属性存储在此处的强类型对象“storageDevice”中,并将其推送到向量上,以便我们稍后使用

StorageDevice.h存储设备.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存储设备.cpp

#include "StorageDevice.h"



StorageDevice::StorageDevice()
{
}


StorageDevice::~StorageDevice()
{
}

Video tutorial and source code c++ download here视频教程和源代码 C++ 在这里下载

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM