Linux top、mpstat、htop 原理详解

1. 概述

1.1 工具与内核的关系

top、mpstat、htop 这三个工具都是通过读取 Linux 内核提供的 /proc 文件系统来获取系统统计信息的。它们本身并不进行统计,而是读取内核已经统计好的数据,然后进行计算和展示。

数据流向

bash 复制代码
内核统计模块(时钟中断、进程切换等)
  ↓
更新内核数据结构(kcpustat、task_struct 等)
  ↓
/proc 文件系统(虚拟文件系统接口)
  ↓
用户空间工具(top、mpstat、htop)
  ↓
读取、计算、显示

关键点

  1. 统计发生在内核:所有统计都在内核中完成
  2. 工具只负责读取和计算:工具读取内核数据,然后计算百分比、差值等
  3. 实时性 :每次读取 /proc 文件时,内核实时生成数据

1.2 内核统计的核心机制

时钟中断(Timer Interrupt)

Linux 内核通过时钟中断定期更新统计信息。每次时钟中断时(通常每 1-10 毫秒,取决于 HZ 配置):

  1. 更新当前进程的 CPU 时间
  2. 更新系统负载
  3. 更新进程状态

进程切换

进程切换时,内核会:

  1. 保存前一个进程的统计信息
  2. 恢复下一个进程的统计信息
  3. 更新进程切换计数器

关键数据结构

  1. kcpustat:每个 CPU 的统计信息
  2. task_struct:每个进程的统计信息
  3. 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);  // 空闲时间
}

关键点

  1. TICK_NSEC:一个时钟周期的时间(通常 1-10 毫秒,取决于 HZ 配置)
  2. user_tick:标志位,表示这个时钟周期是在用户空间还是内核空间
  3. 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);
}

详细步骤说明

  1. 更新进程统计

    • p->utime += cputime:累加进程的用户空间 CPU 时间
    • account_group_user_time():更新进程组的用户空间时间
    • 作用 :用于 /proc/[pid]/stat 中的 utime 字段
  2. 更新全局统计

    • task_group_account_field():更新全局 CPU 统计
    • 根据 nice 值
      • nice > 0:累加到 CPUTIME_NICE
      • nice <= 0:累加到 CPUTIME_USER
    • 作用 :用于 /proc/stat 中的 user 和 nice 字段
  3. 数据结构

    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);
}

详细步骤说明

  1. 检查虚拟机时间

    • 如果进程标志包含 PF_VCPU 且不在中断中,统计为虚拟机时间
  2. 根据中断状态分类

    • 硬件中断hardirq_count() > 0CPUTIME_IRQ
    • 软件中断in_serving_softirq()CPUTIME_SOFTIRQ
    • 普通内核时间 :其他情况 → CPUTIME_SYSTEM
  3. 更新统计

    • 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;  // 空闲
}

详细步骤说明

  1. 检查 I/O 等待

    • rq->nr_iowait:运行队列中等待 I/O 的进程数
    • 如果有进程等待 I/O,统计为 CPUTIME_IOWAIT
    • 否则统计为 CPUTIME_IDLE
  2. 为什么区分 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;
}

关键点

  1. 实时计算 :每次读取 /proc/stat 时,内核实时汇总所有 CPU 的统计
  2. 时间单位转换nsec_to_clock_t() 将纳秒转换为时钟周期(jiffies)
  3. 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;
    // ...
}

关键点

  1. 差值计算:CPU 使用率是时间差值的百分比,不是绝对值的百分比
  2. 时间单位:统计值以时钟周期(jiffies)为单位,需要转换为纳秒或秒
  3. 多核系统:每个 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. 1 分钟负载exp(-1/60),反映短期负载
  2. 5 分钟负载exp(-5/60),反映中期负载
  3. 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;
}

详细步骤说明

  1. 收集活跃进程数

    • calc_load_tasks:全局原子变量,累加所有 CPU 的活跃进程数
    • 活跃进程数nr_running + nr_uninterruptible
  2. 指数衰减计算

    • 公式newload = load * exp + active * (1 - exp)
    • exp:衰减因子(EXP_1, EXP_5, EXP_15)
    • 作用:平滑负载值,避免剧烈波动
  3. 更新频率

    • 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;
}

