BPF_PROG_TYPE_RAW_TRACEPOINT (raw tracepoint) について

BPFのプログラムタイプの一つにBPF_PROG_TYPE_RAW_TRACEPOINTがあります (commit).これを利用するとtracepointの変換前の引数にアクセスすることができます.

例としてkernel/sched/core.cで定義されるsched_swtich のtracepointを考えます.

     trace_sched_switch(preempt, prev, next);

このtracepointはinclude/trace/events.sched.hで定義されます.

TRACE_EVENT(sched_switch,

    TP_PROTO(bool preempt,
         struct task_struct *prev,
         struct task_struct *next),

    TP_ARGS(preempt, prev, next),

    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->next_comm, next->comm, TASK_COMM_LEN);
        __entry->prev_pid    = prev->pid;
        __entry->prev_prio   = prev->prio;
        __entry->prev_state  = __trace_sched_switch_state(preempt, prev);
        memcpy(__entry->prev_comm, prev->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)
);

ここで,通常のtracepointであれば,TP_STRUCT__entryで定義されている項目 (+ tracepointに共通の項目)にアクセスできます.これは以下のtracing/events/sched/sched_switch/formatからも確認できます.

# cat /sys/kernel/debug/tracing/events/sched/sched_switch/format
name: sched_switch
ID: 318
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:char prev_comm[16];       offset:8;       size:16;        signed:1;
        field:pid_t prev_pid;   offset:24;      size:4; signed:1;
        field:int prev_prio;    offset:28;      size:4; signed:1;
        field:long prev_state;  offset:32;      size:8; signed:1;
        field:char next_comm[16];       offset:40;      size:16;        signed:1;
        field:pid_t next_pid;   offset:56;      size:4; signed:1;
        field:int next_prio;    offset:60;      size:4; signed:1;

print fmt: "prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s%s ==> next_comm=%s next_pid=%d next_prio=%d", REC->prev_comm, REC->prev_pid, REC->prev_prio, (REC->prev_state & ((((0x0000 | 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040) + 1) << 1) - 1)) ? __print_flags(REC->prev_state & ((((0x0000 | 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040) + 1) << 1) - 1), "|", { 0x0001, "S" }, { 0x0002, "D" }, { 0x0004, "T" }, { 0x0008, "t" }, { 0x0010, "X" }, { 0x0020, "Z" }, { 0x0040, "P" }, { 0x0080, "I" }) : "R", REC->prev_state & (((0x0000 | 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040) + 1) << 1) ? "+" : "", REC->next_comm, REC->next_pid, REC->next_prio

一方でraw tracepiontの場合,アクセスできるのはtrace_sched_switch()に与えた引数 (TP_PROTOの引数)になります.sched_switchの場合は

 TP_PROTO(bool preempt,
         struct task_struct *prev,
         struct task_struct *next)

になります.

raw tracepintのもともとの導入の経緯は,sched_switchにおいてprevnexttask_structの中身にアクセスしたいという要求からだったようです(参考).通常のtracepointであればBPFプログラム呼び出し前にtracepointのデータを整形する必要がありますが,raw tracepointではその必要がないため,その分のオーバヘッドが抑えられます.ただし,raw tracepointの引数で与えられたポインタのデータにアクセスするにはbpf_probe_read()を使う必要があります.基本的にはtarcepiontの方が扱いやすいので,そちらを使えば良いと思いますが,パフォーマンスが問題であったり,あるいはtracepointの情報だけでは不足している場合はraw tracepointを検討してみると良いと思います.