freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

如何获取Linux、Mac、Windows的系统进程信息
2023-03-07 21:57:49
所属地 浙江省

系统CPU使用率

让我们从系统CPU使用率计算开始(不是每个进程的CPU使用率,我们将在下面解释这一点!)CPU在一个(时钟)周期内最多可以执行给定数量的指令,因此它在一个周期内执行的指令越多,它的使用率就越高。

不过,需要注意的一件非常重要的事情是:此信息只能在给定的时间量内计算。现在你可能想知道为什么会有这样的限制?CPU时间只随时间增长(就像Unix时间戳一样)。因此,如果您试图只计算当前时间的CPU使用率,您实际上会从计算机的引导中获得CPU使用率(为了简化操作),这不是很有用……

因此,如果您希望更准确地计算CPU使用率,则必须在给定的时间跨度内进行计算。首先,获取所需的CPU时间和CPU信息,然后等待一段时间(比如一秒钟),再次获取该信息,然后从“新”信息中减去“旧”信息,从而计算出CPU使用率。

不管怎样,现在让我们来看看如何获得这些信息:

在Linux上,该信息位于/proc/stat中。该文件如下所示:

cpu  7032633 6839 1618386 246526491 110129 0 1392 0 0 0
cpu0 441666 276 94030 15411157 7027 0 208 0 0 0
cpu1 521796 1252 92976 15336333 6958 0 86 0 0 0

第一行是“全局”使用情况(所有处理器都是如此)。如果我们想要每个处理器的详细信息,我们必须查看其他行。如果您对每个值都有兴趣,我建议您查看man proc并查找/proc/stat

简单地说,我们将所有值相加,得到处理器的“总时间”,它由“空闲时间”(不做任何事情的时间)和“工作时间”组成。然后进行简单的计算,我们就可以知道它的用法了:

cpu_usage = (new_work_time - old_work_time) / (new_total_time - old_total_time) * 100

在Mac上,我们首先需要使用sysctl获取处理器数量。然后,我们调用HOST_PROCESSOR_INFO函数,该函数填充一个I32数组。

该数组可以被视为一个“块”数组,其中每个“块”包含每个处理器的数据。要转到下一个块,我们将libc::CPU_STATE_MAX添加到当前位置。然后我们就会得到我们想要的价值。伪代码:

offset = 0
from 0 to nb_processors:
    value_we_want = cpu_info[offset + index_of_value_we_want]
    offset += libc::CPU_STATE_MAX

所以最后,我们需要计算:“使用中”时间和“空闲”时间,然后进行简单的计算:

total = idle + in_use
cpu_usage = in_use / total * 100

就是这样!至于“全局”CPU使用率,我们只需将它们全部相加,并将结果除以处理器数量。

Windows API提供的信息与我们之前讲的方式完全不同。要获取CPU使用率,我们需要使用一个完全不透明的“查询引擎”。首先,我们使用PdhOpenQueryA创建一个到查询引擎的“连接”,然后创建查询(看起来像“%处理器时间”),接着我们使用PdhLookupPerfNameByIndexW将其翻译一下(否则根据本地语言可能找不到)。最后,我们使用PdhGetFormattedCounterValue运行查询。就是这样!

就像MacOS一样,FreeBSD在很大程度上依赖于sysctl调用来检索系统信息。要获得系统的CPU使用率,您需要使用kern.cp_time和kern.cp_Times调用sysctl。第一个需要一个CPUSTATES元素数组,而第二个需要一个CPUSTATES*nb_处理器元素数组。两者都是c_ulong类型的数组。

然后,这两者的情况再次相同:对于每个CPU(因此cp_time和cp_Times内的每个CPU),您可以按如下方式计算CPU使用率:

您遍历这些值并将所有值添加到一个变量中。从这个变量中减去“旧的”值,就可以得到您的CPU使用了多少时间。现在,要获得这些时间中有多少用于计算,您需要减去CPU[CP_IDLE](旧的和新的!)从它那里。

则计算结果为:

cpu_usage = total_time_without_idle / total_time * 100

系统内存信息

这些信息出人意料地复杂,很难弄清楚。让我们看看为什么在每个平台上:

Linux中,要同时获得交换空间和内存使用率,信息位于/proc/meminfo中。该文件如下所示:

