现在的位置: 首页 > 操作系统 > 正文

Systemtap中内核trace事件如何实现

2020年02月19日 操作系统 ⁄ 共 5198字 ⁄ 字号 评论关闭

  内核中定义了一系列的trace point,这些trace point在特定的内核函数中被触发调用时被记录,而对应到systemtap中就是 kernel.trace 类型的probe事件,可以使用命令来查看系统所有的trace point:

  $ sudo stap -L 'kernel.trace("*")' | more

  kernel.trace("9p:9p_client_req") $clnt:struct p9_client* $type:int8_t $tag:int

  kernel.trace("9p:9p_client_res") $clnt:struct p9_client* $type:int8_t $tag:int $err:int

  kernel.trace("9p:9p_protocol_dump") $clnt:struct p9_client* $pdu:struct p9_fcall*

  换言之,通过systemtap能够对这些已经静态注册的内核调用记录点进行监控、跟踪。

  以下来解释trace point在内核的实现以及与systemtap相关的内容。

  数据结构

  内核通过 DECLARE_TRACE 来声明一个trace point:

  DECLARE_TRACE(subsys_eventname,

  TP_PROTO(int firstarg, struct task_struct *p),

  TP_ARGS(firstarg, p));

  在这里:

  subsys_eventname是定义trace事件的唯一字符串,又能拆解成两部分:subsys就是子系统的名称,而eventname是事件名称。比如下面将作为实例的 softirq_entry ,就定义了一个在 softirq 子系统中的 entry 事件。

  TP_PROTO(int firstarg, struct task_struct *p):定义了传入trace函数的参数原型。

  TP_ARGS(firstarg, p):定义了参数名称,其类型与TP_PROTO中的类型一一对应。

  这个宏的定义如下:

  // include/linux/tracepoint.h

  #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 定义如下:

  #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), 0); \

  if (IS_ENABLED(CONFIG_LOCKDEP) && (cond)) { \

  rcu_read_lock_sched_notrace(); \

  rcu_dereference_sched(__tracepoint_##name.funcs);\

  rcu_read_unlock_sched_notrace(); \

  } \

  } \

  __DECLARE_TRACE_RCU(name, PARAMS(proto), PARAMS(args), \

  PARAMS(cond), PARAMS(data_proto), PARAMS(data_args)) \

  static inline int \

  register_trace_##name(void (*probe)(data_proto), void *data) \

  { \

  return tracepoint_probe_register(&__tracepoint_##name, \

  (void *)probe, data); \

  } \

  static inline int \

  register_trace_prio_##name(void (*probe)(data_proto), void *data,\

  int prio) \

  { \

  return tracepoint_probe_register_prio(&__tracepoint_##name, \

  (void *)probe, data, prio); \

  } \

  static inline int \

  unregister_trace_##name(void (*probe)(data_proto), void *data) \

  { \

  return tracepoint_probe_unregister(&__tracepoint_##name,\

  (void *)probe, data); \

  } \

  static inline void \

  check_trace_callback_type_##name(void (*cb)(data_proto)) \

  { \

  } \

  static inline bool \

  trace_##name##_enabled(void) \

  { \

  return static_key_false(&__tracepoint_##name.key); \

  }

  可以看到,这个宏做了如下的事情:

  声明了一个类型为 tracepoint 的结构体变量 __tracepoint_##name 。

  定义了几个相关的函数,分别用于处理trace event、注册、注销等。其中需要重点关注的是宏trace_##name ,这里定义了对对应的traceevent进行跟踪的函数。

  其中,宏里面一个字符串跟着 ##name 表示这个字符串与name的连接形成的字符串。

  这里的结构体 tracepoint 定义如下:

  // include/linux/tracepoint-defs.h

  struct tracepoint {

  const char *name; /* Tracepoint name */

  struct static_key key;

  int (*regfunc)(void);

  void (*unregfunc)(void);

  struct tracepoint_func __rcu *funcs;

  };

  该结构体中分别定义了:

  traceevent名称。

  注册、注销、被触发时的处理函数。

  以上只是声明了 tracepoint 结构体变量,而具体定义变量的宏是 DEFINE_TRACE :

  // include/linux/tracepoint.h

  #define DEFINE_TRACE(name) \

  DEFINE_TRACE_FN(name, NULL, NULL);

  #define DEFINE_TRACE_FN(name, reg, unreg) \

  static const char __tpstrtab_##name[] \

  __attribute__((section("__tracepoints_strings"))) = #name; \

  struct tracepoint __tracepoint_##name \

  __attribute__((section("__tracepoints"))) = \

  { __tpstrtab_##name, STATIC_KEY_INIT_FALSE, reg, unreg, NULL };\

  static struct tracepoint * const __tracepoint_ptr_##name __used \

  __attribute__((section("__tracepoints_ptrs"))) = \

  &__tracepoint_##name;

  因此, DEFINE_TRACE 的作用就是:

  在 __tracepoints_strings section中定义了字符串数组变量 __tpstrtab_##name ,其值为name。

  在 __tracepoints section中定义了结构体tracepoint变量 __tracepoint_##name 。

  以上解释了trace point相关的数据结构、宏、变量等,下面以一个实例来展开说明。

  实例

  这里以软中断被调用时的入口trace event为例,其定义如下:

  DEFINE_EVENT(softirq, softirq_entry,

  TP_PROTO(unsigned int vec_nr),

  TP_ARGS(vec_nr)

  );

  这里的宏 DEFINE_EVENT 不过是前面 DECLARE_TRACE 宏的一个包装:

  #define DEFINE_EVENT(template, name, proto, args) \

  DECLARE_TRACE(name, PARAMS(proto), PARAMS(args))

  从上面的讨论可以知道,这里声明了一个名为 __tracepoint_softirq_entry 的 tracepoint 类型结构体。而根据我们前面对宏的展开分析, trace_##name 也就是这里展开的 trace_softirq_entry 是对这个trace event进行调用的入口,果然在 __do_softirq 函数中看到了它的身影:

  // kernel/softirq.c

  asmlinkage __visible void __softirq_entry __do_softirq(void)

  {

  // ...

  trace_softirq_entry(vec_nr);

  // ...

  }

  systemtap相关

  这里需要注意的另一个问题是,每个systemtap中的kernel.trace当时可以知道的参数,除了trace event本身的参数之外,还有当时所在嵌入函数内部的变量,比如这里的 softirq_entry 这个probe,在systemtap对应的tapset中是这样的:

  probe softirq.entry = kernel.trace("irq_softirq_entry") !,

  kernel.trace("softirq_entry") ?

  {

  # kernels < 2.6.37   h = @choose_defined($h, 0)   vec = @choose_defined($vec, 0)   action = (@defined($h) ? @cast($h,"softirq_action","kernel")->action : 0)

  # kernels >= 2.6.37

  vec_nr = @choose_defined($vec_nr, 0)

  }

  这里可以的变量 h 类型是在内核中的头文件 中定义的 softirq_action,因为这个变量就是在上面的函数 __do_softirq 中定义的:

  // kernel/softirq.c

  asmlinkage __visible void __softirq_entry __do_softirq(void)

  {

  // ...

  struct softirq_action *h;

  // ...

  trace_softirq_entry(vec_nr);

  // ...

  }

  所以,要看一个systemtap的kernel.trace能引用哪些变量,除了看其自身,还包括看其所嵌入函数的上下文中的变量,最好直接到对应的tapset的说明,因为-L只能打印出这个kernel.trace自身定义的变量:

  $ sudo stap -L 'kernel.trace("softirq_entry")'

  kernel.trace("irq:softirq_entry") $vec_nr:unsigned int

  总结:

  内核中的trace事件以 trace_* 来命名。

  看到systemtap中的 'kernel.trace("xx")' ,其对应的内核代码可以使用 trace_xx 来搜索,通过阅读这个trace事件所嵌入的代码也可以或者这个probe事件能打印的变量。

抱歉!评论已关闭.