直接用 fopen("/proc/loadavg", "r") 配合 fgets 读取最轻量可靠;字段为1/5/15分钟平均负载(活动任务数),非CPU使用率;解析时跳过非浮点字段更健壮;日志需无缓冲或及时刷新,时间戳建议用 clock_gettime。

读取 /proc/loadavg 的正确打开方式
直接用 fopen("/proc/loadavg", "r") 是最轻量、最可靠的做法。/proc 是内核虚拟文件系统,不占磁盘空间,也不触发实际 I/O,fopen + fscanf 足够——别用 system("cat /proc/loadavg") 或 popen,会多开进程、引入信号干扰和 shell 解析风险。
常见错误:用 open() 配合 read() 手动解析,结果忘记 \0 结尾或缓冲区溢出;或者用 std::ifstream 却没检查 failbit,文件一瞬不可读(比如容器 cgroup 限制)就静默失败。
- 始终检查返回值:
fopen返回NULL时说明路径不存在或权限不足(极少见,但 chroot 或 unshare 场景下可能) - 用固定大小缓冲区(如
char buf[256]),fgets(buf, sizeof(buf), fp)比fscanf更安全,避免格式串不匹配导致跳读 -
/proc/loadavg第一行永远只有一行,末尾有换行符,别假设它“一定”是 5 个浮点数加一个整数
解析 loadavg 字段的陷阱与真实含义
一行内容形如 "1.23 0.98 0.76 2/345 12345",但字段语义容易误解:2/345 不是“2 个 CPU 核心”,而是 running/total 进程状态计数(R 状态进程数 / 当前存活任务总数);12345 是最近一次 fork 的 PID,不是启动时间。
关键点:三个浮点数是过去 1/5/15 分钟的指数衰减平均负载,单位是“活动任务数”,不是 CPU 使用率。1.0 表示平均有 1 个任务在运行或等待 CPU —— 如果你有 8 核,1.0 其实很空闲。
立即学习“C++免费学习笔记(深入)”;
- 用
sscanf(buf, "%lf %lf %lf %*s %*d", &l1, &l5, &l15)忽略中间两个字段更稳妥,%*s跳过2/345,%*d跳过 PID,避免因格式微变(如内核版本升级新增字段)崩掉 - 不要对这三个值做四则运算(比如算“平均负载”),它们是独立计算的指数加权值,没有线性关系
- 如果程序需要判断是否过载,比较对象应该是你的 CPU 核心数(
sysconf(_SC_NPROCESSORS_ONLN)),而不是硬编码 1.0
写日志时绕开 fwrite 缓冲与时间精度问题
直接 fprintf(log_fp, "%ld %.2f %.2f %.2f\n", time(NULL), l1, l5, l15) 看似简单,但有两个底层坑:一是 log_fp 默认是全缓冲,进程崩溃前最后一行可能丢失;二是 time(NULL) 只有秒级精度,高频采样(如每秒一次)会导致大量重复时间戳。
- 用
setvbuf(log_fp, NULL, _IONBF, 0)设为无缓冲,或每次fflush(log_fp)强刷——后者更省系统调用,适合低频记录(如每 5 秒) - 需要毫秒级时间戳就别碰
time(),改用clock_gettime(CLOCK_REALTIME, &ts),然后ts.tv_sec * 1000 + ts.tv_nsec / 1000000 - 日志文件建议用追加模式打开:
fopen("load.log", "a"),避免多进程写入时覆盖;若单进程,可fseek(log_fp, 0, SEEK_END)确保位置正确
信号安全与长期运行的稳定性要点
如果你把这逻辑放进一个常驻进程(比如监控 agent),SIGCHLD、SIGHUP 或 logrotate 的 SIGUSR1 都可能打断文件操作。/proc 文件虽轻,但 fopen 和 fgets 并非异步信号安全函数,不能在信号处理函数里调用。
- 不要在
signal()处理器中读/proc/loadavg;改用自管道(self-pipe trick)或 signalfd(Linux 特有)把信号转成文件描述符事件 - 定期重开日志文件(比如每天零点),避免单文件无限增长;但重开前必须
fclose原句柄,否则 fd 泄露——Linux 进程 fd 上限默认 1024,撑满后所有open都失败 - 内核保证
/proc/loadavg读取是原子的,但长度固定(通常
真正难的不是读那几个数字,而是让这个行为在机器重启、日志轮转、cgroup 冻结、OOM killer 杀进程等几十种 Linux 底层扰动下,依然能持续吐出可用数据——每个 fopen 后面都得跟着 if (!fp) { /* 降级策略 */ },而不是直接 exit(1)。



















