如何在Windows中获取当前进程的所有子进程的句柄

How to obtain handles for all children process of current process in Windows?

本文关键字:进程 句柄 子进程 Windows 获取      更新时间:2023-10-16

为了在Windows操作系统上进行性能监控,我需要一个可以报告任意进程的用户和内核时间的程序。在POSIX系统上,标准time实用程序完全可以,因为它报告挂钟时间,用户时间和内核时间。

对于Windows,默认情况下没有这样的实用程序。我环顾四周,找到了至少三种选择。正如我在下面解释的那样,它们实际上都不适合我的需求。

  1. timeit来自Windows SDK(不记得确切的版本)。它不再分发、支持或保证在现代系统上工作。我无法测试它。
  2. 天鹅座的time .与具有相似输出格式的 POSIX 对应项几乎相同。
  3. Johnson (John) Hart timep.exe,可在源代码和二进制文件中获得,用于他的书"Windows System Programming, 4th Edition"。这是一个非常简单的实用程序,它使用 WinAPI 的GetProcessTimes()来获取相同的三个值。我怀疑Cygwin的time在这方面没有什么不同。

现在的问题是:GetProcessTimes()只报告由timep直接生成的PID的时间,而不是它的子级。这使得timetimep对我来说都毫无用处。

我的目标 EXE 应用程序通常是通过一个 BAT 文件生成的,该文件会再调用一个 BAT 文件;这两个 BAT 都用于调整环境或更改命令行参数:

timep.exe
|    
+---wrapper.bat
              |
              +--- real-wrapper.bat
                                  |
                                  +--- application.exe

wrapper.bat的《泰晤士报》报道对application.exe一无所知。显然,POSIX(fork-exec)和Win32(CreateProcess)的进程创建模型非常不同,这使得我的目标很难在Windows上实现。

我想尝试编写我自己的time变体.它必须递归地总结给定过程及其所有孩子、孙子等的时间。到目前为止,我可以想象以下方法:

  1. CreateProcess()并获取其 PID(根 PID)和句柄;将此句柄添加到列表中
  2. 枚举系统中的所有进程;为每个进程枚举
    1. 将其 PID 与根 PID 进行比较。如果相等,则获取 PID 并为其句柄,将其添加到句柄列表中。
    2. 对于每个新的 PID,重复进程扫描阶段以收集更多子句柄
    3. 递归,直到没有新的进程句柄添加到列表中
  3. 等待列表中所有收集的句柄终止。
  4. 对于每个句柄,调用GetProcessTimes()并汇总它们
  5. 报告结果

这个算法很糟糕,因为它是有问题的——子进程可以在任何进程的生命周期的后期创建,或者它们可以在我们有机会获得它们的句柄之前终止。在这两种情况下,报告的结果时间都将不正确。

我的问题是:有更好的解决方案吗?


编辑:我能够通过使用作业对象实现我的目标。下面是从我的应用程序中提取的代码片段,与从进程及其所有子进程获取内核和用户时间有关。希望它能为某人节省一些时间。

我用Windows 8.1 x64和VS 2015测试了它,但它至少应该向后移植到Windows 7。对于 32 位主机(我不确定),可能需要对long long类型进行一些摆弄 - 我不熟悉 CL.EXE 在此类平台上处理它们的方式。

#include <windows.h>
#include <string>
#include <cassert>
#include <iostream>
/* ... */
STARTUPINFO startUp;
PROCESS_INFORMATION procInfo;

/* Start program in paused state */
PROCESS_INFORMATION procInfo;
if (!CreateProcess(NULL, CmdParams, NULL, NULL, TRUE,
    CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS, NULL, NULL, &startUp, &procInfo)) {
    DWORD err = GetLastError();
    // TODO format error message
    std::cerr << "Unable to start the process: " << err << std::endl;
    return 1;
}
HANDLE hProc = procInfo.hProcess;
/* Create job object and attach the process to it */
HANDLE hJob = CreateJobObject(NULL, NULL); // XXX no security attributes passed
assert(hJob != NULL);
int ret = AssignProcessToJobObject(hJob, hProc);
assert(ret);
/* Now run the process and allow it to spawn children */
ResumeThread(procInfo.hThread);
/* Block until the process terminates */
if (WaitForSingleObject(hProc, INFINITE) != WAIT_OBJECT_0) {
    DWORD err = GetLastError();
    // TODO format error message
    std::cerr << "Failed waiting for process termination: " << err << std::endl;
    return 1;
}
DWORD exitcode = 0;
ret = GetExitCodeProcess(hProc, &exitcode);
assert(ret);
/* Calculate wallclock time in nanoseconds.
   Ignore user and kernel times (third and fourth return parameters) */
FILETIME createTime, exitTime, unusedTime;
ret = GetProcessTimes(hProc, &createTime, &exitTime, &unusedTime, &unusedTime);
assert(ret);
LONGLONG createTimeNs = (LONGLONG)createTime.dwHighDateTime << 32 | createTime.dwLowDateTime;
LONGLONG exitTimeNs = (LONGLONG)exitTime.dwHighDateTime << 32 | exitTime.dwLowDateTime;
LONGLONG wallclockTimeNs = exitTimeNs - createTimeNs;
/* Get total user and kernel times for all processes of the job object */
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION jobInfo;
ret = QueryInformationJobObject(hJob, JobObjectBasicAccountingInformation,
    &jobInfo, sizeof(jobInfo), NULL);
assert(ret);
if (jobInfo.ActiveProcesses != 0) {
    std::cerr << "Warning: there are still " 
        << jobInfo.ActiveProcesses 
        << " alive children processes" << std::endl;
    /* We may kill survived processes, if desired */
    TerminateJobObject(hJob, 127);
}
/* Get kernel and user times in nanoseconds */
LONGLONG kernelTimeNs = jobInfo.TotalKernelTime.QuadPart;
LONGLONG userTimeNs = jobInfo.TotalUserTime.QuadPart;
/* Clean up a bit */
CloseHandle(hProc);
CloseHandle(hJob);
是的

,从 timep 开始.exe创建一个作业,并使用作业记帐。子进程(除非在其自己的作业中创建)与其父进程共享作业。

这几乎跳过了您的步骤 2-4

我已经将这个问题的解决方案打包到一个名为chronos的独立Windows程序中。它创建一个作业对象,然后在其中生成一个请求的进程。后来生成的所有子项都保留在同一作业对象中,因此可以在以后进行核算。