详细步骤说明

  1. 计算增量

    • nr_active:当前 CPU 的活跃进程数
    • delta:与上次的差值
    • 作用:只累加变化量,减少同步开销
  2. 累加到全局

    • atomic_long_add():原子操作,累加到全局变量
    • 作用:所有 CPU 的增量累加后,得到全局活跃进程数
  3. 更新频率

    • 每个 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));  // 系统时间
    // ...
}

关键字段的来源

  1. PID、PPID :从 task_struct 直接读取
  2. CPU 时间 :从 task_struct->utimetask_struct->stime 读取
  3. 内存信息 :从 mm_struct 读取
  4. 状态 :从 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;
}

关键点

  1. 差值计算:CPU 使用率是时间差值的百分比
  2. 多核系统:如果进程使用多个 CPU 核心,CPU 使用率可能超过 100%
  3. 时间单位:需要将时钟周期转换为秒或百分比

5. top、mpstat、htop 工具详解

5.1 top 工具详解

2.1 top 是什么?

top 的定义

top 是 Linux 系统中最经典的进程监控工具,用于实时显示系统中运行的进程信息、CPU 使用率、内存使用情况等。

top 的特点

  1. 实时更新:默认每 3 秒更新一次
  2. 交互式:支持键盘命令操作
  3. 详细信息:显示进程的 CPU、内存、状态等信息
  4. 轻量级:资源占用小,适合生产环境

top 的安装

bash 复制代码
# Debian/Ubuntu
apt-get install procps

# CentOS/RHEL
yum install procps-ng

# 验证安装
top --version

2.2 top 的输出详解

top 的输出分为两部分

  1. 系统摘要信息(顶部)
  2. 进程列表(下方)

系统摘要信息详解

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

详细字段说明

  1. 第一行:系统时间和负载

    • 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 核心数
  2. 第二行:任务统计

    • 285 total:总进程数
    • 1 running:正在运行的进程数
    • 284 sleeping:睡眠状态的进程数
    • 0 stopped:停止状态的进程数
    • 0 zombie:僵尸进程数
  3. 第三行: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 百分比
  4. 第四行:内存使用

    • 8192.0 total:总内存(8 GB)
    • 1024.0 free:空闲内存(1 GB)
    • 2048.0 used:已使用内存(2 GB)
    • 5120.0 buff/cache:缓冲区和缓存内存(5 GB)
  5. 第五行:交换分区使用

    • 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

字段详细说明

  1. PID(Process ID)

    • 含义:进程 ID
    • 作用:唯一标识一个进程
    • 范围:1-4194304(通常)
  2. USER

    • 含义:进程所有者
    • 作用:显示进程以哪个用户身份运行
  3. PR(Priority)

    • 含义:进程优先级
    • 范围:-20 到 19(实时进程为负数)
    • 作用:影响进程调度优先级
  4. NI(Nice)

    • 含义:nice 值
    • 范围:-20 到 19
    • 作用:调整进程优先级(nice 值越小,优先级越高)
  5. VIRT(Virtual Memory)

    • 含义:虚拟内存大小
    • 单位:KB、MB、GB
    • 作用:进程可以访问的总虚拟地址空间
  6. RES(Resident Memory)

    • 含义:物理内存使用量(常驻内存)
    • 单位:KB、MB、GB
    • 作用:进程实际占用的物理内存
  7. SHR(Shared Memory)

    • 含义:共享内存大小
    • 单位:KB、MB、GB
    • 作用:与其他进程共享的内存
  8. S(Status)

    • 含义:进程状态
      • R(Running):正在运行或可运行
      • S(Sleeping):可中断睡眠
      • D(Disk Sleep):不可中断睡眠(通常等待 I/O)
      • T(Stopped):已停止
      • Z(Zombie):僵尸进程
      • I(Idle):空闲(内核线程)
  9. %CPU

    • 含义:CPU 使用率
    • 计算方式:进程在采样周期内的 CPU 使用时间 / 采样周期
    • 范围:0-100%(多核系统可能超过 100%)
  10. %MEM

    • 含义:内存使用率
    • 计算方式:RES / 总内存 * 100%
    • 范围:0-100%
  11. TIME+

    • 含义:累计 CPU 时间
    • 格式:分:秒.毫秒
    • 作用:显示进程总共使用的 CPU 时间
  12. COMMAND

    • 含义:进程命令
    • 显示:进程的命令行或程序名