MemTotal:       32499128 kB
MemFree:         3013464 kB
MemAvailable:    7337880 kB

所以很容易解析,以获得我们需要的值。现在我们可以从“有趣”的部分开始了。

首先,所有这些值都不是以kB为单位,而是以KiB为单位,这与手册和文件所说的相反,并且sysinfo API返回kB,因此需要进行转换。如果你想看细节的话,关于这一点的讨论在这一期中发生了。

然后,您需要知道实际使用哪些值来计算已用内存。计算如下(使用/proc/meminfo文件中的名称):

MemTotal - MemFree - Buffers - Cached - SReclaimable

对于其他内存值,这要容易得多。MemTotal表示内存总量,MemAvailable表示可用内存(这不同于“空闲”内存!)。掉期交易也是如此:SwapTotal和SwapFree。

当然,我们需要将它们全部从KiB转换为KB。

在MacOS上对于交换空间,没有问题。我们使用带有VM_SWAPUSAGE参数的sysctl。总内存量也几乎一样,我们同样使用sysctl,但是使用HW_MEMSIZE参数。对于这两个值,它们以“字节”返回,因此我们需要除以1000将它们转换为KB。问题再次出现在计算已用内存时。为了获取它,我们使用host_statistics64函数和HOST_VM_INFO64参数来填充vm_statistics64结构体。它包含许多字段,并且所有字段都以“页数”表示,因此要将其转换为KB的使用量,只需要将该数字乘以页面大小(可以使用sysconf获取)。要获取可用内存,没有什么复杂的东西。

TotalRAM - (active_count + inactive_count + wire_count + speculative_count - purgeable_count)

在Windows上,调用GlobalMemory yStatusEx会给出总内存数和可用内存数。请注意,sysinfo将可用内存视为Windows上的空闲内存。

对于交换,我们需要调用GetPerformanceInfo,然后进行以下计算(PageSize由GetPerformanceInfo返回):

SWAP_TOTAL=(Committee Limit-PhysicalTotal)*页面大小

SWAP_USED=(Committee Total-PhysicalTotal)*页面大小

最后,因为所有4个值都是以字节为单位的,所以我们将它们除以1000。

进程CPU使用率

我们已经了解了如何获取系统CPU使用率和列出正在运行的进程,现在让我们看看如何获取一个进程使用了多少时间。需要注意的一点是,与处理器不同,由于多线程的原因,进程CPU使用率可能超过100%。如果你有两个线程以100%运行,那么该进程的总使用率为200%。

提醒一下:对于系统CPU使用率计算,我们需要将工作时间除以总时间。为了了解进程使用了多少CPU,我们将进程时间除以总时间。在这一点上,最大的问题是如何获得过程的时间。

您可以在/proc/[进程id]/stat中找到它。它由utime(用户时间)和stime(系统时间)组成。该文件如下所示:

1 (systemd) S 0 1 1 0 -1 4194560 335815 45130177 1981 1535413 14596 10132 91899 33291 20 0 1 0 24 172781568 2878 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 0 0 0 36 0 0 0 0 0 0 0 0 0 0

对于人类来说,阅读起来不是很容易,但对于解析来说却非常棒!(如果您想了解每个值是什么,请使用man proc。)

Utime是第13个值,stime是第14个值。

然后计算如下:

cpu_usage = ((utime - old_utime) + (stime - old_stime)) * nb_processors * 100 / total_time

在Mac上,有两种方法可以计算进程的CPU使用率。由于某些原因,由于新的MacBook配备了M1处理器,所以“旧”的MacBook不再准确。在这里,我们只描述“新的”版本。

在执行任何操作之前,我们需要获取“系统时间间隔”。我们通过从HOST_PROCESSOR_INFO中检索“ticks”计数来获得它,然后将其转换为纳秒,以便我们以后可以使用它。这是很多不是很有趣的技术细节,但如果你感兴趣,你可以在这里看看我们是如何做到的。

然后,我们需要从proc_pidinfo函数获取proc_taskinfo结构。然后这只是一个简单的计算(pti是proc_taskinfo结构):

cpu_usage = ((pti.stime + pti.utime) - (old_stime + old_utime)) / system_time_interval * 100

