(8)Linux使用C语言读取proc/stat等cpu使用数据

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的使用率。

pstop命令也是通过读取/proc中的信息来获取CPU使用率的。例如,top命令会周期性地读取/proc/stat,然后计算出CPU的总时间和各个时间片的使用情况,从而计算出CPU的使用率。ps命令则更多地关注进程级别的CPU使用情况,它通过读取/proc/[pid]/stat(其中[pid]是进程的ID)来获取特定进程的CPU使用时间,然后计算出该进程的CPU使用率。

要手动计算CPU使用率,我们可以采取以下步骤:

  1. 读取/proc/stat文件,记录下CPU时间片的初始值。
  2. 休息一段时间(比如1秒)。
  3. 再次读取/proc/stat文件,记录下CPU时间片的新值。
  4. 计算每种类型的CPU时间片的差值,然后计算出总的CPU时间。
  5. 使用非空闲时间片与总时间的比例来计算CPU的使用率。

这个过程通常需要编写脚本或者程序来自动完成。不过,由于现成的pstop命令已经提供了这些功能,大多数用户和开发者会直接使用这些工具,而不是从头计算CPU使用率。

总之,/proc文件系统为我们提供了一个直接读取系统运行状态的接口,而工具如pstop则利用这些信息以更方便的方式为用户展示系统的状态,包括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文件还会包含多行以cpu0cpu1等开头的数据,分别表示每一个核的时间片信息

除了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字段名描述示例格式说明
1pid进程ID以数字形式表示,例如:17248
2comm命令名称以字符串形式表示,可能被截断,例如:bash
3state进程状态以单个字符表示,例如:R(运行中)、S(睡眠)、D(不可中断睡眠)等
4ppid父进程ID以数字形式表示,例如:17200
5pgrp进程组ID以数字形式表示,例如:17248
6session会话ID以数字形式表示,例如:17200
7tty_nr控制终端的设备号以数字形式表示,例如:0
8tpgid控制终端的前台进程组ID以数字形式表示,例如:17248
9flags进程标志以数字形式表示,例如:0x000100
10minflt小型页面错误次数以数字形式表示,例如:552
11cminflt子进程产生的小型页面错误次数以数字形式表示,例如:460
12majflt大型页面错误次数以数字形式表示,例如:460
13cmajflt子进程产生的大型页面错误次数以数字形式表示,例如:460
14utime用户模式下的时间以时钟滴答数表示,例如:175628
15stime内核模式下的时间以时钟滴答数表示,例如:0
16cutime死亡子进程/线程在用户模式下的时间以时钟滴答数表示,子进程结束时,累加到父进程上面。
17cstime死亡子进程/线程在内核模式下的时间以时钟滴答数表示,子进程结束时,累加到父进程上面。
18priority进程优先级以数字形式表示,例如:120
19nice进程nice值以数字形式表示,例如:0
20num_threads进程中的线程数以数字形式表示,例如:1
21itrealvalue间隔定时器的剩余时间以时钟滴答数表示,例如:0
22starttime进程启动时间以时钟滴答数表示,例如:769041601
23vsize虚拟内存大小以字节为单位表示,例如:131168
24rss常驻集大小以页面数表示,例如:13484
25rsslim常驻集大小限制以字节为单位表示,例如:0
26startcode程序文本的起始地址以地址形式表示,例如:400000
27endcode程序文本的结束地址以地址形式表示,例如:400000
28startstack栈的起始地址以地址形式表示,例如:8000
29kstkesp栈指针的当前值以地址形式表示,例如:8000
30kstkeip指令指针的当前值以地址形式表示,例如:8000
31signal待处理信号的位图以十进制数字表示,例如:0
32blocked阻塞信号的位图以十进制数字表示,例如:0
33sigignore忽略信号的位图以十进制数字表示,例如:0
34sigcatch捕获信号的位图以十进制数字表示,例如:0
35wchan等待的通道以地址形式表示,例如:400000
36nswap交换出去的页面数以数字形式表示,例如:0
37cnswap子进程交换出去的页面数以数字形式表示,例如:0
38exit_signal退出信号以数字形式表示,例如:0
39processor最后执行的CPU号以数字形式表示,例如:0
40rt_priority实时调度优先级以数字形式表示,例如:0
41policy调度策略以数字形式表示,例如:0
42delayacct_blkio_ticks累积的块I/O延迟以时钟滴答数表示,例如:150
43guest_time虚拟CPU运行时间以时钟滴答数表示,例如:0
44cguest_time死亡子进程/线程的虚拟CPU运行时间以时钟滴答数表示,例如:0
45start_data数据段的起始地址以地址形式表示,例如:400000
46end_data数据段的结束地址以地址形式表示,例如:400000
47start_brk堆的起始地址以地址形式表示,例如:400000
48arg_start命令行参数的起始地址以地址形式表示,例如:400000
49arg_end命令行参数的结束地址以地址形式表示,例如:400000
50env_start环境变量的起始地址以地址形式表示,例如:400000
51env_end环境变量的结束地址以地址形式表示,例如:400000
52exit_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系统中,这个值通常被定义为HZHZ的值依赖于具体的内核配置和硬件平台,但常见的值有100、250、300或者1000。例如,如果HZ值为1000,那么每个滴答的时间长度就是1毫秒。