2.3 top 的交互命令

常用交互命令

  1. 排序命令

    • P:按 CPU 使用率排序
    • M:按内存使用率排序
    • T:按运行时间排序
    • N:按 PID 排序
  2. 显示命令

    • f:选择显示字段
    • o:改变字段显示顺序
    • x:高亮显示排序列
    • b:切换粗体显示
  3. 进程操作

    • k:杀死进程(需要输入 PID)
    • r:修改进程优先级(nice 值)
    • d:修改更新延迟
  4. 其他命令

    • h:显示帮助
    • q:退出 top
    • 1:切换显示所有 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 文件系统读取数据

  1. /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
    # ...
  2. /proc/[pid]/stat:进程统计信息

    bash 复制代码
    cat /proc/1234/stat
    # 输出:PID comm state ppid pgrp session tty_nr tpgid flags ...
  3. /proc/[pid]/status:进程详细状态

    bash 复制代码
    cat /proc/1234/status
    # 输出:Name, State, Pid, PPid, ...
  4. /proc/meminfo:内存信息

    bash 复制代码
    cat /proc/meminfo
    # 输出:MemTotal, MemFree, MemAvailable, ...
  5. /proc/loadavg:系统负载

    bash 复制代码
    cat /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 的特点

  1. 多核支持:显示每个 CPU 核心的统计信息
  2. 详细统计:提供更详细的 CPU 统计信息
  3. 命令行工具:适合脚本和自动化
  4. 历史数据:可以显示历史统计信息

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

字段详细说明

  1. CPU

    • all:所有 CPU 的平均值
    • 0, 1, 2, 3:各个 CPU 核心的编号
  2. %usr(User)

    • 含义:用户空间 CPU 使用率
    • 计算方式:用户空间进程的 CPU 时间 / 总时间
    • 与 top 的关系 :对应 top 的 %us
  3. %nice

    • 含义:nice 调整后的用户空间 CPU 使用率
    • 计算方式:nice 值不为 0 的进程的 CPU 时间 / 总时间
    • 与 top 的关系 :对应 top 的 %ni
  4. %sys(System)

    • 含义:内核空间 CPU 使用率
    • 计算方式:内核代码的 CPU 时间 / 总时间
    • 与 top 的关系 :对应 top 的 %sy
  5. %iowait(I/O Wait)

    • 含义:等待 I/O 的 CPU 百分比
    • 计算方式:CPU 空闲且等待 I/O 完成的时间 / 总时间
    • 与 top 的关系 :对应 top 的 %wa
    • 注意:iowait 高可能表示 I/O 瓶颈
  6. %irq(Interrupt)

    • 含义:硬件中断 CPU 百分比
    • 计算方式:处理硬件中断的时间 / 总时间
    • 与 top 的关系 :对应 top 的 %hi
  7. %soft(Software Interrupt)

    • 含义:软件中断 CPU 百分比
    • 计算方式:处理软件中断的时间 / 总时间
    • 与 top 的关系 :对应 top 的 %si
  8. %steal(Steal Time)

    • 含义:虚拟化环境中被其他虚拟机占用的 CPU 百分比
    • 计算方式:被 hypervisor 占用的 CPU 时间 / 总时间
    • 与 top 的关系 :对应 top 的 %st
    • 注意:steal 高表示虚拟机资源不足
  9. %guest(Guest)

    • 含义:运行虚拟机的 CPU 百分比
    • 计算方式:运行虚拟机的时间 / 总时间
  10. %gnice(Guest Nice)

    • 含义:nice 调整后的虚拟机 CPU 百分比
  11. %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 的特点

  1. 彩色显示:使用颜色区分不同类型的进程
  2. 鼠标支持:支持鼠标操作
  3. 树状显示:可以显示进程树
  4. 更直观:界面更友好,信息更清晰
  5. 实时搜索:支持实时搜索进程

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

