守护 spinlock
。
当使用 spinlock
进行统计时 eBPF Talk: 正确地进行统计,必须要使用 bpf_spin_lock()
和 bpf_spin_unlock()
来保护 spinlock
变量:
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
的形式:
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
的实现如下:
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]。
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
和 perfevent
在使用 ringbuf
和 perfevent
时,如果使用 reserve()
和 discard()/commit()
,可以使用 cleanup
特性来简化代码。
比如 ringbuf
,伪代码如下:
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
特性可以简化代码,使得资源管理更加简单。
cleanup: https://clang.llvm.org/docs/AttributeReference.html#cleanup