/proc 文件系统实战:原来 top、htop 都是靠读文件实现的
一、/proc是什么:假装是文件系统的内核接口
乍一看,/proc 就是个普通目录,对吧?但真相是,它压根不在硬盘上。它是一个由内核在内存中实时维护的虚拟文件系统(procfs)。每次你读取 /proc 下的一个文件,内核都会现场“生成”对应的数据返回给你,数据是活的。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
$ mount | grep proc
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
$ ls /proc
1 42 1234 ... # 数字目录 = 正在运行的进程(PID)
cpuinfo meminfo net sys ... # 系统全局信息

先随手试几个命令,感受一下它的脉搏:
# 当前进程的 PID
echo $$ # 假设是 12345
# 查看进程状态
cat /proc/12345/status
# 查看内存信息
cat /proc/meminfo | head -5
# 查看 CPU 信息
cat /proc/cpuinfo | grep "model name" | head -1
# 查看系统负载(1/5/15 分钟平均负载)
cat /proc/loada vg
二、/proc/[PID]/里有什么宝藏?
每个正在运行的进程,在 /proc 下都有一个以自己 PID 命名的专属目录。这里面藏着的,正是 top、htop 这些监控工具的原始数据源。

光说不练假把式,我们直接动手看看真实数据长什么样:
# 找到 nginx 的 PID
pidof nginx # 假设是 1234
# CPU 时间(第14、15字段)
cat /proc/1234/stat | awk '{print "utime="$14, "stime="$15}'
# 物理内存(单位 kB)
grep VmRSS /proc/1234/status
# 打开的文件数量
ls /proc/1234/fd | wc -l
# 实际磁盘 IO
cat /proc/1234/io
三、CPU 使用率的计算原理:不是直接读出来的
这里有个最常见的理解误区:/proc/[PID]/stat 里并没有直接存储着“CPU 使用率 3.2%”这样的现成数字。
它存储的是进程自启动以来累计消耗的 CPU 时间,单位是 jiffies(1 jiffie 可能是 10ms 或 4ms,取决于内核配置)。那么,top 是怎么算出那个百分比的?答案是:两次采样做差。