获取jiffies时间长度的方法如下:

  1. 查看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值。

  2. 计算jiffies时间长度:一旦你知道了HZ值,就可以计算出一个jiffy的时间长度。公式为:

    时间长度(秒)= 1 / HZ
    

    如果HZ是1000,那么每个jiffy的时间长度就是1/1000秒,即1毫秒。

  3. 查看当前的jiffies值:你可以通过查看/proc/timer_list文件或者使用内核API来获取当前的jiffies值。

  4. 转换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语言编写,并包含了一些头文件和结构体定义。

代码的流程如下:

  1. 首先,定义了一个名为my_cpu的结构体,用于存储CPU的使用率数据。该结构体包含了各种不同类型的CPU时间,如用户态CPU时间、内核态CPU时间、空闲CPU时间等。

  2. 接下来,定义了一个名为my_msleep的静态函数,用于在指定的时间间隔内进行睡眠。该函数使用了nanosleep函数来实现,可以处理中断并保证睡眠时间的准确性。

  3. 然后,定义了一些宏来计算CPU的总时间、空闲时间和使用率。这些宏根据CPU结构体中的各个时间字段进行计算,并提供了方便的接口来获取这些值。

  4. 在接下来的代码中,定义了一些常量,如CPU核数和CPU数据个数。

  5. 紧接着,定义了一个名为my_cpu_deal_data的函数,用于处理两次采集的CPU使用数据,并以合适的格式打印出来。该函数计算了CPU使用率的差值,并将结果格式化为一个字符串,然后使用printf函数打印出来。

  6. get_cpu_usage函数中,首先打开了/proc/stat文件,如果文件打开失败,则打印错误信息并返回false

  7. 然后,通过循环读取/proc/stat文件中的数据,获取CPU的使用情况,并调用my_cpu_deal_data函数处理数据并打印出来。在每次读取数据之前,会先将文件指针移动到文件起始处,并刷新文件缓冲区。然后,使用MY_CPU_FSCANF宏从文件中读取CPU数据,并将其存储在cpu_info数组中。

  8. 最后,关闭文件并返回true

  9. 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_cpumy_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_datamy_process_deal_dataget_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也有这个限制。

对于性能监视和调优来说,趋势变化、瞬间峰值、平均值意义更大,单纯几次数据无法提供有效参考,需要进行长期采样

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/411489.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端学习---- 前端HTML基本元素的介绍

