确定性自动机插桩

由 dot2k 创建的 RV 监视器文件,名为“$MODEL_NAME.c”,包含一个专门用于插桩的部分。

在 [1] 中创建的 wip.dot 监视器示例中,它将如下所示

/*
 * This is the instrumentation part of the monitor.
 *
 * This is the section where manual work is required. Here the kernel events
 * are translated into model's event.
 *
 */
static void handle_preempt_disable(void *data, /* XXX: fill header */)
{
      da_handle_event_wip(preempt_disable_wip);
}

static void handle_preempt_enable(void *data, /* XXX: fill header */)
{
      da_handle_event_wip(preempt_enable_wip);
}

static void handle_sched_waking(void *data, /* XXX: fill header */)
{
      da_handle_event_wip(sched_waking_wip);
}

static int enable_wip(void)
{
      int retval;

      retval = da_monitor_init_wip();
      if (retval)
              return retval;

      rv_attach_trace_probe("wip", /* XXX: tracepoint */, handle_preempt_disable);
      rv_attach_trace_probe("wip", /* XXX: tracepoint */, handle_preempt_enable);
      rv_attach_trace_probe("wip", /* XXX: tracepoint */, handle_sched_waking);

      return 0;
}

该部分的顶部注释解释了总体思想:插桩部分将内核事件转换为模型事件。

追踪回调函数

前三个函数是 wip 模型中三个事件各自回调处理函数的起点。开发者不一定需要使用它们:它们只是起点。

以…为例

void handle_preempt_disable(void *data, /* XXX: fill header */)
{
       da_handle_event_wip(preempt_disable_wip);
}

模型中的 preempt_disable 事件直接连接到 preemptirq:preempt_disable。preemptirq:preempt_disable 事件在 include/trace/events/preemptirq.h 中有以下签名:

TP_PROTO(unsigned long ip, unsigned long parent_ip)

因此,handle_preempt_disable() 函数将如下所示

void handle_preempt_disable(void *data, unsigned long ip, unsigned long parent_ip)

在这种情况下,内核事件与自动机事件一一对应,确实,此函数不需要进行其他更改。

下一个处理函数 handle_preempt_enable() 具有与 handle_preempt_disable() 相同的参数列表。不同之处在于 preempt_enable 事件将用于将系统与模型同步。

最初,模型处于初始状态。然而,系统可能处于也可能不处于初始状态。监视器在得知系统已达到初始状态之前无法开始处理事件。否则,监视器和系统可能会不同步。

查看自动机定义,可以发现系统和模型在 preempt_enable 执行后预期会返回初始状态。因此,它可以在监视部分初始化时用于同步系统和模型。

启动通过一个特殊的处理函数通知,即“da_handle_start_event_$(MONITOR_NAME)(event)”,在本例中为

da_handle_start_event_wip(preempt_enable_wip);

因此,回调函数将如下所示

void handle_preempt_enable(void *data, unsigned long ip, unsigned long parent_ip)
{
      da_handle_start_event_wip(preempt_enable_wip);
}

最后,“handle_sched_waking()”将如下所示

void handle_sched_waking(void *data, struct task_struct *task)
{
      da_handle_event_wip(sched_waking_wip);
}

解释留给读者作为练习。

启用和禁用函数

dot2k 自动创建两个特殊函数

enable_$(MONITOR_NAME)()
disable_$(MONITOR_NAME)()

这些函数分别在监视器启用和禁用时被调用。

它们应该用于将插桩连接和分离到正在运行的系统。开发者必须在相关函数中添加所有需要将其监视器连接和分离到系统的操作。

对于 wip 情况,这些函数被命名为

enable_wip()
disable_wip()

但不需要更改,因为:默认情况下,这些函数会连接和分离 tracepoints_to_attach,这对于此情况已足够。

插桩辅助函数

为了完成插桩,处理函数需要在监视启用阶段连接到内核事件。

RV 接口也方便了这一步骤。例如,宏“rv_attach_trace_probe()”用于将 wip 模型事件连接到相关的内核事件。dot2k 自动在启用阶段为每个模型事件添加“rv_attach_trace_probe()”函数调用,作为建议。

例如,来自 wip 示例模型

static int enable_wip(void)
{
      int retval;

      retval = da_monitor_init_wip();
      if (retval)
              return retval;

      rv_attach_trace_probe("wip", /* XXX: tracepoint */, handle_preempt_enable);
      rv_attach_trace_probe("wip", /* XXX: tracepoint */, handle_sched_waking);
      rv_attach_trace_probe("wip", /* XXX: tracepoint */, handle_preempt_disable);

      return 0;
}

探针需要在禁用阶段分离。

[1] wip 模型介绍于

Documentation/trace/rv/deterministic_automata.rst

wip 监视器介绍于

Documentation/trace/rv/da_monitor_synthesis.rst