Detecting SSD in Windows

人盡茶涼 提交于 2019-12-18 03:57:00

问题


I would like to change the performance and behaviour of my C++ application, according to whether the system drive is an SSD or not. Example:

  • With SSD, I want my gameserver application to load each map fully, with all objects in order to maximize performance.
  • With HDD, I want my gameserver application to load only the essential objects and entities in each map, with no external objects loaded.

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. I've also seen Is there any way of detecting if a drive is a SSD?, but the solution only applies to 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.

In Windows, using C++, is there any way to get whether the main system drive is an SSD or not?


回答1:


You can use the Microsoft WMI Class MSFT_PhysicalDisk. The mediatype of 4 is SSD and SpindleSpeed will be 0. for more details see link.

https://msdn.microsoft.com/en-us/library/windows/desktop/hh830532(v=vs.85)




回答2:


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:

//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:

#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. 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.

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. I'm not saying that it will be in your case as well. It's just my finding with the drives that I own.




回答3:


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. In three years the invention of [...] may make regular hard drives faster than SSDs which would break your code. 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.

EDIT: A HDD is not actually the same as a slow SSD. While they are both fast at reading and writing a HDD needs significant time for seeking. 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. You will probably get away with only implementing the sequential strategy as that should still work ok with SSDs. 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.




回答4:


Yes, there is a high chance of determining whether a drive is an SSD. SSD typically support the TRIM command, so I would check to see if the drive supports the TRIM command.

In Windows, you can use IOCTL_STORAGE_QUERY_PROPERTY to get the DEVICE_TRIM_DESCRIPTOR structure which will tell you if TRIM is enabled.

If you really know what you're doing, you can get the raw IDENTIFY DEVICE package, and interpret the data yourself. For SATA drives it would be word 169 bit 0.




回答5:


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.




回答6:


The best way I found was using the MSFT_PhysicalDisk in the ROOT\\microsoft\windows\storage namespace with WMI

This gives you two properties

  • SpindleSpeed
  • MediaType

The Media Type gives you values

  • 0 Unspecified
  • 3 HDD
  • 4 SSD
  • 5 SCM

And a spindle speed of 0 is pretty self-explanatory

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)
        {
            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.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()
{
}

Video tutorial and source code c++ download here



来源:https://stackoverflow.com/questions/23363115/detecting-ssd-in-windows

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!