1. 概述
1.1 工具与内核的关系
top、mpstat、htop 这三个工具都是通过读取 Linux 内核提供的 /proc 文件系统来获取系统统计信息的。它们本身并不进行统计,而是读取内核已经统计好的数据,然后进行计算和展示。
数据流向:
bash
内核统计模块(时钟中断、进程切换等)
↓
更新内核数据结构(kcpustat、task_struct 等)
↓
/proc 文件系统(虚拟文件系统接口)
↓
用户空间工具(top、mpstat、htop)
↓
读取、计算、显示
关键点:
- 统计发生在内核:所有统计都在内核中完成
- 工具只负责读取和计算:工具读取内核数据,然后计算百分比、差值等
- 实时性 :每次读取
/proc文件时,内核实时生成数据
1.2 内核统计的核心机制
时钟中断(Timer Interrupt):
Linux 内核通过时钟中断定期更新统计信息。每次时钟中断时(通常每 1-10 毫秒,取决于 HZ 配置):
- 更新当前进程的 CPU 时间
- 更新系统负载
- 更新进程状态
进程切换:
进程切换时,内核会:
- 保存前一个进程的统计信息
- 恢复下一个进程的统计信息
- 更新进程切换计数器
关键数据结构:
kcpustat:每个 CPU 的统计信息task_struct:每个进程的统计信息avenrun:系统负载平均值数组
2. 内核 CPU 统计原理
2.1 CPU 时间统计的更新机制
时钟中断触发统计更新:
每次时钟中断时(scheduler_tick()),内核会调用 account_process_tick() 来更新当前进程的 CPU 时间统计。
c
// kernel/sched/core.c
void scheduler_tick(void)
{
struct rq *rq = cpu_rq(cpu);
struct task_struct *curr = rq->curr;
// 更新运行队列时钟
update_rq_clock(rq);
// 更新当前进程的调度信息
curr->sched_class->task_tick(rq, curr, 0);
// 更新全局负载
calc_global_load_tick(rq);
}
account_process_tick() 的工作原理:
c
// kernel/sched/cputime.c
void account_process_tick(struct task_struct *p, int user_tick)
{
u64 cputime = TICK_NSEC; // 一个时钟周期的时间(纳秒)
u64 steal = steal_account_process_time(ULONG_MAX);
cputime -= steal;
if (user_tick)
account_user_time(p, cputime); // 用户空间时间
else if ((p != this_rq()->idle) || (irq_count() != HARDIRQ_OFFSET))
account_system_time(p, HARDIRQ_OFFSET, cputime); // 内核空间时间
else
account_idle_time(cputime); // 空闲时间
}
关键点:
- TICK_NSEC:一个时钟周期的时间(通常 1-10 毫秒,取决于 HZ 配置)
- user_tick:标志位,表示这个时钟周期是在用户空间还是内核空间
- steal time:虚拟化环境中被其他虚拟机占用的时间
2.2 account_user_time() 的详细实现
account_user_time() 的作用:
将用户空间的 CPU 时间累加到进程和全局统计中。
c
// kernel/sched/cputime.c
void account_user_time(struct task_struct *p, u64 cputime)
{
// 1. 累加到进程的用户空间时间
p->utime += cputime;
account_group_user_time(p, cputime);
// 2. 根据 nice 值决定累加到哪个全局统计
int index = (task_nice(p) > 0) ? CPUTIME_NICE : CPUTIME_USER;
// 3. 累加到全局 CPU 统计
task_group_account_field(p, index, cputime);
// 4. 更新进程会计信息
acct_account_cputime(p);
}
详细步骤说明:
-
更新进程统计:
p->utime += cputime:累加进程的用户空间 CPU 时间account_group_user_time():更新进程组的用户空间时间- 作用 :用于
/proc/[pid]/stat中的 utime 字段
-
更新全局统计:
task_group_account_field():更新全局 CPU 统计- 根据 nice 值 :
- nice > 0:累加到
CPUTIME_NICE - nice <= 0:累加到
CPUTIME_USER
- nice > 0:累加到
- 作用 :用于
/proc/stat中的 user 和 nice 字段
-
数据结构:
c// 每个 CPU 的统计信息 struct kernel_cpustat { u64 cpustat[NR_STATS]; }; // 统计类型 enum cpu_usage_stat { CPUTIME_USER, CPUTIME_NICE, CPUTIME_SYSTEM, CPUTIME_SOFTIRQ, CPUTIME_IRQ, CPUTIME_IDLE, CPUTIME_IOWAIT, CPUTIME_STEAL, CPUTIME_GUEST, CPUTIME_GUEST_NICE, NR_STATS };
2.3 account_system_time() 的详细实现
account_system_time() 的作用:
将内核空间的 CPU 时间累加到进程和全局统计中,并根据中断状态决定累加到哪个统计类型。
c
// kernel/sched/cputime.c
void account_system_time(struct task_struct *p, int hardirq_offset, u64 cputime)
{
int index;
// 检查是否是虚拟机时间
if ((p->flags & PF_VCPU) && (irq_count() - hardirq_offset == 0)) {
account_guest_time(p, cputime);
return;
}
// 根据中断状态决定统计类型
if (hardirq_count() - hardirq_offset)
index = CPUTIME_IRQ; // 硬件中断
else if (in_serving_softirq())
index = CPUTIME_SOFTIRQ; // 软件中断
else
index = CPUTIME_SYSTEM; // 普通内核时间
account_system_index_time(p, cputime, index);
}
详细步骤说明:
-
检查虚拟机时间:
- 如果进程标志包含
PF_VCPU且不在中断中,统计为虚拟机时间
- 如果进程标志包含
-
根据中断状态分类:
- 硬件中断 :
hardirq_count() > 0→CPUTIME_IRQ - 软件中断 :
in_serving_softirq()→CPUTIME_SOFTIRQ - 普通内核时间 :其他情况 →
CPUTIME_SYSTEM
- 硬件中断 :
-
更新统计:
account_system_index_time():累加到进程和全局统计p->stime += cputime:累加进程的内核空间时间
2.4 account_idle_time() 的详细实现
account_idle_time() 的作用:
统计 CPU 空闲时间,并根据 I/O 等待状态决定是空闲还是 I/O 等待。
c
// kernel/sched/cputime.c
void account_idle_time(u64 cputime)
{
u64 *cpustat = kcpustat_this_cpu->cpustat;
struct rq *rq = this_rq();
// 检查是否有进程在等待 I/O
if (atomic_read(&rq->nr_iowait) > 0)
cpustat[CPUTIME_IOWAIT] += cputime; // I/O 等待
else
cpustat[CPUTIME_IDLE] += cputime; // 空闲
}
详细步骤说明:
-
检查 I/O 等待:
rq->nr_iowait:运行队列中等待 I/O 的进程数- 如果有进程等待 I/O,统计为
CPUTIME_IOWAIT - 否则统计为
CPUTIME_IDLE
-
为什么区分 I/O 等待和空闲:
- I/O 等待:CPU 空闲是因为等待 I/O 完成,可能表示 I/O 瓶颈
- 空闲:CPU 空闲是因为没有可运行的进程
2.5 /proc/stat 的生成原理
show_stat() 函数:
/proc/stat 文件由 show_stat() 函数生成,它遍历所有 CPU,汇总统计信息。
c
// fs/proc/stat.c
static int show_stat(struct seq_file *p, void *v)
{
u64 user, nice, system, idle, iowait, irq, softirq, steal;
u64 guest, guest_nice;
// 初始化
user = nice = system = idle = iowait = irq = softirq = steal = 0;
guest = guest_nice = 0;
// 遍历所有 CPU,累加统计
for_each_possible_cpu(i) {
struct kernel_cpustat kcpustat;
u64 *cpustat = kcpustat.cpustat;
// 获取 CPU 统计信息
kcpustat_cpu_fetch(&kcpustat, i);
// 累加各项统计
user += cpustat[CPUTIME_USER];
nice += cpustat[CPUTIME_NICE];
system += cpustat[CPUTIME_SYSTEM];
idle += get_idle_time(&kcpustat, i);
iowait += get_iowait_time(&kcpustat, i);
irq += cpustat[CPUTIME_IRQ];
softirq += cpustat[CPUTIME_SOFTIRQ];
steal += cpustat[CPUTIME_STEAL];
guest += cpustat[CPUTIME_GUEST];
guest_nice += cpustat[CPUTIME_GUEST_NICE];
}
// 输出汇总统计(cpu 行)
seq_put_decimal_ull(p, "cpu ", nsec_to_clock_t(user));
seq_put_decimal_ull(p, " ", nsec_to_clock_t(nice));
// ... 其他字段
// 输出每个 CPU 的统计(cpu0, cpu1, ...)
for_each_online_cpu(i) {
// ... 类似处理
}
return 0;
}
关键点:
- 实时计算 :每次读取
/proc/stat时,内核实时汇总所有 CPU 的统计 - 时间单位转换 :
nsec_to_clock_t()将纳秒转换为时钟周期(jiffies) - NO_HZ 处理 :
get_idle_time()和get_iowait_time()处理 NO_HZ 模式下的时间统计
2.6 工具如何计算 CPU 使用率
top/mpstat 的计算方式:
工具读取两次 /proc/stat,计算差值,然后计算百分比。
c
// 伪代码示例
void calculate_cpu_usage(void)
{
// 第一次读取
read_proc_stat(&stat1);
sleep(1); // 等待 1 秒
// 第二次读取
read_proc_stat(&stat2);
// 计算差值
total = (stat2.user - stat1.user) +
(stat2.nice - stat1.nice) +
(stat2.system - stat1.system) +
(stat2.idle - stat1.idle) +
(stat2.iowait - stat1.iowait) +
(stat2.irq - stat1.irq) +
(stat2.softirq - stat1.softirq) +
(stat2.steal - stat1.steal);
// 计算百分比
user_percent = (stat2.user - stat1.user) * 100.0 / total;
system_percent = (stat2.system - stat1.system) * 100.0 / total;
idle_percent = (stat2.idle - stat1.idle) * 100.0 / total;
// ...
}
关键点:
- 差值计算:CPU 使用率是时间差值的百分比,不是绝对值的百分比
- 时间单位:统计值以时钟周期(jiffies)为单位,需要转换为纳秒或秒
- 多核系统:每个 CPU 核心单独计算,或汇总后计算平均值
3. 系统负载平均值统计原理
3.1 负载平均值的定义
负载平均值是什么:
负载平均值是系统中可运行进程和不可中断进程的平均数量,是一个指数衰减平均值。
公式:
scss
load(t) = load(t-1) * exp(-5/60) + n * (1 - exp(-5/60))
其中:
load(t):当前负载值load(t-1):上一次负载值n:当前活跃进程数(nr_running + nr_uninterruptible)exp(-5/60):衰减因子(5 分钟负载)
三种负载平均值:
- 1 分钟负载 :
exp(-1/60),反映短期负载 - 5 分钟负载 :
exp(-5/60),反映中期负载 - 15 分钟负载 :
exp(-15/60),反映长期负载
3.2 负载平均值的计算原理
calc_global_load() 函数:
c
// kernel/sched/loadavg.c
void calc_global_load(void)
{
unsigned long sample_window;
long active, delta;
sample_window = READ_ONCE(calc_load_update);
if (time_before(jiffies, sample_window + 10))
return;
// 处理 NO_HZ 模式下的增量
delta = calc_load_nohz_read();
if (delta)
atomic_long_add(delta, &calc_load_tasks);
// 获取当前活跃进程数
active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;
// 更新三种负载平均值
avenrun[0] = calc_load(avenrun[0], EXP_1, active); // 1 分钟
avenrun[1] = calc_load(avenrun[1], EXP_5, active); // 5 分钟
avenrun[2] = calc_load(avenrun[2], EXP_15, active); // 15 分钟
WRITE_ONCE(calc_load_update, sample_window + LOAD_FREQ);
}
calc_load() 函数:
c
// kernel/sched/loadavg.c
static unsigned long
calc_load(unsigned long load, unsigned long exp, unsigned long active)
{
unsigned long newload;
newload = load * exp + active * (FIXED_1 - exp);
if (active >= load)
newload += FIXED_1 - exp;
return newload >> FSHIFT;
}
详细步骤说明:
-
收集活跃进程数:
calc_load_tasks:全局原子变量,累加所有 CPU 的活跃进程数- 活跃进程数 :
nr_running + nr_uninterruptible
-
指数衰减计算:
- 公式 :
newload = load * exp + active * (1 - exp) - exp:衰减因子(EXP_1, EXP_5, EXP_15)
- 作用:平滑负载值,避免剧烈波动
- 公式 :
-
更新频率:
- LOAD_FREQ:每 5 秒更新一次(5 * HZ)
- 延迟更新:等待 10 个时钟周期,确保所有 CPU 都更新了
3.3 每个 CPU 的负载贡献
calc_global_load_tick() 函数:
每个 CPU 在时钟中断时更新自己的负载贡献。
c
// kernel/sched/loadavg.c
void calc_global_load_tick(struct rq *this_rq)
{
long delta;
if (time_before(jiffies, this_rq->calc_load_update))
return;
// 计算活跃进程数的增量
delta = calc_load_fold_active(this_rq, 0);
if (delta)
atomic_long_add(delta, &calc_load_tasks);
this_rq->calc_load_update += LOAD_FREQ;
}
calc_load_fold_active() 函数:
c
// kernel/sched/loadavg.c
long calc_load_fold_active(struct rq *this_rq, long adjust)
{
long nr_active, delta = 0;
// 计算当前活跃进程数
nr_active = this_rq->nr_running - adjust;
nr_active += (int)this_rq->nr_uninterruptible;
// 计算与上次的差值
if (nr_active != this_rq->calc_load_active) {
delta = nr_active - this_rq->calc_load_active;
this_rq->calc_load_active = nr_active;
}
return delta;
}
详细步骤说明:
-
计算增量:
nr_active:当前 CPU 的活跃进程数delta:与上次的差值- 作用:只累加变化量,减少同步开销
-
累加到全局:
atomic_long_add():原子操作,累加到全局变量- 作用:所有 CPU 的增量累加后,得到全局活跃进程数
-
更新频率:
- 每个 CPU 每 5 秒更新一次
- 全局负载每 5 秒计算一次
3.4 /proc/loadavg 的生成
get_avenrun() 函数:
c
// kernel/sched/loadavg.c
void get_avenrun(unsigned long *loads, unsigned long offset, int shift)
{
loads[0] = (avenrun[0] + offset) << shift;
loads[1] = (avenrun[1] + offset) << shift;
loads[2] = (avenrun[2] + offset) << shift;
}
/proc/loadavg 的格式:
0.52 0.58 0.61 2/285 12345
- 0.52:1 分钟负载
- 0.58:5 分钟负载
- 0.61:15 分钟负载
- 2/285:当前运行进程数 / 总进程数
- 12345:最近创建的进程 PID
4. 进程统计信息生成原理
4.1 /proc/[pid]/stat 的生成
do_task_stat() 函数:
/proc/[pid]/stat 文件由 do_task_stat() 函数生成,它从 task_struct 中读取各种统计信息。
c
// fs/proc/array.c
static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task, int whole)
{
// 1. 获取进程基本信息
pid_t ppid = task_tgid_nr_ns(task->real_parent, ns);
pid_t tgid = task_tgid_nr_ns(task, ns);
// 2. 获取 CPU 时间
task_cputime_adjusted(task, &utime, &stime);
// 3. 获取内存信息
struct mm_struct *mm = get_task_mm(task);
unsigned long vsize = mm ? mm->total_vm : 0;
unsigned long rss = mm ? get_mm_rss(mm) : 0;
// 4. 输出所有字段
seq_put_decimal_ull(m, "", pid_nr_ns(pid, ns)); // PID
seq_puts(m, " (");
proc_task_name(m, task, false); // 进程名
seq_puts(m, ") ");
seq_putc(m, state); // 状态
seq_put_decimal_ll(m, " ", ppid); // 父进程 PID
// ... 其他字段
seq_put_decimal_ull(m, " ", nsec_to_clock_t(utime)); // 用户时间
seq_put_decimal_ull(m, " ", nsec_to_clock_t(stime)); // 系统时间
// ...
}
关键字段的来源:
- PID、PPID :从
task_struct直接读取 - CPU 时间 :从
task_struct->utime和task_struct->stime读取 - 内存信息 :从
mm_struct读取 - 状态 :从
task_struct->__state读取
4.2 task_cputime_adjusted() 的作用
task_cputime_adjusted() 函数:
这个函数获取进程的 CPU 时间,并进行调整以确保单调性。
c
// kernel/sched/cputime.c
void task_cputime_adjusted(struct task_struct *p, u64 *ut, u64 *st)
{
struct task_cputime cputime = {
.sum_exec_runtime = p->se.sum_exec_runtime,
};
task_cputime(p, &cputime.utime, &cputime.stime);
cputime_adjust(&cputime, &p->prev_cputime, ut, st);
}
cputime_adjust() 的作用:
确保 CPU 时间的单调性,避免因为时钟精度问题导致时间倒退。
c
// kernel/sched/cputime.c
void cputime_adjust(struct task_cputime *curr, struct prev_cputime *prev,
u64 *ut, u64 *st)
{
u64 rtime = curr->sum_exec_runtime;
u64 stime = curr->stime;
u64 utime = curr->utime;
// 确保 stime + utime == rtime
if (stime == 0) {
utime = rtime;
} else if (utime == 0) {
stime = rtime;
} else {
// 按比例分配
stime = mul_u64_u64_div_u64(stime, rtime, stime + utime);
}
// 确保单调性
if (stime < prev->stime)
stime = prev->stime;
utime = rtime - stime;
if (utime < prev->utime) {
utime = prev->utime;
stime = rtime - utime;
}
prev->stime = stime;
prev->utime = utime;
*ut = prev->utime;
*st = prev->stime;
}
4.3 工具如何计算进程 CPU 使用率
top 的计算方式:
c
// 伪代码示例
void calculate_process_cpu_usage(struct process_info *proc)
{
// 读取两次 /proc/[pid]/stat
read_proc_pid_stat(proc->pid, &stat1);
sleep(1); // 等待 1 秒
read_proc_pid_stat(proc->pid, &stat2);
// 计算 CPU 时间差值
utime_delta = stat2.utime - stat1.utime;
stime_delta = stat2.stime - stat1.stime;
total_cputime = utime_delta + stime_delta;
// 计算 CPU 使用率(百分比)
// 注意:多核系统中可能超过 100%
cpu_percent = (total_cputime * 100.0) / (1.0 * HZ); // HZ 是时钟频率
// 计算内存使用率
mem_percent = (proc->rss * 100.0) / total_memory;
}
关键点:
- 差值计算:CPU 使用率是时间差值的百分比
- 多核系统:如果进程使用多个 CPU 核心,CPU 使用率可能超过 100%
- 时间单位:需要将时钟周期转换为秒或百分比
5. top、mpstat、htop 工具详解
5.1 top 工具详解
2.1 top 是什么?
top 的定义:
top 是 Linux 系统中最经典的进程监控工具,用于实时显示系统中运行的进程信息、CPU 使用率、内存使用情况等。
top 的特点:
- 实时更新:默认每 3 秒更新一次
- 交互式:支持键盘命令操作
- 详细信息:显示进程的 CPU、内存、状态等信息
- 轻量级:资源占用小,适合生产环境
top 的安装:
bash
# Debian/Ubuntu
apt-get install procps
# CentOS/RHEL
yum install procps-ng
# 验证安装
top --version
2.2 top 的输出详解
top 的输出分为两部分:
- 系统摘要信息(顶部)
- 进程列表(下方)
系统摘要信息详解:
yaml
top - 10:30:45 up 5 days, 2:15, 3 users, load average: 0.52, 0.58, 0.61
Tasks: 285 total, 1 running, 284 sleeping, 0 stopped, 0 zombie
%Cpu(s): 5.2 us, 2.1 sy, 0.0 ni, 92.5 id, 0.1 wa, 0.0 hi, 0.1 si, 0.0 st
MiB Mem : 8192.0 total, 1024.0 free, 2048.0 used, 5120.0 buff/cache
MiB Swap: 4096.0 total, 4096.0 free, 0.0 used. 5120.0 avail Mem
详细字段说明:
-
第一行:系统时间和负载:
10:30:45:当前系统时间up 5 days, 2:15:系统运行时间(5 天 2 小时 15 分钟)3 users:当前登录用户数load average: 0.52, 0.58, 0.61:系统负载平均值- 0.52:1 分钟平均负载
- 0.58:5 分钟平均负载
- 0.61:15 分钟平均负载
- 含义:负载值表示可运行进程的平均数量
- 单核 CPU:负载 1.0 表示 CPU 100% 使用
- 多核 CPU:负载值应该除以 CPU 核心数
-
第二行:任务统计:
285 total:总进程数1 running:正在运行的进程数284 sleeping:睡眠状态的进程数0 stopped:停止状态的进程数0 zombie:僵尸进程数
-
第三行:CPU 使用率:
5.2 us:用户空间 CPU 使用率(5.2%)2.1 sy:内核空间 CPU 使用率(2.1%)0.0 ni:nice 调整后的用户空间 CPU 使用率92.5 id:空闲 CPU 百分比(92.5%)0.1 wa:等待 I/O 的 CPU 百分比0.0 hi:硬件中断 CPU 百分比0.1 si:软件中断 CPU 百分比0.0 st:虚拟化环境中被其他虚拟机占用的 CPU 百分比
-
第四行:内存使用:
8192.0 total:总内存(8 GB)1024.0 free:空闲内存(1 GB)2048.0 used:已使用内存(2 GB)5120.0 buff/cache:缓冲区和缓存内存(5 GB)
-
第五行:交换分区使用:
4096.0 total:总交换分区(4 GB)4096.0 free:空闲交换分区(4 GB)0.0 used:已使用交换分区(0 GB)5120.0 avail Mem:可用内存(5 GB,包括缓存)
进程列表字段详解:
perl
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 root 20 0 1024000 512000 256000 S 5.2 6.2 10:30.45 test
字段详细说明:
-
PID(Process ID):
- 含义:进程 ID
- 作用:唯一标识一个进程
- 范围:1-4194304(通常)
-
USER:
- 含义:进程所有者
- 作用:显示进程以哪个用户身份运行
-
PR(Priority):
- 含义:进程优先级
- 范围:-20 到 19(实时进程为负数)
- 作用:影响进程调度优先级
-
NI(Nice):
- 含义:nice 值
- 范围:-20 到 19
- 作用:调整进程优先级(nice 值越小,优先级越高)
-
VIRT(Virtual Memory):
- 含义:虚拟内存大小
- 单位:KB、MB、GB
- 作用:进程可以访问的总虚拟地址空间
-
RES(Resident Memory):
- 含义:物理内存使用量(常驻内存)
- 单位:KB、MB、GB
- 作用:进程实际占用的物理内存
-
SHR(Shared Memory):
- 含义:共享内存大小
- 单位:KB、MB、GB
- 作用:与其他进程共享的内存
-
S(Status):
- 含义:进程状态
- 值 :
- R(Running):正在运行或可运行
- S(Sleeping):可中断睡眠
- D(Disk Sleep):不可中断睡眠(通常等待 I/O)
- T(Stopped):已停止
- Z(Zombie):僵尸进程
- I(Idle):空闲(内核线程)
-
%CPU:
- 含义:CPU 使用率
- 计算方式:进程在采样周期内的 CPU 使用时间 / 采样周期
- 范围:0-100%(多核系统可能超过 100%)
-
%MEM:
- 含义:内存使用率
- 计算方式:RES / 总内存 * 100%
- 范围:0-100%
-
TIME+:
- 含义:累计 CPU 时间
- 格式:分:秒.毫秒
- 作用:显示进程总共使用的 CPU 时间
-
COMMAND:
- 含义:进程命令
- 显示:进程的命令行或程序名
2.3 top 的交互命令
常用交互命令:
-
排序命令:
P:按 CPU 使用率排序M:按内存使用率排序T:按运行时间排序N:按 PID 排序
-
显示命令:
f:选择显示字段o:改变字段显示顺序x:高亮显示排序列b:切换粗体显示
-
进程操作:
k:杀死进程(需要输入 PID)r:修改进程优先级(nice 值)d:修改更新延迟
-
其他命令:
h:显示帮助q:退出 top1:切换显示所有 CPU 或汇总u:按用户过滤进程
2.4 top 的配置
配置文件:
top 的配置文件位于 ~/.toprc,可以保存用户的设置。
常用配置:
bash
# 设置更新延迟(秒)
top -d 1 # 每 1 秒更新一次
# 只显示特定用户的进程
top -u username
# 批处理模式(适合脚本)
top -b -n 1 # 运行一次后退出
# 显示特定 PID 的进程
top -p 1234,5678
批处理模式示例:
bash
# 保存 top 输出到文件
top -b -n 1 > top_output.txt
# 在脚本中使用
top -b -n 1 | grep "test"
2.5 top 的数据来源
top 从 /proc 文件系统读取数据:
-
/proc/stat:系统整体统计信息bashcat /proc/stat # 输出: # cpu 12345 0 5678 89012 1234 0 567 0 0 0 # cpu0 1234 0 567 8901 123 0 56 0 0 0 # ... -
/proc/[pid]/stat:进程统计信息bashcat /proc/1234/stat # 输出:PID comm state ppid pgrp session tty_nr tpgid flags ... -
/proc/[pid]/status:进程详细状态bashcat /proc/1234/status # 输出:Name, State, Pid, PPid, ... -
/proc/meminfo:内存信息bashcat /proc/meminfo # 输出:MemTotal, MemFree, MemAvailable, ... -
/proc/loadavg:系统负载bashcat /proc/loadavg # 输出:0.52 0.58 0.61 2/285 12345
数据更新机制:
- top 定期读取
/proc文件系统 - 计算两次读取之间的差值
- 显示统计信息
3. mpstat 工具详解
3.1 mpstat 是什么?
mpstat 的定义:
mpstat(Multi-Processor Statistics)是 sysstat 工具包中的一个工具,用于显示每个 CPU 核心的详细统计信息。
mpstat 的特点:
- 多核支持:显示每个 CPU 核心的统计信息
- 详细统计:提供更详细的 CPU 统计信息
- 命令行工具:适合脚本和自动化
- 历史数据:可以显示历史统计信息
mpstat 的安装:
bash
# Debian/Ubuntu
apt-get install sysstat
# CentOS/RHEL
yum install sysstat
# 验证安装
mpstat --version
3.2 mpstat 的输出详解
基本用法:
bash
# 显示所有 CPU 的统计信息
mpstat
# 每 2 秒更新一次,共 5 次
mpstat 2 5
# 显示每个 CPU 核心的统计信息
mpstat -P ALL
# 显示特定 CPU 核心的统计信息
mpstat -P 0,1
mpstat 输出示例:
perl
Linux 5.15.147 (mr536) 12/15/2025 _aarch64_ (4 CPU)
10:30:45 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
10:30:45 all 5.20 0.00 2.10 0.10 0.00 0.10 0.00 0.00 0.00 92.50
10:30:45 0 6.00 0.00 2.50 0.00 0.00 0.20 0.00 0.00 0.00 91.30
10:30:45 1 4.50 0.00 1.80 0.20 0.00 0.00 0.00 0.00 0.00 93.50
10:30:45 2 5.80 0.00 2.20 0.10 0.00 0.10 0.00 0.00 0.00 91.80
10:30:45 3 4.30 0.00 1.90 0.10 0.00 0.10 0.00 0.00 0.00 93.60
字段详细说明:
-
CPU:
all:所有 CPU 的平均值0, 1, 2, 3:各个 CPU 核心的编号
-
%usr(User):
- 含义:用户空间 CPU 使用率
- 计算方式:用户空间进程的 CPU 时间 / 总时间
- 与 top 的关系 :对应 top 的
%us
-
%nice:
- 含义:nice 调整后的用户空间 CPU 使用率
- 计算方式:nice 值不为 0 的进程的 CPU 时间 / 总时间
- 与 top 的关系 :对应 top 的
%ni
-
%sys(System):
- 含义:内核空间 CPU 使用率
- 计算方式:内核代码的 CPU 时间 / 总时间
- 与 top 的关系 :对应 top 的
%sy
-
%iowait(I/O Wait):
- 含义:等待 I/O 的 CPU 百分比
- 计算方式:CPU 空闲且等待 I/O 完成的时间 / 总时间
- 与 top 的关系 :对应 top 的
%wa - 注意:iowait 高可能表示 I/O 瓶颈
-
%irq(Interrupt):
- 含义:硬件中断 CPU 百分比
- 计算方式:处理硬件中断的时间 / 总时间
- 与 top 的关系 :对应 top 的
%hi
-
%soft(Software Interrupt):
- 含义:软件中断 CPU 百分比
- 计算方式:处理软件中断的时间 / 总时间
- 与 top 的关系 :对应 top 的
%si
-
%steal(Steal Time):
- 含义:虚拟化环境中被其他虚拟机占用的 CPU 百分比
- 计算方式:被 hypervisor 占用的 CPU 时间 / 总时间
- 与 top 的关系 :对应 top 的
%st - 注意:steal 高表示虚拟机资源不足
-
%guest(Guest):
- 含义:运行虚拟机的 CPU 百分比
- 计算方式:运行虚拟机的时间 / 总时间
-
%gnice(Guest Nice):
- 含义:nice 调整后的虚拟机 CPU 百分比
-
%idle(Idle):
- 含义:空闲 CPU 百分比
- 计算方式:CPU 空闲时间 / 总时间
- 与 top 的关系 :对应 top 的
%id
3.3 mpstat 的高级用法
显示中断统计:
bash
# 显示每个 CPU 的中断统计
mpstat -I SUM
# 显示特定中断的统计
mpstat -I SCPU
输出示例:
makefile
Linux 5.15.147 (mr536) 12/15/2025 _aarch64_ (4 CPU)
10:30:45 CPU intr/s
10:30:45 all 1234.56
10:30:45 0 345.67
10:30:45 1 234.56
10:30:45 2 345.67
10:30:45 3 308.66
显示 CPU 频率:
bash
# 显示 CPU 频率信息(如果支持)
mpstat -f
JSON 格式输出:
bash
# 以 JSON 格式输出(某些版本支持)
mpstat -o JSON
3.4 mpstat 的数据来源
mpstat 从 /proc/stat 读取数据:
bash
cat /proc/stat
# 输出:
# cpu 12345 0 5678 89012 1234 0 567 0 0 0
# cpu0 1234 0 567 8901 123 0 56 0 0 0
# cpu1 1234 0 567 8901 123 0 56 0 0 0
# ...
/proc/stat 字段含义:
sql
cpu user nice system idle iowait irq softirq steal guest guest_nice
- user:用户空间 CPU 时间(jiffies)
- nice:nice 调整后的用户空间 CPU 时间
- system:内核空间 CPU 时间
- idle:空闲 CPU 时间
- iowait:等待 I/O 的 CPU 时间
- irq:硬件中断 CPU 时间
- softirq:软件中断 CPU 时间
- steal:被其他虚拟机占用的 CPU 时间
- guest:运行虚拟机的 CPU 时间
- guest_nice:nice 调整后的虚拟机 CPU 时间
计算方式:
mpstat 读取两次 /proc/stat,计算差值,然后除以时间间隔:
ini
%usr = (user2 - user1) / (total2 - total1) * 100%
4. htop 工具详解
4.1 htop 是什么?
htop 的定义:
htop 是 top 的增强版本,提供了更友好的用户界面和更强大的功能。
htop 的特点:
- 彩色显示:使用颜色区分不同类型的进程
- 鼠标支持:支持鼠标操作
- 树状显示:可以显示进程树
- 更直观:界面更友好,信息更清晰
- 实时搜索:支持实时搜索进程
htop 的安装:
bash
# Debian/Ubuntu
apt-get install htop
# CentOS/RHEL
yum install htop
# 验证安装
htop --version
4.2 htop 的界面详解
htop 的界面布局:
ini
1 [||||||||||||||||||| 49.2%] Tasks: 285, 1 thr; 1 running
2 [||||||||||||||||||||||||||||||||||| 78.5%] Load average: 0.52 0.58 0.61
3 [||||||||||||||||||||||||||||||||||| 82.3%] Uptime: 5 days, 02:15:30
4 [||||||||||||||||||||||||||||||||||| 91.2%]
Mem[|||||||||||||||||||||||||||||||||||2.0G/8.0G]
Swp[ 0K/4.0G]
PID USER PRI NI VIRT RES SHR CPU% MEM% TIME+ Command
1234 root 20 0 1000M 512M 256M 5.2 6.2 10:30.45 test
界面元素详解:
-
CPU 使用率条:
- 显示:每个 CPU 核心的使用率
- 颜色 :
- 绿色:用户空间
- 红色:内核空间
- 蓝色:低优先级(nice)
- 黄色:I/O 等待
- 作用:直观显示每个 CPU 的负载
-
内存使用条:
- 显示:内存使用情况
- 颜色 :
- 绿色:已使用内存
- 蓝色:缓冲区和缓存
- 作用:直观显示内存使用情况
-
交换分区条:
- 显示:交换分区使用情况
- 颜色:通常为灰色或黄色
- 作用:显示交换分区的使用情况
-
进程列表:
- 显示:与 top 类似的进程信息
- 优势:支持鼠标操作和实时搜索
4.3 htop 的交互命令
常用交互命令:
-
排序命令:
F6:选择排序字段F5:树状显示t:切换树状显示
-
进程操作:
F9:杀死进程F7:降低进程优先级(增加 nice 值)F8:提高进程优先级(减少 nice 值)k:杀死进程(需要输入 PID)
-
显示选项:
F2:设置界面F3:搜索进程F4:过滤进程F10:退出
-
其他命令:
h:显示帮助q:退出 htopu:按用户过滤H:切换显示线程P:按 CPU 排序M:按内存排序
4.4 htop 的高级功能
树状显示:
htop 可以以树状结构显示进程,显示进程之间的父子关系:
yaml
PID USER PRI NI VIRT RES SHR CPU% MEM% TIME+ Command
1 root 20 0 100M 10M 5M 0.0 0.1 0:05.30 systemd
1234 root 20 0 200M 50M 20M 2.5 0.6 0:10.45 ├─ systemd-logind
1235 root 20 0 150M 30M 15M 1.2 0.4 0:05.20 │ └─ dbus-daemon
1236 root 20 0 300M 100M 50M 5.2 1.2 0:15.30 └─ NetworkManager
实时搜索:
按 F3 可以搜索进程,支持实时过滤。
颜色编码:
htop 使用颜色编码不同类型的进程:
- 绿色:普通进程
- 蓝色:低优先级进程
- 红色:内核线程
- 黄色:I/O 等待
自定义显示:
按 F2 可以自定义界面显示,包括:
- 显示哪些字段
- 颜色方案
- 更新延迟
4.5 htop 的数据来源
htop 的数据来源与 top 相同:
/proc/stat:系统统计信息/proc/[pid]/stat:进程统计信息/proc/[pid]/status:进程详细状态/proc/meminfo:内存信息/proc/loadavg:系统负载
htop 的优势:
- 更友好的界面:彩色显示,更直观
- 鼠标支持:可以用鼠标操作
- 实时搜索:快速查找进程
- 树状显示:显示进程关系
5. 工具对比和选择
5.1 功能对比
| 特性 | top | mpstat | htop |
|---|---|---|---|
| 界面类型 | 文本 | 命令行 | 彩色文本 |
| 鼠标支持 | 否 | 否 | 是 |
| CPU 核心显示 | 汇总/所有 | 每个核心 | 每个核心 |
| 进程管理 | 是 | 否 | 是 |
| 脚本友好 | 是(批处理模式) | 是 | 否 |
| 资源占用 | 低 | 低 | 中等 |
| 学习曲线 | 中等 | 低 | 低 |
5.2 使用场景
top 适合:
- 生产环境:资源占用小,稳定可靠
- 远程终端:不需要图形界面支持
- 脚本自动化:批处理模式适合脚本
- 快速查看:简单直接
mpstat 适合:
- CPU 分析:需要详细的 CPU 统计信息
- 多核系统:需要查看每个核心的负载
- 脚本和自动化:命令行工具,适合脚本
- 性能基准测试:可以记录历史数据
htop 适合:
- 交互式使用:需要频繁操作进程
- 本地终端:有图形界面支持
- 进程管理:需要管理进程(杀死、调整优先级等)
- 学习和教学:界面友好,适合学习
5.3 组合使用
实际使用建议:
- 日常监控:使用 htop(如果支持)或 top
- CPU 分析:使用 mpstat 查看详细的 CPU 统计
- 脚本自动化:使用 top 批处理模式或 mpstat
- 故障排查:组合使用三个工具
示例脚本:
bash
#!/bin/bash
# 监控系统资源
# 使用 top 获取进程信息
echo "=== Top Processes ==="
top -b -n 1 | head -20
# 使用 mpstat 获取 CPU 统计
echo "=== CPU Statistics ==="
mpstat -P ALL 1 1
# 使用 htop 的批处理模式(如果支持)
# htop -d 1 -n 1
6. 工具与内核的关系
6.1 数据来源:/proc 文件系统
/proc 文件系统是什么:
/proc 是一个虚拟文件系统,提供内核和进程信息的接口。它不是真正的文件系统,而是内核数据的接口。
/proc 的关键文件:
-
/proc/stat:- 内容:系统整体统计信息
- 更新:实时更新
- 用途:top、mpstat 读取 CPU 统计
-
/proc/[pid]/stat:- 内容:单个进程的统计信息
- 格式:空格分隔的字段
- 用途:top、htop 读取进程信息
-
/proc/[pid]/status:- 内容:进程的详细状态
- 格式:键值对
- 用途:更易读的进程信息
-
/proc/meminfo:- 内容:内存使用情况
- 格式:键值对
- 用途:top、htop 显示内存信息
-
/proc/loadavg:- 内容:系统负载平均值
- 格式:5 个数字
- 用途:显示系统负载
6.2 内核如何提供这些数据
内核统计信息的更新:
-
时钟中断:
- 每次时钟中断时更新 CPU 统计
- 更新进程的运行时间
- 更新系统负载
-
进程切换:
- 进程切换时更新进程统计
- 更新 CPU 使用时间
- 更新进程状态
-
内存管理:
- 内存分配/释放时更新内存统计
- 更新进程的内存使用
内核代码示例:
c
// kernel/sched/core.c
void scheduler_tick(void)
{
// 更新运行队列时钟
update_rq_clock(rq);
// 更新当前进程的统计信息
curr->sched_class->task_tick(rq, curr, 0);
// 更新全局负载
calc_global_load_tick(rq);
}
/proc/stat 的更新:
c
// kernel/kernel_stat.c
void account_user_time(struct task_struct *p, u64 cputime)
{
// 更新用户空间 CPU 时间
p->utime += cputime;
// 更新 /proc/stat 中的 user 字段
// ...
}
6.3 工具读取数据的方式
top 的读取方式:
c
// top 的简化伪代码
while (running) {
// 读取系统统计
read_file("/proc/stat", &stat_data);
// 读取内存信息
read_file("/proc/meminfo", &mem_data);
// 遍历所有进程
for (each /proc/[pid]) {
read_file("/proc/[pid]/stat", &proc_data);
// 解析和显示
}
// 等待更新间隔
sleep(update_interval);
}
mpstat 的读取方式:
c
// mpstat 的简化伪代码
// 读取 /proc/stat
read_file("/proc/stat", &stat_data);
// 解析每个 CPU 的数据
for (each cpu in stat_data) {
parse_cpu_stat(cpu, &cpu_info);
// 计算百分比
calculate_percentages(cpu_info);
// 显示
}
htop 的读取方式:
htop 的读取方式与 top 类似,但增加了:
- 缓存机制:减少文件读取
- 增量更新:只更新变化的数据
- 异步读取:不阻塞界面
7. 实际应用场景
7.1 系统性能监控
日常监控:
bash
# 使用 htop 实时监控
htop
# 使用 top 监控
top
# 使用 mpstat 监控 CPU
mpstat -P ALL 1
性能分析:
bash
# 识别 CPU 瓶颈
mpstat -P ALL 1 10 # 每 1 秒更新,共 10 次
# 识别高 CPU 使用率的进程
top -b -n 1 | sort -k9 -rn | head -10
# 识别高内存使用率的进程
top -b -n 1 | sort -k10 -rn | head -10
7.2 故障排查
CPU 使用率过高:
bash
# 1. 使用 top 查看哪些进程占用 CPU
top
# 2. 使用 mpstat 查看每个核心的负载
mpstat -P ALL 1
# 3. 如果某个核心负载高,可能是进程绑定
# 检查进程的 CPU 亲和性
taskset -p <PID>
内存使用率过高:
bash
# 1. 使用 top 查看内存使用
top
# 2. 查看详细的内存信息
cat /proc/meminfo
# 3. 查看哪些进程占用内存
top -b -n 1 | sort -k10 -rn | head -10
系统负载过高:
bash
# 1. 查看系统负载
uptime
# 或
cat /proc/loadavg
# 2. 使用 top 查看负载
top # 第一行显示 load average
# 3. 使用 mpstat 查看 CPU 使用情况
mpstat -P ALL 1
7.3 进程管理
查找进程:
bash
# 使用 top 搜索
top
# 按 'u' 键,输入用户名
# 使用 htop 搜索
htop
# 按 F3 键,输入进程名
管理进程:
bash
# 使用 top 杀死进程
top
# 按 'k' 键,输入 PID
# 使用 htop 管理进程
htop
# 按 F9 键,选择进程,选择信号
调整进程优先级:
bash
# 使用 top 调整
top
# 按 'r' 键,输入 PID,输入 nice 值
# 使用 htop 调整
htop
# 选择进程,按 F7(降低)或 F8(提高)
8. 高级技巧和最佳实践
8.1 性能优化
减少工具的资源占用:
-
增加更新间隔:
bashtop -d 5 # 每 5 秒更新一次 -
限制显示的进程数:
bashtop -n 20 # 只显示前 20 个进程 -
使用批处理模式:
bashtop -b -n 1 # 运行一次后退出
8.2 脚本自动化
监控脚本示例:
bash
#!/bin/bash
# 系统监控脚本
LOG_FILE="/var/log/system_monitor.log"
while true; do
echo "=== $(date) ===" >> $LOG_FILE
# CPU 统计
mpstat -P ALL 1 1 >> $LOG_FILE
# 内存使用
free -h >> $LOG_FILE
# Top 进程
top -b -n 1 | head -20 >> $LOG_FILE
sleep 60 # 每 60 秒记录一次
done
告警脚本示例:
bash
#!/bin/bash
# CPU 使用率告警
CPU_THRESHOLD=80
while true; do
CPU_USAGE=$(top -b -n 1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
if (( $(echo "$CPU_USAGE > $CPU_THRESHOLD" | bc -l) )); then
echo "Warning: CPU usage is ${CPU_USAGE}%"
# 发送告警
fi
sleep 10
done
8.3 与进程冻结的关系
监控进程冻结状态:
bash
# 查看进程状态
top
# 查找状态为 'D'(不可中断睡眠)的进程
# 这些进程可能是被冻结的进程
# 使用 htop 查看
htop
# 按 't' 键切换树状显示
# 查看进程状态
监控系统挂起过程:
bash
# 在系统挂起前,记录进程状态
top -b -n 1 > /tmp/before_suspend.txt
# 系统挂起后,恢复后记录
top -b -n 1 > /tmp/after_resume.txt
# 对比差异
diff /tmp/before_suspend.txt /tmp/after_resume.txt
9. 总结
9.1 关键点
-
top:
- 经典的进程监控工具
- 适合生产环境和脚本
- 功能强大但界面简单
-
mpstat:
- 多处理器统计工具
- 提供详细的 CPU 统计信息
- 适合 CPU 分析和脚本
-
htop:
- top 的增强版本
- 界面友好,支持鼠标操作
- 适合交互式使用
9.2 最佳实践
-
根据场景选择工具:
- 生产环境:使用 top
- CPU 分析:使用 mpstat
- 交互式使用:使用 htop
-
组合使用:
- 日常监控:htop 或 top
- 详细分析:mpstat
- 脚本自动化:top 或 mpstat
-
理解数据来源:
- 所有工具都从
/proc文件系统读取数据 - 理解
/proc文件系统的结构有助于理解工具输出
- 所有工具都从
6. free 工具详解
6.1 free 工具是什么?
free 的定义:
free 是 Linux 系统中用于显示系统内存使用情况的命令行工具。它读取 /proc/meminfo 文件,并以人类可读的格式显示内存统计信息。
free 的特点:
- 简单直接:显示内存使用情况的摘要
- 多种单位:支持 KB、MB、GB 等单位
- 实时数据:每次运行都读取最新的内存统计
- 轻量级:资源占用极小
free 的安装:
bash
# Debian/Ubuntu
apt-get install procps
# CentOS/RHEL
yum install procps-ng
# 验证安装
free --version
6.2 free 的输出详解
free 输出示例:
bash
$ free -h
total used free shared buff/cache available
Mem: 8.0G 2.0G 1.0G 256M 5.0G 4.8G
Swap: 4.0G 0B 4.0G
字段详细说明:
-
total:
- 含义:总内存大小
- 来源 :
/proc/meminfo的MemTotal - 计算:系统启动时检测到的物理内存总量
-
used:
- 含义:已使用内存
- 计算 :
total - free - buff/cache - 注意:这个值不包括缓冲区和缓存
-
free:
- 含义:空闲内存
- 来源 :
/proc/meminfo的MemFree - 计算:完全没有被使用的物理内存
-
shared:
- 含义:共享内存
- 来源 :
/proc/meminfo的Shmem - 计算:tmpfs 和共享内存段使用的内存
-
buff/cache:
- 含义:缓冲区和缓存内存
- 计算 :
Buffers + Cached + SReclaimable - 作用:可以释放用于应用程序
-
available:
- 含义:可用内存
- 来源 :
/proc/meminfo的MemAvailable - 计算:估计可用于启动新应用程序的内存(不包括交换)
6.3 /proc/meminfo 的生成原理
meminfo_proc_show() 函数:
/proc/meminfo 文件由 meminfo_proc_show() 函数生成,它汇总各种内存统计信息。
c
// fs/proc/meminfo.c
static int meminfo_proc_show(struct seq_file *m, void *v)
{
struct sysinfo i;
unsigned long committed;
long cached;
long available;
unsigned long pages[NR_LRU_LISTS];
unsigned long sreclaimable, sunreclaim;
// 1. 获取基本内存信息
si_meminfo(&i);
si_swapinfo(&i);
// 2. 计算缓存内存
cached = global_node_page_state(NR_FILE_PAGES) -
total_swapcache_pages() - i.bufferram;
if (cached < 0)
cached = 0;
// 3. 获取 LRU 列表统计
for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++)
pages[lru] = global_node_page_state(NR_LRU_BASE + lru);
// 4. 计算可用内存
available = si_mem_available();
// 5. 获取 slab 统计
sreclaimable = global_node_page_state_pages(NR_SLAB_RECLAIMABLE_B);
sunreclaim = global_node_page_state_pages(NR_SLAB_UNRECLAIMABLE_B);
// 6. 输出各项统计
show_val_kb(m, "MemTotal: ", i.totalram);
show_val_kb(m, "MemFree: ", i.freeram);
show_val_kb(m, "MemAvailable: ", available);
show_val_kb(m, "Buffers: ", i.bufferram);
show_val_kb(m, "Cached: ", cached);
// ... 更多字段
}
关键函数说明:
-
si_meminfo():
- 作用:获取基本内存信息
- 来源 :
global_zone_page_state(NR_FREE_PAGES)等 - 输出 :
totalram、freeram、bufferram等
-
global_node_page_state():
- 作用:获取节点级别的页面统计
- 来源 :
vmstat子系统 - 统计类型 :
NR_FILE_PAGES、NR_ANON_MAPPED等
-
si_mem_available():
- 作用:计算可用内存
- 公式 :
free + reclaimable - reserved - reclaimable:包括缓存、slab 可回收部分等
6.4 内存统计的更新机制
页面状态统计:
Linux 内核使用 vmstat 子系统跟踪各种页面状态。每次页面状态变化时,都会更新相应的计数器。
c
// mm/vmstat.c
void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
long delta)
{
struct per_cpu_pageset __percpu *pcp = zone->pageset;
s8 __percpu *p = pcp->vm_stat_diff + item;
// 更新每 CPU 统计
*p += delta;
// 如果超过阈值,更新全局统计
if (unlikely(*p > pcp->stat_threshold || *p < -pcp->stat_threshold)) {
zone_page_state_add(*p, zone, item);
*p = 0;
}
}
统计类型:
c
// include/linux/mmzone.h
enum zone_stat_item {
NR_FREE_PAGES, // 空闲页面
NR_ZONE_LRU_BASE, // LRU 列表基数
NR_ZONE_INACTIVE_ANON, // 非活跃匿名页面
NR_ZONE_ACTIVE_ANON, // 活跃匿名页面
NR_ZONE_INACTIVE_FILE, // 非活跃文件页面
NR_ZONE_ACTIVE_FILE, // 活跃文件页面
NR_ZONE_UNEVICTABLE, // 不可回收页面
NR_ZONE_WRITE_PENDING, // 等待写入的页面
NR_MLOCK, // 锁定的页面
NR_PAGETABLE, // 页表页面
NR_KERNEL_STACK_KB, // 内核栈
NR_BOUNCE, // 反弹缓冲区
NR_ZONE_WRITE_BACK, // 回写页面
NR_ZONE_CMA_FREE, // CMA 空闲页面
// ...
};
内存分配时的统计更新:
c
// mm/page_alloc.c
static inline void __free_one_page(struct page *page, unsigned long pfn,
struct zone *zone, unsigned int order)
{
// 释放页面
// ...
// 更新统计
__mod_zone_page_state(zone, NR_FREE_PAGES, 1 << order);
}
6.5 free 工具的计算方式
free 工具读取数据:
c
// free 工具的简化伪代码
void read_meminfo(void)
{
FILE *fp = fopen("/proc/meminfo", "r");
char line[256];
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "MemTotal:", 9) == 0) {
sscanf(line, "MemTotal: %lu kB", &mem_total);
} else if (strncmp(line, "MemFree:", 8) == 0) {
sscanf(line, "MemFree: %lu kB", &mem_free);
} else if (strncmp(line, "Buffers:", 8) == 0) {
sscanf(line, "Buffers: %lu kB", &buffers);
} else if (strncmp(line, "Cached:", 7) == 0) {
sscanf(line, "Cached: %lu kB", &cached);
} else if (strncmp(line, "SReclaimable:", 13) == 0) {
sscanf(line, "SReclaimable: %lu kB", &sreclaimable);
} else if (strncmp(line, "MemAvailable:", 13) == 0) {
sscanf(line, "MemAvailable: %lu kB", &mem_available);
}
// ... 更多字段
}
fclose(fp);
// 计算 used
used = total - free - (buffers + cached + sreclaimable);
// 计算 buff/cache
buff_cache = buffers + cached + sreclaimable;
}
关键点:
- 实时读取 :每次运行 free 都重新读取
/proc/meminfo - 计算 used :
used = total - free - buff/cache - 单位转换:内核以 KB 为单位,free 可以转换为 MB、GB 等
7. slabtop 工具详解
7.1 slabtop 工具是什么?
slabtop 的定义:
slabtop 是 Linux 系统中用于实时显示内核 slab 分配器使用情况的工具。它读取 /proc/slabinfo 文件,并以类似 top 的界面显示各个 slab 缓存的统计信息。
slabtop 的特点:
- 实时更新:默认每 3 秒更新一次
- 交互式:支持键盘命令操作
- 详细统计:显示每个 slab 缓存的详细信息
- 排序功能:可以按不同字段排序
slabtop 的安装:
bash
# Debian/Ubuntu
apt-get install procps
# CentOS/RHEL
yum install procps-ng
# 验证安装
slabtop --version
7.2 slabtop 的输出详解
slabtop 输出示例:
erlang
Active / Total Objects (% used) : 123456 / 234567 (52.6%)
Active / Total Slabs (% used) : 1234 / 2345 (52.6%)
Active / Total Caches (% used) : 123 / 234 (52.6%)
Active / Total Size (% used) : 12345678 / 23456789 (52.6%)
Minimum / Average / Maximum Object : 0.01K / 0.05K / 0.10K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
12345 1234 10% 0.05K 123 100 1234K kmalloc-64
23456 2345 20% 0.10K 234 100 2345K kmalloc-128
字段详细说明:
-
OBJS:
- 含义:对象总数
- 来源 :
slabinfo.num_objs - 计算 :
total_slabs * objects_per_slab
-
ACTIVE:
- 含义:活跃对象数
- 来源 :
slabinfo.active_objs - 计算 :
num_objs - free_objs
-
USE:
- 含义:使用率
- 计算 :
active_objs / num_objs * 100%
-
OBJ SIZE:
- 含义:对象大小
- 来源 :
kmem_cache->size
-
SLABS:
- 含义:slab 总数
- 来源 :
slabinfo.num_slabs
-
OBJ/SLAB:
- 含义:每个 slab 的对象数
- 来源 :
slabinfo.objects_per_slab
-
CACHE SIZE:
- 含义:缓存总大小
- 计算 :
num_slabs * (1 << cache_order) * PAGE_SIZE
-
NAME:
- 含义:缓存名称
- 来源 :
kmem_cache->name
7.3 /proc/slabinfo 的生成原理
slab_show() 函数:
/proc/slabinfo 文件由 slab_show() 函数生成,它遍历所有 slab 缓存并显示统计信息。
c
// mm/slab_common.c
static int slab_show(struct seq_file *m, void *p)
{
struct kmem_cache *s = list_entry(p, struct kmem_cache, list);
if (p == slab_caches.next)
print_slabinfo_header(m);
cache_show(s, m);
return 0;
}
static void cache_show(struct kmem_cache *s, struct seq_file *m)
{
struct slabinfo sinfo;
memset(&sinfo, 0, sizeof(sinfo));
get_slabinfo(s, &sinfo);
seq_printf(m, "%-17s %6lu %6lu %6u %4u %4d",
s->name, sinfo.active_objs, sinfo.num_objs, s->size,
sinfo.objects_per_slab, (1 << sinfo.cache_order));
seq_printf(m, " : tunables %4u %4u %4u",
sinfo.limit, sinfo.batchcount, sinfo.shared);
seq_printf(m, " : slabdata %6lu %6lu %6lu",
sinfo.active_slabs, sinfo.num_slabs, sinfo.shared_avail);
slabinfo_show_stats(m, s);
seq_putc(m, '\n');
}
get_slabinfo() 函数:
c
// mm/slab.c
void get_slabinfo(struct kmem_cache *cachep, struct slabinfo *sinfo)
{
unsigned long active_objs, num_objs, active_slabs;
unsigned long total_slabs = 0, free_objs = 0, shared_avail = 0;
unsigned long free_slabs = 0;
int node;
struct kmem_cache_node *n;
// 遍历所有节点,累加统计
for_each_kmem_cache_node(cachep, node, n) {
spin_lock_irq(&n->list_lock);
total_slabs += n->total_slabs;
free_slabs += n->free_slabs;
free_objs += n->free_objects;
if (n->shared)
shared_avail += n->shared->avail;
spin_unlock_irq(&n->list_lock);
}
// 计算总对象数
num_objs = total_slabs * cachep->num;
// 计算活跃 slab 数
active_slabs = total_slabs - free_slabs;
// 计算活跃对象数
active_objs = num_objs - free_objs;
// 填充统计信息
sinfo->active_objs = active_objs;
sinfo->num_objs = num_objs;
sinfo->active_slabs = active_slabs;
sinfo->num_slabs = total_slabs;
sinfo->shared_avail = shared_avail;
sinfo->limit = cachep->limit;
sinfo->batchcount = cachep->batchcount;
sinfo->shared = cachep->shared;
sinfo->objects_per_slab = cachep->num;
sinfo->cache_order = cachep->gfporder;
}
7.4 Slab 分配器的统计机制
Slab 分配器结构:
c
// mm/slab.h
struct kmem_cache {
struct array_cache __percpu *cpu_cache; // 每 CPU 缓存
struct kmem_cache_node *node[MAX_NUMNODES]; // 每节点缓存
unsigned int size; // 对象大小
unsigned int num; // 每个 slab 的对象数
unsigned int gfporder; // slab 的页面数(2^gfporder)
// ...
};
struct kmem_cache_node {
struct list_head slabs_full; // 满 slab 列表
struct list_head slabs_partial; // 部分 slab 列表
struct list_head slabs_free; // 空闲 slab 列表
unsigned long total_slabs; // 总 slab 数
unsigned long free_slabs; // 空闲 slab 数
unsigned long free_objects; // 空闲对象数
// ...
};
统计更新机制:
-
分配对象时:
c// mm/slab.c void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags) { // 从每 CPU 缓存分配 // 如果为空,从节点缓存填充 // ... // 更新统计 STATS_INC_ACTIVE(cachep); STATS_INC_ALLOCED(cachep); } -
释放对象时:
c// mm/slab.c void kmem_cache_free(struct kmem_cache *cachep, void *objp) { // 释放到每 CPU 缓存 // 如果满,释放到节点缓存 // ... // 更新统计 STATS_DEC_ACTIVE(cachep); } -
创建 slab 时:
c// mm/slab.c static int cache_grow(struct kmem_cache *cachep, gfp_t flags, int nodeid) { // 分配页面创建新 slab // ... // 更新统计 n->total_slabs++; STATS_INC_GROWN(cachep); } -
销毁 slab 时:
c// mm/slab.c static void slabs_destroy(struct kmem_cache *cachep, struct list_head *list) { // 释放 slab 的页面 // ... // 更新统计 n->total_slabs--; n->free_slabs--; }
7.5 slabtop 工具的计算方式
slabtop 工具读取数据:
c
// slabtop 工具的简化伪代码
void read_slabinfo(void)
{
FILE *fp = fopen("/proc/slabinfo", "r");
char line[1024];
// 跳过头部
fgets(line, sizeof(line), fp); // 跳过版本信息
fgets(line, sizeof(line), fp); // 跳过空行
// 读取每个缓存的信息
while (fgets(line, sizeof(line), fp)) {
struct slab_cache cache;
// 解析行
sscanf(line, "%s %lu %lu %u %u %u",
cache.name, &cache.active_objs, &cache.num_objs,
&cache.obj_size, &cache.objects_per_slab, &cache.pages_per_slab);
// 计算使用率
cache.usage = (cache.active_objs * 100.0) / cache.num_objs;
// 计算缓存大小
cache.cache_size = cache.num_slabs * cache.pages_per_slab * PAGE_SIZE;
// 添加到列表
add_cache(&cache);
}
fclose(fp);
// 排序和显示
sort_caches();
display_caches();
}
关键点:
- 实时读取 :每次更新都重新读取
/proc/slabinfo - 解析格式:slabinfo 文件有固定的格式
- 计算使用率 :
usage = active_objs / num_objs * 100% - 排序显示:可以按不同字段排序
7.6 Slab 分配器的工作原理
Slab 分配器的目的:
- 减少内存碎片:为相同大小的对象使用专门的缓存
- 提高分配速度:预分配对象,避免频繁的页面分配
- 缓存友好:对象在内存中连续,提高缓存命中率
Slab 的三级结构:
-
每 CPU 缓存(array_cache):
- 作用:快速分配和释放
- 结构:LIFO 队列
- 优势:无锁操作,速度快
-
节点缓存(kmem_cache_node):
- 作用:管理 slab 列表
- 结构:三个列表(full、partial、free)
- 优势:NUMA 感知
-
Slab 页面:
- 作用:实际存储对象
- 结构:一个或多个连续页面
- 优势:对象连续,缓存友好
分配流程:
markdown
1. 尝试从每 CPU 缓存分配
↓ 成功
返回对象
↓ 失败
2. 从节点缓存的 partial 列表获取对象
↓ 成功
填充每 CPU 缓存,返回对象
↓ 失败
3. 从节点缓存的 free 列表获取 slab
↓ 成功
初始化 slab,返回对象
↓ 失败
4. 分配新页面创建 slab
↓
初始化 slab,返回对象
释放流程:
markdown
1. 释放到每 CPU 缓存
↓ 未满
完成
↓ 已满
2. 批量释放到节点缓存
↓
更新统计
8. 总结
8.1 工具对比
| 工具 | 数据来源 | 主要功能 | 更新方式 |
|---|---|---|---|
| top | /proc/stat, /proc/[pid]/stat |
进程监控 | 定期读取 |
| mpstat | /proc/stat |
CPU 统计 | 定期读取 |
| htop | 同 top | 进程监控(增强) | 定期读取 |
| free | /proc/meminfo |
内存统计 | 每次运行读取 |
| slabtop | /proc/slabinfo |
Slab 统计 | 定期读取 |
8.2 内核统计的核心机制
- 时钟中断:定期更新 CPU 和负载统计
- 页面分配/释放:更新内存统计
- Slab 分配/释放:更新 slab 统计
- 进程切换:更新进程统计
8.3 工具的工作原理
- 读取 :从
/proc文件系统读取数据 - 计算:计算差值、百分比等
- 显示:格式化输出