/ Windows / 125浏览

Windows 中遍历进程

背景

Windows 中经常遇到需要遍历所有进程的诉求,Windows 中有多种方式可以遍历进程。

下面将对常规和不常规的方式做一个汇总。

效果

先看对比效果:

如下图是四个方式遍历一次当前所有进程所花费的时长。可以看到 native 的速度遥遥领先。

那接下来就看看,这几种方式是怎么做的。

方法一:EnumProcesses

EnumProcesses 是最常用到的,也是最直接的方式了。

示例

DWORD aProcesses[1024] = { 0 }, cbNeeded, cProcesses;
EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded);

注意

  • EnumProcesses 使用中,无法提前预测当前系统中存在多少个进程,所以通常采取一个大数组的方式来获取。
  • 如果想获取总共多少个进程,可以通过 cbNeeded/sizeof(DWORD) 来获取。
  • 如果 cbNeeded/sizeof(DWORD) 与数组大小一致,可能是因为缓冲区不够,建议将缓冲区增大后再次枚举
  • 只包含进程ID,如果要获取其他信息,需要先通过 OpenProcess 获取进程句柄来获取
  • 枚举速度中偏上

方法二:CreateToolhelp32Snapshot

还有一种常见的方式,就是通过 CreateToolhelp32Snapshot 拍快照,再通过 Process32Next 来枚举进程的方式。

示例

void EnumProcessBySnap(std::vector<DWORD>& pids) {
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE) {
        std::cerr << "CreateToolhelp32Snapshot (of processes) failed: " << GetLastError() << std::endl;
        return;
    }

    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // Retrieve information about the first process
    if (!Process32First(hProcessSnap, &pe32)) {
        std::cerr << "Process32First failed: " << GetLastError() << std::endl; // show cause of failure
        CloseHandle(hProcessSnap);          // clean the snapshot object
        return;
    }

    do
    {
        pids.push_back(pe32.th32ProcessID);
    } while (Process32Next(hProcessSnap, &pe32));
}

注意

  • 快照中除了包含进程ID,还包含线程、父进程和优先级信息,所以占用的内存会相对比较大。需要结合自己的诉求
  • 枚举速度最慢
  • 占用的内存最大

方法三:NtQuerySystemInformation

通过 NtQuerySystemInformation + SystemProcessInformation 可以获取到包含 SYSTEM_PROCESS_INFORMATION 结构的数组。

示例

void EnumProcessByQueryInformation(std::vector<DWORD>& pids) {
    DWORD   dwSize = 0xFFFFF;
    LPTSTR pBuf = new TCHAR[dwSize];
    PSYSTEM_PROCESS_INFORMATION pInfo = { 0 };

    pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuf;
    auto status = NtQuerySystemInformation(SystemProcessInformation, pInfo, dwSize, &dwSize);
    if (!NT_SUCCESS(status)) {
        std::cerr << "NtQuerySystemInformation failed: " << status << std::endl;
        return;
    }

    while (pInfo->NextEntryOffset)
    {
        pids.push_back(HandleToULong(pInfo->UniqueProcessId));

        pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryOffset);
    }
}

注意

  • EnumProcesses 一样,缓冲区需要采用大数组来存储,缓冲区不够的时候,会报错
  • 包含的信息较多,具体看 SYSTEM_PROCESS_INFORMATION 结构
  • 由于其调用链较短,在获取足够数据的情况下,速度较快
  • 占用内存较多

方法四:NtGetNextProcess

此方式是一个导出但未文档化的接口,可以从 或者 phnt 中找到对应的接口。

示例

void EnumProcess(std::vector<DWORD>& pids) {
    HANDLE hProcess = nullptr;
    HANDLE nextProcess = NULL;
    while (NT_SUCCESS(NtGetNextProcess(hProcess, PROCESS_QUERY_INFORMATION, 0, 0, &nextProcess))) {
        auto pid = GetProcessId(nextProcess);
        pids.push_back(pid);
        hProcess = nextProcess;
    }
}

注意

  • 使用此方式,请确保与运行的系统是兼容的。可以看对应版本的 ntoskrnl.exe 有无导出此接口
  • 其返回的进程句柄,可以用来获取你想要的信息
  • 此方法直接读取内核对象的链表,所以跟用户态拿到的进程列表存在出入。0(System Idle Process)、4(System)、332(Secure system)、374(Registry)等进程无法获取,还有部分进程内核进程对象存在,但用户态进程不一定存在,需要额外进行处理。请谨慎使用。
  • 获取速度最快(比其他方式快了近10倍)

总结

不同的方式可以适应不同的诉求,各有利弊,需要了解详细后再选择使用。

如何通过 C++ 实时监听 ETW 事件
如何通过 C++ 实时监听 ETW 事件
【译】调查并确定 Windows 运行速度变慢问题
【译】调查并确定 Windows 运行速度变慢问题
【译】丢失的 WPA 文档 —— 磁盘使用
【译】丢失的 WPA 文档 —— 磁盘使用
【译】丢失的 WPA 文档 —— CPU 调度
【译】丢失的 WPA 文档 —— CPU 调度
【译】丢失的 WPA 文档 —— CPU 采样
【译】丢失的 WPA 文档 —— CPU 采样
如何通过 PDH(Performance Data Helper) 获取性能计数器的值

0

  1. This post has no comment yet

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注