如何获取Windows当前进程的所有子进程句柄?
How to obtain handles for all children process of current process in Windows?
为了在 Windows OS 上进行性能监控,我需要一个可以报告任意进程的用户和内核时间的程序。在 POSIX 系统上,标准 time
实用程序完全可以,因为它报告挂钟时间、用户时间和内核时间。
对于Windows,默认情况下没有这样的实用程序。我环顾四周,发现至少三种选择。正如我在下面解释的那样,none 实际上符合我的需要。
timeit
来自 Windows SDK(不记得确切的版本)。它不再分发、支持或保证在现代系统上工作。我无法测试它。
- Cygwin 的
time
。几乎与具有相似输出格式的 POSIX 对应物相同。
timep.exe
作者 Johnson (John) Hart,在 source code 和他的书 "Windows System Programming, 4th Edition" 的二进制文件中可用。这是一个非常简单的实用程序,它使用 WinAPI 的 GetProcessTimes()
来获取完全相同的三个值。我怀疑 Cygwin 的 time
在这方面没有什么不同。
现在的问题是:GetProcessTimes()
只报告由 timep
直接生成的 PID 的时间,但 不是它的子 。这使得 time
和 timep
对我来说都毫无用处。
我的目标 EXE 应用程序通常是通过调用另一个 BAT 文件的 BAT 文件生成的;两个 BAT 都用于调整环境或更改命令行参数:
timep.exe
|
+---wrapper.bat
|
+--- real-wrapper.bat
|
+--- application.exe
仅 wrapper.bat
的报告时间与 application.exe
无关。
显然,POSIX (fork-exec) 和 Win32 (CreateProcess) 的进程创建模型非常不同,这使得我的目标在 Windows.
上难以实现
我想尝试编写自己的 time
变体。它必须递归地总结给定进程及其所有子进程、孙进程等的时间。到目前为止,我可以想象以下方法:
CreateProcess()
并获取其PID(root PID)并处理;将此句柄添加到列表
- 枚举系统中的所有进程;对于每个过程
- 将其PID 与根PID 进行比较。如果相等,获取PID和句柄,添加到句柄列表中。
- 对于每个新的 PID,重复进程扫描阶段以收集更多子句柄
- 向下递归,直到没有新的进程句柄添加到列表中
- 等待列表中所有收集到的句柄终止。
- 对每个handle,调用
GetProcessTimes()
并求和
- 报告结果
这个算法很糟糕,因为它很活泼——子进程可能在任何进程的生命周期后期创建,或者它们可以在我们有机会获得它们的句柄之前终止。在这两种情况下,报告的结果时间都是不正确的。
我的问题是:有更好的解决方案吗?
编辑:我能够通过使用 Job Objects 实现我的目标。下面是从我的应用程序中提取的代码片段,与从进程及其所有子进程中获取内核和用户时间有关。希望它能为某人节省一些时间。
我用 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 创建一个工作,并使用 job accounting。子进程(除非在它们自己的作业中创建)与其父进程共享作业。
这几乎跳过了您的第 2-4 步
我已将此问题的解决方案打包到 Windows 的独立程序中,名为 chronos。它创建一个作业 object,然后在其中生成一个请求的进程。所有 children 之后产生的都留在同一个工作 object 中,因此可以在以后计算。
为了在 Windows OS 上进行性能监控,我需要一个可以报告任意进程的用户和内核时间的程序。在 POSIX 系统上,标准 time
实用程序完全可以,因为它报告挂钟时间、用户时间和内核时间。
对于Windows,默认情况下没有这样的实用程序。我环顾四周,发现至少三种选择。正如我在下面解释的那样,none 实际上符合我的需要。
timeit
来自 Windows SDK(不记得确切的版本)。它不再分发、支持或保证在现代系统上工作。我无法测试它。- Cygwin 的
time
。几乎与具有相似输出格式的 POSIX 对应物相同。 timep.exe
作者 Johnson (John) Hart,在 source code 和他的书 "Windows System Programming, 4th Edition" 的二进制文件中可用。这是一个非常简单的实用程序,它使用 WinAPI 的GetProcessTimes()
来获取完全相同的三个值。我怀疑 Cygwin 的time
在这方面没有什么不同。
现在的问题是:GetProcessTimes()
只报告由 timep
直接生成的 PID 的时间,但 不是它的子 。这使得 time
和 timep
对我来说都毫无用处。
我的目标 EXE 应用程序通常是通过调用另一个 BAT 文件的 BAT 文件生成的;两个 BAT 都用于调整环境或更改命令行参数:
timep.exe
|
+---wrapper.bat
|
+--- real-wrapper.bat
|
+--- application.exe
仅 wrapper.bat
的报告时间与 application.exe
无关。
显然,POSIX (fork-exec) 和 Win32 (CreateProcess) 的进程创建模型非常不同,这使得我的目标在 Windows.
我想尝试编写自己的 time
变体。它必须递归地总结给定进程及其所有子进程、孙进程等的时间。到目前为止,我可以想象以下方法:
CreateProcess()
并获取其PID(root PID)并处理;将此句柄添加到列表- 枚举系统中的所有进程;对于每个过程
- 将其PID 与根PID 进行比较。如果相等,获取PID和句柄,添加到句柄列表中。
- 对于每个新的 PID,重复进程扫描阶段以收集更多子句柄
- 向下递归,直到没有新的进程句柄添加到列表中
- 等待列表中所有收集到的句柄终止。
- 对每个handle,调用
GetProcessTimes()
并求和 - 报告结果
这个算法很糟糕,因为它很活泼——子进程可能在任何进程的生命周期后期创建,或者它们可以在我们有机会获得它们的句柄之前终止。在这两种情况下,报告的结果时间都是不正确的。
我的问题是:有更好的解决方案吗?
编辑:我能够通过使用 Job Objects 实现我的目标。下面是从我的应用程序中提取的代码片段,与从进程及其所有子进程中获取内核和用户时间有关。希望它能为某人节省一些时间。
我用 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 创建一个工作,并使用 job accounting。子进程(除非在它们自己的作业中创建)与其父进程共享作业。
这几乎跳过了您的第 2-4 步
我已将此问题的解决方案打包到 Windows 的独立程序中,名为 chronos。它创建一个作业 object,然后在其中生成一个请求的进程。所有 children 之后产生的都留在同一个工作 object 中,因此可以在以后计算。