一个设想
以前打游戏总是喜欢开个微星小飞机监测一下硬件状态,看看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;
}
|
这个函数需要传入两个指针参数:total
和 idle
,分别指向 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
指针有没有指向合理的位置来判断是否需要抛出异常。
读取文件并写入变量
这一步的主要流程是:
- 逐行读取这个文件
- 检查这一行的开头是否为
cpu
- 如果是,就将每个数字写入对应的变量
- 计算
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
类型的变量,并且用sscanf
从line
中读取各个时间值,%*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;
}
|
(仅测试,不做异常处理了)