Linux内核获取CPU占用率的接口

在 Linux 内核中,获取 CPU 占用率通常涉及读取系统统计信息。以下是主要的内核接口和方法:


1. /proc/stat 接口(用户空间)

路径/proc/stat
内容:提供系统启动以来的 CPU 时间累积值(单位:USER_HZ,通常 1/100 秒)。
格式

1
2
cpu   user nice system idle iowait irq softirq steal guest guest_nice
cpu0 ... # 每个 CPU 核心的单独统计

计算占用率
1
2
3
total_time = user + nice + system + idle + iowait + irq + softirq + steal;
idle_time = idle + iowait; // 通常将 iowait 视为空闲
usage = (total_time - idle_time) * 100 / total_time;


2. 内核数据结构(驱动/模块开发)

在内核模块中,可直接访问以下数据结构:

  • 全局统计kernel_cpustat(每 CPU 变量)
    1
    2
    3
    4
    5
    6
    7
    #include <linux/kernel_stat.h>

    struct kernel_cpustat *stat = kcpustat_cpu(cpu); // 获取指定 CPU 的统计
    u64 user = stat->cpustat[CPUTIME_USER];
    u64 system = stat->cpustat[CPUTIME_SYSTEM];
    u64 idle = stat->cpustat[CPUTIME_IDLE];
    // 其他状态:CPUTIME_NICE, CPUTIME_IOWAIT 等
  • 运行队列统计struct rq(调度器相关)
    1
    2
    3
    #include <linux/sched/sysctl.h>
    struct rq *rq = cpu_rq(cpu);
    u64 nr_running = rq->nr_running; // 当前运行队列中的任务数

3. 计算瞬时占用率

需两次采样并计算差值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 第一次采样
get_cpu_stats(&prev_total, &prev_idle);

// 等待一段时间(如 1 秒)
msleep(1000);

// 第二次采样
get_cpu_stats(&curr_total, &curr_idle);

// 计算差值
total_delta = curr_total - prev_total;
idle_delta = curr_idle - prev_idle;

// 计算占用率
usage = (total_delta - idle_delta) * 100 / total_delta;


4. Perf 事件(性能监控)

使用 perf_event_open 系统调用监控 CPU 周期:

1
2
3
4
5
6
7
8
#include <linux/perf_event.h>

struct perf_event_attr attr = {
.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_CPU_CYCLES,
};
int fd = perf_event_open(&attr, -1, cpu, -1, 0);
read(fd, &count, sizeof(count)); // 读取 CPU 周期计数


5. 调度器统计

  • 调度器钩子:注册 scheduler_tick 钩子统计任务执行时间。
  • **/proc/<pid>/stat**:进程级 CPU 时间(utimestime)。

注意事项

  1. 时间单位
    • /proc/stat 使用 USER_HZ(通常 100 Hz)。
    • 内核数据结构使用 NSEC_PER_SEC(纳秒)。
  2. 多核处理
    需为每个 CPU 核心单独统计。
  3. 抢占安全
    内核模块中访问每 CPU 数据时需禁用抢占:
    1
    2
    3
    get_cpu();    // 禁用抢占
    // 访问每 CPU 数据
    put_cpu(); // 启用抢占