获得进程ID(或在Windows上调用的“进程句柄”)后,您需要获取进程时间(使用GetProcessTimes)和系统时间(使用GetSystemTimes)。那么这(再次)只是一个简单的计算:

denominator = (global_kernel_time - old_global_kernel_time) + (global_user_time - old_global_user_time)
((kernel_time - old_kernel_time) + (user_time - old_user_time)) / denominator * 100

进程内存信息

进程内存分为“常驻”内存和“虚拟”内存。让我们看看它是如何计算出来的。

在Linux上,同样,此信息可在/proc/[进程ID]/stat中获得(有关更多信息,请查看进程CPU使用情况)。“虚拟”内存是第22个条目,“常驻”内存是第23个条目。就是这样。

MacOS这里也不是很复杂。首先,我们需要调用proc_pidinfo函数,并将PID、PROC_PIDTASKINFO常量和proc_taskinfo结构作为参数传递给它。然后我们只需从字段中获取值:pti_resident_size表示驻留内存,pti_virtual_size表示虚拟内存。

Windows上,当迭代NtQuerySystemInformation返回的内容时,信息存储在SYSTEM_PROCESS_INFORMATION结构字段中:WorkingSetSize用于“常驻”内存,VirtualSize用于“虚拟”内存。

进程环境变量

在Windows上获取这些信息更加复杂,而且仅适用于64位指针宽度的进程…

因此,首先我们需要调用NtQueryInformationProcess来获取ProcessBasicInformation的内容。 然后,我们使用上一次调用得到的PebBaseAddress字段调用ReadProcessMemory,以获取PEB信息。 我们再次调用ReadProcessMemory,但这次使用PEB结构中的ProcessParameters字段,以获取RTL_USER_PROCESS_PARAMETERS信息。 此时,我们几乎完成了。由于我们已经填充了RTL_USER_PROCESS_PARAMETERS结构,因此我们可以访问其Environment字段。但在继续之前,我们需要知道该字段的长度。为此:

首先,我们调用VirtualQueryEx函数以填充MEMORY_BASIC_INFORMATION结构(用于RegionSize和BaseAddress字段)。 然后,我们按照以下方式计算大小:RegionSize-Environment.offset_from(BaseAddress)。 最后,我们将大小转换为usize并返回。 从这一点开始,只需将其编码为UTF-16并在环境变量之间分割(它们与Linux上的一样使用\0分隔)。

Linux中,信息存储在/proc/[进程PID]/environ中。每个环境变量以\0结尾,因此通过在此字符上拆分,将其转换为Vec相当容易。

MacOS中,这种实现非常棘手。我们使用sysctl和KERN_PROCARGS2来获取进程运行参数。这里有一点额外的解释:C中的main函数如下所示:

int main(int argc, char **argv) {}

argc告诉您收到了多少个参数,而argv包含这些参数。因此,例如:

ls -l some_folder

在这里,argv的简化版本为:["ls", "-l", "some_folder"],argc为3。

但实际上,main函数可以接收第三个参数:

int main(int argc, char **argv, char **env) {}

这并不总是这样(旧的POSIX不允许),但为了简化事情,我们假设可以这样做(在旧的和“不那么旧”的POSIX中都可以使用extern environ;来获取环境变量的访问权限)。

那么现在你可能想知道我为什么提供了所有这些信息?答案很简单,因为通过sysctl调用,我们可以访问所有程序的参数,包括第三个参数!

因此,首先我们跳过argc和argv,然后从env中提取我们正在寻找的信息:

/---------------\ 0x00000000
| ::::::::::::: |
|---------------| <-- Beginning of data returned by sysctl() is here.
| argc          |
|---------------| <-- First "argv" element.
| exec_path     |
|---------------|
| 0             |
|---------------|
| arg[0]        |
|---------------|
| 0             |
|---------------|
| arg[n]        |
|---------------|
| 0             |
|---------------| <-- First environment variable.
| env[0]        |
|---------------|
| 0             |
|---------------|
| env[n]        |
|---------------|
| ::::::::::::: |
|---------------| <-- Top of stack.
:               :
:               :
\---------------/ 0xffffffff

可以看出自己实现一个跨平台的系统信息获取功能确实有些繁琐和难度;
最后推荐一个Rust开源项目,解决这些问题,参考:https://github.com/GuillaumeGomez/sysinfo

# 系统安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录