一&#xff1a;显示相关的HTML基础知识 1. 推荐的前端编写工具 2. VScode的html速写规则&#xff08;从a标签开始再用&#xff09; ①、&#xff01;&#xff1a;代表生成html的基本框架元素 ②、html元素&#xff1a;直接书写html,不需要加<>,按回车会自动生成 ③、{}…

linux下执行文件包含^M,将window文件格式内容转为linux格式

查看文件内容 cat -v jvm_options 报错信息 ./bin/install-plugin.sh: /bigdata/opt/s/seatunnelsgg/apache-seatunnel-2.3.4/mvnw: /bin/sh^M: bad interpreter: No such file or directory install connector : connector-selectdb-cloud安装工具 yum install -y dos2uni…

YOLOv9中的“ADown”结构!

ADown结构出炉啦&#xff0c;收藏起来写论文用&#xff01; 1.代码&#xff1a; 代码路径&#xff1a;yolov9-main->models->common.py&#xff0c;代码如下&#xff1a; class ADown(nn.Module):def __init__(self, c1, c2): # ch_in, ch_out, shortcut, kernels, gro…

Z 字形变换

题目链接 Z 字形变换 题目描述 注意点 s 由英文字母&#xff08;小写和大写&#xff09;、‘,’ 和 ‘.’ 组成1 < numRows < 1000 解答思路 方法一是模拟整个Z字形变换思路&#xff0c;使用一个二维数组存储变换后的矩阵&#xff0c;首先需要确定这个矩阵的行数row和…

PDF控件Spire.PDF for .NET【安全】演示:从加密的 PDF 文档中删除密码

Spire.PDF for .NET 是一款独立 PDF 控件&#xff0c;用于 .NET 程序中创建、编辑和操作 PDF 文档。使用 Spire.PDF 类库&#xff0c;开发人员可以新建一个 PDF 文档或者对现有的 PDF 文档进行处理&#xff0c;且无需安装 Adobe Acrobat。 E-iceblue 功能类库Spire 系列文档处…

C++11右值引用

文章目录 左值左值引用 右值右值引用左值引用和右值引用左值引用和右值引用总结 右值引用使用场景和意义左值引用的使用场景左值引用的缺点右值引用移动构造移动赋值 右值引用的其他使用场景 万能引用完美转发完美转发的实际应用场景 C11之前就有了引用的语法&#xff0c;而C11…

Rust升级慢,使用国内镜像进行加速

背景 rustup 是 Rust 官方的跨平台 Rust 安装工具&#xff0c;国内用户使用rustup update的时候&#xff0c;网速非常慢&#xff0c;可以使用国内的阿里云镜像源来进行加速 0x01 配置方法 1. Linux与Mac OS用户配置环境变量 修改~/.bash_profile文件添加如下内容&#xff1…

Three.js-04轨道控制器

1.导入 说明&#xff1a;相机围绕目标进行轨道运动。也就是可以通过鼠标拖拽进行移动视角。 import { OrbitControls } from three/addons/controls/OrbitControls.js; 2.使用 说明&#xff1a;构造controls对象&#xff0c;再调用update方法&#xff1b;为了使效果更为明显…

【数据结构与算法】常用算法 前缀和

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《数据结构与算法&#xff1a;初学者入门指南》&#x1f4d8;&am…

刘知远LLM——Transformer与预训练模型

文章目录 注意力机制原理介绍注意力机制的各种变式注意力机制的特点 Transformer结构概述Transformer整体结构 输入层byte pair encodingpositional encoding Transformer BlockEncoder BlockMulti-Head Attention Decoder Block其他tricks总结 预训练语言模型语言建模概述预训…

DP读书:《半导体物理学(第八版)》(一)绪论 3min速通

DP读书&#xff1a;《半导体物理学&#xff08;第八版&#xff09;》刘恩科 3min速通半导体物理之绪论 DP读书&#xff1a;《半导体物理学&#xff08;第八版&#xff09;》刘恩科绪论第一章 半导体中的电子状态1.1 半导体的晶格结构和结合性质1.1.1 金刚石型结构和共价键1.1.2…

