【问题标题】:How to get a reliable memory usage information for a 64-bit process from a 32-bit process?如何从 32 位进程中获取 64 位进程的可靠内存使用信息?
【发布时间】:2018-05-15 01:44:43
【问题描述】:

我的目标是获取任意进程的内存使用信息。我从我的 32 位进程中执行以下操作:

HANDLE hProc = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, 0, pid);
if(hProc)
{
    PROCESS_MEMORY_COUNTERS_EX pmx = {0};
    if(::GetProcessMemoryInfo(hProc, (PROCESS_MEMORY_COUNTERS*)&pmx, sizeof(pmx)))
    {
        wprintf(L"Working set: %.02f MB\n", pmx.WorkingSetSize / (1024.0 * 1024.0));
        wprintf(L"Private bytes: %.02f MB\n", pmx.PrivateUsage / (1024.0 * 1024.0));
    }

    ::CloseHandle(hProc);
}

问题是,如果pid进程是64位进程,它可能分配了超过4GB的内存,这会溢出pmx.WorkingSetSizepmx.PrivateUsage,这两个都是32位变量一个 32 位进程。所以在这种情况下,GetProcessMemoryInfo 没有失败,而是成功,两个指标都返回为UINT_MAX——这是错误的!

所以我想知道,是否有可靠的 API 可以从 32 位应用程序中的任意进程检索内存使用情况?

【问题讨论】:

  • 没有 API AFAIK。你能做的最好的事情是从你的 32 位进程中执行一个 64 位进程来获取信息并捕获输出。
  • 普通进程不处理系统中的另一个任意进程。这只做特殊的实用程序,显示系统信息的设计,调试器等。这种实用程序几乎总是必须是本机位。所以必须是 2 个单独的版本 - 用于 32 位和 64 位窗口

标签: c++ winapi memory windows-applications wow64


【解决方案1】:

为什么不将此应用程序编译为 64 位,然后您应该能够收集 32 位和 64 位进程的内存使用情况。

【讨论】:

  • 除了在 x64 平台上重新编译一个旧的和广泛的 32 位应用程序的明显困难之外,我不想这样做也是因为我希望我的应用程序在 32 位和 64 位上运行- 单个图像文件中的位操作系统。
【解决方案2】:

WMI Win32_Process 提供程序有很多 64 位内存编号。不确定您所追求的一切是否存在。