这下就明白了,为什么 top 默认每 3 秒刷新一次。它每隔 3 秒做一次快照,然后用进程在这 3 秒内消耗的 CPU 时间增量,除以系统在这 3 秒内的总 CPU 时间增量,从而得到这 3 秒内的平均 CPU 使用率。系统总 CPU 时间从 /proc/stat 读取,进程 CPU 时间则来自 /proc/[PID]/stat。
四、动手实现:迷你进程监控器
纸上得来终觉浅,现在我们把上面的知识串起来,写一个真正能跑的迷你进程监控工具,核心逻辑不到100行。
#include
#include
#include
#include
typedef struct {
long utime, stime; // 进程 CPU 时间
long total_cpu; // 系统总 CPU 时间
long vm_rss; // 物理内存 KB
long rchar, wchar; // IO 读写字节
} ProcInfo;
// 读进程 CPU 时间(/proc/PID/stat 第14、15字段)
void read_proc_stat(int pid, ProcInfo *info) {
char path[64];
snprintf(path, sizeof(path), "/proc/%d/stat", pid);
FILE *f = fopen(path, "r");
if (!f) return;
// 跳过前13个字段,读第14(utime)和第15(stime)
long dummy; char name[256]; char state;
fscanf(f, "%ld %s %c %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
&dummy, name, &state,
&dummy, &dummy, &dummy, &dummy, &dummy,
&dummy, &dummy, &dummy, &dummy, &dummy,
&info->utime, &info->stime);
fclose(f);
}
// 读系统总 CPU 时间(/proc/stat 第1行)
long read_total_cpu() {
FILE *f = fopen("/proc/stat", "r");
long user, nice, system, idle, iowait, irq, softirq;
fscanf(f, "cpu %ld %ld %ld %ld %ld %ld %ld",
&user, &nice, &system, &idle, &iowait, &irq, &softirq);
fclose(f);
return user + nice + system + idle + iowait + irq + softirq;
}
// 读物理内存(/proc/PID/status 中的 VmRSS)
void read_memory(int pid, ProcInfo *info) {
char path[64], line[128];
snprintf(path, sizeof(path), "/proc/%d/status", pid);
FILE *f = fopen(path, "r");
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "VmRSS:", 6) == 0) {
sscanf(line, "VmRSS: %ld", &info->vm_rss);
break;
}
}
fclose(f);
}
// 读 IO 字节数(/proc/PID/io)
void read_io(int pid, ProcInfo *info) {
char path[64], line[128];
snprintf(path, sizeof(path), "/proc/%d/io", pid);
FILE *f = fopen(path, "r");
if (!f) return; // 需要 root 才能读其他用户的 io
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "rchar:", 6) == 0) sscanf(line, "rchar: %ld", &info->rchar);
if (strncmp(line, "wchar:", 6) == 0) sscanf(line, "wchar: %ld", &info->wchar);
}
fclose(f);
}
int main(int argc, char *argv[]) {
if (argc < 2) { printf("用法: %s \n", argv[0]); return 1; }
int pid = atoi(argv[1]);
ProcInfo prev = {}, curr = {};
while (1) {
read_proc_stat(pid, &prev);
prev.total_cpu = read_total_cpu();
read_memory(pid, &prev);
read_io(pid, &prev);
sleep(1); // 采样间隔 1 秒
read_proc_stat(pid, &curr);
curr.total_cpu = read_total_cpu();
read_memory(pid, &curr);
read_io(pid, &curr);
// 计算 CPU 使用率
long proc_delta = (curr.utime + curr.stime) - (prev.utime + prev.stime);
long total_delta = curr.total_cpu - prev.total_cpu;
double cpu_pct = total_delta > 0 ? (double)proc_delta / total_delta * 100.0 : 0.0;
// 计算 IO 速率(字节/秒)
long read_rate = curr.rchar - prev.rchar;
long write_rate = curr.wchar - prev.wchar;
printf("\033[2J\033[H"); // 清屏
printf("PID: %d\n", pid);
printf("CPU: %.1f%%\n", cpu_pct);
printf("内存: %ld KB (%.1f MB)\n", curr.vm_rss, curr.vm_rss / 1024.0);
printf("读 IO: %ld B/s\n", read_rate);
printf("写 IO: %ld B/s\n", write_rate);
printf("FD 数量: ");
fflush(stdout);
// 统计打开的文件描述符数量
char fd_path[64];
snprintf(fd_path, sizeof(fd_path), "ls /proc/%d/fd 2>/dev/null | wc -l", pid);
system(fd_path);
prev = curr;
}
return 0;
}
编译并运行它:
gcc -O2 -o minimon minimon.c
./minimon 1234 # 监控 PID 为 1234 的进程
你会看到类似这样的实时输出:
PID: 1234
CPU: 12.3%
内存: 45312 KB (44.3 MB)
读 IO: 8192 B/s
写 IO: 4096 B/s
FD 数量: 23
五、/proc的其他实用技巧
快速查看进程完整命令行:
cat /proc/1234/cmdline | tr '\0' ' '
# 输出示例:/usr/sbin/nginx -g daemon off;
查看进程打开了哪些文件/连接:
ls -la /proc/1234/fd
# 输出示例:
# lrwxrwxrwx 0 -> /dev/null
# lrwxrwxrwx 1 -> pipe:[12345]
# lrwxrwxrwx 3 -> socket:[67890]
# lrwxrwxrwx 4 -> /var/log/nginx/access.log
读取/修改内核参数(无需重启):
# 查看最大文件描述符数
cat /proc/sys/fs/file-max
# 开启 IP 转发(Docker/K8s 必须开)
echo 1 > /proc/sys/net/ipv4/ip_forward
# 等价于 sysctl -w net.ipv4.ip_forward=1
查看系统整体内存使用:
cat /proc/meminfo
# MemTotal: 16384000 kB
# MemFree: 2048000 kB
# Cached: 4096000 kB ← Page Cache
# Buffers: 512000 kB
# ...
查看所有进程的内存总使用量(比 free 命令更精确):
# 把所有进程的 VmRSS 加起来
grep VmRSS /proc/*/status 2>/dev/null | awk '{sum+=$2} END {print sum/1024 " MB"}'
六、高频面试题精析
Q:/proc目录里的文件真的存在磁盘上吗?
不存在。/proc 是 procfs 虚拟文件系统,挂载在内存里。读取一个 /proc 文件时,内核会实时生成对应数据返回;写入某些文件(如 /proc/sys/ 下的文件)则会直接修改内核参数。这些文件没有 inode 对应的磁盘块,用 ls -l 看大小显示为 0,但内容是实时、真实的。
Q:top显示的 CPU 使用率是怎么算出来的?
top 定期读取 /proc/[PID]/stat(获取进程 CPU 时间)和 /proc/stat(获取系统总 CPU 时间),进行两次采样。然后用进程 CPU 时间的增量,除以系统总 CPU 时间的增量,得到百分比。这就是为什么刚启动 top 时,通常需要等待一个刷新周期(比如3秒)后,数据才会变得准确。
Q:VmRSS和VmSize有什么区别?
VmSize 是进程的虚拟内存大小——它包括了所有映射的地址空间,但其中大部分可能并没有对应的物理内存页。VmRSS(Resident Set Size)则是实际占用的物理内存——即已经加载到 RAM 中的页面。监控内存使用率时应该看 VmRSS,VmSize 通常会因为包含大量共享库和映射空间而显得虚高。
Q:为什么有些进程的/proc/[PID]/io读不了?
读取其他用户进程的 io 文件需要 root 权限(或 CAP_SYS_PTRACE 能力)。读取自己进程的 io 则无需特殊权限。这是内核出于安全考虑的设计——IO 数据能够揭示进程的行为模式,不应随意暴露给非特权用户。
七、结语
/proc 堪称 Linux 系统最透明的一扇窗。内核把自己几乎所有的运行时状态,都以文件的形式陈列于此,对任何有权限的读者开放。
我们日常使用的 top、htop、ps、lsof、netstat 等工具,本质上都是 /proc 的“读者”和“翻译官”。现在,你不仅知道了它们的数据从何而来,也掌握了亲手打造专属监控工具的能力。下次再看到进程指标跳动时,你看到的将不再是一个神秘的数字,而是一段可以直接触摸和计算的数据流。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
AI智能体社交网络Moltbook卖身Meta,创始人只靠AI助手没写一行代码
Meta收购AI社交网络平台Moltbook,人才争夺战升温 科技界又迎来一笔关键收购。3月11日,Facebook母公司Meta正式宣布,已将专为AI智能体打造的社交网络平台Moltbook收入麾下,该公司的创始团队也将随之加入Meta的AI研究部门。 这一动作释放的信号再清晰不过:当能够执行现实
苹果宣布下调中国App Store佣金率 “苹果税”迎来新调整
苹果宣布下调中国App Store用金率 “苹果税”迎来新调整 3月13日,苹果公司发布了一则重要公告,宣布将对中国内地App Store的用金费率结构进行调整。根据公告内容,自2026年3月15日起,适用于中国内地App Store中iOS及iPadOS应用的用金比例将正式下调。这意味着,被业内广
比亚迪发布第二代刀片电池及闪充技术 只用9分钟就能充饱
比亚迪发布第二代刀片电池及闪充技术 只用9分钟就能充饱 2026年3月5日,深圳的一场发布会,很可能成为新能源汽车充电体验的分水岭。比亚迪正式发布了第二代刀片电池及配套的闪充技术,一举刷新了全球量产车的充电速度纪录。具体来看,从10%充至70%仅需5分钟,而从10%充至97%也只要9分钟。更令人印象
人越少活越多,还要替AI背锅!起底亚马逊打工人:裁员3万换来利润狂飙
编辑 | 王凤枝 在亚马逊当个“幸存者”,有时比被裁掉更像一种酷刑。 短短几个月,这家巨头手起刀落,将三万名员工扫地出门。但真正让人窒息的,是那些侥幸保住工位的人突然发现:昔日同事的座位空了,活儿却一点不落地全砸在了自己肩上。 一边是被无限拉长的工作时间和持续透支的身体,另一边是高层强推下来、时刻威
谷歌发布Gemini 3.1 Flash-Lite,主打“快与省”,性能碾压 2.5 Flash
谷歌推出Gemini 3 1 Flash-Lite:专为高吞吐量场景设计的“快刀手” 3月4日,谷歌正式发布了Gemini 3系列的最新成员——Gemini 3 1 Flash-Lite。官方将其定位为该系列中速度最快、性价比最高的模型,并明确表示,这款新模型就是为开发者的大规模、高吞吐量工作负载量
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

