ebpf 札记(3): 一个跟踪 kernel I/O request 生命周期的脚本

2023-10-04
1分钟阅读时长

上周睡眠一直有问题,没有继续看 samples 里面的代码。写点别的来充数。

现在在追踪 kernel I/O request ,一个 request 被创建出来之后,会在多个函数中传递,直到被释放。 这个过程中它被那些内核函数调用?这是一个复杂的问题。幸好很多处理 request 的函数都是以 struct request* 作为入参, 这给我们一个跟踪 request 的思路。

同样的思路我还用来跟踪 nginx 里面的运行过程。

#!/usr/bin/env bpftrace

BEGIN {
  @count = 0;
}

kretprobe:blk_mq_alloc_request
{
  if (@req == 0) {
    @req = retval;
    printf("%llu, %d,  %s\n", (struct request *)@req, pid, probe);
    printf("comm: %s, kstack: \n%s\n", comm, kstack);
  }
  if (retval == @req) {
    printf("duplicated req: %d, %llu\n", pid, @req);
  }
}

kprobe:blk_*
{
  if (@req != 0) {
    if (arg1 == @req || arg0 == @req) {
      $rid = arg0;
      if (arg1 == @req) {
	$rid = arg1
      }
      printf("%llu, %s\n", (struct request *)$rid, probe);
      printf("%llu, %s\n%s\n", @req, probe, kstack);
    }
  }
}

kprobe:blk_mq_free_request
/ @req > 0 /
{
  if (@req == arg0) {
    @count += 1;
    if (@count > 3) {
      exit();
    }
    @req = 0;
  }
}

现在 bpftrace 的 kprobe section 不支持更复杂的匹配方式,倒是挺遗憾的。我想 「匹配所有 blk_ 开头的 kprobe 但是 blk_mq_free_request 除外」,但是没有找到方法实现。

我还糊了一个 python 脚本解释拿到的栈:

(root): entry_SYSCALL_64_after_hwframe
|
\-entry_SYSCALL_64_after_hwframe
 |
 \-do_syscall_64
  |
  \-__x64_sys_ioctl
   |
   \-sg_ioctl
    |
    \-sg_ioctl_common
     |
     \-sg_new_write.isra.0
      |
      \-sg_common_write.isra.0
       |
       \-kretprobe_trampoline(blk_mq_alloc_request)
       |
       \-blk_rq_map_user_iov
       |
       \-blk_account_io_start
       |
       \-blk_mq_sched_insert_request
        |
        \-blk_mq_run_hw_queue
         |
         \-__blk_mq_delay_run_hw_queue
          |
          \-__blk_mq_run_hw_queue
           |
           \-blk_mq_sched_dispatch_requests
            |
            \-__blk_mq_sched_dispatch_requests
             |
             \-blk_mq_get_driver_tag
             |
             \-blk_mq_dispatch_rq_list
              |
              \-blk_mq_start_request
              |
              \-scsi_queue_rq
               |
               \-blk_add_timer
      |
      \-blk_rq_map_user
       |
       \-blk_rq_append_bio
      |
      \-blk_execute_rq_nowait
       |
       \-blk_mq_request_bypass_insert

(root): secondary_startup_64_no_verify
|
\-secondary_startup_64_no_verify
 |
 \-start_secondary
  |
  \-cpu_startup_entry
   |
   \-do_idle
    |
    \-flush_smp_call_function_from_idle
     |
     \-do_softirq
      |
      \-__softirqentry_text_start
       |
       \-blk_done_softirq
        |
        \-blk_complete_reqs
         |
         \-scsi_complete
          |
          \-scsi_finish_command
           |
           \-scsi_io_completion
            |
            \-scsi_end_request
             |
             \-blk_stat_add
             |
             \-blk_account_io_done
             |
             \-__blk_mq_end_request
              |
              \-blk_put_request
              |
              \-sg_rq_end_io
               |
               \-blk_mq_free_request

TODO:

  1. 为什么 blk_mq_alloc_request 会重入?