界面元素详解

  1. CPU 使用率条

    • 显示:每个 CPU 核心的使用率
    • 颜色
      • 绿色:用户空间
      • 红色:内核空间
      • 蓝色:低优先级(nice)
      • 黄色:I/O 等待
    • 作用:直观显示每个 CPU 的负载
  2. 内存使用条

    • 显示:内存使用情况
    • 颜色
      • 绿色:已使用内存
      • 蓝色:缓冲区和缓存
    • 作用:直观显示内存使用情况
  3. 交换分区条

    • 显示:交换分区使用情况
    • 颜色:通常为灰色或黄色
    • 作用:显示交换分区的使用情况
  4. 进程列表

    • 显示:与 top 类似的进程信息
    • 优势:支持鼠标操作和实时搜索

4.3 htop 的交互命令

常用交互命令

  1. 排序命令

    • F6:选择排序字段
    • F5:树状显示
    • t:切换树状显示
  2. 进程操作

    • F9:杀死进程
    • F7:降低进程优先级(增加 nice 值)
    • F8:提高进程优先级(减少 nice 值)
    • k:杀死进程(需要输入 PID)
  3. 显示选项

    • F2:设置界面
    • F3:搜索进程
    • F4:过滤进程
    • F10:退出
  4. 其他命令

    • h:显示帮助
    • q:退出 htop
    • u:按用户过滤
    • 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 的优势

  1. 更友好的界面:彩色显示,更直观
  2. 鼠标支持:可以用鼠标操作
  3. 实时搜索:快速查找进程
  4. 树状显示:显示进程关系

5. 工具对比和选择

5.1 功能对比

特性 top mpstat htop
界面类型 文本 命令行 彩色文本
鼠标支持
CPU 核心显示 汇总/所有 每个核心 每个核心
进程管理
脚本友好 是(批处理模式)
资源占用 中等
学习曲线 中等

5.2 使用场景

top 适合

  1. 生产环境:资源占用小,稳定可靠
  2. 远程终端:不需要图形界面支持
  3. 脚本自动化:批处理模式适合脚本
  4. 快速查看:简单直接

mpstat 适合

  1. CPU 分析:需要详细的 CPU 统计信息
  2. 多核系统:需要查看每个核心的负载
  3. 脚本和自动化:命令行工具,适合脚本
  4. 性能基准测试:可以记录历史数据

htop 适合

  1. 交互式使用:需要频繁操作进程
  2. 本地终端:有图形界面支持
  3. 进程管理:需要管理进程(杀死、调整优先级等)
  4. 学习和教学:界面友好,适合学习

5.3 组合使用

实际使用建议

  1. 日常监控:使用 htop(如果支持)或 top
  2. CPU 分析:使用 mpstat 查看详细的 CPU 统计
  3. 脚本自动化:使用 top 批处理模式或 mpstat
  4. 故障排查:组合使用三个工具

示例脚本

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 的关键文件

  1. /proc/stat

    • 内容:系统整体统计信息
    • 更新:实时更新
    • 用途:top、mpstat 读取 CPU 统计
  2. /proc/[pid]/stat

    • 内容:单个进程的统计信息
    • 格式:空格分隔的字段
    • 用途:top、htop 读取进程信息
  3. /proc/[pid]/status

    • 内容:进程的详细状态
    • 格式:键值对
    • 用途:更易读的进程信息
  4. /proc/meminfo

    • 内容:内存使用情况
    • 格式:键值对
    • 用途:top、htop 显示内存信息
  5. /proc/loadavg

    • 内容:系统负载平均值
    • 格式:5 个数字
    • 用途:显示系统负载

6.2 内核如何提供这些数据

内核统计信息的更新

  1. 时钟中断

    • 每次时钟中断时更新 CPU 统计
    • 更新进程的运行时间
    • 更新系统负载
  2. 进程切换

    • 进程切换时更新进程统计
    • 更新 CPU 使用时间
    • 更新进程状态
  3. 内存管理

    • 内存分配/释放时更新内存统计
    • 更新进程的内存使用

