bpf_timer 是 eBPF 里基于 hrtimer 实现的定时器。

bpf_timer 最初的需求是在 perf events bpf prog 中做定期采样;后来在 XDP 中可以用来做垃圾回收和健康检测。

bpf_timer demo

在 eBPF 代码里使用 bpf_timer 时,主要有两个步骤:

  1. 定义一个值包含 struct bpf_timer 的 bpf map。
  2. 在 bpf prog 里调用 bpf_timer_init()bpf_timer_set_callback()bpf_timer_start()

示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

struct tcp_timer {
    struct bpf_timer timer;
};

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 10);
    __type(key, struct sk_key);
    __type(value, struct tcp_timer);
} tcp_timers SEC(".maps");

static int
timer_cb(struct bpf_map *map, struct sk_key *key, struct tcp_timer *timer)
{
    bpf_printk("timer_cb, new connection 0x%x -> 0x%x\n", key->saddr, key->daddr);
    return 0;
}

static __noinline void
handle_new_connection(void *ctx, struct sock *sk)
{
    // ...

    bpf_printk("handle_new_connection, new connection 0x%x -> 0x%x\n", key.saddr, key.daddr);

    struct tcp_timer init_timer = {};
    struct tcp_timer *timer = bpf_map_lookup_or_try_init(&tcp_timers, &key, &init_timer);
    // ...

    int ret;
    ret = bpf_timer_init(&timer->timer, &tcp_timers, CLOCK_BOOTTIME);
    //...

    ret = bpf_timer_set_callback(&timer->timer, timer_cb);
    // ...

    ret = bpf_timer_start(&timer->timer, 100, 0);
    // ...
}

SEC("fentry/tcp_connect")
int BPF_PROG(tcp_connect, struct sock *sk)
{
    handle_new_connection(ctx, sk);

    return 0;
}

SEC("fexit/inet_csk_complete_hashdance")
int BPF_PROG(inet_csk_complete_hashdance, struct sock *sk, struct sock *child,
    struct request_sock *req, bool own_req, struct sock *ret)
{
    if (ret)
        handle_new_connection(ctx, ret);

    return 0;
}

完整代码请看:GitHub - bpf-timer

例子的运行效果如下:

1
2
3
4
5
# cat /sys/kernel/debug/tracing/trace_pipe
          <idle>-0       [001] ..s31  6447.898964: bpf_trace_printk: handle_new_connection, new connection 0x8a01a8c0 -> 0xc01a8c0
     ksoftirqd/1-23      [001] ..s1.  6447.899336: bpf_trace_printk: timer_cb, new connection 0x8a01a8c0 -> 0xc01a8c0
           <...>-7423    [000] ...11  6452.363356: bpf_trace_printk: handle_new_connection, new connection 0xf02000a -> 0x64aae940
            curl-7423    [000] ..s2.  6452.363578: bpf_trace_printk: timer_cb, new connection 0xf02000a -> 0x64aae940

bpf_timer 限制

不过,bpf_timer 有不少限制;甚至,上面的 demo 改成 XDP 就不能运行了。

首先,bpf_timer 只能在如下 bpf map 里使用:

  1. BPF_MAP_TYPE_HASH
  2. BPF_MAP_TYPE_LRU_HASH
  3. BPF_MAP_TYPE_ARRAY
  4. map in map

其次,bpf_timer 不支持 tracing bpf prog:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ${KERNEL}/kernel/bpf/verifier.c

static bool is_tracing_prog_type(enum bpf_prog_type type)
{
    switch (type) {
    case BPF_PROG_TYPE_KPROBE:
    case BPF_PROG_TYPE_TRACEPOINT:
    case BPF_PROG_TYPE_PERF_EVENT:
    case BPF_PROG_TYPE_RAW_TRACEPOINT:
    case BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE:
        return true;
    default:
        return false;
    }
}

static int check_map_prog_compatibility(struct bpf_verifier_env *env,
                    struct bpf_map *map,
                    struct bpf_prog *prog)

{
    // ...
    if (btf_record_has_field(map->record, BPF_TIMER)) {
        if (is_tracing_prog_type(prog_type)) {
            verbose(env, "tracing progs cannot use bpf_timer yet\n");
            return -EINVAL;
        }
    }
    // ...
}

接着,bpf_timer 不能和 struct bpf_spin_lock 一起使用,因为 bpf_timer 在内核运行的时候的真实面貌如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ${KERNEL}/kernel/bpf/helpers.c

/* the actual struct hidden inside uapi struct bpf_timer */
struct bpf_timer_kern {
    struct bpf_hrtimer *timer;
    /* bpf_spin_lock is used here instead of spinlock_t to make
     * sure that it always fits into space reserved by struct bpf_timer
     * regardless of LOCKDEP and spinlock debug flags.
     */
    struct bpf_spin_lock lock;
} __attribute__((aligned(8)));

最后,bpf_timer_init() 只支持 CLOCK_MONOTONICCLOCK_REALTIMECLOCK_BOOTTIME 三种 clockid。如果对时间精度不敏感,任选一个即可。

额外情况,在 NMI context 里,bpf_timer helpers 都会返回 -EOPNOTSUPP

小结

bpf_timer 是 eBPF 里基于 hrtimer 实现的定时器。

在使用 bpf_timer 时,需要注意以上限制。