Linux内核之tracepoint介绍

概述

Tracepoint 是一种在 Linux 内核中插入的静态探测点,用于跟踪和调试内核行为。它允许开发者在特定位置插入代码,收集运行时信息,而不会显著影响性能。

原理

特性

  • 静态探测点: 在内核预定义, 自定义的函数可以插入在这里
  • 动态启用: 默认不启用, 减少性能开销
  • 低开销

定义

有以下几种宏定义的方式:

  • TRACE_EVENT
  • DECLARE_TRACE
  • DEFINE_TRACE

以sched_switch为例, 这个是用TRACE_EVENT 宏定义的, 他会生成一个trace_xxx的函数

/*
* Tracepoint for task switches, performed by the scheduler:
*/
TRACE_EVENT(sched_switch,

TP_PROTO(bool preempt,
struct task_struct *prev,
struct task_struct *next,
unsigned int prev_state),

TP_ARGS(preempt, prev, next, prev_state),

TP_STRUCT__entry(
__array( char, prev_comm, TASK_COMM_LEN )
__field( pid_t, prev_pid )
__field( int, prev_prio )
__field( long, prev_state )
__array( char, next_comm, TASK_COMM_LEN )
__field( pid_t, next_pid )
__field( int, next_prio )
),

TP_fast_assign(
memcpy(__entry->prev_comm, prev->comm, TASK_COMM_LEN);
__entry->prev_pid = prev->pid;
__entry->prev_prio = prev->prio;
__entry->prev_state = __trace_sched_switch_state(preempt, prev_state, prev);
memcpy(__entry->next_comm, next->comm, TASK_COMM_LEN);
__entry->next_pid = next->pid;
__entry->next_prio = next->prio;
/* XXX SCHED_DEADLINE */
),

TP_printk("prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s%s ==> next_comm=%s next_pid=%d next_prio=%d",
__entry->prev_comm, __entry->prev_pid, __entry->prev_prio,

(__entry->prev_state & (TASK_REPORT_MAX - 1)) ?
__print_flags(__entry->prev_state & (TASK_REPORT_MAX - 1), "|",
{ TASK_INTERRUPTIBLE, "S" },
{ TASK_UNINTERRUPTIBLE, "D" },
{ __TASK_STOPPED, "T" },
{ __TASK_TRACED, "t" },
{ EXIT_DEAD, "X" },
{ EXIT_ZOMBIE, "Z" },
{ TASK_PARKED, "P" },
{ TASK_DEAD, "I" }) :
"R",

__entry->prev_state & TASK_REPORT_MAX ? "+" : "",
__entry->next_comm, __entry->next_pid, __entry->next_prio)
);

使用

static void __sched notrace __schedule(unsigned int sched_mode) {
...
trace_sched_switch(sched_mode & SM_MASK_PREEMPT, prev, next, prev_state);
...
}

分类

完整的探针可以用如下命令查看

sudo bpftrace -l "tracepoint:*"

如果要查看带分类的,树形结构,可以通过这种方式查看

sudo tree /sys/kernel/debug/tracing/events -I 'enable|filter|format|id|trigger'

下面将列举部分子系统的tracepoint

1. 块设备层(Block Layer)

用于跟踪块设备 I/O 操作。

Tracepoint 事件名称 描述
block:block_bio_backmerge 当两个 BIO 请求向后合并时触发
block:block_bio_bounce 当 BIO 请求需要 bounce buffer 时触发
block:block_bio_complete 当 BIO 请求完成时触发
block:block_bio_frontmerge 当两个 BIO 请求向前合并时触发
block:block_bio_queue 当 BIO 请求被加入队列时触发
block:block_bio_remap 当 BIO 请求被重映射时触发
block:block_dirty_buffer 当 buffer 被标记为脏时触发
block:block_getrq 当获取一个请求时触发
block:block_plug 当 block 层被 plug(暂停)时触发
block:block_rq_complete 当请求完成时触发
block:block_rq_insert 当请求被插入队列时触发
block:block_rq_issue 当请求被下发到设备时触发
block:block_rq_merge 当请求被合并时触发
block:block_rq_remap 当请求被重映射时触发
block:block_rq_requeue 当请求被重新加入队列时触发
block:block_sleeprq 当请求因资源不足而进入睡眠时触发
block:block_split 当请求被分割时触发
block:block_touch_buffer 当 buffer 被访问时触发
block:block_unplug 当 block 层被 unplug(恢复)时触发