【讨论】:

    【解决方案3】:

    有一个可靠的 API,称为“Performance Data Helpers”。

    Windows 的 stock perfmon 实用程序是 Windows 性能计数器应用程序的经典示例。 Process Explorer 也使用它来收集进程统计信息。

    它的优点是您甚至不需要SeDebugPrivilege 即可获得PROCESS_VM_READ 对其他进程的访问权限。
    请注意,该访问权限仅限于属于 Performance Monitoring Users 组的用户。

    PDH 背后的理念是:

    • 查询对象
      • 一个或多个计数器
    • 根据要求或定期创建样本
    • 获取您要求的数据

    开始需要做更多的工作,但最终还是很容易。我所做的是设置一个永久的 PDH 查询,这样我就可以在我的应用程序的整个生命周期中重复使用它。

    有一个缺点:默认情况下,操作系统会为同名的进程创建编号条目。这些编号的条目甚至会在进程终止或创建新条目时发生变化。因此,您必须考虑到这一点并交叉检查进程 ID (PID),实际上是在为要获取内存使用的进程打开句柄的同时。

    您可以在下面找到一个简单的 PDH 包装器替代 GetProcessMemoryInfo()。 当然,有足够的空间来调整以下代码或根据您的需要进行调整。我还看到有人已经创建了更通用的 C++ 包装器。

    声明

    #include <tuple>
    #include <array>
    #include <vector>
    #include <stdint.h>
    #include <Pdh.h>
    
    #pragma comment(lib, "Pdh.lib")
    
    
    class process_memory_info
    {
    private:
        using pd_t = std::tuple<DWORD, ULONGLONG, ULONGLONG>; // performance data type
        static constexpr size_t pidIdx = 0;
        static constexpr size_t wsIdx = 1;
        static constexpr size_t pbIdx = 2;
        struct less_pd
        {
            bool operator ()(const pd_t& left, const pd_t& right) const
            {
                return std::get<pidIdx>(left) < std::get<pidIdx>(right);
            }
        };
    
    public:
        ~process_memory_info();
    
        bool setup_query();
        bool take_sample();
        std::pair<uintmax_t, uintmax_t> get_memory_info(DWORD pid) const;
    
    private:
        PDH_HQUERY pdhQuery_ = nullptr;
        std::array<PDH_HCOUNTER, std::tuple_size_v<pd_t>> pdhCounters_ = {};
        std::vector<pd_t> perfData_;
    };
    

    实施

    #include <memory>
    #include <execution>
    #include <algorithm>
    #include <stdlib.h>
    
    using std::unique_ptr;
    using std::pair;
    using std::array;
    using std::make_unique;
    using std::get;
    
    
    process_memory_info::~process_memory_info()
    {
        PdhCloseQuery(pdhQuery_);
    }
    
    bool process_memory_info::setup_query()
    {
        if (pdhQuery_)
            return true;
        if (PdhOpenQuery(nullptr, 0, &pdhQuery_))
            return false;
    
        size_t i = 0;
        for (auto& counterPath : array<PDH_COUNTER_PATH_ELEMENTS, std::tuple_size_v<pd_t>>{ {
            { nullptr, L"Process", L"*", nullptr, 0, L"ID Process" },
            { nullptr, L"Process", L"*", nullptr, 0, L"Working Set" },
            { nullptr, L"Process", L"*", nullptr, 0, L"Private Bytes" }
            }})
        {
            wchar_t pathStr[PDH_MAX_COUNTER_PATH] = {};
    
            DWORD size;
            PdhMakeCounterPath(&counterPath, pathStr, &(size = _countof(pathStr)), 0);
            PdhAddEnglishCounter(pdhQuery_, pathStr, 0, &pdhCounters_[i++]);
        }
    
        return true;
    }
    
    bool process_memory_info::take_sample()
    {
        if (PdhCollectQueryData(pdhQuery_))
            return false;
    
        DWORD nItems = 0;
        DWORD size;
        PdhGetFormattedCounterArray(pdhCounters_[0], PDH_FMT_LONG, &(size = 0), &nItems, nullptr);
        auto valuesBuf = make_unique<BYTE[]>(size);
        PdhGetFormattedCounterArray(pdhCounters_[0], PDH_FMT_LONG, &size, &nItems, PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.get()));
        unique_ptr<PDH_FMT_COUNTERVALUE_ITEM[]> pidValues{ PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.release()) };
    
        valuesBuf = make_unique<BYTE[]>(size);
        PdhGetFormattedCounterArray(pdhCounters_[1], PDH_FMT_LARGE, &size, &nItems, PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.get()));
        unique_ptr<PDH_FMT_COUNTERVALUE_ITEM[]> wsValues{ PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.release()) };
    
        valuesBuf = make_unique<BYTE[]>(size);
        PdhGetFormattedCounterArray(pdhCounters_[2], PDH_FMT_LARGE, &size, &nItems, PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.get()));
        unique_ptr<PDH_FMT_COUNTERVALUE_ITEM[]> pbValues{ PPDH_FMT_COUNTERVALUE_ITEM(valuesBuf.release()) };
    
        perfData_.clear();
        perfData_.reserve(nItems);
        for (size_t i = 0, n = nItems; i < n; ++i)
        {
            perfData_.emplace_back(pidValues[i].FmtValue.longValue, wsValues[i].FmtValue.largeValue, pbValues[i].FmtValue.largeValue);
        }
        std::sort(std::execution::par_unseq, perfData_.begin(), perfData_.end(), less_pd{});
    
        return true;
    }
    
    pair<uintmax_t, uintmax_t> process_memory_info::get_memory_info(DWORD pid) const
    {
        auto it = std::lower_bound(perfData_.cbegin(), perfData_.cend(), pd_t{ pid, 0, 0 }, less_pd{});
    
        if (it != perfData_.cend() && get<pidIdx>(*it) == pid)
            return { get<wsIdx>(*it), get<pbIdx>(*it) };
        else
            return {};
    }
    
    
    int main()
    {
        process_memory_info pmi;
        pmi.setup_query();
    
        DWORD pid = 4;
    
        pmi.take_sample();
        auto[workingSet, privateBytes] = pmi.get_memory_info(pid);
    
        return 0;
    }
    

    【讨论】:

    • 感谢分享。一些问题:1)它的本地化程度如何?当我必须处理(英文)字符串来获取指标时,我总是畏缩不前。 2) 与GetProcessMemoryInfo() 调用相比,运行它的开销是多少? 3) 它运行在什么最低操作系统上? 4) 您能否详细说明成为"Performance Monitoring Users group" 成员的过程?最后一个可能是真正的交易杀手。顺便说一句,如果在 64 位操作系统上运行,我最终通过创建自己的 64 位进程来解决我最初的问题。它还解决了与错误位数相关的其他问题。
    • 1) 您到底想本地化什么?那些英文字符串只是用来设置计数器,但不会到外面。 2) 与 GetProcessMemoryInfo() 相比,我不知道开销。我认为当您只有几个进程要查询时,它会高得多。 3) 自 Win XP/Server 2003 起可用。 4) 与 PROCESS_VM_READ+SeDebugPrivilege 相比,我不认为它是一个交易杀手。我从MSDN 获取了这些信息。根据我的测试,普通用户可以访问数据。
    • 将一个 Windows 用户放入一个用户组是一个比给你自己的进程SeDebugPrivilege 权限更复杂的全局 过程。至于本地化,我的意思是 "ID Process" 字符串不会变成类似于法语中的 "Processus d'identification" 的东西,对吗?
    • 路径可以本地化,因为微软的想法是让用户选择他们想要查看的内容(参见 perfmon)。在这种情况下,我使用PdhAddEnglishCounter 来设置查询,它在德国操作系统上工作。 SeDebugPrivilege 当然很容易启用。但这仅在它已经在进程令牌中时才可行-仅在运行提升时才AFAIK-并且您的进程处于高完整性级别。 [我有一个提升的应用程序,由于 UIPI,它以中等完整性重新启动 - 这就是我首先需要 PDH 的原因。]
    猜你喜欢
    • 2017-03-05
    • 1970-01-01
    • 2011-08-08
    • 2012-06-05
    • 1970-01-01
    • 2011-01-01
    • 2020-08-04
    • 1970-01-01
    • 2011-11-18
    相关资源
    最近更新 更多