守护 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
特性可以简化代码,使得资源管理更加简单。