2. 调度器(Scheduler)

  • 用于跟踪进程调度相关事件。
Tracepoint 事件名称 描述
sched:sched_kthread_stop 当内核线程被停止时触发
sched:sched_kthread_stop_ret 当内核线程停止操作返回时触发
sched:sched_migrate_task 当任务从一个 CPU 迁移到另一个 CPU 时触发
sched:sched_move_numa 当任务在 NUMA 节点之间迁移时触发
sched:sched_pi_setprio 当任务的优先级(PI,优先级继承)被设置时触发
sched:sched_process_exec 当进程执行一个新的程序时触发
sched:sched_process_exit 当进程退出时触发
sched:sched_process_fork 当进程 fork 出一个新进程时触发
sched:sched_process_free 当进程资源被释放时触发
sched:sched_process_hang 当进程挂起时触发
sched:sched_process_wait 当进程进入等待状态时触发
sched:sched_stat_blocked 统计任务被阻塞的时间
sched:sched_stat_iowait 统计任务等待 I/O 的时间
sched:sched_stat_runtime 统计任务运行的时间
sched:sched_stat_sleep 统计任务睡眠的时间
sched:sched_stat_wait 统计任务等待的时间
sched:sched_stick_numa 当任务被固定在某个 NUMA 节点时触发
sched:sched_swap_numa 当任务在 NUMA 节点之间交换时触发
sched:sched_switch 当任务切换发生时触发
sched:sched_wait_task 当任务进入等待状态时触发
sched:sched_wake_idle_without_ipi 当唤醒空闲 CPU 且不需要发送 IPI(处理器间中断)时触发
sched:sched_wakeup 当任务被唤醒时触发
sched:sched_wakeup_new 当新创建的任务被唤醒时触发
sched:sched_waking 当任务即将被唤醒时触发

3. 文件系统(File System)

  • 用于跟踪文件系统操作。
  • 示例:
    • ext4:ext4_read_file:ext4 文件系统的读操作。
    • xfs:xfs_file_read:XFS 文件系统的读操作。

4. 网络(Network)

  • 用于跟踪网络数据包的收发和处理。
  • 示例:
    • net:netif_rx:网络设备接收数据包。
    • net:net_dev_queue:网络设备发送数据包。
Tracepoint 事件名称 描述
net:napi_gro_frags_entry 当 NAPI 的 GRO(Generic Receive Offload)处理分片数据包开始时触发
net:napi_gro_frags_exit 当 NAPI 的 GRO 处理分片数据包结束时触发
net:napi_gro_receive_entry 当 NAPI 的 GRO 处理接收数据包开始时触发
net:napi_gro_receive_exit 当 NAPI 的 GRO 处理接收数据包结束时触发
net:net_dev_queue 当网络设备将数据包加入队列时触发
net:net_dev_start_xmit 当网络设备开始发送数据包时触发
net:net_dev_xmit 当网络设备发送数据包时触发
net:net_dev_xmit_timeout 当网络设备发送数据包超时时触发
net:netif_receive_skb 当内核接收一个 skb(socket buffer)时触发
net:netif_receive_skb_entry 当内核开始接收一个 skb 时触发
net:netif_receive_skb_exit 当内核完成接收一个 skb 时触发
net:netif_receive_skb_list_entry 当内核开始接收一个 skb 列表时触发
net:netif_receive_skb_list_exit 当内核完成接收一个 skb 列表时触发
net:netif_rx 当网络接口接收数据包时触发
net:netif_rx_entry 当网络接口开始接收数据包时触发
net:netif_rx_exit 当网络接口完成接收数据包时触发
net:netif_rx_ni_entry 当网络接口在非中断上下文中开始接收数据包时触发
net:netif_rx_ni_exit 当网络接口在非中断上下文中完成接收数据包时触发

5. 系统调用(System Calls)

  • 用于跟踪系统调用的进入和退出。
  • 示例:
    • syscalls:sys_enter_openopen 系统调用的进入。
    • syscalls:sys_exit_openopen 系统调用的退出。

6. 内存管理(Memory Management)

  • 用于跟踪内存分配和释放。
  • 示例:
    • kmem:kmalloc:内核内存分配。
    • kmem:kfree:内核内存释放。
