目标是取得指定 Windows 进程的 CPU 使用率、I/O 速率、内存快照和调度状态,并说明这些结果属于哪个进程实例和哪个时间窗口。要完成这件事,分为 4 步。
PID(Process Identifier,进程标识符)是 Windows 分配给运行中进程的数值编号。进程退出后,这个数值未来可以交给另一个进程使用。进程对象由内核维护,包含创建时间、线程集合、访问权限和退出状态。查询方用 OpenProcess 得到 HANDLE(句柄)。句柄是当前进程持有的受访问控制引用,指向一个具体进程对象,使用完必须关闭。
同一个进程对象连接着多个 Windows 组件。内核调度器把全部线程的用户态和内核态 CPU 时间累积到进程层。内存管理器提供工作集和提交量快照。I/O 管理器积累读、写与其它传输量。对象管理器维护句柄表项。调度器读取优先级类、动态优先级提升设置和处理器亲和范围。各来源的更新时刻并不完全相同,样本需要保存字段状态与采样时钟。
整个过程为:输入 PID -> 建立样本和进程句柄 -> 读取样本 A -> 等待实际时间窗口 -> 读取样本 B -> 核验实例与计时条件 -> 输出快照或差分结果。
一个样本分为三类数据。实例身份由 PID 和创建时间组成。累计计数包括 CPU 时间和 I/O 传输字节。瞬时属性包括工作集、私有提交量、句柄数、优先级类、优先级提升设置和亲和掩码。累计量只有放在两个有效样本之间才有速率含义。瞬时属性的单个数值只描述读取瞬间,报告中应附带采样时间和字段成功状态。
采样结构需要让读取失败可见。零工作集、零 I/O 增量和零句柄变化都可能是真实结果。把 API 失败也写成零会把“未知”伪装成“没有活动”。下面的结构把值、单位和字段状态分开。FieldStatus::error 只在 available 为 false 时解释为失败代码,成功字段的错误码没有业务含义。
完成第 1 步后,已有一条能区分“读取失败”和“真实零值”的样本结构,也已持有指向目标进程实例的有效句柄。接下来要从这个进程对象取得创建时间、累计 CPU 时间和两个时钟,才能让后续速率计算有身份依据和实际分母。
FILETIME 用两个 32 位字段表达一个 64 位计数,单位为 100ns(1 秒等于 10,000,000 个单位),基准点为 1601-01-01 UTC。GetProcessTimes 的创建时间、退出时间、内核 CPU 时间和用户 CPU 时间都采用这一单位。用户态 CPU 时间表示线程在应用代码和用户态库中获得处理器的累计时间。内核态 CPU 时间表示线程在系统服务、驱动和内核工作中获得处理器的累计时间。两者相加得到进程累计 CPU 时间。
CPU 百分比需要真实的采样窗口。单逻辑处理器基准使用 ΔCPU / ΔActiveTime × 100,其中 ΔCPU 是内核与用户累计时间的差,ΔActiveTime 来自 QueryUnbiasedInterruptTime。多线程进程可以同时在多个逻辑处理器上运行,因此单逻辑处理器基准能够超过 100%。全机份额需要额外记录可用逻辑处理器数、处理器组、作业对象限制和 CPU 集合范围,再选择相应分母。