内核代码示例

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 性能优化

减少工具的资源占用

  1. 增加更新间隔

    bash 复制代码
    top -d 5  # 每 5 秒更新一次
  2. 限制显示的进程数

    bash 复制代码
    top -n 20  # 只显示前 20 个进程
  3. 使用批处理模式

    bash 复制代码
    top -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 关键点

  1. top

    • 经典的进程监控工具
    • 适合生产环境和脚本
    • 功能强大但界面简单
  2. mpstat

    • 多处理器统计工具
    • 提供详细的 CPU 统计信息
    • 适合 CPU 分析和脚本
  3. htop

    • top 的增强版本
    • 界面友好,支持鼠标操作
    • 适合交互式使用

9.2 最佳实践

  1. 根据场景选择工具

    • 生产环境:使用 top
    • CPU 分析:使用 mpstat
    • 交互式使用:使用 htop
  2. 组合使用

    • 日常监控:htop 或 top
    • 详细分析:mpstat
    • 脚本自动化:top 或 mpstat
  3. 理解数据来源

    • 所有工具都从 /proc 文件系统读取数据
    • 理解 /proc 文件系统的结构有助于理解工具输出

6. free 工具详解

6.1 free 工具是什么?

free 的定义

free 是 Linux 系统中用于显示系统内存使用情况的命令行工具。它读取 /proc/meminfo 文件,并以人类可读的格式显示内存统计信息。

free 的特点

  1. 简单直接:显示内存使用情况的摘要
  2. 多种单位:支持 KB、MB、GB 等单位
  3. 实时数据:每次运行都读取最新的内存统计
  4. 轻量级:资源占用极小

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

字段详细说明

  1. total

    • 含义:总内存大小
    • 来源/proc/meminfoMemTotal
    • 计算:系统启动时检测到的物理内存总量
  2. used

    • 含义:已使用内存
    • 计算total - free - buff/cache
    • 注意:这个值不包括缓冲区和缓存
  3. free

    • 含义:空闲内存
    • 来源/proc/meminfoMemFree
    • 计算:完全没有被使用的物理内存
  4. shared

    • 含义:共享内存
    • 来源/proc/meminfoShmem
    • 计算:tmpfs 和共享内存段使用的内存
  5. buff/cache

    • 含义:缓冲区和缓存内存
    • 计算Buffers + Cached + SReclaimable
    • 作用:可以释放用于应用程序
  6. available

    • 含义:可用内存
    • 来源/proc/meminfoMemAvailable
    • 计算:估计可用于启动新应用程序的内存(不包括交换)

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);
    // ... 更多字段
}

关键函数说明

  1. si_meminfo()

    • 作用:获取基本内存信息
    • 来源global_zone_page_state(NR_FREE_PAGES)
    • 输出totalramfreerambufferram
  2. global_node_page_state()

    • 作用:获取节点级别的页面统计
    • 来源vmstat 子系统
    • 统计类型NR_FILE_PAGESNR_ANON_MAPPED
  3. 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;
}

关键点

  1. 实时读取 :每次运行 free 都重新读取 /proc/meminfo
  2. 计算 usedused = total - free - buff/cache
  3. 单位转换:内核以 KB 为单位,free 可以转换为 MB、GB 等

7. slabtop 工具详解

7.1 slabtop 工具是什么?

slabtop 的定义

slabtop 是 Linux 系统中用于实时显示内核 slab 分配器使用情况的工具。它读取 /proc/slabinfo 文件,并以类似 top 的界面显示各个 slab 缓存的统计信息。

slabtop 的特点

  1. 实时更新:默认每 3 秒更新一次
  2. 交互式:支持键盘命令操作
  3. 详细统计:显示每个 slab 缓存的详细信息
  4. 排序功能:可以按不同字段排序

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

