Linux使用C语言读取proc/stat数据
Author: OnceDay Date: 2024年2月23日
漫漫长路,才刚刚开始…
全系列文章可查看专栏: Linux实践记录_Once_day的博客-CSDN博客
参考文档:
-
proc(5) - Linux manual page (man7.org)
-
The /proc Filesystem — The Linux Kernel documentation
-
kernel.org/doc/Documentation/filesystems/proc.txt
-
Linux中通过/proc/stat等文件计算Cpu使用率 - 苦涩的茶 - 博客园 (cnblogs.com)
-
【linux】/proc/stat计算cpu使用率_linux /proc/stat-CSDN博客
-
ps vs top:CPU占用率统计的两种不同方式 - 知乎 (zhihu.com)
文章目录
- Linux使用C语言读取proc/stat数据
- 1. 概述
- 1.1 `/proc`与cpu运行状态
- 1.2 `/proc/stat`文件介绍
- 1.3 进程和线程`stat`文件介绍
- 1.4 `/proc/pid/stat`和`/proc/pid/task/tid/stat`文件字段
- 2. 编码读取数据
- 2.1 读取CPU使用状态
- 2.2 iowait时间分析
- 2.3 jiffies和HZ值获取
- 2.4 nanosleep处理中断场景
- 2.5 实现简易my-top功能
- 2.6 my-top支持进程统计
- 2.7 my-top支持线程统计
1. 概述
1.1 /proc
与cpu运行状态
在Linux系统中,/proc
文件系统包含了系统运行时的信息,其中也包括了CPU的使用情况。/proc
实际上是一个虚拟文件系统,它将内核和系统信息以文件的形式展现给用户和程序。要从/proc
获取CPU的使用率,我们可以读取/proc/stat
文件,这个文件包含了一系列以空格分隔的数字,其中第一行以cpu
开头的数字就是关键信息,它们分别代表了不同类型的CPU时间。
具体来说,/proc/stat
文件的第一行通常如下所示:
cpu 3357 0 4313 1362393 ...
这些数字分别代表了用户态的CPU时间(user time),用户态的带nice值的CPU时间(nice time),系统态的CPU时间(system time),空闲态的CPU时间(idle time),等等。通过计算这些值之间的差异,我们可以得到CPU的使用率。
ps
和top
命令也是通过读取/proc
中的信息来获取CPU使用率的。例如,top
命令会周期性地读取/proc/stat
,然后计算出CPU的总时间和各个时间片的使用情况,从而计算出CPU的使用率。ps
命令则更多地关注进程级别的CPU使用情况,它通过读取/proc/[pid]/stat
(其中[pid]
是进程的ID)来获取特定进程的CPU使用时间,然后计算出该进程的CPU使用率。
要手动计算CPU使用率,我们可以采取以下步骤:
- 读取
/proc/stat
文件,记录下CPU时间片的初始值。 - 休息一段时间(比如1秒)。
- 再次读取
/proc/stat
文件,记录下CPU时间片的新值。 - 计算每种类型的CPU时间片的差值,然后计算出总的CPU时间。
- 使用非空闲时间片与总时间的比例来计算CPU的使用率。
这个过程通常需要编写脚本或者程序来自动完成。不过,由于现成的ps
和top
命令已经提供了这些功能,大多数用户和开发者会直接使用这些工具,而不是从头计算CPU使用率。
总之,/proc
文件系统为我们提供了一个直接读取系统运行状态的接口,而工具如ps
和top
则利用这些信息以更方便的方式为用户展示系统的状态,包括CPU的使用情况。
1.2 /proc/stat
文件介绍
/proc/stat
文件位于Linux操作系统的/proc
文件系统中,这是一个虚拟的文件系统,它提供了一个窗口来查看内核内部的状态信息。/proc/stat
文件包含了一系列的系统活动统计信息,其中最顶部的行提供了关于CPU时间的总体统计数据,如下所示:
ubuntu->~:$ cat /proc/stat
cpu 5212303 98033 4412675 1366830682 2104350 0 230625 0 0 0
cpu0 1349225 24355 1108662 341657078 565455 0 78071 0 0 0
cpu1 1275002 26606 1107325 341728080 546113 0 58402 0 0 0
cpu2 1312912 22750 1113665 341672358 474698 0 48507 0 0 0
cpu3 1275163 24321 1083022 341773164 518083 0 45644 0 0 0
intr 2748394551 0 13 0 0 24 0 3 0 0 0 0 0 1481 0 3430680 0 0 0 0 0 0 0 0 0 0 43389078 0 10126249 9959286 8616424 10047657 ......
ctxt 4858953613
btime 1705245476
processes 14935187
procs_running 1
procs_blocked 0
softirq 713548476 1 152241528 7 72799169 44852747 0 372533 234911393 28936 208342162
下面是/proc/stat
文件中关于CPU统计信息的字段含义(第一个cpu字段):
字段 | 含义 |
---|---|
user | 从系统启动开始累积到当前时刻,处于用户态的运行时间,不包括 nice 值为负进程。 |
nice | 从系统启动开始累积到当前时刻,nice 值为负的进程所占用的CPU时间。 |
system | 从系统启动开始累积到当前时刻,处于内核态的运行时间。 |
idle | 从系统启动开始累积到当前时刻,除IO等待时间外的其它等待时间。 |
iowait | 从系统启动开始累积到当前时刻,IO等待时间。 |
irq | 从系统启动开始累积到当前时刻,硬中断时间。 |
softirq | 从系统启动开始累积到当前时刻,软中断时间。 |
steal | 管理程序运行的虚拟化环境中,其他操作系统占用的时间。 |
guest | 运行虚拟机(guest)所占用的CPU时间,该时间不包括guest_nice 时间。 |
guest_nice | 运行一个带nice值的guest所占用的CPU时间。 |
每个数字代表的是系统启动以来至今的CPU时间,单位通常为jiffies(节拍数)。为了获取CPU的利用率,通常需要在不同时间点读取这些值,并计算差值。
在多核CPU系统中,/proc/stat
文件还会包含多行以cpu0
、cpu1
等开头的数据,分别表示每一个核的时间片信息。
除了CPU时间信息,/proc/stat
文件还包含了其他统计数据,例如中断、上下文切换、启动以来的时间等,如下所示:
字段名 | 描述 |
---|---|
intr | 自系统启动以来服务的中断次数,第一列是总中断次数,后面的列分别对应每个特定的编号中断 |
ctxt | 所有CPU上下文切换的总次数 |
btime | 系统启动时间,以Unix纪元以来的秒数表示 |
processes | 创建的进程和线程的数量,包括通过fork()和clone()系统调用创建的 |
procs_running | 正在运行或准备运行的线程总数(即可运行的线程总数) |
procs_blocked | 当前因等待I/O完成而阻塞的进程数 |
softirq | 自系统启动以来服务的软中断次数,第一列是所有软中断的服务次数,后面的列分别对应每个特定的软中断 |
这些数据对于系统监控工具和性能分析工具非常重要,它们可以用这些数据来计算出系统的各种性能指标。例如,系统管理员可能会使用这些数据来监控CPU使用情况,分析系统负载,或者检测可能的性能瓶颈。这些数据也经常用于系统级性能调优和故障排查。
1.3 进程和线程stat
文件介绍
在Linux操作系统中,/proc
文件系统是一个虚拟的文件系统,它提供了一个窗口来查看内核中的各种信息,其中包括了进程的详细信息。每一个正在运行的进程都有一个对应的以其进程ID(PID)命名的目录,位于/proc
下。例如,如果一个进程的PID是1234,那么它的信息就存放在/proc/1234
目录中。
/proc/pid/stat
文件包含了与特定PID相关的进程的状态信息。这个文件为每个进程提供了一系列的统计数据,例如进程的运行状态、内存使用情况、调度信息等。这些数据对于理解进程的行为及性能调优非常有用。它是了解系统中单个进程表现的关键入口点。
另一方面,/proc/pid/task
目录包含了与该进程相关的所有线程的信息,每个线程也被分配了一个唯一的线程ID(TID)。在多线程应用中,每个线程都可能执行不同的任务或相同任务的不同部分。/proc/pid/task/tid/stat
文件则是包含了特定线程的状态信息。如果你想了解某个特定线程的细节,这个文件将提供丰富的信息,如线程的CPU时间统计,以及线程的调度优先级等。
那么,/proc/pid/stat
和/proc/pid/task/tid/stat
之间的区别主要在于它们的粒度不同。前者提供的是进程级别的信息,而后者提供的是线程级别的信息。在Linux中,线程被视为轻量级进程(LWP),因此每个线程在/proc
中都有自己的stat
文件。
联系在于,它们都是从内核中抽象出来的信息,用于描述进程和线程的当前状态。实际上,如果一个进程只有一个线程,那么/proc/pid/stat
和/proc/pid/task/tid/stat
文件中的很多信息会是相同的,因为线程就是该进程的唯一执行路径。
原理上,当你访问这些文件时,内核会动态生成文件内容,这意味着你每次读取这些文件时,都会得到最新的进程或线程状态信息。这种设计使得这些文件非常适合用来监控和调试系统中的进程和线程。
用途方面,系统管理员和开发人员可以利用这些文件来监控进程和线程的健康状况,进行性能分析或者调试程序。例如,如果一个程序出现性能瓶颈,通过这些文件可以查看哪个线程占用了过多的CPU时间或者是否有线程处于长时间的等待状态。此外,一些性能监控工具和故障排查工具,如top和ps,背后也是通过读取这些文件来获取所需信息的。
简而言之,/proc/pid/stat
和/proc/pid/task/tid/stat
在Linux系统中是两个非常强大的工具,它们为系统的监控和性能调优提供了必要的数据支持,同时也是系统内核透明化的一个体现,使得用户和开发人员能够更加直观地理解和管理系统中的进程和线程。
1.4 /proc/pid/stat
和/proc/pid/task/tid/stat
文件字段
字段具体信息可以参考内核代码fs/proc/array.c
,可以在proc(5) - Linux manual page (man7.org)文档直接查看。
两个文件包含的信息字段是一致的,这些信息以特定的格式列出,每个字段都有其特定的含义。以下是这些字段的总结,以表格形式展示:
ID | 字段名 | 描述 | 示例格式说明 |
---|---|---|---|
1 | pid | 进程ID | 以数字形式表示,例如:17248 |
2 | comm | 命令名称 | 以字符串形式表示,可能被截断,例如:bash |
3 | state | 进程状态 | 以单个字符表示,例如:R (运行中)、S (睡眠)、D (不可中断睡眠)等 |
4 | ppid | 父进程ID | 以数字形式表示,例如:17200 |
5 | pgrp | 进程组ID | 以数字形式表示,例如:17248 |
6 | session | 会话ID | 以数字形式表示,例如:17200 |
7 | tty_nr | 控制终端的设备号 | 以数字形式表示,例如:0 |
8 | tpgid | 控制终端的前台进程组ID | 以数字形式表示,例如:17248 |
9 | flags | 进程标志 | 以数字形式表示,例如:0x000100 |
10 | minflt | 小型页面错误次数 | 以数字形式表示,例如:552 |
11 | cminflt | 子进程产生的小型页面错误次数 | 以数字形式表示,例如:460 |
12 | majflt | 大型页面错误次数 | 以数字形式表示,例如:460 |
13 | cmajflt | 子进程产生的大型页面错误次数 | 以数字形式表示,例如:460 |
14 | utime | 用户模式下的时间 | 以时钟滴答数表示,例如:175628 |
15 | stime | 内核模式下的时间 | 以时钟滴答数表示,例如:0 |
16 | cutime | 死亡子进程/线程在用户模式下的时间 | 以时钟滴答数表示,子进程结束时,累加到父进程上面。 |
17 | cstime | 死亡子进程/线程在内核模式下的时间 | 以时钟滴答数表示,子进程结束时,累加到父进程上面。 |
18 | priority | 进程优先级 | 以数字形式表示,例如:120 |
19 | nice | 进程nice值 | 以数字形式表示,例如:0 |
20 | num_threads | 进程中的线程数 | 以数字形式表示,例如:1 |
21 | itrealvalue | 间隔定时器的剩余时间 | 以时钟滴答数表示,例如:0 |
22 | starttime | 进程启动时间 | 以时钟滴答数表示,例如:769041601 |
23 | vsize | 虚拟内存大小 | 以字节为单位表示,例如:131168 |
24 | rss | 常驻集大小 | 以页面数表示,例如:13484 |
25 | rsslim | 常驻集大小限制 | 以字节为单位表示,例如:0 |
26 | startcode | 程序文本的起始地址 | 以地址形式表示,例如:400000 |
27 | endcode | 程序文本的结束地址 | 以地址形式表示,例如:400000 |
28 | startstack | 栈的起始地址 | 以地址形式表示,例如:8000 |
29 | kstkesp | 栈指针的当前值 | 以地址形式表示,例如:8000 |
30 | kstkeip | 指令指针的当前值 | 以地址形式表示,例如:8000 |
31 | signal | 待处理信号的位图 | 以十进制数字表示,例如:0 |
32 | blocked | 阻塞信号的位图 | 以十进制数字表示,例如:0 |
33 | sigignore | 忽略信号的位图 | 以十进制数字表示,例如:0 |
34 | sigcatch | 捕获信号的位图 | 以十进制数字表示,例如:0 |
35 | wchan | 等待的通道 | 以地址形式表示,例如:400000 |
36 | nswap | 交换出去的页面数 | 以数字形式表示,例如:0 |
37 | cnswap | 子进程交换出去的页面数 | 以数字形式表示,例如:0 |
38 | exit_signal | 退出信号 | 以数字形式表示,例如:0 |
39 | processor | 最后执行的CPU号 | 以数字形式表示,例如:0 |
40 | rt_priority | 实时调度优先级 | 以数字形式表示,例如:0 |
41 | policy | 调度策略 | 以数字形式表示,例如:0 |
42 | delayacct_blkio_ticks | 累积的块I/O延迟 | 以时钟滴答数表示,例如:150 |
43 | guest_time | 虚拟CPU运行时间 | 以时钟滴答数表示,例如:0 |
44 | cguest_time | 死亡子进程/线程的虚拟CPU运行时间 | 以时钟滴答数表示,例如:0 |
45 | start_data | 数据段的起始地址 | 以地址形式表示,例如:400000 |
46 | end_data | 数据段的结束地址 | 以地址形式表示,例如:400000 |
47 | start_brk | 堆的起始地址 | 以地址形式表示,例如:400000 |
48 | arg_start | 命令行参数的起始地址 | 以地址形式表示,例如:400000 |
49 | arg_end | 命令行参数的结束地址 | 以地址形式表示,例如:400000 |
50 | env_start | 环境变量的起始地址 | 以地址形式表示,例如:400000 |
51 | env_end | 环境变量的结束地址 | 以地址形式表示,例如:400000 |
52 | exit_code | 线程的退出状态 | 以数字形式表示,例如:0 |
请注意,这些字段的确切含义和格式可能会随着Linux内核的不同版本而有所变化。在实际使用中,应参考特定版本的内核文档以获取最准确的信息。
2. 编码读取数据
2.1 读取CPU使用状态
在C语言中,可以通过读取/proc/stat
文件来获取CPU使用信息。下面是一个C语言的示例代码:
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
/* cpu使用率核心数据信息结构体定义 */
struct my_cpu {
uint64_t user; /* 用户态CPU时间 */
uint64_t nice; /* 低优先级用户态CPU时间 */
uint64_t system; /* 内核态CPU时间 */
uint64_t idle; /* 空闲CPU时间 */
uint64_t iowait; /* 等待I/O操作CPU时间 */
uint64_t irq; /* 硬中断CPU时间 */
uint64_t softirq; /* 软中断CPU时间 */
uint64_t steal; /* 虚拟机CPU时间 */
uint64_t guest; /* 虚拟机低优先级CPU时间 */
uint64_t guest_nice; /* 虚拟机低优先级用户态CPU时间 */
};
#define MY_CPU_FSCANF_ARGS 11
#define MY_CPU_FSCANF_BUF_SIZE 16
#define MY_CPU_FSCANF(fp, buf, cpu) \
fscanf(fp, "%16s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", buf, &((cpu)->user), \
&((cpu)->nice), &((cpu)->system), &((cpu)->idle), &((cpu)->iowait), &((cpu)->irq), \
&((cpu)->softirq), &((cpu)->steal), &((cpu)->guest), &((cpu)->guest_nice))
/* 格式化打印my cpu中所有字段的数据 */
static void my_cpu_dump_data(struct my_cpu *cpu_info)
{
printf(
"CPU user: %lu, nice: %lu, system: %lu, idle: %lu, iowait: %lu, irq: %lu, softirq: %lu, "
"steal: %lu, guest: %lu, guest_nice: %lu.\n",
cpu_info->user, cpu_info->nice, cpu_info->system, cpu_info->idle, cpu_info->iowait,
cpu_info->irq, cpu_info->softirq, cpu_info->steal, cpu_info->guest, cpu_info->guest_nice);
return;
}
/**
* 获取指定CPU核的使用率, 通过读取/proc/stat文件数据实现.
* 对于/proc/stat文件的具体解释, 可参考Linux kernel代码: fs/proc/stat.c.
* 不同版本的Linux内核, /proc/stat文件的格式可能会有所不同(有问题请分析源码).
*/
bool get_cpu_usage(void)
{
FILE *fp;
struct my_cpu cpu_info;
char buf[MY_CPU_FSCANF_BUF_SIZE + 1];
/* 打开全局cpu状态信息虚拟文件 */
fp = fopen("/proc/stat", "r");
if (fp == NULL) {
printf("Open /proc/stat file error, %s(%d)\n", strerror(errno), errno);
return false;
}
/* 读取cpu数据 */
if (MY_CPU_FSCANF(fp, buf, &cpu_info) != MY_CPU_FSCANF_ARGS) {
goto error;
}
my_cpu_dump_data(&cpu_info);
error:
fclose(fp);
return true;
}
int main(void)
{
return get_cpu_usage();
}
这段代码的功能是获取指定CPU核的使用率。它通过读取Linux系统中的/proc/stat
文件来获取CPU的状态信息。
代码中定义了一个名为struct my_cpu
的结构体,用于存储CPU的各项数据,如用户态CPU时间、内核态CPU时间、空闲CPU时间等。
函数get_cpu_usage
是主要的函数,它打开/proc/stat
文件并读取其中的数据。如果文件打开失败,会输出错误信息并返回false
。如果成功打开文件,代码会使用MY_CPU_FSCANF
宏来解析文件中的数据,并将解析结果存储在cpu_info
结构体中。然后,函数调用my_cpu_dump_data
函数来打印CPU的各项数据。
最后,main
函数调用get_cpu_usage
函数,并将其返回值作为程序的返回值。
编译并运行这段程序,就可以得到读到CPU的使用信息,编译时使用gcc编译器,例如:
ubuntu->cs-test:$ ./read-stat
CPU user: 5277601, nice: 98034, system: 4424430, idle: 1367840657, iowait: 2106431, irq: 0, softirq: 231362, steal: 0, guest: 0, guest_nice: 0.
ubuntu->cs-test:$ ./read-stat
CPU user: 5277602, nice: 98034, system: 4424432, idle: 1367840946, iowait: 2106431, irq: 0, softirq: 231362, steal: 0, guest: 0, guest_nice: 0.
请注意,这段代码只是一个示例,用于展示如何从/proc/stat
中读取和计算CPU使用率。
2.2 iowait时间分析
在操作系统的性能监控和分析领域,iowait是一个常见的术语,它指的是CPU等待I/O(输入/输出)操作完成的时间。当你检视系统的性能时,可能会遇到iowait值,它通常被视为一个性能指标,可以反映出系统处理I/O请求的效率。
iowait通常不被归类为CPU的“空闲时间”,也不完全是“运行时间”。其实,iowait是一种特殊状态,当CPU没有其他可执行的任务,同时正在等待I/O操作(例如,从硬盘读取数据或向硬盘写入数据)完成时,就会处于这种状态。在这段时间里,CPU在逻辑上是空闲的,因为它不能执行其他计算任务,但实际上它又在等待I/O操作的完成,所以并不能算作真正的空闲时间。
从系统性能的角度看,较高的iowait可能意味着I/O子系统是性能瓶颈。如果CPU频繁进入iowait状态,可能表示存储设备响应慢,或者I/O操作过于繁重,导致CPU需要等待I/O操作完成后才能继续处理其他任务。在这种情况下,虽然CPU的利用率可能不高,但系统的整体性能可能因为I/O延迟而受到影响。
因此,iowait是系统管理员在性能调优时需要关注的一个重要指标。如果系统的iowait值异常高,可能需要考虑升级硬件、优化存储系统或者重新设计应用程序的I/O模式,以减少I/O延迟对系统性能的影响。
iowait既不是CPU的空闲时间,也不算是CPU的运行时间。它是一个介于两者之间的状态,反映了CPU等待I/O操作的时间。虽然在这段时间内CPU没有进行计算处理,但系统性能仍可能因高iowait而受到影响。
在本文里,我们把iowait算到空闲时间里面去,但这并非一定是正确的行为(iowait有时也是程序的瓶颈),而且该字段的值并不完全可靠:
- CPU 本身不会等待 I/O 操作完成;
iowait
实际上是指任务在等待 I/O 完成时所花费的时间。当一个 CPU 进入空闲状态,等待任务的 I/O 操作时,另一个任务会被调度到该 CPU 上执行。 - 在多核 CPU 上,等待 I/O 完成的任务并不会在任何 CPU 上运行,因此计算每个 CPU 的
iowait
时间是困难的。 - 该字段的值在某些情况下可能会减少。
iowait
时间的统计对于分析系统性能和瓶颈非常有用,尤其是在 I/O 密集型的应用场景中。高 iowait
值可能表明系统正在经历 I/O 瓶颈,需要进一步的调查和优化。
2.3 jiffies和HZ值获取
在Linux操作系统中,jiffies
是一个内核维护的全局变量,用来记录自系统启动以来的滴答数(tick count),这是一个持续增长的计数器。滴答是操作系统的基本时间单位,每个滴答代表了操作系统时钟中断的一次触发。时钟中断是操作系统时间管理的基础,它允许操作系统进行任务调度,如进程切换和资源管理等。
要获取jiffies
的时间长度,需要知道系统的时钟中断频率,即每秒中断的次数。在Linux系统中,这个值通常被定义为HZ
。HZ
的值依赖于具体的内核配置和硬件平台,但常见的值有100、250、300或者1000。例如,如果HZ
值为1000,那么每个滴答的时间长度就是1毫秒。
获取jiffies
时间长度的方法如下:
-
查看
HZ
值:在某些Linux系统中,可以直接查看内核配置文件来确定HZ
值,例如/boot/config-$(uname -r)
文件。使用以下命令:ubuntu->cs-test:$ cat /boot/config-$(uname -r) | grep CONFIG_HZ # CONFIG_HZ_PERIODIC is not set # CONFIG_HZ_100 is not set CONFIG_HZ_250=y # CONFIG_HZ_300 is not set # CONFIG_HZ_1000 is not set CONFIG_HZ=250
这个命令会输出当前内核的
CONFIG_HZ
值。 -
计算jiffies时间长度:一旦你知道了
HZ
值,就可以计算出一个jiffy
的时间长度。公式为:时间长度(秒)= 1 / HZ
如果
HZ
是1000,那么每个jiffy
的时间长度就是1/1000秒,即1毫秒。 -
查看当前的
jiffies
值:你可以通过查看/proc/timer_list
文件或者使用内核API来获取当前的jiffies
值。 -
转换
jiffies
到实际时间:如果你有一个特定的jiffies值,可以通过将它除以HZ
来得到自系统启动以来经过的秒数。
jiffies
是内核抽象时间的一种,对于开发者和系统管理员来说,更常用的时间获取函数可能是gettimeofday()
或者clock_gettime()
,它们可以提供更精确的时间信息。然而,直接操作jiffies可能更适用于内核模块编程或对操作系统时钟行为有特殊要求的场合。
在C语言中,可以通过调用sysconf()
函数来获取HZ
值,例如:
//...(省略2.1中的代码)...
int main(void)
{
int64_t hz;
hz = sysconf(_SC_CLK_TCK);
printf("HZ value is: %ld\n", hz);
get_cpu_usage();
sleep(1);
get_cpu_usage();
return 0;
}
编译并运行上述代码,将输出系统的HZ
值(没有走系统调用,通过内核共享数据直接拿到的值)。
ubuntu->cs-test:$ gcc -o read-stat read-stat.c
ubuntu->cs-test:$ ./read-stat
HZ value is: 100
CPU user: 5296072, nice: 98034, system: 4431347, idle: 1368556746, iowait: 2110570, irq: 0, softirq: 231715, steal: 0, guest: 0, guest_nice: 0.
CPU user: 5296072, nice: 98034, system: 4431348, idle: 1368557143, iowait: 2110570, irq: 0, softirq: 231715, steal: 0, guest: 0, guest_nice: 0.
sysconf获取的jiffies频率是100Hz,实际间隔1s,系统增加耗时也是(idle相减)400 jiffies,这里需要注意,cpu统计的是全部核的数据,因此需要除以4(四个核),那就是100 jiffies,说明sysconf读取的数据没有问题。
2.4 nanosleep处理中断场景
在Linux系统中,nanosleep
允许进程以纳秒为单位暂停执行。如果在调用 nanosleep
期间,进程接收到了信号,那么 nanosleep
会被中断,函数会返回 -1
,并将 errno
设置为 EINTR
。如果提供了 timespec
结构体的指针作为 nanosleep
的第二个参数 rem
,那么该结构体会被填充剩余的睡眠时间。
例如,如果你的程序中有如下的 nanosleep
调用:
struct timespec req, rem;
req.tv_sec = 0;
req.tv_nsec = 1000000000; // 1秒
int ret = nanosleep(&req, &rem);
如果在这一秒内,进程接收到了一个信号,nanosleep
会被中断,ret
将会是 -1,errno
将会是 EINTR
,而 rem
结构体将包含剩余的睡眠时间。
在实际编程中,可以通过循环调用 nanosleep
并检查返回值来处理这种情况,例如:
int ret;
do {
ret = nanosleep(&req, &rem);
} while (ret == -1 && errno == EINTR);
这样,如果 nanosleep
被信号中断,循环会继续,直到它成功执行了指定的睡眠时间。
2.5 实现简易my-top功能
这里我们读取/proc/stat
数据,在2.1节的基础上,按照指定间隔持续读取数据,计算差值,输出全部和分cpu使用情况,代码如下:
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
/* cpu使用率核心数据信息结构体定义 */
struct my_cpu {
uint64_t user; /* 用户态CPU时间 */
uint64_t nice; /* 低优先级用户态CPU时间 */
uint64_t system; /* 内核态CPU时间 */
uint64_t idle; /* 空闲CPU时间 */
uint64_t iowait; /* 等待I/O操作CPU时间 */
uint64_t irq; /* 硬中断CPU时间 */
uint64_t softirq; /* 软中断CPU时间 */
uint64_t steal; /* 虚拟机CPU时间 */
uint64_t guest; /* 虚拟机低优先级CPU时间 */
uint64_t guest_nice; /* 虚拟机低优先级用户态CPU时间 */
};
/* 可以处理中断的sleep函数, 睡眠指定时间长度(ms) */
static void my_msleep(int ms)
{
struct timespec ts;
int32_t ret;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
do {
ret = nanosleep(&ts, &ts);
} while (ret == -1 && errno == EINTR);
return;
}
#define MY_CPU_FSCANF_ARGS 11
#define MY_CPU_FSCANF_BUF_SIZE 16
#define MY_CPU_FSCANF(fp, buf, cpu) \
fscanf(fp, "%16s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", buf, &((cpu)->user), \
&((cpu)->nice), &((cpu)->system), &((cpu)->idle), &((cpu)->iowait), &((cpu)->irq), \
&((cpu)->softirq), &((cpu)->steal), &((cpu)->guest), &((cpu)->guest_nice))
/* 计算cpu使用率, iowait也算在空闲cpu时间里面 */
#define MY_CPU_TOTAL(cpu) \
((cpu)->user + (cpu)->nice + (cpu)->system + (cpu)->idle + (cpu)->iowait + (cpu)->irq + \
(cpu)->softirq + (cpu)->steal + (cpu)->guest + (cpu)->guest_nice)
#define MY_CPU_IDLE(cpu) ((cpu)->idle + (cpu)->iowait)
#define MY_CPU_USAGE(cpu) (MY_CPU_TOTAL(cpu) - MY_CPU_IDLE(cpu))
#define MY_CPU_NUM 4 /* 4核cpu */
#define MY_CPU_DATA_NUM (MY_CPU_NUM + 1) /* cpu数据个数 */
/* 处理两次采集的cpu使用数据, 然后以合适的格式打印出去 */
static void my_cpu_deal_data(struct my_cpu *cpu_info, struct my_cpu *cpu_info_old)
{
int32_t i, len;
uint64_t diff_total[MY_CPU_DATA_NUM], diff_idle[MY_CPU_DATA_NUM], diff_usage[MY_CPU_DATA_NUM];
char buffer[1024];
for (i = 0; i < MY_CPU_DATA_NUM; i++) {
diff_total[i] = MY_CPU_TOTAL(&cpu_info[i]) - MY_CPU_TOTAL(&cpu_info_old[i]);
diff_idle[i] = MY_CPU_IDLE(&cpu_info[i]) - MY_CPU_IDLE(&cpu_info_old[i]);
diff_usage[i] = diff_total[i] - diff_idle[i];
}
len = snprintf(buffer, sizeof(buffer),
"CPU total: %lu, idle: %lu, usage: %lu, %.2f%%: ", diff_total[0], diff_idle[0],
diff_usage[0], (double)diff_usage[0] * 100 / diff_total[0]);
for (i = 1; i < MY_CPU_DATA_NUM; i++) {
len += snprintf(buffer + len, sizeof(buffer) - len, "core%d %.2f%%, ", (i - 1),
(double)diff_usage[i] * 100 / diff_total[i]);
}
printf("%s\n", buffer);
return;
}
/**
* 获取指定CPU核的使用率, 通过读取/proc/stat文件数据实现.
* 对于/proc/stat文件的具体解释, 可参考Linux kernel代码: fs/proc/stat.c.
* 不同版本的Linux内核, /proc/stat文件的格式可能会有所不同(有问题请分析源码).
*/
bool get_cpu_usage(uint32_t interval_ms)
{
FILE *fp;
int32_t ret, i;
struct my_cpu cpu_info[MY_CPU_DATA_NUM], cpu_info_old[MY_CPU_DATA_NUM];
char buf[MY_CPU_FSCANF_BUF_SIZE + 1];
/* 打开全局cpu状态信息虚拟文件 */
fp = fopen("/proc/stat", "r");
if (fp == NULL) {
printf("Open /proc/stat file error, %s(%d)\n", strerror(errno), errno);
return false;
}
/* 先读取一次原始数据, 然后后续连续间隔指定时间获取数据, 并实时输出使用率 */
for (i = 0; i < MY_CPU_DATA_NUM; i++) {
if (MY_CPU_FSCANF(fp, buf, &cpu_info_old[i]) != MY_CPU_FSCANF_ARGS) {
goto error;
}
}
/* 循环读取cpu使用情况, 并且打印数据 */
do {
/* 移动fp文件指针到文件起始处, 刷新文件缓冲, 持续读取数据 */
fflush(fp);
if (fseek(fp, 0, SEEK_SET) != 0) {
printf("Seek /proc/stat file error, %s(%d)\n", strerror(errno), errno);
goto error;
}
my_msleep(interval_ms);
/* 循环读取所有核的cpu数据 */
for (i = 0; i < MY_CPU_DATA_NUM; i++) {
if (MY_CPU_FSCANF(fp, buf, &cpu_info[i]) != MY_CPU_FSCANF_ARGS) {
goto error;
}
}
my_cpu_deal_data(cpu_info, cpu_info_old);
memcpy(cpu_info_old, cpu_info, sizeof(cpu_info_old));
} while (1);
error:
fclose(fp);
return true;
}
/* 给定间隔时间 */
int main(int argc, char *argv[])
{
uint32_t interval_ms;
if (argc != 2) {
printf("Usage: %s <interval_ms>\n", argv[0]);
return -1;
}
interval_ms = atoi(argv[1]);
get_cpu_usage(interval_ms);
return 0;
}
这段代码的作用是获取指定CPU核的使用率,并通过读取/proc/stat
文件中的数据来实现。它使用了C语言编写,并包含了一些头文件和结构体定义。
代码的流程如下:
-
首先,定义了一个名为
my_cpu
的结构体,用于存储CPU的使用率数据。该结构体包含了各种不同类型的CPU时间,如用户态CPU时间、内核态CPU时间、空闲CPU时间等。 -
接下来,定义了一个名为
my_msleep
的静态函数,用于在指定的时间间隔内进行睡眠。该函数使用了nanosleep
函数来实现,可以处理中断并保证睡眠时间的准确性。 -
然后,定义了一些宏来计算CPU的总时间、空闲时间和使用率。这些宏根据CPU结构体中的各个时间字段进行计算,并提供了方便的接口来获取这些值。
-
在接下来的代码中,定义了一些常量,如CPU核数和CPU数据个数。
-
紧接着,定义了一个名为
my_cpu_deal_data
的函数,用于处理两次采集的CPU使用数据,并以合适的格式打印出来。该函数计算了CPU使用率的差值,并将结果格式化为一个字符串,然后使用printf
函数打印出来。 -
在
get_cpu_usage
函数中,首先打开了/proc/stat
文件,如果文件打开失败,则打印错误信息并返回false
。 -
然后,通过循环读取
/proc/stat
文件中的数据,获取CPU的使用情况,并调用my_cpu_deal_data
函数处理数据并打印出来。在每次读取数据之前,会先将文件指针移动到文件起始处,并刷新文件缓冲区。然后,使用MY_CPU_FSCANF
宏从文件中读取CPU数据,并将其存储在cpu_info
数组中。 -
最后,关闭文件并返回
true
。 -
在
main
函数中,首先检查命令行参数的数量,如果不是2个,则打印用法信息并返回-1。然后,将命令行参数转换为整数,并将其作为参数调用get_cpu_usage
函数。
这段代码通过读取/proc/stat
文件中的数据,实时获取指定CPU核的使用率,并以合适的格式打印出来。它使用了结构体、宏和函数来组织和处理数据,以及文件操作函数来读取和关闭文件。
下面是编译运行情况(中间运行了一个死循环,间隔1000ms):
ubuntu->cs-test:$ gcc -o my-top my-top.c
ubuntu->cs-test:$ ./my-top
Usage: ./my-top <interval_ms>
ubuntu->cs-test:$ ./my-top 1000
CPU total: 399, idle: 390, usage: 9, 2.26%: core0 5.00%, core1 0.00%, core2 2.00%, core3 2.00%,
CPU total: 398, idle: 397, usage: 1, 0.25%: core0 1.00%, core1 0.00%, core2 0.00%, core3 1.00%,
CPU total: 398, idle: 397, usage: 1, 0.25%: core0 0.00%, core1 0.99%, core2 0.00%, core3 0.00%,
CPU total: 398, idle: 393, usage: 5, 1.26%: core0 4.00%, core1 0.00%, core2 1.00%, core3 1.00%,
CPU total: 398, idle: 395, usage: 3, 0.75%: core0 0.00%, core1 1.00%, core2 1.00%, core3 1.98%,
CPU total: 397, idle: 396, usage: 1, 0.25%: core0 0.00%, core1 0.00%, core2 1.00%, core3 0.00%,
CPU total: 404, idle: 287, usage: 117, 28.96%: core0 10.00%, core1 15.69%, core2 74.00%, core3 16.00%,
CPU total: 401, idle: 293, usage: 108, 26.93%: core0 5.77%, core1 1.01%, core2 100.00%, core3 2.97%,
CPU total: 400, idle: 295, usage: 105, 26.25%: core0 1.98%, core1 2.97%, core2 100.00%, core3 0.00%,
CPU total: 402, idle: 295, usage: 107, 26.62%: core0 1.02%, core1 1.98%, core2 100.00%, core3 1.01%,
CPU total: 399, idle: 297, usage: 102, 25.56%: core0 0.00%, core1 1.00%, core2 100.00%, core3 0.99%,
CPU total: 399, idle: 298, usage: 101, 25.31%: core0 0.00%, core1 1.98%, core2 100.00%, core3 0.00%,
CPU total: 401, idle: 295, usage: 106, 26.43%: core0 3.92%, core1 0.00%, core2 100.00%, core3 1.01%,
CPU total: 401, idle: 294, usage: 107, 26.68%: core0 2.97%, core1 1.01%, core2 100.00%, core3 3.96%,
CPU total: 399, idle: 295, usage: 104, 26.07%: core0 1.01%, core1 1.98%, core2 100.00%, core3 0.00%,
CPU total: 400, idle: 376, usage: 24, 6.00%: core0 1.00%, core1 0.00%, core2 22.00%, core3 1.01%,
CPU total: 397, idle: 394, usage: 3, 0.76%: core0 1.00%, core1 0.00%, core2 1.01%, core3 0.00%,
CPU total: 399, idle: 396, usage: 3, 0.75%: core0 0.00%, core1 1.00%, core2 2.00%, core3 0.00%,
CPU total: 398, idle: 397, usage: 1, 0.25%: core0 1.00%, core1 0.00%, core2 0.00%, core3 0.00%,
cpu使用数据还是比较准确的,和top对比,数据误差较小,用于日常分析和数据收集足以。
下面尝试间隔100ms输出数据,如下:
ubuntu->cs-test:$ ./my-top 100
CPU total: 40, idle: 40, usage: 0, 0.00%: core0 0.00%, core1 0.00%, core2 0.00%, core3 0.00%,
CPU total: 40, idle: 40, usage: 0, 0.00%: core0 0.00%, core1 0.00%, core2 0.00%, core3 0.00%,
CPU total: 39, idle: 39, usage: 0, 0.00%: core0 0.00%, core1 9.09%, core2 0.00%, core3 0.00%,
CPU total: 41, idle: 40, usage: 1, 2.44%: core0 0.00%, core1 0.00%, core2 0.00%, core3 0.00%,
CPU total: 42, idle: 39, usage: 3, 7.14%: core0 0.00%, core1 10.00%, core2 9.09%, core3 0.00%,
CPU total: 39, idle: 39, usage: 0, 0.00%: core0 0.00%, core1 10.00%, core2 0.00%, core3 0.00%,
CPU total: 39, idle: 39, usage: 0, 0.00%: core0 0.00%, core1 0.00%, core2 0.00%, core3 0.00%,
CPU total: 40, idle: 40, usage: 0, 0.00%: core0 9.09%, core1 0.00%, core2 0.00%, core3 0.00%,
CPU total: 41, idle: 40, usage: 1, 2.44%: core0 0.00%, core1 0.00%, core2 9.09%, core3 0.00%,
可以看到,最小调度精度在1个jiffies,所以百分比例最小是10ms,在这个尺度上,调度数据也能揭示cpu其实堵塞还是存在的,要想快速实时响应,必须采用实时进程,禁止cpu核调度。
2.6 my-top支持进程统计
实现代码如下,通过遍历/proc/pid/stat
,获取进程消耗的cpu时间:
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <dirent.h>
#include <ctype.h>
/* cpu使用率核心数据信息结构体定义 */
struct my_cpu {
uint64_t user; /* 用户态CPU时间 */
uint64_t nice; /* 低优先级用户态CPU时间 */
uint64_t system; /* 内核态CPU时间 */
uint64_t idle; /* 空闲CPU时间 */
uint64_t iowait; /* 等待I/O操作CPU时间 */
uint64_t irq; /* 硬中断CPU时间 */
uint64_t softirq; /* 软中断CPU时间 */
uint64_t steal; /* 虚拟机CPU时间 */
uint64_t guest; /* 虚拟机低优先级CPU时间 */
uint64_t guest_nice; /* 虚拟机低优先级用户态CPU时间 */
};
/* 可以处理中断的sleep函数, 睡眠指定时间长度(ms) */
static void my_msleep(int ms)
{
struct timespec ts;
int32_t ret;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
do {
ret = nanosleep(&ts, &ts);
} while (ret == -1 && errno == EINTR);
return;
}
#define MY_CPU_FSCANF_ARGS 11
#define MY_CPU_FSCANF_BUF_SIZE 16
#define MY_CPU_FSCANF(fp, buf, cpu) \
fscanf(fp, "%16s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", buf, &((cpu)->user), \
&((cpu)->nice), &((cpu)->system), &((cpu)->idle), &((cpu)->iowait), &((cpu)->irq), \
&((cpu)->softirq), &((cpu)->steal), &((cpu)->guest), &((cpu)->guest_nice))
/* 计算cpu使用率, iowait也算在空闲cpu时间里面 */
#define MY_CPU_TOTAL(cpu) \
((cpu)->user + (cpu)->nice + (cpu)->system + (cpu)->idle + (cpu)->iowait + (cpu)->irq + \
(cpu)->softirq + (cpu)->steal + (cpu)->guest + (cpu)->guest_nice)
#define MY_CPU_IDLE(cpu) ((cpu)->idle + (cpu)->iowait)
#define MY_CPU_USAGE(cpu) (MY_CPU_TOTAL(cpu) - MY_CPU_IDLE(cpu))
#define MY_CPU_NUM 4 /* 4核cpu */
#define MY_CPU_DATA_NUM (MY_CPU_NUM + 1) /* cpu数据个数 */
/* 处理两次采集的cpu使用数据, 然后以合适的格式打印出去 */
static int32_t my_cpu_deal_data(struct my_cpu *cpu_info, struct my_cpu *cpu_info_old)
{
int32_t i, len;
uint64_t diff_total[MY_CPU_DATA_NUM], diff_idle[MY_CPU_DATA_NUM], diff_usage[MY_CPU_DATA_NUM];
char buffer[1024];
for (i = 0; i < MY_CPU_DATA_NUM; i++) {
diff_total[i] = MY_CPU_TOTAL(&cpu_info[i]) - MY_CPU_TOTAL(&cpu_info_old[i]);
diff_idle[i] = MY_CPU_IDLE(&cpu_info[i]) - MY_CPU_IDLE(&cpu_info_old[i]);
diff_usage[i] = diff_total[i] - diff_idle[i];
}
len = snprintf(buffer, sizeof(buffer),
"CPU total: %lu, idle: %lu, usage: %lu, %.2f%%: ", diff_total[0], diff_idle[0],
diff_usage[0], (double)diff_usage[0] * 100 * 4 / diff_total[0]);
for (i = 1; i < MY_CPU_DATA_NUM; i++) {
len += snprintf(buffer + len, sizeof(buffer) - len, "core%d %.2f%%, ", (i - 1),
(double)diff_usage[i] * 100 / diff_total[i]);
}
printf("%s\n", buffer);
return diff_total[0];
}
/* 定义进程信息, 组成链表的格式 */
struct my_process {
struct my_process *next; /* 下一个进程 */
int32_t pid; /* 进程ID */
int32_t processor; /* 进程运行的CPU核心 */
char comm[64]; /* 进程名 */
uint64_t utime; /* 用户态CPU时间 */
uint64_t stime; /* 内核态CPU时间 */
int64_t cutime; /* 死亡子进程/线程用户态CPU时间 */
int64_t cstime; /* 死亡子进程/线程内核态CPU时间 */
uint64_t guest_time; /* 虚拟机用户态CPU时间 */
int64_t cguest_time; /* 虚拟机低优先级用户态CPU时间 */
};
#define MY_PROCESS_TOTAL(cpu) ((cpu)->utime + (cpu)->stime + (cpu)->guest_time)
/* 打印my_process数据 */
static void my_process_dump_data(struct my_process *process_info)
{
printf(
"PID: %d, comm: %s, processor: %d, utime: %lu, stime: %lu, cutime: %ld, cstime: %ld, "
"guest_time: %lu, cguest_time: %ld.\n",
process_info->pid, process_info->comm, process_info->processor, process_info->utime,
process_info->stime, process_info->cutime, process_info->cstime, process_info->guest_time,
process_info->cguest_time);
return;
}
#define MY_PROCESS_CHUNK_NUM 256 /* 最小内存分配单位 */
/* 一次收集一大块内存, 后续再慢慢释放 */
static struct my_process *my_process_alloc_chunk(int32_t count)
{
int32_t i;
struct my_process *chunk;
/* 分配内存 */
chunk = (struct my_process *)malloc(count * sizeof(struct my_process));
if (chunk == NULL) {
printf("Failed to allocate memory for process chunk.\n");
return NULL;
}
/* 初始化内存 */
memset(chunk, 0, count * sizeof(struct my_process));
/* 挂载内存为链表 */
for (i = 0; i < count - 1; i++) {
chunk[i].next = &(chunk[i + 1]);
}
return chunk;
}
/* 空闲my process对象链表 */
static struct my_process *free_list = NULL;
/* 获取一个空闲的my process对象 */
static struct my_process *my_process_alloc(void)
{
struct my_process *process;
/* 如果free_list为NULL, 则收集一大块内存 */
if (free_list == NULL) {
free_list = my_process_alloc_chunk(MY_PROCESS_CHUNK_NUM);
if (free_list == NULL) {
return NULL;
}
}
/* 返回一个空闲的my process对象 */
process = free_list;
free_list = free_list->next;
process->next = NULL;
return process;
}
/* 回收一个空闲的my process对象到空闲链表中 */
static void my_process_free(struct my_process *process)
{
memset(process, 0, sizeof(struct my_process));
process->next = free_list;
free_list = process;
return;
}
/**
* 读取/proc/pid/stat数据:
* pid: 第一个字段, 进程ID, int32_t类型
* comm: 第二个字段, 进程名, char[16]类型
* utime: 第14个字段, 用户态CPU时间, uint64_t类型
* stime: 第15个字段, 内核态CPU时间, uint64_t类型
* cutime: 第16个字段, 死亡子进程/线程用户态CPU时间, int64_t类型
* cstime: 第17个字段, 死亡子进程/线程内核态CPU时间, int64_t类型
* processor: 第39个字段, 进程运行的CPU核心, int64_t类型
* guest_time: 第42个字段, 虚拟机用户态CPU时间, uint64_t类型
* cguest_time: 第43个字段, 虚拟机低优先级用户态CPU时间, int64_t类型
* 显示pid, comm, cpu占比, 运行cpu信息
*/
bool get_pid_stat(struct my_process *my_process, const char *dir_name)
{
int32_t ret;
FILE *fp;
char *data[52];
char path[256], comm[64], data_buf[1024];
/* 打开进程状态信息虚拟文件 */
snprintf(path, sizeof(path), "/proc/%s/stat", dir_name);
fp = fopen(path, "r");
if (fp == NULL) {
printf("Open /proc/%s/stat file error, %s(%d)\n", dir_name, strerror(errno), errno);
return false;
}
/* 一次读入全部数据 */
ret = fread(data_buf, 1, sizeof(data_buf) - 1, fp);
if (ret <= 0) {
printf("Read /proc/%s/stat file error, %s(%d)\n", dir_name, strerror(errno), errno);
goto error;
}
data_buf[ret] = '\0';
/* 通过' '分割字符串, 选出目标数据 */
data[0] = strtok(data_buf, " ");
for (ret = 1; ret < 52; ret++) {
data[ret] = strtok(NULL, " ");
}
/* 保存数据 */
my_process->pid = atoi(data[0]);
my_process->processor = atoi(data[38]);
my_process->utime = strtoull(data[13], NULL, 10);
my_process->stime = strtoull(data[14], NULL, 10);
my_process->cutime = strtoll(data[15], NULL, 10);
my_process->cstime = strtoll(data[16], NULL, 10);
my_process->guest_time = strtoull(data[41], NULL, 10);
my_process->cguest_time = strtoll(data[42], NULL, 10);
strncpy(my_process->comm, data[1], sizeof(my_process->comm));
my_process->comm[sizeof(my_process->comm) - 1] = '\0';
error:
fclose(fp);
return true;
}
/* 判断一个目录名是否是纯数字 */
static bool is_pid_directory(const char *dir_name)
{
while (*dir_name) {
if (!isdigit(*dir_name)) {
return false;
}
dir_name++;
}
return true;
}
/* 遍历所有/proc下面的进程目录, 输出进程信息 */
struct my_process *get_all_pid_stat(void)
{
uint64_t pid, count;
DIR *proc_dir;
struct dirent *entry;
struct my_process *process, *process_list;
/* 读取/proc内核信息虚拟目录信息 */
proc_dir = opendir("/proc");
if (proc_dir == NULL) {
printf("Failed to open /proc directory, %s(%d).\n", strerror(errno), errno);
return NULL;
}
/* 遍历整个/proc目录, 找到进程子目录, 逐个判断是否为目标进程 */
count = 0;
process_list = NULL;
while ((entry = readdir(proc_dir)) != NULL) {
if (entry->d_type == DT_DIR && is_pid_directory(entry->d_name)) {
/* 获取my process对象 */
process = my_process_alloc();
if (process == NULL) {
printf("Failed to allocate memory for process.\n");
break;
}
/* 获取进程数据, 并写入数据到process中 */
get_pid_stat(process, entry->d_name);
if (process_list == NULL) {
process_list = process;
} else {
/* 目录按照从小到大顺序读入, 插入顺序刚好是反的 */
process->next = process_list;
process_list = process;
}
count++;
}
}
closedir(proc_dir);
/* printf("Total %lu process(es) found.\n", count); */
return process_list;
}
/* 处理之后的进程cpu使用间隔数据 */
struct my_process_data {
struct my_process *my_process;
struct my_process *my_process_old;
struct my_process_data *next;
uint64_t diff_utime;
uint64_t diff_stime;
uint64_t diff_time;
int32_t core;
int32_t pid;
const char *comm;
};
/* 处理所有进程数据, 按照cpu占用率, 输出前20个进程信息 */
static void my_process_deal_data(
struct my_process *my_process, struct my_process *my_process_old, int32_t total_cpu)
{
int32_t i, count;
struct my_process_data *sort_data, *data, *prev;
count = 0;
sort_data = NULL;
/* 计算进程消耗cpu时间 */
while (my_process_old && my_process) {
if (my_process_old->pid == my_process->pid) {
data = (struct my_process_data *)malloc(sizeof(struct my_process_data));
if (data == NULL) {
printf("Failed to allocate memory for process data.\n");
return;
}
data->diff_utime = my_process->utime - my_process_old->utime;
data->diff_stime = my_process->stime - my_process_old->stime;
data->diff_time = MY_PROCESS_TOTAL(my_process) - MY_PROCESS_TOTAL(my_process_old);
data->core = my_process->processor;
data->pid = my_process->pid;
data->comm = my_process->comm;
data->my_process = my_process;
data->my_process_old = my_process_old;
data->next = NULL;
/* 插入为排序数据 */
if (sort_data == NULL) {
sort_data = data;
} else if (sort_data->diff_time < data->diff_time) {
data->next = sort_data;
sort_data = data;
} else {
prev = sort_data;
while (prev->next && prev->next->diff_time > data->diff_time) {
prev = prev->next;
}
data->next = prev->next;
prev->next = data;
}
/* 进行下一轮遍历处理 */
my_process = my_process->next;
my_process_old = my_process_old->next;
count++;
continue;
}
/* 进程ID不匹配, 释放内存(从大到小排序) */
if (my_process_old->pid > my_process->pid) {
my_process_old = my_process_old->next;
} else {
/* 保留process, 用于下一轮数据对比 */
my_process = my_process->next;
}
}
/* 打印进程信息, 如果进程消耗cpu资源未统计到(0), 则pass, 不进行输出 */
while (sort_data) {
if (sort_data->diff_time != 0) {
printf(" %s[%d], core: %d, user: %3lu, sys: %3lu, total: %.2f%%.\n", sort_data->comm,
sort_data->pid, sort_data->core, sort_data->diff_utime, sort_data->diff_stime,
(double)(sort_data->diff_time) * 100 * MY_CPU_NUM / total_cpu);
}
prev = sort_data;
sort_data = sort_data->next;
free(prev);
}
printf(" Total %d process(es) matched.\n", count);
return;
}
/**
* 获取指定CPU核的使用率, 通过读取/proc/stat文件数据实现.
* 对于/proc/stat文件的具体解释, 可参考Linux kernel代码: fs/proc/stat.c.
* 不同版本的Linux内核, /proc/stat文件的格式可能会有所不同(有问题请分析源码).
*/
bool get_cpu_usage(uint32_t interval_ms)
{
FILE *fp;
int32_t ret, i, total_cpu;
struct my_cpu cpu_info[MY_CPU_DATA_NUM], cpu_info_old[MY_CPU_DATA_NUM];
char buf[MY_CPU_FSCANF_BUF_SIZE + 1];
struct my_process *my_process, *my_process_old, *temp_process;
/* 打开全局cpu状态信息虚拟文件 */
fp = fopen("/proc/stat", "r");
if (fp == NULL) {
printf("Open /proc/stat file error, %s(%d)\n", strerror(errno), errno);
return false;
}
/* 先读取一次原始数据, 然后后续连续间隔指定时间获取数据, 并实时输出使用率 */
for (i = 0; i < MY_CPU_DATA_NUM; i++) {
if (MY_CPU_FSCANF(fp, buf, &cpu_info_old[i]) != MY_CPU_FSCANF_ARGS) {
goto error;
}
}
/* 读取进程数据 */
my_process_old = get_all_pid_stat();
if (my_process_old == NULL) {
goto error;
}
/* 循环读取cpu使用情况, 并且打印数据 */
do {
/* 移动fp文件指针到文件起始处, 刷新文件缓冲, 持续读取数据 */
fflush(fp);
if (fseek(fp, 0, SEEK_SET) != 0) {
printf("Seek /proc/stat file error, %s(%d)\n", strerror(errno), errno);
goto error;
}
/* 等待一段时间 */
my_msleep(interval_ms);
/* 读取进程cpu使用数据 */
my_process = get_all_pid_stat();
if (my_process == NULL) {
goto error;
}
/* 循环读取所有核的cpu数据 */
for (i = 0; i < MY_CPU_DATA_NUM; i++) {
if (MY_CPU_FSCANF(fp, buf, &cpu_info[i]) != MY_CPU_FSCANF_ARGS) {
goto error;
}
}
/* 处理数据, 打印信息 */
total_cpu = my_cpu_deal_data(cpu_info, cpu_info_old);
my_process_deal_data(my_process, my_process_old, total_cpu);
/* 释放my_process_old数据 */
while (my_process_old) {
temp_process = my_process_old;
my_process_old = my_process_old->next;
my_process_free(temp_process);
}
my_process_old = my_process;
memcpy(cpu_info_old, cpu_info, sizeof(cpu_info_old));
} while (1);
error:
fclose(fp);
return false;
}
/* 给定间隔时间 */
int main(int argc, char *argv[])
{
uint32_t interval_ms;
if (argc != 2) {
printf("Usage: %s <interval_ms>\n", argv[0]);
return -1;
}
interval_ms = atoi(argv[1]);
get_cpu_usage(interval_ms);
return 0;
}
这段代码是一个用于监控和显示CPU使用率的程序。它通过读取系统的/proc/stat
文件和/proc/[pid]/stat
文件来获取CPU和进程的相关信息,并计算出CPU的使用率和各个进程的CPU占用情况。
代码中定义了两个结构体:my_cpu
和my_process
,分别用于存储CPU的使用情况和进程的信息。my_cpu
结构体包含了用户态CPU时间、内核态CPU时间、空闲CPU时间等各个字段,而my_process
结构体包含了进程ID、进程名、CPU核心、用户态CPU时间、内核态CPU时间等字段。
代码中使用了一些宏定义来计算CPU的总时间、空闲时间和使用率。其中,MY_CPU_TOTAL
宏用于计算CPU的总时间,MY_CPU_IDLE
宏用于计算CPU的空闲时间,MY_CPU_USAGE
宏用于计算CPU的使用率。
代码中还定义了一些辅助函数,如my_msleep
函数用于实现可处理中断的睡眠功能,my_process_alloc_chunk
函数用于分配一大块内存并将其挂载为链表,my_process_alloc
函数用于获取一个空闲的my_process
对象,my_process_free
函数用于将一个my_process
对象回收到空闲链表中。
代码中的get_pid_stat
函数用于读取/proc/[pid]/stat
文件的数据,并将其保存到my_process
结构体中。is_pid_directory
函数用于判断一个目录名是否是纯数字,get_all_pid_stat
函数用于遍历/proc目录下的进程目录,并获取所有进程的信息。
代码中的my_cpu_deal_data
函数用于处理两次采集的CPU使用数据,并以合适的格式打印出来。my_process_deal_data
函数用于处理所有进程的数据,并按照CPU占用率排序,输出前20个进程的信息。
最后,代码中的get_cpu_usage
函数是程序的核心函数,它通过循环读取/proc/stat
文件和/proc目录下的进程信息,并实时计算和显示CPU的使用率和各个进程的CPU占用情况。该函数会不断循环执行,直到程序被手动终止。
整个程序的作用是实时监控系统的CPU使用率和各个进程的CPU占用情况,并将结果以合适的格式打印出来。通过这个程序,可以方便地了解系统的CPU负载情况,以及各个进程对CPU资源的占用程度。
下面是实际运行数据输出:
ubuntu->cs-test:$ gcc -g -o my-top-plus my-top-plus.c
ubuntu->cs-test:$ ./my-top-plus 2000
CPU total: 799, idle: 789, usage: 10, 5.01%: core0 0.51%, core1 1.49%, core2 0.50%, core3 1.50%,
(YDService)[1720], core: 1, user: 2, sys: 1, total: 1.50%.
(barad_agent)[1367234], core: 0, user: 0, sys: 2, total: 1.00%.
(barad_agent)[1367233], core: 0, user: 1, sys: 0, total: 0.50%.
(code-903b1e9d89)[2569313], core: 1, user: 1, sys: 0, total: 0.50%.
Total 142 process(es) matched.
CPU total: 799, idle: 794, usage: 5, 2.50%: core0 0.50%, core1 1.01%, core2 1.00%, core3 0.50%,
(YDService)[1720], core: 1, user: 1, sys: 1, total: 1.00%.
(rcu_sched)[13], core: 1, user: 0, sys: 1, total: 0.50%.
(node)[2569442], core: 0, user: 0, sys: 1, total: 0.50%.
(barad_agent)[1367234], core: 0, user: 0, sys: 1, total: 0.50%.
Total 142 process(es) matched.
CPU total: 799, idle: 795, usage: 4, 2.00%: core0 1.00%, core1 0.50%, core2 0.00%, core3 0.50%,
(barad_agent)[1367234], core: 0, user: 1, sys: 1, total: 1.00%.
(YDService)[1720], core: 1, user: 0, sys: 2, total: 1.00%.
Total 142 process(es) matched.
CPU total: 797, idle: 792, usage: 5, 2.51%: core0 0.50%, core1 0.50%, core2 0.51%, core3 0.50%,
(YDService)[1720], core: 1, user: 2, sys: 1, total: 1.51%.
(node)[2569417], core: 2, user: 1, sys: 0, total: 0.50%.
(barad_agent)[1367234], core: 0, user: 1, sys: 0, total: 0.50%.
Total 142 process(es) matched.
......
2.7 my-top支持线程统计
进程级别的信息仍然不够详细,有时候我们想知道线程级别的耗时情况(PS和TOP都支持线程),那我们的my-top自然也要实现线程级别的追踪信息,这需要对上面的程序稍加改进,如下:
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <dirent.h>
#include <ctype.h>
/* cpu使用率核心数据信息结构体定义 */
struct my_cpu {
uint64_t user; /* 用户态CPU时间 */
uint64_t nice; /* 低优先级用户态CPU时间 */
uint64_t system; /* 内核态CPU时间 */
uint64_t idle; /* 空闲CPU时间 */
uint64_t iowait; /* 等待I/O操作CPU时间 */
uint64_t irq; /* 硬中断CPU时间 */
uint64_t softirq; /* 软中断CPU时间 */
uint64_t steal; /* 虚拟机CPU时间 */
uint64_t guest; /* 虚拟机低优先级CPU时间 */
uint64_t guest_nice; /* 虚拟机低优先级用户态CPU时间 */
};
/* 可以处理中断的sleep函数, 睡眠指定时间长度(ms) */
static void my_msleep(int ms)
{
struct timespec ts;
int32_t ret;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
do {
ret = nanosleep(&ts, &ts);
} while (ret == -1 && errno == EINTR);
return;
}
#define MY_CPU_FSCANF_ARGS 11
#define MY_CPU_FSCANF_BUF_SIZE 16
#define MY_CPU_FSCANF(fp, buf, cpu) \
fscanf(fp, "%16s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", buf, &((cpu)->user), \
&((cpu)->nice), &((cpu)->system), &((cpu)->idle), &((cpu)->iowait), &((cpu)->irq), \
&((cpu)->softirq), &((cpu)->steal), &((cpu)->guest), &((cpu)->guest_nice))
/* 计算cpu使用率, iowait也算在空闲cpu时间里面 */
#define MY_CPU_TOTAL(cpu) \
((cpu)->user + (cpu)->nice + (cpu)->system + (cpu)->idle + (cpu)->iowait + (cpu)->irq + \
(cpu)->softirq + (cpu)->steal + (cpu)->guest + (cpu)->guest_nice)
#define MY_CPU_IDLE(cpu) ((cpu)->idle + (cpu)->iowait)
#define MY_CPU_USAGE(cpu) (MY_CPU_TOTAL(cpu) - MY_CPU_IDLE(cpu))
#define MY_CPU_NUM 4 /* 4核cpu */
#define MY_CPU_DATA_NUM (MY_CPU_NUM + 1) /* cpu数据个数 */
/* 处理两次采集的cpu使用数据, 然后以合适的格式打印出去 */
static int32_t my_cpu_deal_data(struct my_cpu *cpu_info, struct my_cpu *cpu_info_old)
{
int32_t i, len;
uint64_t diff_total[MY_CPU_DATA_NUM], diff_idle[MY_CPU_DATA_NUM], diff_usage[MY_CPU_DATA_NUM];
char buffer[1024];
for (i = 0; i < MY_CPU_DATA_NUM; i++) {
diff_total[i] = MY_CPU_TOTAL(&cpu_info[i]) - MY_CPU_TOTAL(&cpu_info_old[i]);
diff_idle[i] = MY_CPU_IDLE(&cpu_info[i]) - MY_CPU_IDLE(&cpu_info_old[i]);
diff_usage[i] = diff_total[i] - diff_idle[i];
}
len = snprintf(buffer, sizeof(buffer),
"CPU total: %lu, idle: %lu, usage: %lu, %.2f%%: ", diff_total[0], diff_idle[0],
diff_usage[0], (double)diff_usage[0] * 100 * 4 / diff_total[0]);
for (i = 1; i < MY_CPU_DATA_NUM; i++) {
len += snprintf(buffer + len, sizeof(buffer) - len, "core%d %.2f%%, ", (i - 1),
(double)diff_usage[i] * 100 / diff_total[i]);
}
printf("%s\n", buffer);
return diff_total[0];
}
/* 定义进程信息, 组成链表的格式 */
struct my_process {
struct my_process *next; /* 下一个进程 */
struct my_process *lwp; /* 进程的线程 */
int32_t pid; /* 进程ID */
int32_t processor; /* 进程运行的CPU核心 */
char comm[64]; /* 进程名 */
uint64_t utime; /* 用户态CPU时间 */
uint64_t stime; /* 内核态CPU时间 */
int64_t cutime; /* 死亡子进程/线程用户态CPU时间 */
int64_t cstime; /* 死亡子进程/线程内核态CPU时间 */
uint64_t guest_time; /* 虚拟机用户态CPU时间 */
int64_t cguest_time; /* 虚拟机低优先级用户态CPU时间 */
};
#define MY_PROCESS_TOTAL(cpu) ((cpu)->utime + (cpu)->stime + (cpu)->guest_time)
/* 打印my_process数据 */
static void my_process_dump_data(struct my_process *process_info)
{
printf(
"PID: %d, comm: %s, processor: %d, utime: %lu, stime: %lu, cutime: %ld, cstime: %ld, "
"guest_time: %lu, cguest_time: %ld.\n",
process_info->pid, process_info->comm, process_info->processor, process_info->utime,
process_info->stime, process_info->cutime, process_info->cstime, process_info->guest_time,
process_info->cguest_time);
return;
}
#define MY_PROCESS_CHUNK_NUM 256 /* 最小内存分配单位 */
/* 一次收集一大块内存, 后续再慢慢释放 */
static struct my_process *my_process_alloc_chunk(int32_t count)
{
int32_t i;
struct my_process *chunk;
/* 分配内存 */
chunk = (struct my_process *)malloc(count * sizeof(struct my_process));
if (chunk == NULL) {
printf("Failed to allocate memory for process chunk.\n");
return NULL;
}
/* 初始化内存 */
memset(chunk, 0, count * sizeof(struct my_process));
/* 挂载内存为链表 */
for (i = 0; i < count - 1; i++) {
chunk[i].next = &(chunk[i + 1]);
}
return chunk;
}
/* 空闲my process对象链表 */
static struct my_process *free_list = NULL;
/* 获取一个空闲的my process对象 */
static struct my_process *my_process_alloc(void)
{
struct my_process *process;
/* 如果free_list为NULL, 则收集一大块内存 */
if (free_list == NULL) {
free_list = my_process_alloc_chunk(MY_PROCESS_CHUNK_NUM);
if (free_list == NULL) {
return NULL;
}
}
/* 返回一个空闲的my process对象 */
process = free_list;
free_list = free_list->next;
process->next = NULL;
process->lwp = NULL;
return process;
}
/* 回收一个空闲的my process对象到空闲链表中 */
static void my_process_free(struct my_process *process)
{
memset(process, 0, sizeof(struct my_process));
process->next = free_list;
free_list = process;
return;
}
/**
* 读取/proc/pid/stat数据:
* pid: 第一个字段, 进程ID, int32_t类型
* comm: 第二个字段, 进程名, char[16]类型
* utime: 第14个字段, 用户态CPU时间, uint64_t类型
* stime: 第15个字段, 内核态CPU时间, uint64_t类型
* cutime: 第16个字段, 死亡子进程/线程用户态CPU时间, int64_t类型
* cstime: 第17个字段, 死亡子进程/线程内核态CPU时间, int64_t类型
* processor: 第39个字段, 进程运行的CPU核心, int64_t类型
* guest_time: 第42个字段, 虚拟机用户态CPU时间, uint64_t类型
* cguest_time: 第43个字段, 虚拟机低优先级用户态CPU时间, int64_t类型
* 显示pid, comm, cpu占比, 运行cpu信息
*/
bool get_pid_stat(struct my_process *my_process, const char *dir_name)
{
int32_t ret;
FILE *fp;
char *data[52];
char comm[64], data_buf[1024];
/* 打开进程状态信息虚拟文件 */
fp = fopen(dir_name, "r");
if (fp == NULL) {
printf("Open %s file error, %s(%d)\n", dir_name, strerror(errno), errno);
return false;
}
/* 一次读入全部数据 */
ret = fread(data_buf, 1, sizeof(data_buf) - 1, fp);
if (ret <= 0) {
printf("Read %s file error, %s(%d)\n", dir_name, strerror(errno), errno);
goto error;
}
data_buf[ret] = '\0';
/* 通过' '分割字符串, 选出目标数据 */
data[0] = strtok(data_buf, " ");
for (ret = 1; ret < 52; ret++) {
data[ret] = strtok(NULL, " ");
}
/* 保存数据 */
my_process->pid = atoi(data[0]);
my_process->processor = atoi(data[38]);
my_process->utime = strtoull(data[13], NULL, 10);
my_process->stime = strtoull(data[14], NULL, 10);
my_process->cutime = strtoll(data[15], NULL, 10);
my_process->cstime = strtoll(data[16], NULL, 10);
my_process->guest_time = strtoull(data[41], NULL, 10);
my_process->cguest_time = strtoll(data[42], NULL, 10);
strncpy(my_process->comm, data[1], sizeof(my_process->comm));
my_process->comm[sizeof(my_process->comm) - 1] = '\0';
error:
fclose(fp);
return true;
}
/* 判断一个目录名是否是纯数字 */
static bool is_pid_directory(const char *dir_name)
{
while (*dir_name) {
if (!isdigit(*dir_name)) {
return false;
}
dir_name++;
}
return true;
}
/* 遍历所有/proc下面的进程目录, 输出进程信息 */
struct my_process *get_all_pid_stat(bool is_thread, const char *prefix_dir)
{
uint64_t pid, count;
DIR *proc_dir;
struct dirent *entry;
struct my_process *process, *process_list;
char path[512];
/* 读取/proc内核信息虚拟目录信息 */
proc_dir = opendir(prefix_dir);
if (proc_dir == NULL) {
printf("Failed to open %s directory, %s(%d).\n", prefix_dir, strerror(errno), errno);
return NULL;
}
/* 遍历整个目录, 找到进程(线程)子目录, 逐个判断是否为目标进程(线程) */
count = 0;
process_list = NULL;
while ((entry = readdir(proc_dir)) != NULL) {
if (entry->d_type == DT_DIR && is_pid_directory(entry->d_name)) {
/* 获取my process对象 */
process = my_process_alloc();
if (process == NULL) {
printf("Failed to allocate memory for process.\n");
break;
}
/* 获取进程数据, 并写入数据到process中 */
snprintf(path, sizeof(path), "%s/%s/stat", prefix_dir, entry->d_name);
get_pid_stat(process, path);
/* 如果不是线程读取数据, 那么再读取一次线程数据 */
if (!is_thread) {
snprintf(path, sizeof(path), "/proc/%s/task", entry->d_name);
process->lwp = get_all_pid_stat(true, path);
}
if (process_list == NULL) {
process_list = process;
} else {
/* 目录按照从小到大顺序读入, 插入顺序刚好是反的 */
process->next = process_list;
process_list = process;
}
count++;
}
}
closedir(proc_dir);
/* printf("Total %lu process(es) found.\n", count); */
return process_list;
}
/* 处理之后的进程cpu使用间隔数据 */
struct my_process_data {
struct my_process *my_process;
struct my_process *my_process_old;
struct my_process_data *next;
struct my_process_data *lwp;
uint64_t diff_utime;
uint64_t diff_stime;
uint64_t diff_time;
int32_t core;
int32_t pid;
const char *comm;
};
/* 处理所有进程数据, 按照cpu占用率, 输出前20个进程信息 */
static struct my_process_data *my_process_deal_data(
struct my_process *my_process, struct my_process *my_process_old, bool is_thread)
{
int32_t count;
struct my_process_data *sort_data, *data, *prev;
count = 0;
sort_data = NULL;
/* 计算进程消耗cpu时间 */
while (my_process_old && my_process) {
if (my_process_old->pid == my_process->pid) {
data = (struct my_process_data *)malloc(sizeof(struct my_process_data));
if (data == NULL) {
printf("Failed to allocate memory for process data.\n");
return NULL;
}
data->diff_utime = my_process->utime - my_process_old->utime;
data->diff_stime = my_process->stime - my_process_old->stime;
data->diff_time = MY_PROCESS_TOTAL(my_process) - MY_PROCESS_TOTAL(my_process_old);
data->core = my_process->processor;
data->pid = my_process->pid;
data->comm = my_process->comm;
data->my_process = my_process;
data->my_process_old = my_process_old;
data->next = NULL;
/* 插入为排序数据 */
if (sort_data == NULL) {
sort_data = data;
} else if (sort_data->diff_time < data->diff_time) {
data->next = sort_data;
sort_data = data;
} else {
prev = sort_data;
while (prev->next && prev->next->diff_time > data->diff_time) {
prev = prev->next;
}
data->next = prev->next;
prev->next = data;
}
/* 处理子线程的数据 */
if (!is_thread) {
data->lwp = my_process_deal_data(my_process->lwp, my_process_old->lwp, true);
}
/* 进行下一轮遍历处理 */
my_process = my_process->next;
my_process_old = my_process_old->next;
count++;
continue;
}
/* 进程ID不匹配, 释放内存(从大到小排序) */
if (my_process_old->pid > my_process->pid) {
my_process_old = my_process_old->next;
} else {
/* 保留process, 用于下一轮数据对比 */
my_process = my_process->next;
}
}
if (!is_thread) {
printf(" Total %d process(es) matched.\n", count);
}
return sort_data;
}
/* 打印进程和线程cpu使用数据 */
static void my_process_dump_all_data(struct my_process_data *sort_data, int32_t total_cpu)
{
struct my_process_data *prev, *lwp;
/* 打印进程信息, 如果进程消耗cpu资源未统计到(0), 则pass, 不进行输出 */
while (sort_data) {
if (sort_data->diff_time != 0) {
printf(" %s[%d], core: %d, user: %3lu, sys: %3lu, total: %.2f%%.\n", sort_data->comm,
sort_data->pid, sort_data->core, sort_data->diff_utime, sort_data->diff_stime,
(double)(sort_data->diff_time) * 100 * MY_CPU_NUM / total_cpu);
lwp = sort_data->lwp;
while (lwp) {
if (lwp->diff_time != 0) {
printf(" %s[%d], core: %d, user: %3lu, sys: %3lu, total: %.2f%%.\n",
lwp->comm, lwp->pid, lwp->core, lwp->diff_utime, lwp->diff_stime,
(double)(lwp->diff_time) * 100 * MY_CPU_NUM / total_cpu);
}
prev = lwp;
lwp = lwp->next;
free(prev);
}
}
prev = sort_data;
sort_data = sort_data->next;
free(prev);
}
printf("\n");
return;
}
/**
* 获取指定CPU核的使用率, 通过读取/proc/stat文件数据实现.
* 对于/proc/stat文件的具体解释, 可参考Linux kernel代码: fs/proc/stat.c.
* 不同版本的Linux内核, /proc/stat文件的格式可能会有所不同(有问题请分析源码).
*/
bool get_cpu_usage(uint32_t interval_ms)
{
FILE *fp;
int32_t ret, i, total_cpu;
struct my_cpu cpu_info[MY_CPU_DATA_NUM], cpu_info_old[MY_CPU_DATA_NUM];
char buf[MY_CPU_FSCANF_BUF_SIZE + 1];
struct my_process *my_process, *my_process_old, *temp_process, *temp_lwp;
struct my_process_data *sort_data;
/* 打开全局cpu状态信息虚拟文件 */
fp = fopen("/proc/stat", "r");
if (fp == NULL) {
printf("Open /proc/stat file error, %s(%d)\n", strerror(errno), errno);
return false;
}
/* 先读取一次原始数据, 然后后续连续间隔指定时间获取数据, 并实时输出使用率 */
for (i = 0; i < MY_CPU_DATA_NUM; i++) {
if (MY_CPU_FSCANF(fp, buf, &cpu_info_old[i]) != MY_CPU_FSCANF_ARGS) {
goto error;
}
}
/* 读取进程数据 */
my_process_old = get_all_pid_stat(false, "/proc");
if (my_process_old == NULL) {
goto error;
}
/* 循环读取cpu使用情况, 并且打印数据 */
do {
/* 移动fp文件指针到文件起始处, 刷新文件缓冲, 持续读取数据 */
fflush(fp);
if (fseek(fp, 0, SEEK_SET) != 0) {
printf("Seek /proc/stat file error, %s(%d)\n", strerror(errno), errno);
goto error;
}
/* 等待一段时间 */
my_msleep(interval_ms);
/* 读取进程cpu使用数据 */
my_process = get_all_pid_stat(false, "/proc");
if (my_process == NULL) {
goto error;
}
/* 循环读取所有核的cpu数据 */
for (i = 0; i < MY_CPU_DATA_NUM; i++) {
if (MY_CPU_FSCANF(fp, buf, &cpu_info[i]) != MY_CPU_FSCANF_ARGS) {
goto error;
}
}
/* 处理数据, 打印信息 */
total_cpu = my_cpu_deal_data(cpu_info, cpu_info_old);
sort_data = my_process_deal_data(my_process, my_process_old, false);
my_process_dump_all_data(sort_data, total_cpu);
/* 释放my_process_old数据 */
while (my_process_old) {
temp_lwp = my_process_old->lwp;
while (temp_lwp) {
temp_process = temp_lwp;
temp_lwp = temp_lwp->next;
my_process_free(temp_process);
}
temp_process = my_process_old;
my_process_old = my_process_old->next;
my_process_free(temp_process);
}
my_process_old = my_process;
memcpy(cpu_info_old, cpu_info, sizeof(cpu_info_old));
} while (1);
error:
fclose(fp);
return false;
}
/* 给定间隔时间 */
int main(int argc, char *argv[])
{
uint32_t interval_ms;
if (argc != 2) {
printf("Usage: %s <interval_ms>\n", argv[0]);
return -1;
}
interval_ms = atoi(argv[1]);
get_cpu_usage(interval_ms);
return 0;
}
这个代码相比于2.6中,对my_process_dump_all_data
和my_process_deal_data
、get_all_pid_stat
等函数做了更改,主要是支持递归读取线程堆栈信息。
代码中的get_cpu_usage
函数是核心函数,它通过读取/proc/stat
文件来获取CPU的使用情况,并通过计算差值来得到CPU的使用率。同时,它还调用了get_all_pid_stat
函数来获取所有进程的相关信息。
get_all_pid_stat
函数遍历了/proc
目录下的所有进程目录,并读取每个进程的/proc/pid/stat
文件来获取进程的相关信息。它使用了递归的方式来处理进程的线程信息,即先获取进程的信息,然后再获取线程的信息。
my_process_deal_data
函数用于处理进程和线程的CPU使用数据,并按照CPU占用率进行排序。它通过计算两次采集的CPU时间差来得到进程和线程的CPU使用时间,并计算出CPU占用率。最后,它将数据按照CPU占用率从高到低进行排序。
my_process_dump_all_data
函数用于打印进程和线程的CPU使用数据。它遍历排序后的数据,并按照一定的格式输出进程和线程的相关信息。
需要注意的是,代码中使用了一些特定的Linux系统文件和目录来获取CPU和进程的信息,这些文件和目录的格式和内容可能会因不同的Linux内核版本而有所不同。因此,在使用这段代码时,需要根据实际情况进行适当的调整和修改。
此外,代码中还包含了一些辅助函数和宏定义,用于处理时间、内存分配和释放等操作。这些函数和宏定义的作用是提供一些便利的功能,使代码更加模块化和可读性更高。
下面是实际编译测试输出情况,如下:
ubuntu->cs-test:$ gcc -g -o my-top-plus my-top-plus.c
ubuntu->cs-test:$ ./my-top-plus 1000
CPU total: 403, idle: 399, usage: 4, 3.97%: core0 0.99%, core1 1.94%, core2 1.00%, core3 0.00%,
Total 142 process(es) matched.
(my-top-plus)[2694157], core: 2, user: 0, sys: 1, total: 0.99%.
(my-top-plus)[2694157], core: 2, user: 0, sys: 1, total: 0.99%.
(YDService)[1720], core: 2, user: 1, sys: 0, total: 0.99%.
(YDService)[1766], core: 2, user: 1, sys: 0, total: 0.99%.
(YDService)[1722], core: 2, user: 0, sys: 1, total: 0.99%.
(YDService)[1748], core: 3, user: 0, sys: 1, total: 0.99%.
(cpptools)[2569482], core: 3, user: 1, sys: 0, total: 0.99%.
CPU total: 401, idle: 394, usage: 7, 6.98%: core0 0.99%, core1 2.00%, core2 3.92%, core3 1.01%,
Total 142 process(es) matched.
(barad_agent)[1367234], core: 0, user: 1, sys: 1, total: 2.00%.
(barad_agent)[1367268], core: 3, user: 1, sys: 0, total: 1.00%.
(YDService)[1720], core: 2, user: 1, sys: 1, total: 2.00%.
(node)[2569417], core: 2, user: 1, sys: 0, total: 1.00%.
(code-903b1e9d89)[2569313], core: 1, user: 0, sys: 1, total: 1.00%.
CPU total: 401, idle: 398, usage: 3, 2.99%: core0 0.00%, core1 1.98%, core2 0.00%, core3 0.00%,
Total 142 process(es) matched.
(barad_agent)[1367234], core: 0, user: 1, sys: 0, total: 1.00%.
(YDService)[1720], core: 2, user: 0, sys: 1, total: 1.00%.
(YDService)[1790], core: 3, user: 0, sys: 1, total: 1.00%.
(YDService)[1766], core: 2, user: 0, sys: 1, total: 1.00%.
可以看到,精简而有效的输出了全局/进程/线程的cpu使用情况,对于使用率0或者太小的进程/线程,则没有输出。
需要注意,由于jiffies精度有限和数据读取存在误差,因此线程/进程/全局cpu使用率之和不能满足相等的CPU关系,这是无法避免的,即使ps/top也有这个限制。
对于性能监视和调优来说,趋势变化、瞬间峰值、平均值意义更大,单纯几次数据无法提供有效参考,需要进行长期采样。