Tracepoint 事件名称 描述
kmem:kfree 当内存被释放(kfree)时触发
kmem:kmalloc 当内存通过 kmalloc 分配时触发
kmem:kmalloc_node 当内存通过 kmalloc_node 在指定 NUMA 节点分配时触发
kmem:kmem_cache_alloc 当内存通过 slab 分配器(kmem_cache_alloc)分配时触发
kmem:kmem_cache_alloc_node 当内存通过 slab 分配器在指定 NUMA 节点(kmem_cache_alloc_node)分配时触发
kmem:kmem_cache_free 当内存通过 slab 分配器释放(kmem_cache_free)时触发
kmem:mm_page_alloc 当物理页被分配时触发
kmem:mm_page_alloc_extfrag 当物理页分配时发生外部碎片化时触发
kmem:mm_page_alloc_zone_locked 当物理页在锁定内存区域(zone)中分配时触发
kmem:mm_page_free 当物理页被释放时触发
kmem:mm_page_free_batched 当物理页批量释放时触发
kmem:mm_page_pcpu_drain 当每 CPU 页缓存(per-CPU page cache)被清空时触发
kmem:rss_stat 当进程的 RSS(Resident Set Size)统计信息更新时触发

7. 中断(Interrupts)

  • 用于跟踪硬件中断。
  • 示例:
    • irq:irq_handler_entry:中断处理函数进入。
    • irq:irq_handler_exit:中断处理函数退出。
Tracepoint 事件名称 描述
irq:irq_handler_entry 当中断处理程序开始执行时触发
irq:irq_handler_exit 当中断处理程序执行结束时触发
irq:softirq_entry 当软中断处理程序开始执行时触发
irq:softirq_exit 当软中断处理程序执行结束时触发
irq:softirq_raise 当软中断被触发(raise)时触发

8. TLB

  • 用于跟踪TLB事件
事件名称 描述
tlb:tlb_flush 当 TLB 被刷新时触发。TLB 刷新通常发生在页表更新、上下文切换或进程地址空间变化时。
tlb_flush_mmu 当 MMU(Memory Management Unit)执行 TLB 刷新时触发。
tlb_flush_range 当 TLB 刷新特定地址范围时触发。
tlb_flush_all 当整个 TLB 被刷新时触发。
tlb_flush_kernel_range 当内核地址空间的 TLB 刷新时触发。
tlb_flush_user_range 当用户地址空间的 TLB 刷新时触发。

9. 系统调用

用于统计各种系统调用

事件名称 描述
sys_enter_xx 进入xx系统调用
sys_exit_xx 退出xx系统调用

使用

通过bpftrace

这个脚本将会追踪TLB刷新的事件,并打印他的进程名和pid

sudo bpftrace -e 'tracepoint:tlb:tlb_flush { printf("TLB flushed by %s (pid: %d)\n", comm, pid); }’

另一个脚本, 将追踪irq处理的统计

sudo bpftrace -e 'tracepoint:irq:irq_handler_entry { @[args->irq, str(args->data_loc_name)] = count(); } END { printf("\nInterrupt count by IRQ number and name:\n"); print(@); }'

某个tracepoint的参数可以通过ftrace提供的接口查看

sudo cat /sys/kernel/debug/tracing/events/irq/irq_handler_entry/format

显示如下:

name: irq_handler_entry
ID: 148
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
field:int common_pid; offset:4; size:4; signed:1;

field:int irq; offset:8; size:4; signed:1;
field:__data_loc char[] name; offset:12; size:4; signed:1;

print fmt: "irq=%d name=%s", REC->irq, __get_str(name)

*注意name的引用, 要加data_loc_指定类型才可以

其他内核探测方案

kprobe

Kprobe 是动态插入的探测点,可以在运行时插入到任意内核函数中。灵活性更高,但是性能相对也会更差一些,具体的对比如下

特性 Tracepoint Kprobe
性能开销 较高
稳定性 高(接口稳定) 低(依赖内核函数实现)
灵活性 低(只能追踪预定义的事件) 高(可以追踪任意内核函数)
实现方式 静态(内核代码中预定义) 动态(运行时插桩)
适用场景 生产环境监控 调试和开发环境

总结

  • 通过使用tracepoint,我们可以掌握linux内核运行的内部信息和应用程序是如何与内核进行交互的
  • 通过bpftrace,可以更加方便的使用tracepoint,并且可以实现更多的功能
  • 可以通过ftrace提供的文件系统接口,查看tracepoint的定义和参数
作者

deepwzh

发布于

2025-01-16

更新于

2025-01-16

许可协议

评论