在一些项目开发中,实时显示 ARM-Linux 开发板运行时的数据非常有必要,这可以帮助开发人员识别问题并进行调试。通过查看运行时数据,开发人员可以快速定位程序中的错误或异常,并及时进行修复。同时,实时数据可以用于评估系统的性能,并确定任何潜在的性能瓶颈。通过监视 CPU、内存和其他系统资源的使用情况,开发人员可以识别可能导致性能下降的问题,并采取相应的措施进行优化。 在一些应用场景中,显示实时数据可以提供用户界面和交互性,使用户能够与系统进行交互并实时查看系统状态。这对于一些需要用户介入的应用程序或系统来说尤其重要。
本文将以 OrangePi ZERO 2 开发板为例,介绍各种实时数据的获取方法。
一、获取开发板运行时间
在Linux系统中,可以使用uptime
命令查看系统的运行时间和平均负载。该命令的输出通常包括系统当前时间、系统已经运行的时间以及系统的平均负载情况。
要查看系统的运行时间,只需在终端中输入以下命令:
uptime
执行该命令后,会输出类似以下内容的信息:
03:22:22 up 5 days, 8:14, 2 users, load average: 0.08, 0.15, 0.18
其中,“up”后面的部分显示了系统已经运行的时间,例如 “5 days, 8:14” 表示系统已经运行了 5 天 8 小时 14 分钟。
[!IMPORTANT]
这段输出包含了几个关键信息:
系统当前时间: 在这个例子中,系统当前时间是 03:22:22。
已经运行的时间: 在这个例子中,“up 5 days, 8:14”表示系统已经连续运行了5天8小时14分钟。
用户数量: 在这个例子中,系统当前有2个用户登录。
平均负载: 平均负载是系统的一个指标,它表示在最近1分钟、5分钟和15分钟内的平均进程等待队列长度。在这个例子中,平均负载为0.08、0.15和0.18,分别对应最近1分钟、5分钟和15分钟内的平均负载。
平均负载是一个反映系统繁忙程度的指标,它可以帮助用户了解系统当前的工作负载情况。通常来说,如果平均负载接近CPU核心数量,说明系统负载比较高,可能需要进一步优化或增加系统资源。
当然了,上面显示的信息相对来说比较多,而且也不是全部都有必要,如果只是为了获取运行时间,可以在 proc/uptime
里获取,这个文件记录了系统从启动以来的运行时间和空闲时间。这个文件包含了两个数值,以空格分隔:
- 第一个数值表示系统从启动以来的总运行时间(以秒为单位)。
- 第二个数值表示系统自启动以来处于空闲状态的时间(以秒为单位)。
这些数值可以用来计算系统的负载、计算系统的运行时间,或者用于其他系统监控和管理任务。通常,这个文件是用于系统工具和监控程序获取系统运行时间和空闲时间的数据。
要获取系统从启动以来的总运行时间,可以使用下面的函数来获取:
double getUptimeString(void)
{
FILE *uptimeFile = fopen("/proc/uptime", "r");
if (uptimeFile == NULL) {
perror("Error opening uptime file!");
exit(1);
}
double uptime;
if (fscanf(uptimeFile, "%lf", &uptime) == 1) {
fclose(uptimeFile);
return uptime;
} else {
fclose(uptimeFile);
return -1.0;
}
}
这个函数所获取的时间单位为秒,并不方便阅读,因此我写了完整的程序,在获取到系统运行时间后,转换成运行天数以及 24 小时制的时分秒显示方式,具体代码如下:
#include <stdio.h>
#include <stdlib.h>
#define SECONDS_IN_MINUTE 60
#define MINUTES_IN_HOUR 60
#define HOURS_IN_DAY 24
#define SECONDS_IN_DAY (SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY)
#define SECONDS_IN_HOUR (SECONDS_IN_MINUTE * SECONDS_IN_MINUTE)
double getUptimeString(void)
{
FILE *uptimeFile = fopen("/proc/uptime", "r");
if (uptimeFile == NULL) {
perror("Error opening uptime file!");
exit(1);
}
double uptime;
if (fscanf(uptimeFile, "%lf", &uptime) == 1) {
fclose(uptimeFile);
return uptime;
} else {
fclose(uptimeFile);
return -1.0;
}
}
void formatUptime(int *uptime, int size)
{
if (size != 4)
return;
double uptime_temp = getUptimeString();
if (uptime_temp < 0) {
perror("Failed to retrieve system uptime.");
exit(1);
} else {
uptime[0] = (int)(uptime_temp / SECONDS_IN_DAY);
uptime_temp -= uptime[0] * SECONDS_IN_DAY;
uptime[1] = (int)(uptime_temp / SECONDS_IN_HOUR);
uptime_temp -= uptime[1] * SECONDS_IN_HOUR;
uptime[2] = (int)(uptime_temp / SECONDS_IN_MINUTE);
uptime_temp -= uptime[2] * SECONDS_IN_MINUTE;
uptime[3] = (int)uptime_temp;
}
}
int main(void)
{
int uptime[4] = {0};
formatUptime(uptime, (sizeof(uptime) / sizeof(uptime[0])));
printf("Uptime: %ddays %02d:%02d:%02d\n", uptime[0], uptime[1], uptime[2],
uptime[3]);
return 0;
}
代码运行结果如下:
二、获取 CPU 使用状态
在 Linux 系统中,获取 CPU 状态的方法很多,例如,top
、htop
等命令。但是如果想要很直观的获取当前 CPU 的使用率,在 proc/stat
文件中有很详细的记录。
/proc/stat
文件是 Linux 系统中一个特殊的文件,用于记录各个 CPU 和整个系统的各种统计信息。这些统计信息通常以一行的形式显示,每行的开头表示统计项目的类型,后面的数字表示相应的值。其中,第一行就是 CPU 总体使用的情况。如下图所示,统计各个 CPU 的运行时间,包括用户态、系统态、空闲态、等待I/O等。
简单解读一下第一行信息的含义,这里包含的就是 CPU 时间统计记录了各个 CPU 在不同状态下的运行时间(单位:ms),以及整个系统的总体 CPU 使用情况。
这些是 CPU 时间统计中常见的状态,它们分别代表 CPU 在不同状态下所花费的时间。以下是对这些状态的详细解释:
用户态(user):CPU 在执行用户空间进程代码时所花费的时间。这包括用户进程、用户态系统调用等。在这个状态下,CPU 正在执行应用程序代码,例如运行一个用户编写的程序。
优先级(nice):优先级(nice)是指用户对进程的调度优先级进行调整的机制。在 Linux 中,nice 值用来指定进程的调度优先级,数值越小,优先级越高。
nice
状态下记录的是在用户态运行的带有非标准优先级(即非零nice值)的进程所占用的 CPU 时间。系统态(system):CPU 在执行内核代码(系统调用、中断处理等)时所花费的时间。系统态的时间主要用于处理内核任务,比如文件系统操作、内存管理等。在这个状态下,CPU 正在执行操作系统内核的代码。
空闲态(idle):CPU 处于空闲状态的时间,即没有运行任何任务的时间。在这个状态下,CPU 没有要执行的任务,可以用来执行节能措施等。
等待I/O(iowait):CPU 在等待 I/O 操作完成时所花费的时间。这包括等待磁盘 I/O、网络 I/O 等。在这个状态下,CPU 被阻塞等待某些 I/O 操作完成。
硬中断(irq):CPU 在处理硬中断(由硬件设备触发的中断)时所花费的时间。硬中断是由硬件设备(如网卡、磁盘控制器等)发送给 CPU 的信号,需要立即处理。
软中断(softirq):CPU 在处理软中断(由内核软件触发的中断)时所花费的时间。软中断是由操作系统内核在特定条件下触发的中断,用于处理一些延迟敏感的任务,如网络数据包的处理。
虚拟时钟(Virtual Clock,也称为Steal Time):在虚拟化环境中使用的一种 CPU 时间统计状态。虚拟时钟表示在一个虚拟化环境中,CPU 被宿主系统(hypervisor)“偷走”用于服务其他虚拟机的时间。这种情况通常发生在共享物理 CPU 的虚拟化环境中,多个虚拟机共享同一个物理 CPU。
宏调度延迟(Guest):宏调度延迟表示虚拟机中运行的客户操作系统所消耗的 CPU 时间。当虚拟机中的操作系统执行用户态任务时,这部分时间被归类为宏调度延迟。它表示了虚拟机中的用户态进程执行所消耗的 CPU 时间。
宏调度软中断(Guest_nice):宏调度软中断表示虚拟机中运行的客户操作系统所消耗的低优先级 CPU 时间。与普通的软中断类似,它表示了虚拟机中的一些延迟敏感的任务所消耗的 CPU 时间,但是它们的优先级较低。
这些状态是在 Linux 系统中通过 /proc/stat
文件记录的,它们提供了对 CPU 使用情况的详细统计信息,可以帮助了解系统的负载情况和性能状况。
如果要写一个程序来获取 CPU 的使用占比,其实也很简单,把 CPU 所有状态的时间总和作为分母,再用这个总和减去空闲态的差作为分子,分子比上分母的值就是当前 CPU 的使用占比了。具体代码如下:
float getCpuUsage(void)
{
FILE *statFile = fopen("/proc/stat", "r");
if (statFile == NULL) {
perror("Error opening stat file");
exit(1);
}
char line[256];
if (fgets(line, sizeof(line), statFile)) {
if (strncmp(line, "cpu", 3) == 0) {
unsigned long user, nice, system, idle, iowait, irq, softirq;
sscanf(line, "cpu %lu %lu %lu %lu %lu %lu %lu",
&user, &nice, &system, &idle, &iowait, &irq, &softirq);
unsigned long total = user + nice + system + idle
+ iowait + irq + softirq;
unsigned long non_idle = user + nice + system
+ iowait + irq + softirq;
float usage = 100.0 * (non_idle / (float)total);
fclose(statFile);
return usage;
}
}
fclose(statFile);
return -1.0;
}
该函数返回值可以直接显示在百分号前(已经乘以 100 了),调用时可以直接输出,完整代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
float getCpuUsage(void)
{
FILE *statFile = fopen("/proc/stat", "r");
if (statFile == NULL) {
perror("Error opening stat file");
exit(1);
}
char line[256];
if (fgets(line, sizeof(line), statFile)) {
if (strncmp(line, "cpu", 3) == 0) {
unsigned long user, nice, system, idle, iowait, irq, softirq;
sscanf(line, "cpu %lu %lu %lu %lu %lu %lu %lu",
&user, &nice, &system, &idle, &iowait, &irq, &softirq);
unsigned long total = user + nice + system + idle
+ iowait + irq + softirq;
unsigned long non_idle = user + nice + system
+ iowait + irq + softirq;
float usage = 100.0 * (non_idle / (float)total);
fclose(statFile);
return usage;
}
}
fclose(statFile);
return -1.0;
}
int main(void)
{
printf("CPU_Load: %.02f%%\n", getCpuUsage());
return 0;
}
运行结果如下图:
三、获取 CPU 温度
获取 CPU 的温度,除了性能监控外,很大程度上是为了散热需要。一般情况下,CPU 都会有散热设备,考虑到节能问题,这些散热设备也不会一直打开。当 CPU 负载较轻或者空闲时,散热设备可以不运行,靠自然散热就可以了。随着 CPU 温度升高,散热设备也随之打开,并且温度越高,散热效果越强。
现在的 CPU 内部都有热感应装置,可以自测温度并保存,运行以下命令可以查看 CPU 温度:
cat /sys/class/thermal/thermal_zone*/temp
这将显示各个热区的温度值。通常,/sys/class/thermal/thermal_zone0/temp
是 CPU 温度。
运行上述命令后,可以看到有四个数字,分别代别四个不同的热区的温度,通常来说,这些数字是以毫摄氏度(m°C)为单位的,需要将其除以 1000 来得到实际的摄氏度温度值。
那么怎么知道这分别对应哪些热区的温度呢?运行以下命令可以查看对应的热区是什么了:
cat /sys/class/thermal/thermal_zone*/type
执行结果如下:
这些类型通常代表了各个热区(thermal zone)所对应的硬件组件或位置,以下是各个热区的解释:
- cpu-thermal(CPU Thermal):代表 CPU(中央处理器)的热区,用于监测CPU的温度。
- gpu-thermal(GPU Thermal):代表 GPU(图形处理器)的热区,用于监测GPU的温度。
- ve-thermal(Video Engine Thermal):代表视频引擎(Video Engine)的热区,用于监测视频引擎的温度。(这一条是我猜的,恕我才疏学浅,真的不知道 ve 是什么)
- ddr-thermal(DDR Thermal):代表 DDR(双数据率)内存的热区,用于监测内存的温度。
按这个顺序排序,thermal_zone0
就是 CPU 的热区信息。
那么获取 CPU 温度的函数如下:
float getCpuTemperature(void)
{
float temperature;
FILE *tempFile = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
if (tempFile == NULL) {
perror("Error opening temperature file");
exit(1);
}
fscanf(tempFile, "%f", &temperature);
fclose(tempFile);
return temperature / 1000.0f;
}
在 main
函数中调用该函数后打印,结果如下:
四、获取内存占用率
获取内存占用率的作用在于帮助系统管理员或开发人员了解系统当前内存的使用情况,以及对系统性能和稳定性进行监控和调优。以下是获取内存占用率的一些作用:
性能监控:内存占用率是评估系统性能的重要指标之一。通过监视内存占用率,可以及时发现系统内存是否足够,是否存在内存泄漏或者过度使用内存的问题。
资源分配:了解内存占用率可以帮助系统管理员决定是否需要增加内存容量或者重新分配内存资源。根据内存占用率的情况,可以合理调整进程的内存使用、调整虚拟机的内存分配等,以确保系统资源的充分利用和性能的最大化。
故障排查:当系统出现性能问题或者内存相关的故障时,内存占用率是一项重要的参考指标。通过对比内存占用率的历史数据或者与阈值进行比较,可以快速识别并定位内存问题,并采取相应的措施进行修复。
预测和规划:通过长期监视内存占用率,可以了解系统的内存使用模式和趋势,从而进行合理的规划和预测,例如预测未来内存需求、规划内存扩展、优化内存分配策略等。
查看系统内存使用情况的命令是 free
,但是单纯的使用该命令得到的是如下图的结果:
这是因为 free
命令输出的数据默认单位是 KB,而我们大多数使用的单位是 MB 或者 GB,所以需要带上 -m
选项或者 -g
选项。
-g
选项之所以都是 0,是因为 Orangepi ZREO 2 最大内存也就 1GB(实际不到 1GB)。加上 -m
选项的显示,内存共有 984MB,使用了 313MB。
如果要想知道内存的使用占比,那需要用到 awk
来格式输出一下:
free -m | awk 'NR==2{printf "%dMB/%dMB %.2f%%\n", $3, $2, $3*100/$2 }'
这个命令的作用是:
- 使用
free -m
命令查看系统内存使用情况,其中-m
参数表示以兆字节(MB)为单位显示内存大小。 - 将
free -m
命令的输出通过管道传递给awk
命令进行处理。 - 在
awk
命令中,NR==2
表示只处理free -m
命令输出的第二行(因为free -m
输出了多行,第二行包含了实际的内存使用情况)。 printf "%dMB/%dMB %.2f%%", $3, $2, $3*100/$2
表示格式化输出内存使用情况的字符串,其中$3
是已使用的内存大小(单位为兆字节),$2
是总内存大小(单位为兆字节),$3*100/$2
计算出内存使用率(以百分比形式表示,保留两位小数)。
因此,这段代码的作用是执行 Shell 命令 free -m
来获取系统内存使用情况,并将结果格式化为一行字符串,其中包含已使用内存大小、总内存大小和内存使用率。运行效果如下:
写成 C 语言函数:
void getMemoryUsage(char *usage, int size)
{
FILE *memInfo = popen("free -m | awk 'NR==2{printf \"%dMB/%dMB %.2f%%\", \
$3, $2, $3*100/$2 }'", "r");
if (memInfo == NULL) {
perror("Unable to execute command!");
exit(1);
}
fgets(usage, size, memInfo);
pclose(memInfo);
}
在 main
函数调用后,打印结果如下:
五、获取磁盘使用率
一般获取磁盘使用率是为了性能监控,磁盘使用率可以帮助系统管理员实时监控系统的运行状态。当磁盘使用率高时,可能表明系统存在瓶颈或者资源受限的情况,及时采取措施来解决问题,保证系统的正常运行。另一个就是容量规划,通过分析历史磁盘使用率的变化趋势,可以进行容量规划,合理安排磁盘空间的分配,避免因磁盘空间不足而导致的性能下降或系统故障。
在 Linux 系统中,可以使用 df
命令(disk free)来查看磁盘情况,但是输出的一般不易看懂的内容。
这些数字都是 KB 作为单位,可以加上 -h
选项,这样可以以人类可读的格式显示磁盘使用情况。
不过,这里的信息依旧很多很杂,如果我想直接就知道,磁盘大小是多少,已经用了多少,使用占比是多少,可以在这个命令后面加上根目录,根目录的大小就可以认为是磁盘使用的大小。
这样我们可以很直观的看到,磁盘大小是 15GB,使用 3.7GB,使用率是 26%。
如果要让数据更简短、更直截了当,就需要加上 awk
命令来格式输出一下了,具体如下:
df -h / | awk '$NF=="/"{printf "%sB/%sB %s\n", $3, $2, $5}'
上述命令的具体解释如下:
df -h /
:这部分使用df
命令来显示根目录文件系统的使用情况。其中,-h
选项表示以人类可读的格式显示磁盘使用情况,即以 GB、MB 等单位显示容量。|
:这是管道符号,它将df
命令的输出传递给下一个命令,即awk
。awk '$NF=="/"{printf "%sB/%sB %s\n", $3, $2, $5}'
:这是awk
命令,用于处理df
命令的输出并提取所需的信息。$NF=="/"
:这个条件表示对于每一行,只有当最后一列的内容是 “/”(根目录)时才执行后续操作。这样就可以确保只处理根目录文件系统的信息。{printf "%sB/%sB %s\n", $3, $2, $5}
:这部分是awk
的动作,它使用printf
函数格式化输出根目录文件系统的使用情况。$3
:表示第三列,即已使用的空间。$2
:表示第二列,即总空间。$5
:表示第五列,即已用百分比。
输出格式为
已使用空间/总空间 已用百分比
,如 “20GB/100GB 20%”。
因此,整个命令的作用是获取根目录文件系统的使用情况,并以人类可读的格式输出已使用空间、总空间和已用百分比。具体执行结果如下:
如果写成 C 语言函数,具体代码如下:
void getDiskUsage(char *usage, int size)
{
FILE *diskFile = popen("df -h / | awk '$NF==\"/\"{printf \"%sB/%sB %s\", \
$3, $2, $5}'", "r");
if (diskFile == NULL) {
perror("Error running df command!");
exit(1);
}
fgets(usage, size, diskFile);
pclose(diskFile);
}
在 main
函数调用后,打印结果如下:
六、获取 IP 地址
获取 IP 地址应该是大多数人初学 Linux 时就学会的内容,一般都是安装 net-tools,然后用 ifconfig
命令查看。也可以使用 ip addr
命令查看。
不过用这些命令查看,包含的信息比较多,不止会输出 IP 地址,连同 MAC 地址和网络状态等都会输出。如果要简化输出结果,那就要配合上其他参数了。
以 ip add
命令为例,加上 -4
选项后,可以只显示 IPv4 的地址。
如果我们要进行远程连接该 Linux 主机的话(例如 SSH),那我们只需要知道 eth0
或者 wlan0
的 IPv4 的地址就够了,所以要加上 show
这个选项,同时再加上需要输出的网络接口。
当然了,这些信息依旧很杂,如果只要显示 IP 地址,其他都去掉,那就配合 grep
命令来过滤信息了。根据上图显示的信息,我总结下面的命令,用于直接获取 IP 地址。
# 获取 eth0 的 IPv4 地址
ip -4 addr show eth0 | grep -oP '(?<=inet\s)\\d+(\.\d+){3}'
# 获取 wlan0 的 IPv4 地址
ip -4 addr show wlan0 | grep -oP '(?<=inet\s)\\d+(\.\d+){3}'
执行结果如下,是不是这样就更直观了。
当然了,输入这些命令也是毕竟麻烦,所以可以用程序来代替。
void getEth0IP(char *ip, int size)
{
FILE *eth0IP = popen("ip -4 addr show eth0 |grep -oP \
'(?<=inet\\s)\\d+(\\.\\d+){3}'", "r");
if (eth0IP == NULL) {
perror("Unable to execute command!");
exit(1);
}
fgets(ip, size, eth0IP);
pclose(eth0IP);
}
void getWlan0IP(char *ip, int size)
{
FILE *wlan0IP = popen("ip -4 addr show wlan0 |grep -oP \
'(?<=inet\\s)\\d+(\\.\\d+){3}'", "r");
if (wlan0IP == NULL) {
perror("Unable to execute command!");
exit(1);
}
fgets(ip, size, wlan0IP);
pclose(wlan0IP);
}
在 main
函数调用后,打印结果如下: