首页 > 系统相关 >linux tracepoint增加

linux tracepoint增加

时间:2024-04-03 17:55:35浏览次数:31  
标签:prio pid next 120 comm linux 增加 prev tracepoint

引入tracepoint的背景

当需要获取内核的debug信息时,通常你会通过以下printk的方式打印信息:

void trace_func()
{
    //……
    printk("输出信息");
    //……
}

缺点:

  • 内核中printk是统一控制的,各个模块的printk都会被打印,无法只打印需要关注的模块
  • 如果需要修改/新增打印信息,需要修改所有受影响的printk语句。这些printk分散在代码多处,每个地方都需要修改
  • 嵌入式系统中,如果printk信息量大,console(如果有)有大量的打印输出,用户无法在console输入命令,影响人机交互

内核解决方案

内核采用“插桩”的方法抓取log,“插桩”也称为Tracepoint,Tracepoint是Linux内核预先定义的静态探测点,它分布于内核的各个子系统中,每种Tracepoint有一个name、一个enable开关、一系列桩函数、注册桩函数的函数、卸载桩函数的函数。“桩函数”功能类似于printk,不过“桩函数”并不会把信息打印到console,而是输出到内核的ring buffer(环形缓冲区),缓冲区中的信息通过debugfs对用户呈现。每个tracepoint提供一个钩子来调用probe函数。一个tracepoint可以打开或关闭。打开时,probe函数关联到tracepoint;关闭时,probe函数不关联到tracepoint。tracepoint关闭时对kernel产生的影响很小,只是增加了极少的时间开销(一个分支条件判断),极小的空间开销(一条函数调用语句和几个数据结构)。只有挂载了钩子函数才会真正启用trace功能。这个钩子函数可以由开发者编写内核module来实现,并且需要在钩子函数中获取我们调试所需要的信息并导出到用户态,这样就可以获取内核运行时的信息了。当一个tracepoint打开时,用户提供的probe函数在每次这个tracepoint执行都会被调用。

直接使用tracepoint并不是那么的容易,因此内核提供了event trace功能。event trace的实现依赖于tracepoint机制,内核提前帮我们实现了钩子函数并挂到tracepoint上,当使能一个event trace时,它会输出内容到ftrace ringbuffer中,这样就可以获取到内核运行信息了。当然有时候event trace并不符合我们的需要,那么此时就只得自己编写module来实现需求了。

查看tracepoint

可通过debugfs查看:

root: /5g_build/5g_Main/$ ls /sys/kernel/debug/tracing/events/
block             dma_fence   ext4      ftrace        hyperv     irq          libata   mpx   oom      qdisc         rcu     scsi    sunrpc    udp        writeback
bridge            drm         filelock  gpio          i2c        irq_vectors  mce      napi  pagemap  random        regmap  signal  syscalls  vmscan     xen
compaction        enable      filemap   header_event  intel_ish  jbd2         migrate  net   power    ras           rpm     skb     task      vsyscall   xhci-hcd
context_tracking  exceptions  fs_dax    header_page   iommu      kmem         module   nfs   printk   raw_syscalls  sched   sock    timer     workqueue

系统中定义的tracepoint都在该events目录中,比如查看block子系统中的tracepoint:

root: /5g_build/5g_Main/$ ls /sys/kernel/debug/tracing/events/block/
block_bio_backmerge  block_bio_complete    block_bio_queue  block_dirty_buffer  block_plug      block_rq_complete  block_rq_issue  block_rq_requeue  block_split         block_unplug  filter
block_bio_bounce     block_bio_frontmerge  block_bio_remap  block_getrq         block_rq_abort  block_rq_insert    block_rq_remap  block_sleeprq     block_touch_buffer  enable

还可以通过perf查看:

 root: /5g_build/5g_Main/$ perf list tracepoint
  block:block_bio_backmerge                          [Tracepoint event]
  block:block_bio_bounce                             [Tracepoint event]
  block:block_bio_complete                           [Tracepoint event]
  block:block_bio_frontmerge                         [Tracepoint event]
  block:block_bio_queue                              [Tracepoint event]
  block:block_bio_remap                              [Tracepoint event]
  block:block_dirty_buffer                           [Tracepoint event]
  block:block_getrq                                  [Tracepoint event]
  block:block_plug                                   [Tracepoint event]
  block:block_rq_abort                               [Tracepoint event]
  block:block_rq_complete                            [Tracepoint event]
  block:block_rq_insert                              [Tracepoint event]
  block:block_rq_issue                               [Tracepoint event]
  block:block_rq_remap                               [Tracepoint event]
  block:block_rq_requeue                             [Tracepoint event]
  block:block_sleeprq                                [Tracepoint event]
  block:block_split                                  [Tracepoint event]
  block:block_touch_buffer                           [Tracepoint event]
  block:block_unplug                                 [Tracepoint event]
  bridge:br_fdb_add                                  [Tracepoint event]
  bridge:br_fdb_external_learn_add                   [Tracepoint event]
  bridge:br_fdb_update                               [Tracepoint event]
  bridge:fdb_delete                                  [Tracepoint event]
  compaction:mm_compaction_isolate_freepages         [Tracepoint event]
  compaction:mm_compaction_isolate_migratepages      [Tracepoint event]

Tracepoint数据格式

每个tracepoint都会按照自己定义的格式来输出信息,可以在用户态来查看tracepoint记录的内容格式,比如:

root: /5g_build/5g_Main/$ cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_open/format
name: sys_enter_open
ID: 484
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 nr;   offset:8;       size:4; signed:1;
        field:const char * filename;    offset:16;      size:8; signed:0;
        field:int flags;        offset:24;      size:8; signed:0;
        field:umode_t mode;     offset:32;      size:8; signed:0;

print fmt: "filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx", ((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode))

格式信息可以用来解析二进制的trace流数据,也可以用这个格式中的内容来做trace filter,利用filter功能指定过滤条件后,将只会看到过滤后的事件数据。格式信息中包括两部分内容:

  • 第一部分是通用的格式,这类通用字段都带有common前缀,这是所有的tracepoint event都具备的字段
  • 第二部分就是各个tracepoint所自定义的格式字段,比如上面的nr,filename等等。格式信息的最后一列是tracepoint的打印内容格式,通过这个可以看到打印数据的字段来源。

如何使用Tracepoint

tracepoint复用了ftrace的ringbuffer,当使能了一个tracepoint之后,可以通过cat /sys/kernel/debug/tracing/trace来查看它的输出。另外上文也有提到,还可以通过设置filter过滤事件。

比如想要过滤所有的openat事件,指定过滤所有打开flags为0的事件:

echo flags==0 > /sys/kernel/debug/tracing/events/syscalls/sys_enter_openat/filter
echo 1 > /sys/kernel/debug/tracing/events/syscalls/sys_enter_openat/enable
cat /sys/kernel/debug/tracing/trace_pipe

如果想要把过滤条件清除,只需要写入0到指定的filter即可:

echo 0 > /sys/kernel/debug/tracing/events/syscalls/sys_enter_openat/filter

如果想要跟踪sched_switch这个tracepoint点,设置过滤条件prev_pid和next_pid都为1的进程:

root: /5g_build/5g_Main/$ echo "(prev_pid == 1 || next_pid == 1)" >  /sys/kernel/debug/tracing/events/sched/sched_switch/filter
root: /5g_build/5g_Main/$ echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
root: /5g_build/5g_Main/$ cat /sys/kernel/debug/tracing/trace_pipe
          <idle>-0     [006] d... 2741443.520814: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741443.522103: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741443.522141: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741443.522816: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741443.525867: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741443.526448: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741443.727718: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741443.729145: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741443.729190: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741443.729864: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741443.733095: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741443.733663: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741444.799623: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741444.799777: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741446.049617: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741446.049761: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741446.114969: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741446.116232: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741446.116298: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741446.116756: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741446.119312: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741446.119702: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741446.324820: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741446.326196: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741446.326251: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741446.326945: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [006] d... 2741446.329697: sched_switch: prev_comm=swapper/6 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [006] d... 2741446.330272: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120
          <idle>-0     [000] d... 2741446.526418: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [000] d... 2741446.527526: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
          <idle>-0     [000] d... 2741446.527569: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [000] d... 2741446.528133: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
          <idle>-0     [000] d... 2741446.531700: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [000] d... 2741446.532191: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120
          <idle>-0     [004] d... 2741446.730287: sched_switch: prev_comm=swapper/4 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [004] d... 2741446.731656: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/4 next_pid=0 next_prio=120
          <idle>-0     [004] d... 2741446.731683: sched_switch: prev_comm=swapper/4 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
         systemd-1     [004] d... 2741446.732349: sched_switch: prev_comm=systemd prev_pid=1 prev_prio=120 prev_state=S ==> next_comm=swapper/4 next_pid=0 next_prio=120
          <idle>-0     [004] d... 2741446.734980: sched_switch: prev_comm=swapper/4 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=systemd next_pid=1 next_prio=120
...

可以看到对应的prev_id或者next_pid为1时才会输出

perf中使用tracepoint

借助perf工具,可以跟踪tracepoint事件,前文已经介绍了如何用perf查看tracepoint,假如想要跟踪网络驱动收发包的情况:

perf record -e 'net:netif_rx','net:net_dev_queue' -a   -- sleep 10

记录10秒后,使用:

perf script

来查看记录到的信息,截取部分示例如下:

           node  1072 [001] 2741584.673790: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5bb698af8 len=214
            node  1072 [001] 2741584.673794: net:net_dev_queue: dev=veth_558d087c skbaddr=0xffff92c5bb698af8 len=214
            node  1072 [001] 2741584.673815: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5bb698af8 len=264
            grep     0 [001] 2741584.680569:      net:netif_rx: dev=veth_558d087c skbaddr=0xffff92ca6e714c00 len=155
            node  1072 [001] 2741584.683656: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5bb698cf8 len=98
            node  1072 [001] 2741584.683661: net:net_dev_queue: dev=veth_558d087c skbaddr=0xffff92c5bb698cf8 len=98
            node  1072 [001] 2741584.683682: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5bb698cf8 len=148
            grep     0 [001] 2741584.744696:      net:netif_rx: dev=veth_558d087c skbaddr=0xffff92ca6e715800 len=40
            grep     0 [001] 2741584.744703:      net:netif_rx: dev=veth_558d087c skbaddr=0xffff92ca736b8800 len=40
             awk     0 [006] 2741584.862150: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5fea64100 len=66
       ovs-ofctl     0 [000] 2741584.882151: net:net_dev_queue: dev=eth0 skbaddr=0xffff92caa3dafef8 len=208
       ovs-ofctl     0 [000] 2741584.883350: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5d2a8c100 len=66
       ovs-ofctl     0 [000] 2741584.883375: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5d2a8db00 len=66
       ovs-ofctl     0 [001] 2741584.884316: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5bb69b6f8 len=263
       ovs-ofctl     0 [001] 2741584.885924: net:net_dev_queue: dev=eth0 skbaddr=0xffff92caa3cf9b00 len=66
       ovs-ofctl     0 [001] 2741584.888257: net:net_dev_queue: dev=eth0 skbaddr=0xffff92caa3cf9a00 len=66
       ovs-ofctl     0 [001] 2741584.888843: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5bb69b6f8 len=280
       ovs-ofctl     0 [001] 2741584.888924: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5bb698cf8 len=1107
       ovs-ofctl     0 [001] 2741584.888946: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5bb6984f8 len=104
       ovs-ofctl     0 [001] 2741584.889618: net:net_dev_queue: dev=eth0 skbaddr=0xffff92caa3cf9a00 len=66
       ovs-ofctl     0 [001] 2741584.892419: net:net_dev_queue: dev=eth0 skbaddr=0xffff92caa3cf9800 len=66
              wc     0 [001] 2741584.985872:      net:netif_rx: dev=veth_558d087c skbaddr=0xffff92caa3cf9a00 len=103
              wc     0 [001] 2741584.986657: net:net_dev_queue: dev=eth0 skbaddr=0xffff92caa3cf8000 len=66
            node  1072 [005] 2741584.989219: net:net_dev_queue: dev=eth0 skbaddr=0xffff92ca6fe748f8 len=109
            node  1072 [005] 2741584.989222: net:net_dev_queue: dev=veth_558d087c skbaddr=0xffff92ca6fe748f8 len=109
            node  1072 [005] 2741584.989244: net:net_dev_queue: dev=eth0 skbaddr=0xffff92ca6fe748f8 len=159
              wc     0 [005] 2741585.001911:      net:netif_rx: dev=veth_558d087c skbaddr=0xffff92caa3cf9b00 len=82
            node  1072 [005] 2741585.004758: net:net_dev_queue: dev=eth0 skbaddr=0xffff92ca6fe75af8 len=99
            node  1072 [005] 2741585.004762: net:net_dev_queue: dev=veth_558d087c skbaddr=0xffff92ca6fe75af8 len=99
            node  1072 [005] 2741585.004798: net:net_dev_queue: dev=eth0 skbaddr=0xffff92ca6fe75af8 len=149
           pidof     0 [005] 2741585.053168:      net:netif_rx: dev=veth_558d087c skbaddr=0xffff92ca702cf400 len=83
            node  1072 [000] 2741585.056084: net:net_dev_queue: dev=eth0 skbaddr=0xffff92caa3dac6f8 len=99
            node  1072 [000] 2741585.056089: net:net_dev_queue: dev=veth_558d087c skbaddr=0xffff92caa3dac6f8 len=99
            node  1072 [000] 2741585.056110: net:net_dev_queue: dev=eth0 skbaddr=0xffff92caa3dac6f8 len=149
             awk     0 [000] 2741585.103222:      net:netif_rx: dev=veth_558d087c skbaddr=0xffff92ca6e615d00 len=40
    cpptools-srv 28070 [000] 2741585.184113:      net:netif_rx: dev=veth_558d087c skbaddr=0xffff92ca6e615600 len=87
            node  1072 [004] 2741585.186539: net:net_dev_queue: dev=eth0 skbaddr=0xffff92c5d2f152f8 len=110

添加Tracepoint

数据结构 代码路径
DEFINE_TRACE(name)
DECLARE_TRACE(name, proto, args)
include/linux/tracepoint.h
struct tracepoint include/linux/tracepoint-defs.h
  1. Tracepoint依次执行桩函数,每个桩函数实现不同的debug功能。内核通过register_trace_##name将桩函数添加到Tracepoint中,通过unregister_trace_##name从trace point中移除。(注:##表示字符串连接)
  2. Tracepoint依次执行桩函数,每个桩函数实现不同的debug功能。内核通过register_trace_##name将桩函数添加到Tracepoint中,通过unregister_trace_##name从trace point中移除。(注:##表示字符串连接)
tracepoint {
    const char *name;       /* Tracepoint name */
    struct static_key key;
    int (*regfunc)(void);
    void (*unregfunc)(void);
    struct tracepoint_func __rcu *funcs;
};
  • name:Tracepoint的名字,内核中通过hash表管理所有的trace point,找到对应的hash slot后,需要通过name来识别具体的trace_point
  • key:Tracepoint状态,1表示disable,0表示enable
  • regfunc:添加桩函数的函数
  • unregfunc:卸载桩函数的函数
  • funcs:Tracepoint中所有的桩函数链表
  1. 内核通过#define DECLARE_TRACE(name, proto, args)定义trace point用到的函数,定义的函数原型如下(从代码中摘取了几个,不止以下3个):
