Linux内核中获取单个函数执行耗时的方法

在Linux内核中,获取单个函数的执行耗时可通过多种方法实现,具体选择取决于场景需求(如精度、实时性、是否可修改内核代码等)。以下是常用方法及示例:


1. 使用 printk + 时间函数(简单临时测试)

在函数入口/出口记录时间戳并计算差值(精度:纳秒级)。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <linux/ktime.h>

void my_function(void) {
ktime_t start, end;
s64 delta_us;

start = ktime_get(); // 获取当前时间
// ... 函数逻辑 ...
end = ktime_get();

delta_us = ktime_to_us(ktime_sub(end, start)); // 转换为微秒
printk(KERN_INFO "Execution time: %lld us\n", delta_us);
}

优点:简单直接,无需额外工具。
缺点:需修改代码,printk可能影响性能。


2. 使用 ftrace(动态追踪,无需改代码)

通过内核内置的 ftrace 跟踪函数耗时(精度:纳秒级)。
步骤

  1. 挂载 tracefs
    1
    mount -t tracefs none /sys/kernel/tracing
  2. 设置跟踪函数
    1
    2
    echo function > /sys/kernel/tracing/current_tracer
    echo my_function > /sys/kernel/tracing/set_ftrace_filter
  3. 启用函数图追踪器(获取耗时):
    1
    2
    echo function_graph > /sys/kernel/tracing/current_tracer
    echo my_function > /sys/kernel/tracing/set_graph_function
  4. 开始追踪
    1
    2
    3
    echo 1 > /sys/kernel/tracing/tracing_on
    # 触发函数执行...
    echo 0 > /sys/kernel/tracing/tracing_on
  5. 查看结果
    1
    cat /sys/kernel/tracing/trace
    输出示例
    1
    2
    3
    # CPU  DURATION                  FUNCTION CALLS
    # | | | | | | |
    1) 0.341 us | my_function();
    优点:无需编译内核,动态启用/禁用。
    缺点:需要 root 权限。

3. 使用 perf 工具(性能分析)

通过 perf 统计函数耗时(精度:周期级)。

1
2
3
4
5
6
# 记录函数执行
sudo perf probe -a my_function
sudo perf record -e probe:my_function -g -a sleep 1

# 生成报告
sudo perf report

输出示例

1
2
Overhead  Command  Shared Object  Symbol
99.99% test [kernel] [k] my_function

详细耗时分析

1
sudo perf stat -e cycles:u,instructions:u -b my_function

优点:功能强大,支持采样和调用图。
缺点:需要安装 perf,学习曲线较陡。


4. 使用 kprobes(动态内核插桩)

编写内核模块在函数入口/出口插入钩子(精度:纳秒级)。
示例模块

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
#include <linux/kprobes.h>
#include <linux/ktime.h>

static struct kprobe kp = {
.symbol_name = "my_function",
};

static ktime_t entry_time;

static int handler_pre(struct kprobe *p, struct pt_regs *regs) {
entry_time = ktime_get();
return 0;
}

static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) {
ktime_t exit_time = ktime_get();
s64 delta_us = ktime_to_us(ktime_sub(exit_time, entry_time));
printk("Function %s took %lld us\n", p->symbol_name, delta_us);
}

static int __init kprobe_init(void) {
kp.pre_handler = handler_pre;
kp.post_handler = handler_post;
register_kprobe(&kp);
return 0;
}

static void __exit kprobe_exit(void) {
unregister_kprobe(&kp);
}

module_init(kprobe_init);
module_exit(kprobe_exit);

优点:无需修改内核源码,动态加载模块。
缺点:需编写内核模块,可能影响稳定性。


5. 使用 jiffies(低精度计时)

适用于粗略计时(精度:毫秒级,依赖 HZ 配置)。

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

void my_function(void) {
unsigned long start, end;
start = jiffies;
// ... 函数逻辑 ...
end = jiffies;
printk("Execution time: %lu ms\n", jiffies_to_msecs(end - start));
}

优点:简单,开销小。
缺点:精度低(通常 1ms-10ms)。


选择建议

  • 快速调试printk + ktime_get()
  • 动态追踪ftrace
  • 性能剖析perf
  • 生产环境监控kprobes 模块
  • 历史兼容性jiffies(不推荐新代码)

注意

  • 高精度计时可能受中断、调度影响,建议多次测量取平均。
  • 生产环境避免 printk,改用 trace_printk() 或日志缓冲区。