示例代码(内核模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <linux/kernel_stat.h>
#include <linux/timer.h>

static struct timer_list stats_timer;
static u64 prev_total, prev_idle;

void calc_cpu_usage(struct timer_list *t) {
u64 total = 0, idle = 0;
for (int cpu = 0; cpu < num_online_cpus(); cpu++) {
struct kernel_cpustat *k = kcpustat_cpu(cpu);
idle += k->cpustat[CPUTIME_IDLE] + k->cpustat[CPUTIME_IOWAIT];
total += k->cpustat[CPUTIME_USER] +
k->cpustat[CPUTIME_NICE] +
k->cpustat[CPUTIME_SYSTEM] +
k->cpustat[CPUTIME_IRQ] +
k->cpustat[CPUTIME_SOFTIRQ] +
idle;
}

if (prev_total) {
u64 total_delta = total - prev_total;
u64 idle_delta = idle - prev_idle;
u64 usage = (total_delta - idle_delta) * 100 / total_delta;
printk("CPU Usage: %llu%%\n", usage);
}

prev_total = total;
prev_idle = idle;
mod_timer(&stats_timer, jiffies + HZ); // 每秒触发
}

static int __init my_module_init(void) {
timer_setup(&stats_timer, calc_cpu_usage, 0);
mod_timer(&stats_timer, jiffies + HZ);
return 0;
}

用户空间工具

  • **top/htop**:实时显示 CPU 占用。
  • **mpstat**:多核详细统计(sysstat 包)。
  • **vmstat**:系统整体性能统计。

通过以上接口,可以在内核或用户空间灵活获取 CPU 占用率数据。

Linux内核获取CPU占用率示例

以下是一个完整的 Linux 内核模块示例,演示如何在内核空间获取并计算 CPU 占用率。该模块每秒计算一次系统整体 CPU 占用率,并将结果输出到内核日志中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/kernel_stat.h>
#include <linux/cpumask.h>
#include <linux/sched.h>
#include <linux/topology.h>

static struct timer_list cpu_usage_timer;
static u64 prev_total, prev_idle;

// 计算 CPU 占用率的函数
void calculate_cpu_usage(struct timer_list *unused)
{
u64 total = 0, idle = 0;
unsigned int cpu;

// 遍历所有在线 CPU
for_each_online_cpu(cpu) {
struct kernel_cpustat *kcs = kcpustat_cpu(cpu);

// 累加各种 CPU 状态时间
idle += kcs->cpustat[CPUTIME_IDLE] +
kcs->cpustat[CPUTIME_IOWAIT];

total += kcs->cpustat[CPUTIME_USER] +
kcs->cpustat[CPUTIME_NICE] +
kcs->cpustat[CPUTIME_SYSTEM] +
kcs->cpustat[CPUTIME_IRQ] +
kcs->cpustat[CPUTIME_SOFTIRQ] +
kcs->cpustat[CPUTIME_STEAL] +
idle;
}

// 计算与前一次的差值
if (prev_total != 0 && prev_idle != 0) {
u64 total_delta = total - prev_total;
u64 idle_delta = idle - prev_idle;

// 计算占用率百分比 (0-100)
unsigned long usage = 0;
if (total_delta > 0 && total_delta > idle_delta) {
usage = (total_delta - idle_delta) * 100;
do_div(usage, total_delta);
}

printk(KERN_INFO "CPU Usage: %lu%%\n", usage);
}

// 保存当前值供下次使用
prev_total = total;
prev_idle = idle;

// 重新设置定时器(1秒后再次触发)
mod_timer(&cpu_usage_timer, jiffies + HZ);
}

// 模块初始化
static int __init cpu_usage_init(void)
{
printk(KERN_INFO "CPU Usage Monitor Module Loaded\n");

// 初始化定时器
timer_setup(&cpu_usage_timer, calculate_cpu_usage, 0);

// 立即启动定时器(0秒延迟)
mod_timer(&cpu_usage_timer, jiffies);

return 0;
}

// 模块退出
static void __exit cpu_usage_exit(void)
{
// 删除定时器
del_timer_sync(&cpu_usage_timer);
printk(KERN_INFO "CPU Usage Monitor Module Unloaded\n");
}

module_init(cpu_usage_init);
module_exit(cpu_usage_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("CPU Usage Monitoring Kernel Module");

关键说明:

  1. 工作原理

    • 使用内核定时器每秒触发一次计算
    • 遍历所有在线 CPU 核心
    • 读取 kernel_cpustat 数据结构中的时间计数器
    • 计算与上一次采样的差值
    • 公式:(总时间 - 空闲时间) / 总时间 * 100
  2. 时间统计项

    • CPUTIME_USER:用户空间程序时间
    • CPUTIME_NICE:低优先级任务时间
    • CPUTIME_SYSTEM:内核空间时间
    • CPUTIME_IDLE:空闲时间
    • CPUTIME_IOWAIT:I/O 等待时间
    • CPUTIME_IRQ:硬件中断时间
    • CPUTIME_SOFTIRQ:软件中断时间
    • CPUTIME_STEAL:虚拟化环境中的被偷取时间
  3. 编译与使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 编译
    make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

    # 加载模块
    sudo insmod cpu_usage.ko

    # 查看输出
    dmesg | tail -f

    # 卸载模块
    sudo rmmod cpu_usage
  4. 输出示例

    1
    2
    3
    [ 1234.567890] CPU Usage: 15%
    [ 1235.567901] CPU Usage: 22%
    [ 1236.567912] CPU Usage: 8%

注意事项:

  1. 时间单位

    • 所有时间值单位为纳秒 (ns)
    • 使用 do_div() 处理 64 位除法
  2. 多核处理

    • for_each_online_cpu() 遍历所有活跃 CPU 核心
    • 最终结果是所有核心的平均占用率
  3. 首次运行

    • 第一次运行时不输出,因为没有前一次数据可比较
    • 从第二次定时器触发开始输出结果
  4. 性能影响

    • 计算本身非常轻量级
    • 每秒触发一次,对系统影响极小
    • 在生产环境中使用时,可考虑增加采样间隔

此示例展示了内核空间获取 CPU 占用率的基本方法。在实际应用中,可能需要添加:

  • 每个核心的独立统计
  • 用户空间接口(如通过 /procsysfs
  • 历史数据记录功能
  • 可配置的采样间隔

对于用户空间程序,更简单的方法是直接解析 /proc/stat 文件,实现类似 top 命令的功能。