#define __DECLARE_TRACE(name, proto, args, cond, data_proto, data_args)
extern struct tracepoint __tracepoint_##name;
static inline void trace_##name(proto)
{
    if (static_key_false(&__tracepoint_##name.key))
        __DO_TRACE(&__tracepoint_##name,
        TP_PROTO(data_proto),
        TP_ARGS(data_args),
        TP_CONDITION(cond),,);
}
register_trace_##name(void (*probe)(data_proto), void *data)
{      
    return tracepoint_probe_register(&__tracepoint_##name,
    (void *)probe, data);
}
unregister_trace_##name(void (*probe)(data_proto), void *data)
{
    return tracepoint_probe_unregister(&__tracepoint_##name,(void *)probe, data);
}

第2行声明一个外部trace point变量。然后定义了一些trace point用到的公共函数。

第5行判断trace point是否disable,如果没有disable,那么调用__DO_TRACE遍历执行trace point中的桩函数(通过“函数指针”来实现执行桩函数)。

trace point提供了统一的框架,用void *指向任何函数,所以各个trace point取出桩函数指针后,需要转换成自己的函数指针类型,TP_PROTO(data_proto)传递函数指针类型用于转换,具体的转换在:

#define __DO_TRACE(tp, proto, args, cond, rcuidle)
do {
    struct tracepoint_func *it_func_ptr;
    void *it_func;
    void *__data;
    ……
    it_func_ptr = rcu_dereference_raw((tp)->funcs);
    if (it_func_ptr) {
        do {
            it_func = (it_func_ptr)->func;
            __data = (it_func_ptr)->data;
            ((void(*)(proto))(it_func))(args);
        } while ((++it_func_ptr)->func);
    }

    ……
}while(0)
  1. 桩函数的proto的传递的例子
DEFINE_EVENT_CONDITION(f2fs__submit_page_bio, f2fs_submit_page_write,
    TP_PROTO(struct page *page, struct f2fs_io_info *fio),
    TP_ARGS(page, fio),
    TP_CONDITION(page->mapping)
);

第2行声明了桩函数原型

#define DEFINE_EVENT_CONDITION(template, name, proto, args, cond)
     DEFINE_EVENT(template, name, PARAMS(proto), PARAMS(args))
#define DEFINE_EVENT_CONDITION(template, name, proto, args, cond)
     DEFINE_EVENT(template, name, PARAMS(proto), PARAMS(args))
#define DECLARE_TRACE(name, proto, args)
    __DECLARE_TRACE(name, PARAMS(proto), PARAMS(args),             
    cpu_online(raw_smp_processor_id()),             
    PARAMS(void *__data, proto),                   
    PARAMS(__data, args))

至此执行到__DECLARE_TRACE宏,参考前面说明,提到了何时转换成桩函数指针类型。

从上面可以看出trace point的机制很简单,就是把用于debug的函数指针组织在一个struct trace point变量中,然后依次执行各个函数指针。不过为了避免各个模块重复写代码,内核用了比较复杂的宏而已。

另外也可以发现,使用trace point必须要通过register_trace_##name将桩函数(也就是我们需要的debug函数)添加到trace point中,这个工作只能通过moudule或者修改内核代码实现,对于开发者来说,操作比较麻烦。ftrace开发者们意识到了这点,所以提供了trace event功能,开发者不需要自己去注册桩函数了,易用性较好,后面文章会谈到trace event是如何实现的以及如何使用。

如果用户准备为kernel加入新的tracepoint,每个tracepoint则以下列格式声明:

#include <linux/tracepoint.h>
DECLARE_TRACE(tracepoint_name,
    TPPROTO(trace_function_prototype),
    TPARGS(trace_function_args));

上面的宏定义了一个新的tracepoint叫tracepoint_name。与这个tracepoint关联的probe函数必须与TPPROTO宏定义的函数prototype一致,probe函数的参数列表必须与TPARGS宏定义的一致。

或许用一个例子来解释会比较容易理解。Kernel里面已经包含了一些tracepoints,其中一个叫做sched_wakeup,这个tracepoint在每次scheduler唤醒一个进程时都会被调用。它是这样定义的:

DECLARE_TRACE(sched_wakeup,
    TPPROTO(struct rq *rq, struct task_struct *p),
    TPARGS(rq, p))

实际在kernel中插入这个tracepoint点的是一行如下代码:

trace_sched_wakeup(rq, p);

注意,插入tracepoint的函数名就是将trace_前缀添加到tracepoint_name的前面。除非有一个实际的probe函数关联到这个tracepoint,trace_sched_wakeup()这个只是一个空函数。下面的操作就是将一个probe函数关联到一个tracepoint:

void my_sched_wakeup_tracer(struct rq *rq, struct task_struct *p);
register_trace_sched_wakeup(my_sched_wakeup_tracer);

register_trace_sched_wakeup()函数实际上是DEFINE_TRACE()定义的,它把probe函数my_sched_wakeup_tracer()和tracepoint sched_wakeup关联起来

如何使用 TRACE_EVENT() 宏来创建跟踪点

TRACE_EVENT() 宏的剖析

自动跟踪点具有各种必须满足的要求:

  • 它必须创建一个可以放在内核代码中的跟踪点
  • 它必须创建一个可以挂接到此跟踪点的回调函数
  • 回调函数必须能够以最快的方式将传递给它的数据记录到跟踪环缓冲区中
  • 它必须创建一个函数,可以解析记录到环形缓冲区的数据,并将其转换为跟踪程序可以显示给用户的可读格式

为此,TRACE_EVENT() 宏分为六个组件,它们对应于宏的参数:

TRACE_EVENT(name, proto, args,struct, assign, print)
  • name- 要创建的跟踪点的名称
  • prototype- 跟踪点回调函数的原型
  • args- 与函数原型匹配的参数
  • struct - 跟踪点可以使用(并不是必须的)这个结构来存储传入到跟踪点的数据。
  • assign- 将数据分配给结构的类似于C的方法
  • print- 以人类可读的ASCII格式输出结构的方法

举例
这里可以找到sched_switch跟踪点定义的一个很好的例子。下面将使用该定义来描述TRACE_EVENT()宏的每个部分

除第一个参数外,所有参数都用另一个宏(TP_PROTO、TP_ARGS、TP_STRUCT_uentry、TP_fast_assign和TP_printk)封装。这些宏在处理过程中提供了更多的控制,还允许在TRACE_EVENT()宏中使用逗号

Name
第一个参数是名称。

TRACE_EVENT(sched_switch

这是用于调用此跟踪点的名称。实际使用的跟踪点的名称前面有trace_前缀(即trace_sched_switch)

Prototype
下一个参数是原型。

TP_PROTO(struct rq *rq, struct task_struct *prev, struct task_struct *next)

原型的编写就像您要直接声明跟踪点一样:

trace_sched_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next);

它用作添加到内核代码的跟踪点和回调函数的原型。请记住,跟踪点调用回调函数,就好像在跟踪点的位置调用回调函数一样

Arguments
第三个参数是原型使用的参数。

TP_ARGS(rq, prev, next)

这可能看起来很奇怪,但它不仅是TRACE_EVENT()宏所必需的,而且也是下面的跟踪点基础结构所必需的。跟踪点代码在激活时将调用回调函数(可以将多个回调分配给给定的跟踪点)。创建跟踪点的宏必须同时访问原型和参数。下面是跟踪点宏完成此操作所需的示例:

#define TRACE_POINT(name, proto, args) //\
        void trace_##name(proto)            \
        {                                   \
                if (trace_##name##_active)  \
                        callback(args);     \
        }

Structure

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			)
),

此参数描述将存储在跟踪器环缓冲区中的数据的结构布局。结构的每个元素都由另一个宏定义。这些宏用于自动创建结构,而不是功能类似的。注意宏之间没有任何分隔符(没有逗号或分号)

sched_switch tracepoint使用的宏有:

  • __field(type, name)- 它定义了一个普通的结构元素,比如int var;其中type是int,name是var

  • __array(type, name, len)- 这将定义一个数组项,相当于int name[len];其中type是int,数组的名称是array,数组中的项数是len

    还有其他元素宏将在后面的文章中介绍。来自sched_switch tracepoint的定义将生成一个如下所示的结构:

struct {
	char   prev_comm[TASK_COMM_LEN];
	pid_t  prev_pid;
	int    prev_prio;
	long   prev_state;
	char   next_comm[TASK_COMM_LEN];
	pid_t  next_pid;
	int    next_prio;
};

Assignment

第五个参数定义了将参数中的数据保存到环形缓冲区的方式

TP_fast_assign(
		memcpy(__entry->next_comm, next->comm, TASK_COMM_LEN);
		__entry->prev_pid	= prev->pid;
		__entry->prev_prio	= prev->prio;
		__entry->prev_state	= prev->state;
		memcpy(__entry->prev_comm, prev->comm, TASK_COMM_LEN);
		__entry->next_pid	= next->pid;
		__entry->next_prio	= next->prio;
),
  • TP_fast_assign()中的代码是普通的C代码。一个特殊的变量 __entry表示指向由TP_STRUCT__entry项定义的结构类型的指针,并直接指向环缓冲区。TP_fast_assign用于填充TP_STRUCT_entry中创建的所有字段。然后,可以使用TP_PROTO()和TP_ARGS()定义的参数的变量名来将适当的数据分配到该输入结构中

Print

最后一个参数定义如何使用printk()从TP_STRUCT_uentry结构中打印字段

TP_printk("prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s ==> " \
 		  "next_comm=%s next_pid=%d next_prio=%d",
		__entry->prev_comm, __entry->prev_pid, __entry->prev_prio,
		__entry->prev_state ?
		  __print_flags(__entry->prev_state, "|",
				{ 1, "S"} , { 2, "D" }, { 4, "T" }, { 8, "t" },
				{ 16, "Z" }, { 32, "X" }, { 64, "x" },
				{ 128, "W" }) : "R",
		__entry->next_comm, __entry->next_pid, __entry->next_prio)

变量 __entry 再次被用来引用包含数据的结构体的指针。Format string 就像其他 printf 的 format 一样。__print_flags() 是 TRACE_EVENT() 附带的一组帮助函数的一部分,不要创建新的跟踪点特定的帮助程序,因为这会混淆用户空间工具,这些工具知道TRACE_EVENT() helper宏,但不知道如何处理为单个跟踪点创建的宏

头文件

TRACE_EVENT()宏不能仅仅放置在期望它能与Ftrace或任何其他跟踪程序一起工作的任何位置。包含TRACE_EVENT()宏的头文件必须遵循特定格式。这些头文件通常位于include/trace/events目录中,但不需要。如果它们不在此目录中,则需要其他配置

TRACE_EVENT()头中的第一行不是普通的ifdef_TRACE_SCHED_H,而是

此示例用于调度程序跟踪事件,其他事件头将使用除sched和trace_sched_H之外的其他内容。trace_HEADER_MULTI_READ test允许此文件包含多次;这对于trace_event()宏的处理非常重要。还必须为文件定义跟踪系统,并且跟踪系统必须在if的保护范围之外。TRACE_系统定义文件中TRACE_EVENT()宏所属的组。这也是事件将在debugfs tracing/events目录中分组的目录名。此分组对Ftrace很重要,因为它允许用户按组启用或禁用事件

然后,该文件包含TRACE_EVENT()宏内容所需的任何头。(例如,include<linux/sched.h>)。tracepoint.h文件是必需的

#include <linux/tracepoint.h>

现在可以使用trace_EVENT()宏定义所有跟踪事件。请在TRACE_EVENT()宏上方包含描述跟踪点的注释。以include/trace/events/sched.h为例。文件结尾为:

#endif /* _TRACE_SCHED_H */

/* This part must be outside protection */
#include <trace/define_trace.h>

define_trace.h是创建跟踪点的所有魔力所在,这个文件必须包含在跟踪头文件的底部,而不受endif的保护

使用tracepoint

如果跟踪点不在任何地方使用,那么定义它就没有意义。要使用跟踪点,必须包含跟踪头,但在包含跟踪之前,还必须定义一个C文件(并且只有一个)的创建跟踪点。这将导致define_trace.h创建生成跟踪事件所需的必要函数。在kernel/sched.c中定义了以下内容:

#define CREATE_TRACE_POINTS
#include <trace/events/sched.h>

如果另一个文件需要使用跟踪文件中定义的跟踪点,则它只需要包含跟踪文件,而不需要定义创建跟踪点。为同一头文件定义多次将在生成时导致链接器错误。例如,在kernel/fork.c中,只包含头文件:

#include <trace/events/sched.h>

最后,在代码中使用跟踪点,就像在TRACE_EVENT()宏中定义的一样:

static inline void
context_switch(struct rq *rq, struct task_struct *prev,
				struct task_struct *next)
{
	struct mm_struct *mm, *oldmm;
	
	prepare_task_switch(rq, prev, next);
	trace_sched_switch(rq, prev, next);
	mm = next->mm;
	oldmm = prev->active_mm;

实际使用

  • module_traceevent.h
#undef TRACE_SYSTEM
#define TRACE_SYSTEM xhr_test

#if !defined(_TRACE_TE_TEST_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_TE_TEST_H

#include <linux/tracepoint.h>

TRACE_EVENT(te_test,
    TP_PROTO(int num),
    TP_ARGS(num),
    TP_STRUCT__entry(
        __field(int, output)
        __field(int, count)
    ),
    TP_fast_assign(
        __entry->count++;
        __entry->output = num;
    ),
    TP_printk("count=%d output=%d",
        __entry->count, __entry->output)
);

#endif /* _TRACE_TE_TEST_H */
    
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE module_traceevent

#include <trace/define_trace.h>
  • module_entry.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>

#define CREATE_TRACE_POINTS
#include "module_traceevent.h"

static int xhr_thread(void *arg)
{
    static unsigned long count;

    while (!kthread_should_stop())
    {
        set_current_state(TASK_INTERRUPTIBLE);
        schedule_timeout(HZ);
        trace_te_test(count);
        count++;
    }

    return 0;
}

static struct task_struct *xhr_task;

static __init int __module_init(void)
{
    printk("Hello, %s.\n", __func__);

    xhr_task = kthread_run(xhr_thread, NULL, "xhr-thread");
    if (IS_ERR(xhr_task))
        return -1;

    return 0;
}
static __exit void __module_exit(void)
{
    kthread_stop(xhr_task);
    printk("Hello, %s.\n", __func__);
    return;
}

module_init(__module_init);
module_exit(__module_exit);
MODULE_LICENSE("GPL");
  • Makefile
INCLUDES = -I. -I$(KDIR)/include
MODULE := xhr
obj-m := $(MODULE).o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

CFLAGS_module_entry.o = -I$(src)

.PHONY: all
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

.PHONY: clean
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

$(MODULE)-objs += module_entry.o
  • user space
sudo -s
insmod xhr.ko
cd /sys/kernel/debug/tracing/
echo 1 > tracing_on
echo 1 > events/xhr_test/te_test/enable
cat trace

output:
# entries-in-buffer/entries-written: 8/8   #P:8
#
#                              _-----=> irqs-off
#                             / _----=> need-resched
#                            | / _---=> hardirq/softirq
#                            || / _--=> preempt-depth
#                            ||| /     delay
#           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
#              | |       |   ||||       |         |
      xhr-thread-3732  [000] ....  1739.034184: te_test: count=1 output=48
      xhr-thread-3732  [000] ....  1740.058510: te_test: count=1 output=49
      xhr-thread-3732  [000] ....  1741.081934: te_test: count=1 output=50
      xhr-thread-3732  [000] ....  1742.106095: te_test: count=1 output=51

标签:prio,pid,next,120,comm,linux,增加,prev,tracepoint
From: https://www.cnblogs.com/linhaostudy/p/18113243

相关文章

  • Linux目录和文件管理
    Linux目录和文件管理目录Linux目录和文件管理一、目录层次二、查看文件内容2.1显示命令2.1.1cat2.2分页显示2.2.1more2.2.2less2.3显示文件前后内容2.3.1head2.3.2tail2.3.3wc2.3.4grep2.3.5paste2.3.6split三、压缩,解压缩3.1gzipbzip23.2归档tar四、vim编辑器4.1基......
  • linux 中 vim的map 映射快捷键设置
     001、打开一个测试文件,在末行模式输入下面代码:vima.txtaabbrootddROOtKKAsrOOTDDSddjjf~~~~~~~~~~~~~~~~~~~~:map<F2>:echo"helleworld!"<CR>##在末行模式中输入如下内容,然后回车,后边按F2键则表示执行了echo"hellowworld!......
  • linux设备树
    基本知识:一、各个文件介绍1、DTS:描述设备树的文件,即采用树形结构描述板级设备2、DTB:DTS编译以后得到的二进制文件3、DTC:将DTS编译为DTB的工具,编译命令-makedtbs4、dtsi:描述SOC级信息,而DTS描述的是板级文件,DTS可以引用dtsi文件  二、常用的标准属性属性由键值对......
  • Windows如何优雅的运行ROS2/linux
    Windows如何优雅的运行ROS2/linux前言在ROS/ROS2开发过程中,大家普遍使用到的分布式开发方法都是基于虚拟机/双系统进行,本质上是希望基于Ubuntu良好的生态环境进行,但是两种方式各有各自的好处,也有各自的弊端,例如虚拟机很大而且并不能运行GPU;又比如双系统会导致个人数据碎片化(......
  • Python赚外快指南!找点轻松的活增加收入!
    当今收入低于5000的人至少占到40%,完全不够养活一家人,而且很多小伙伴其实空余时间比较多,特别是大学生,零花钱又不够花,都想靠业余时间找点轻松的活增加收入。但是缺没门路,为此结合我多年编程开发经验,总结了几种用Python赚外快的方法:1.最典型的就是找爬虫外包活儿。这个真是体......
  • Zabbix绝配!Linux用户密码过期预警与自动发现脚本大揭秘
    Zabbix绝配!Linux用户密码过期预警与自动发现脚本大揭秘原创 脚本家园 脚本家园 2024-04-0206:35 云南 1人听过在Linux系统管理中,用户密码的安全性和有效性是至关重要的。为了保持系统的安全性,管理员经常需要确保用户密码定期更换,并提前警告用户其密码即将过期。在这方面,Z......
  • linux sudo漏洞 可导致用户以 root 权限运行命令
    Sudo的全称是“superuserdo”,它是Linux系统管理指令,允许用户在不需要切换环境的前提下以其它用户的权限运行应用程序或命令,通常是以root用户身份运行命令,以减少root用户的登录和管理时间,同时提高安全性。该漏洞是sudo安全策略绕过问题,可导致恶意用户或程序在目标Linux系统......
  • Linux系统下安装MongoDB的详细步骤
    一、概述MongoDB由C++语言编写,是一个介于关系型数据库和非关系型数据之间的产品,是非关系型数据库中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似与json的bson格式,因此可以存储比较复杂的数据类型。MongoDB最大的特点是它支持的查询语言非常强大,其语......
  • Linux内核中的通用PHY驱动
    一. 简介前面一篇文章说明了有线网络的网络硬件方案。常用到的一种方案是:内部集成了MAC网络外设的SOC芯片(处理器),外接一个PHY网络芯片。前面文章也学习了Linux内核中的PHY子系统,也是为(上面这种方案)PHY网络芯片提供的驱动框架。Linux内核内部提供了通用的PHY驱动。本文来学......
  • VUE-axios统一增加请求头并根据接口返回的状态码判断用户登录状态并跳转登录页
    背景:后台接口返回code==501表示用户是未登录状态,需要登录才可访问;main.js中通过http拦截做路由跳转importVuefrom‘vue’importAxiosfrom‘axios’Vue.prototype.$axios=Axiosimport{Loading,Message,MessageBox}from‘element-ui’//超时时间Axios.......