21

我想根据系统驱动器是否为 SSD 来更改我的 C++ 应用程序的性能和行为。例子:

  • 使用 SSD,我希望我的游戏服务器应用程序能够完全加载每个地图以及所有对象,以最大限度地提高性能。
  • 使用 HDD,我希望我的游戏服务器应用程序仅加载每个地图中的基本对象和实体,而不加载外部对象。

我看过http://msdn.microsoft.com/en-gb/library/windows/desktop/aa364939(v=vs.85).aspx,这是一种确定某个驱动器是否为 HDD、CD 的方法ROM、DVD ROM、Removable Media 等,但仍然无法检测主系统驱动器是否为 SSD。我还看到了有什么方法可以检测驱动器是否为 SSD?,但该解决方案仅适用于 Linux。

我认为我可以以某种方式生成一大笔罚款(500MB),然后计算写入文件所需的时间,但是其他系统变量很容易影响结果。

在 Windows 中,使用 C++,有没有办法获得主系统驱动器是否是 SSD?

4

6 回答 6

24

完成一些研究并使用此页面上答案中的信息后,这是我在 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 电缆连接的大型内部 Western Digital HDD(旋转驱动器)和几个外部旋转 HDD。

这是我为他们得到的:

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。我并不是说你的情况也会如此。这只是我对我拥有的驱动器的发现。

于 2015-10-27T02:46:49.810 回答
17

我相信你使用了错误的工具。与其假设驱动器是 SSD,不如让代码在慢速和快速驱动器上运行良好,例如先加载基本对象,然后再加载其余对象。在三年内,[...] 的发明可能会使普通硬盘驱动器比 SSD 更快,这会破坏您的代码。纯粹基于速度也适用于 RAM 磁盘、NFS、USB3.0 棒和其他你没有或不能考虑的东西。

编辑:HDD 实际上与慢速 SSD 不同。虽然它们在读取和写入 HDD 方面都很快,但需要大量时间来寻找。因此,使用两种不同的访问策略是有意义的:通过随机访问为 SSD 挑选重要数据,并为 HDD 顺序读取。您可能只实施顺序策略就可以逃脱惩罚,因为这仍然适用于 SSD。不过,检查 HDD 而不是 SSD 更有意义,因为您需要对 HDD 进行特殊处理,而 SSD、RAMdisc、NFS 等不应受到寻道时间的影响,因此可以进行相同的处理。

于 2014-04-29T11:25:25.103 回答
15

您可以使用 Microsoft WMI 类MSFT_PhysicalDisk。mediatype 4 是 SSD,SpindleSpeed 将为 0。

于 2016-05-12T16:42:03.000 回答
7

是的,很有可能确定驱动器是否为 SSD。SSD 通常支持 TRIM 命令,所以我会检查驱动器是否支持 TRIM 命令。

在 Windows 中,您可以使用IOCTL_STORAGE_QUERY_PROPERTY来获取DEVICE_TRIM_DESCRIPTOR结构,该结构将告诉您是否启用了 TRIM。

如果你真的知道你在做什么,你可以得到原始的 IDENTIFY DEVICE 包,并自己解释数据。对于 SATA 驱动器,它将是字 169 位 0。

于 2015-06-28T10:02:08.673 回答
6

不要打扰驱动器类型。通过读取一些无论如何加载的游戏数据来进行测量,并决定使用哪种策略。(不要忘记做一个配置选项:)

更何况我的直觉告诉我这种方法是错误的。如果有人的磁盘速度较慢,那么预加载应该更重要,因为动态加载会导致卡顿。另一方面,如果驱动器足够快,我不需要浪费内存,因为我可以足够快地动态加载数据。

于 2014-04-29T12:40:11.507 回答
4

我发现的最好方法是使用带有 WMI 的 ROOT\microsoft\windows\storage 命名空间中的MSFT_PhysicalDisk

这为您提供了两个属性

  • 主轴转速
  • 媒体类型

媒体类型为您提供值

  • 0 未指定
  • 3个硬盘
  • 4 固态硬盘
  • 5 单片机

主轴速度为 0 是不言自明的

主文件

#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”中,并将其推送到向量上,以便我们以后使用

存储设备.h

#pragma once
#include <iostream>

using namespace::std;

class StorageDevice
{
public:
    StorageDevice();
    ~StorageDevice();

    string  DeviceId;
    int BusType;
    int HealthStatus;
    int SpindleSpeed;
    int MediaType;

};

存储设备.cpp

#include "StorageDevice.h"



StorageDevice::StorageDevice()
{
}


StorageDevice::~StorageDevice()
{
}

视频教程和源代码c++ 在这里下载

于 2019-05-21T21:48:31.447 回答