详解三种网络适配器:HBA、NIC 和 CNA

目录 前言&#xff1a; 一、主机总线适配器 (HBA) HBA的特点 二、网络接口卡 (NIC) NIC的特点 三、并发网络适配器 (CNA) CNA的特点 四、HBA、NIC 与 CNA的区别 五、结论 前言&#xff1a; 网络中的主机总线适配器 (HBA)、网络接口卡 (NIC) 和并发网络适配器 (CNA) 是…

视频号视频下载教程:如何把微信视频号的视频下载下来

视频号下载相信不少人都多少有一些了解&#xff0c;但今天我们就来细说一下关于视频号视频下载的相关疑问&#xff0c;以及大家经常会问到底如何把微信视频号的视频下载下来&#xff1f; 视频号视频下载教程 视频号链接提取器详细使用指南&#xff0c;教你轻松下载号视频&…

关于 cocos creator 如何打包抖音字节小游戏步骤一

1、cocos creator打开引擎&#xff0c;在顶部选择构建之后&#xff0c;在选择点击构建(ps:具体看项目组的大小&#xff0c;如果是一个简单的不多资源一般不到一分钟&#xff0c;如果项目很大&#xff0c;就至少半个小时以上)&#xff0c;之后 成功构建之后如下所示&#xff1a;…

欢迎免费申报讯方技术HarmonyOS人才训练营!

在今年1月备受瞩目的鸿蒙生态千帆启航仪式上&#xff0c;华为宣布&#xff1a;HarmonyOS NEXT星河预览版正式面向开发者开放申请&#xff0c;意味着鸿蒙将建立更广泛的生态系统&#xff0c;迎来更多的应用和软硬件产品&#xff0c;加速自我技术迭代&#xff0c;同时推动华为全场…

python 进程笔记二(通讯) (概念+示例代码)

1、为什么要掌握进程间通信 Python代码效率由于受制于GIL全局锁限制&#xff0c;多线程不能利用多核CPU来加速&#xff0c;而多进程方式却可以绕过GIL限制, 发挥多CPU加速的优势&#xff0c;达到提高程序的性能的目的。 然而进程间通信却是不得不考虑的问题。 进程不同于线程&a…

投资生涯的核心密码:构建交易逻辑体系

首先&#xff0c;我们需要明确一点&#xff0c;交易中究竟有没有确定性&#xff1f; 确定性是指在某一种形式、或有若干条件时&#xff0c;价格必然会上涨或下跌&#xff0c;也可以决定上涨或下跌的程度。 我认为&#xff0c;没有。迄今为止还没有一个理论能发现即使确定的东西…

金融知识分享系列之:五日线

金融知识分享系列之&#xff1a;五日线 一、股票均线二、五日线三、五日线加量能三、五日线案例四、五日线案例五、五日线案例六、五日线案例七、五日线案例八、五日线案例 一、股票均线 股票均线是一种用于平滑股票价格的指标。它是根据一段时间内的股票价格计算得出的平均值…

PureFlash v1.9.1特性介绍

PureFlashv1.9.1版本特性主要有3个&#xff1a; 1. 支持RDMA网络 使用RDMA协议可以大大减少对CPU的消耗&#xff0c;性能提升30%以上。 PureFlash的网络配置分为存储节点间网络&#xff08;存储后端网&#xff09;和客户端网络&#xff08;前端网&#xff09;。都支持使用RD…

C++:STL(标准模板库)

STL&#xff1a;主要是一些“容器”的集合&#xff1b;“容器”有&#xff1a;vector(数组)、list(双向链表)、deque(双向队列)、set(集合)、map(图&#xff1a;内部结构红黑树) STL也是算法和其他一些组件的集合&#xff0c;是泛型编程的一个经典范例。 STL的目的是标准化组…