Featured image of post 动手做一个硬件监测器吧(一.获取CPU占用率)

动手做一个硬件监测器吧(一.获取CPU占用率)

一个设想

以前打游戏总是喜欢开个微星小飞机监测一下硬件状态,看看CPU占用,显卡温度之类的。但是后来觉得这东西也会吃点资源,还会占用屏幕右上角的一小片区域,于是就想着如果能独立出来一个小屏幕,专门展示硬件状态,肯定会很酷炫且(有点)实用。

正好最近受到了猹猹赞助的开发板和显示屏,那么说干就干,开始挖坑吧!

实现思路

我的思路大概是这样的:

电脑端运行一个轻量的程序,负责获取硬件状态信息,不做额外处理,直接通过串口发送给开发板。

只是为了减少电脑的占用,做到尽可能的轻量。

开发板负责解析硬件状态信息并绘制图形到显示屏,对于部分数据,还可以画个折线统计图。

程序方面我打算是用C语言实现,先来实现PC端的程序吧。

代码实现(PC-CPU部分)

我们先来尝试通过Linux底层接口来获取CPU的占用率和温度。

不是我说,Linux一切皆文件的思想真酷吧。

1.从 /proc/stat 获取CPU占用率

/proc/stat 是什么

/proc/stat 文件在 Linux 系统中存储了与系统统计信息相关的数据,它包含了 CPU、系统引导时间、进程和上下文切换等各种统计信息。

如果你正用着Linux系统看我的文章,你不妨试试运行一下cat /proc/stat 。我这里运行的结果是:

1
2
3
4
5
6
7
cpu  1711097 644 361090 11032180 12086 0 7104 0 0 0
cpu0 213117 74 45293 1379146 1585 0 491 0 0 0
cpu1 214088 51 44558 1380027 1725 0 332 0 0 0
cpu2 217501 34 44412 1378727 1235 0 224 0 0 0
cpu3 216286 180 45075 1378982 1282 0 181 0 0 0
cpu4 213065 47 45294 1379106 1283 0 2303 0 0 0
......(后面太长了不放了)

对于我的需求,只需要关注第一行数据,从左到右的每个数字的意思是:

  • user:用户模式(不包括 nice 优先级调度)花费的时间。
  • nice:用户模式下低优先级进程花费的时间。
  • system:内核模式下花费的时间。
  • idle:空闲时间。
  • iowait:等待 I/O 操作完成的时间。
  • irq:处理硬中断的时间。
  • softirq:处理软中断的时间。
  • steal:虚拟化环境下,等待其他虚拟 CPU 任务完成的时间。
  • guest:为虚拟 CPU 所花费的时间。
  • guest_nice:低优先级的虚拟 CPU 时间。

那么如何计算CPU占用率呢,很简单,可以看下面的公式:

1
2
3
总时间 = user + nice + system + idle + iowait + irq + softirq + steal

CPU 占用率 = [(总时间 - 空闲时间) / 总时间] × 100%

这是瞬时占用率,当然一般我们使用的是在一小段时间内的平均占用率,公式:

1
2
3
CPU 占用率 = [1 - (新空闲时间 - 老空闲时间) / (新总时间 - 老总时间)] × 100%

(这里的“新”,“老”,指的是获取的前后顺序,可以理解成“新获取的“和”之前获取的“)

理解了如何计算,那么就可以尝试写点代码来实现了。

代码实现

定义measure_cpu_time函数

让我们先来定义一个函数吧,就叫measure_cpu_time用来获取CPU时间。

1
2
3
4
int measure_cpu_time(unsigned long *total, unsigned long *idle){

return 0;
}

这个函数需要传入两个指针参数:totalidle,分别指向 unsigned long 类型的变量。利用指针是为了传参方便,这一点在后面可以体现。

读取/proc/stat文件

因为这是一个文件,所以我们可以之间打开它,调用一下fopen函数即可:

1
2
3
4
5
FILE *file = fopen("/proc/stat", "r");
if (file == NULL){
	perror("can't open /prop/stat");
	return 0;
}

这个代码块调用了fopen函数去以只读的方式(即"r"参数)打开/proc/stat,并且返回了一个指针,指向FILE类型,名为file。因为fopen在成功时返回指向 FILE 结构的指针,而在失败时会返回 NULL,所以通过检查file指针有没有指向合理的位置来判断是否需要抛出异常。

读取文件并写入变量

这一步的主要流程是:

  1. 逐行读取这个文件
  2. 检查这一行的开头是否为cpu
  3. 如果是,就将每个数字写入对应的变量
  4. 计算total时间,用指针传参。

这里我先放一下完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if (fgets(line, MAX_LINE_LENGTH, file) != NULL){
    if (strncmp(line, "cpu ", 4) == 0){
        unsigned long user, nice, system, idle_val, iowait, irq, softirq, steal;
        sscanf(line, "%*s %lu %lu %lu %lu %lu %lu %lu %lu", 
                    &user, &nice, &system, &idle_val, &iowait, &irq, &softirq, &steal);

        *total = user + nice + system + idle_val + iowait + irq + softirq + steal;
        *idle = idle_val;
        printf("CPU state retrieved successfully\n");
    }        
}

这里首先调用了fget函数,用来逐行读取读取file文件,每行最长为MAX_LINE_LENGTH,然后保存在line字符串里面,如果没有错误(即返回值为NULL)则继续。

然后调用strncmp函数,用来对字符串做匹配,比较line的前4个字符是不是“cpu ”如果是,则继续。

然后就是定义了很多unsigned long类型的变量,并且用sscanfline中读取各个时间值,%*s跳过第一个字符串(即 “cpu”),后续的%lu将对应的数值依次解析到相应变量中。

最后再打印读取完毕的消息,即printf("CPU state retrieved successfully\n");

完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define MAX_LINE_LENGTH 128

int measure_cpu_time(unsigned long *total, unsigned long *idle){
    FILE *file = fopen("/proc/stat", "r");
    if (file == NULL){
        perror("can't open /prop/stat");
        return 0;
    }

    char line[MAX_LINE_LENGTH];

    if (fgets(line, MAX_LINE_LENGTH, file) != NULL){
        if (strncmp(line, "cpu ", 4) == 0){
            unsigned long user, nice, system, idle_val, iowait, irq, softirq, steal;
            sscanf(line, "%*s %lu %lu %lu %lu %lu %lu %lu %lu", 
                        &user, &nice, &system, &idle_val, &iowait, &irq, &softirq, &steal);

            *total = user + nice + system + idle_val + iowait + irq + softirq + steal;
            *idle = idle_val;
            printf("CPU state retrieved successfully\n");
        }        
    }
    fclose(file);
    return 0;
}

调用测试

main函数这样写就能调用了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int main() {
    unsigned long total, idle, total_old, idle_old;
    float cpu_usage;
    
    measure_cpu_time(&total, &idle);
    total_old = total;
    idle_old = idle;
    
    usleep(100000);
    
    measure_cpu_time(&total, &idle);
    
    cpu_usage = 100.0 * (1 - ((float)(idle - idle_old) / (total - total_old)));

    printf("cpu_usage: %.2f %% \n",cpu_usage);      

    return 0;
}

(仅测试,不做异常处理了)

Have a try!
使用 Hugo 构建
主题 StackJimmy 设计
本博客已稳定运行