字段详细说明

  1. OBJS

    • 含义:对象总数
    • 来源slabinfo.num_objs
    • 计算total_slabs * objects_per_slab
  2. ACTIVE

    • 含义:活跃对象数
    • 来源slabinfo.active_objs
    • 计算num_objs - free_objs
  3. USE

    • 含义:使用率
    • 计算active_objs / num_objs * 100%
  4. OBJ SIZE

    • 含义:对象大小
    • 来源kmem_cache->size
  5. SLABS

    • 含义:slab 总数
    • 来源slabinfo.num_slabs
  6. OBJ/SLAB

    • 含义:每个 slab 的对象数
    • 来源slabinfo.objects_per_slab
  7. CACHE SIZE

    • 含义:缓存总大小
    • 计算num_slabs * (1 << cache_order) * PAGE_SIZE
  8. 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;  // 空闲对象数
    // ...
};

统计更新机制

  1. 分配对象时

    c 复制代码
    // mm/slab.c
    void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
    {
        // 从每 CPU 缓存分配
        // 如果为空,从节点缓存填充
        // ...
        
        // 更新统计
        STATS_INC_ACTIVE(cachep);
        STATS_INC_ALLOCED(cachep);
    }
  2. 释放对象时

    c 复制代码
    // mm/slab.c
    void kmem_cache_free(struct kmem_cache *cachep, void *objp)
    {
        // 释放到每 CPU 缓存
        // 如果满,释放到节点缓存
        // ...
        
        // 更新统计
        STATS_DEC_ACTIVE(cachep);
    }
  3. 创建 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);
    }
  4. 销毁 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();
}

关键点

  1. 实时读取 :每次更新都重新读取 /proc/slabinfo
  2. 解析格式:slabinfo 文件有固定的格式
  3. 计算使用率usage = active_objs / num_objs * 100%
  4. 排序显示:可以按不同字段排序

7.6 Slab 分配器的工作原理

Slab 分配器的目的

  1. 减少内存碎片:为相同大小的对象使用专门的缓存
  2. 提高分配速度:预分配对象,避免频繁的页面分配
  3. 缓存友好:对象在内存中连续,提高缓存命中率

Slab 的三级结构

  1. 每 CPU 缓存(array_cache)

    • 作用:快速分配和释放
    • 结构:LIFO 队列
    • 优势:无锁操作,速度快
  2. 节点缓存(kmem_cache_node)

    • 作用:管理 slab 列表
    • 结构:三个列表(full、partial、free)
    • 优势:NUMA 感知
  3. 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 内核统计的核心机制

  1. 时钟中断:定期更新 CPU 和负载统计
  2. 页面分配/释放:更新内存统计
  3. Slab 分配/释放:更新 slab 统计
  4. 进程切换:更新进程统计

8.3 工具的工作原理

  1. 读取 :从 /proc 文件系统读取数据
  2. 计算:计算差值、百分比等
  3. 显示:格式化输出

相关推荐
俊俊谢7 小时前
华大HC32F460配置JTAG调试引脚为普通GPIO(PB03、PA15等)
嵌入式硬件·嵌入式·arm·嵌入式软件·hc32f460
Shawn_CH1 天前
epoll_wait 及相关函数原理详解
嵌入式
Shawn_CH1 天前
Linux 进程冻结机制原理详解
嵌入式
黑客思维者2 天前
XGW-9000系列高端新能源电站边缘网关硬件架构设计
网络·架构·硬件架构·嵌入式·新能源·计算机硬件·电站
神圣的大喵3 天前
平台无关的嵌入式通用按键管理器
c语言·单片机·嵌入式硬件·嵌入式·按键库
网易独家音乐人Mike Zhou3 天前
【嵌入式模块芯片开发】LP87524电源PMIC芯片配置流程,给雷达供电的延时上电时序及API函数
c语言·stm32·单片机·51单片机·嵌入式·电源·毫米波雷达
Nerd Nirvana3 天前
WSL——Windows Subsystem for Linux流程一览
linux·运维·服务器·windows·嵌入式·wsl·wsl2
rechol3 天前
mcu启动流程
stm32·单片机·mcu·嵌入式
MounRiver_Studio4 天前
RISC-V IDE MRS2使用笔记(七):书签与笔记功能
ide·嵌入式·risc-v