守护 spinlock

当使用 spinlock 进行统计时 eBPF Talk: 正确地进行统计,必须要使用 bpf_spin_lock()bpf_spin_unlock() 来保护 spinlock 变量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
struct xdp_stat_item {
    u64 pkt_cnt;
    u64 pkt_byte;
    struct bpf_spin_lock lock;
};

static __always_inline void
stat_xdp(struct xdp_md *ctx)
{
    stat = (typeof(stat)) bpf_map_lookup_elem(&stats, &key);
    if (stat) {
        bpf_spin_lock(&stat->lock);
        stat->pkt_cnt++;
        stat->pkt_byte += (u64)(ctx->data_end - ctx->data);
        bpf_spin_unlock(&stat->lock);
    }
}

然而,改写成 guard 的形式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static __always_inline void
stat_xdp(struct xdp_md *ctx)
{
    stat = (typeof(stat)) bpf_map_lookup_elem(&stats, &key);
    if (stat) {
        guard(&stat->lock);
        stat->pkt_cnt++;
        stat->pkt_byte += (u64)(ctx->data_end - ctx->data);
    }
}

会更加简洁,且不需要关心 bpf_spin_lock()bpf_spin_unlock() 的调用。

guard 实现

此处,guard 的实现如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct guard_spinlock_t {
    struct bpf_spin_lock *lock;
};

void
guard_spinlock_destructor(struct guard_spinlock_t *guard)
{
    bpf_spin_unlock(guard->lock);
}

#define guard_spinlock_constructor(lock)        \
({                                              \
    struct guard_spinlock_t guard = { lock };   \
    bpf_spin_lock(lock);                        \
    guard;                                      \
})

#define __cleanup(fn) __attribute__((cleanup(fn)))

#define guard(lock)                                                     \
    struct guard_spinlock_t var __cleanup(guard_spinlock_destructor) =  \
        guard_spinlock_constructor(lock)

guard 宏定义了一个 guard_spinlock_t 结构体变量 var,并在作用域结束时,调用 guard_spinlock_destructor() 函数解锁 lock

这儿依赖 clang 编译器的 cleanup 特性。

cleanup 特性

参考 clang 文档:cleanup

1
2
3
This attribute allows a function to be run when a local variable goes out of
scope. The attribute takes the identifier of a function with a parameter type
that is a pointer to the type with the attribute.

翻译:此属性允许在局部变量超出作用域时运行一个函数。该属性接受一个函数的标识符,该函数的参数类型是一个指向具有此属性类型的指针。

比如上面的 guard(&stat->lock),会展开成 struct guard_spinlock_t var __attribute__((cleanup(guard_spinlock_destructor))) = ({ struct guard_spinlock_t guard = { &stat->lock }; bpf_spin_lock(&stat->lock); guard; })。 在定义局部变量 var 时,便已调用 bpf_spin_lock(&stat->lock);而在 var 超出作用域时,会在 guard_spinlock_destructor() 函数中调用 bpf_spin_unlock(&stat->lock)

因此,guard(&stat->lock) 的临界区便是其所在作用域的剩余部分。

cleanup 特性应用到 ringbuf

在使用 ringbuf 时,如果使用 reserve()discard()/commit(),可以使用 cleanup 特性来简化代码。

伪代码如下:

 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
struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
} ringbuf SEC(".maps");

struct ringbuf_data {
    __u8 data[64];
};

struct guard_ringbuf {
    void *data;
    int *err;
};

void
guard_ringbuf_destructor(struct guard_ringbuf *guard)
{
    if (!guard->data)
        return;

    if (*guard->err)
        bpf_ringbuf_discard(guard->data, 0);
    else
        bpf_ringbuf_submit(guard->data, 0);
}

#define guard_ringbuf_constructor(ringbuf, size, err)   \
({                                                      \
    struct guard_ringbuf guard = { };                   \
    guard.err = err;                                    \
    guard.data = bpf_ringbuf_reserve(ringbuf, size, 0); \
    guard;                                              \
})

#define guard_ringbuf(ringbuf, data, err)                           \
    struct guard_ringbuf _g __cleanup(guard_ringbuf_destructor) =   \
        guard_ringbuf_constructor(ringbuf, sizeof(*data), err);     \
    data = (typeof(data)) _g.data;

SEC("xdp")
int xdp_fn(struct xdp_md *ctx)
{
    struct ringbuf_data *data;
    int err = 0;

    guard_ringbuf(&ringbuf, data, &err);
    if (!data)
        return XDP_DROP;

    /* do something with data, and use err to determine whether to commit or
     * discard the data.
     */

    return XDP_PASS;
}

总结

cleanup 特性可以简化代码,使得资源管理更加简单。