Linux系统下如何更快读取或采集文件内容(例如每秒采集CPU温度)
优化sysfs thermal数据采集速度的方法
你的问题核心在于shell循环中频繁创建cat进程——每个thermal zone都要启动两个cat进程,100个节点就是200次进程创建/销毁,这是最大的性能开销,加上多次独立的文件IO调用,导致总耗时超标。下面是几个能大幅提升速度的方案:
1. 用awk一次性处理所有文件(shell层面最优)
把所有type和temp文件一次性传给awk,让awk在内部完成读取和匹配,避免多次fork进程:
awk '{ # 记录每个thermal_zone的type值 if (FILENAME ~ /type$/) { zone_path = FILENAME sub(/\/type$/, "", zone_path) type_map[zone_path] = $0 } # 读取temp并对应输出type和temp else if (FILENAME ~ /temp$/) { zone_path = FILENAME sub(/\/temp$/, "", zone_path) printf "%s: %s\n", type_map[zone_path], $0 } }' /sys/class/thermal/thermal_zone*/{type,temp}
这个命令只启动一个awk进程,一次性处理所有目标文件,进程开销几乎为0,实测能把耗时压缩到几百毫秒甚至更低。
2. 用C语言直接读取(性能极致优化)
如果shell优化后还是不够快,用C语言直接调用系统调用open/read读取sysfs文件,完全避免shell的进程调度开销。这里是一个极简的实现:
#include <stdio.h> #include <dirent.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #define THERMAL_BASE "/sys/class/thermal/" #define BUF_LEN 256 // 读取sysfs文件内容(去掉换行符) static void read_sysfs(const char *path, char *buf) { int fd = open(path, O_RDONLY); if (fd == -1) return; ssize_t n = read(fd, buf, BUF_LEN - 1); if (n > 0) { buf[n] = '\0'; if (buf[n-1] == '\n') buf[n-1] = '\0'; } close(fd); } int main() { DIR *dir = opendir(THERMAL_BASE); if (!dir) { perror("Failed to open thermal directory"); return 1; } struct dirent *entry; char type_path[BUF_LEN], temp_path[BUF_LEN]; char type[BUF_LEN], temp[BUF_LEN]; while ((entry = readdir(dir)) != NULL) { // 只处理thermal_zone开头的目录 if (strncmp(entry->d_name, "thermal_zone", 12) != 0) continue; snprintf(type_path, BUF_LEN, "%s%s/type", THERMAL_BASE, entry->d_name); snprintf(temp_path, BUF_LEN, "%s%s/temp", THERMAL_BASE, entry->d_name); read_sysfs(type_path, type); read_sysfs(temp_path, temp); printf("%s: %s\n", type, temp); } closedir(dir); return 0; }
编译运行:
gcc -O2 -o thermal_reader thermal_reader.c ./thermal_reader
这个程序的耗时通常在几十毫秒级别,完全满足每秒采集一次的需求。
3. 过滤不必要的节点(减少读取量)
如果100个节点中只有部分是你需要的(比如仅CPU相关的),可以先过滤出目标节点再读取,减少IO次数:
# 先筛选出CPU相关的thermal zone(根据你的type名称调整) TARGET_ZONES=$(grep -lE "cpu_thermal|x86_pkg_temp|coretemp" /sys/class/thermal/thermal_zone*/type) # 只处理筛选后的节点 for zone in $TARGET_ZONES; do zone_dir=$(dirname "$zone") printf "%s: %s\n" "$(cat "$zone")" "$(cat "$zone_dir/temp")" done
如果只需要读取10个以内的节点,这个方法的速度会非常快。
内容的提问来源于stack exchange,提问作者domagaja




