eBPF Talk: spinlock 詳解
此 spinlock
指的是 eBPF 代碼裏使用的 struct bpf_spin_lock
。
- bpf: introduce bpf_spin_lock[1] since kernel 5.1
從 eBPF Talk: 正確地進行統計 裏學習到可以使用 spinlock
對統計進行保護。
spinlock
的用法就是那麼簡單。
但是,spinlock
真的那麼簡單嗎?
- 禁止讀寫
spinlock
屬性
按照正常的用法,spinlock
屬性就是用來 bpf_spin_lock()
和 bpf_spin_unlock()
的,不會再作它用。
可是,如果在 eBPF 程序裏對 spinlock
屬性進行讀寫,會發生什麼呢?
答案是:在校驗階段報錯 "bpf_spin_lock cannot be accessed directly by load/store"。
// ${KERNEL}/kernel/bpf/verifier.c
/* check read/write into a map element with possible variable offset */
static int check_map_access(struct bpf_verifier_env *env, u32 regno,
int off, int size, bool zero_size_allowed,
enum bpf_access_src src)
{
// ...
if (map_value_has_spin_lock(map)) {
u32 lock = map->spin_lock_off;
/* if any part of struct bpf_spin_lock can be touched by
* load/store reject this program.
* To check that [x1, x2) overlaps with [y1, y2)
* it is sufficient to check x1 < y2 && y1 < x2.
*/
if (reg->smin_value + off < lock + sizeof(struct bpf_spin_lock) &&
lock < reg->umax_value + off + size) {
verbose(env, "bpf_spin_lock cannot be accessed directly by load/store\n");
return -EACCES;
}
}
// ...
}
- 不使用 BTF 時,能使用
spinlock
嗎?
不可以。
BTF 信息用於校驗階段;在校驗時,需要根據 BTF 信息計算 struct bpf_spin_lock
屬性的 offset。
// ${KERNEL}/kernel/bpf/verifier.c
static int process_spin_lock(struct bpf_verifier_env *env, int regno,
bool is_lock)
{
// ...
if (!map->btf) {
verbose(env,
"map '%s' has to have BTF in order to use bpf_spin_lock\n",
map->name);
return -EINVAL;
}
// ...
}
- 是否可以在
spinlock
裏使用另一個spinlock
呢?
不可以。
爲了預防死鎖,當前版本里不允許使用多個 spinlock
。
// ${KERNEL}/kernel/bpf/verifier.c
static int process_spin_lock(struct bpf_verifier_env *env, int regno,
bool is_lock)
{
// ...
if (is_lock) {
if (cur->active_spin_lock) {
verbose(env,
"Locking two bpf_spin_locks are not allowed\n");
return -EINVAL;
}
cur->active_spin_lock = reg->id;
}
// ...
}
kprobe
裏可以使用spinlock
嗎?
不可以。
因爲無法充分檢查 kprobe
bpf prog 的搶佔情況,所以目前不允許在 kprobe
裏使用 spinlock
。
當前,以下 bpf prog 類型都不允許使用 spinlock
。
-
BPF_PROG_TYPE_SOCKET_FILTER
-
BPF_PROG_TYPE_KPROBE
-
BPF_PROG_TYPE_TRACEPOINT
-
BPF_PROG_TYPE_PERF_EVENT
-
BPF_PROG_TYPE_RAW_TRACEPOINT
-
BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE
-
sleepable bpf prog(
fentry.s
/fmod_ret.s
/fexit.s
/lsm.s
/iter.s
)
// ${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)
{
enum bpf_prog_type prog_type = resolve_prog_type(prog);
if (map_value_has_spin_lock(map)) {
if (prog_type == BPF_PROG_TYPE_SOCKET_FILTER) {
verbose(env, "socket filter progs cannot use bpf_spin_lock yet\n");
return -EINVAL;
}
if (is_tracing_prog_type(prog_type)) {
verbose(env, "tracing progs cannot use bpf_spin_lock yet\n");
return -EINVAL;
}
if (prog->aux->sleepable) {
verbose(env, "sleepable progs cannot use bpf_spin_lock yet\n");
return -EINVAL;
}
}
// ...
}
spinlock
的限制與安全檢查
P.S. 以下內容翻譯自 "bpf: introduce bpf_spin_lock" 的 commit message。
-
bpf_spin_lock
只允許在 HASH 和 ARRAY maps 裏使用。 -
爲了安全分析,BTF 信息是強制性帶上的。
-
bpf prog 裏一次只能使用一個
bpf_spin_lock
,因爲多個會導致死鎖。 -
每個 map 元素只允許有一個
struct bpf_spin_lock
。不過,允許 bpf prog 使用任意數量的bpf_spin_lock
是非常非常容易實現的。 -
當
bpf_spin_lock
使用時,不允許使用函數調用(包括bpf2bpf
、幫助函數)。 -
bpf prog 必須在 return 前
bpf_spin_unlock()
。 -
bpf prog 可以通過
bpf_spin_lock()
/bpf_spin_unlock()
訪問struct bpf_spin_lock
。 -
不允許讀寫
struct bpf_spin_lock lock;
屬性。 -
爲了使用
bpf_spin_lock()
幫助函數,map value 的 BTF 信息必須是一個 struct,而且struct bpf_spin_lock anyname;
屬性需要在最外層。不允許將bpf_spin_lock
內嵌到其它 struct 中。 -
"map_lookup" 系統調用不能將
bpf_spin_lock
複製到用戶態。 -
"map_update" 系統調用和幫助函數不能更新
bpf_spin_lock
屬性。 -
bpf_spin_lock
不能在棧上,也不能在網絡包中。bpf_spin_lock
只能在 HASH 或者 ARRAY map value 中。 -
bpf_spin_lock
對 root 用戶和所有程序類型開放。 -
不允許在 map-in-map 的內層 map 裏使用
bpf_spin_lock
。 -
在
bpf_spin_lock
臨界區內不允許使用 "ld_abs"。 -
因爲缺乏搶佔檢查,不允許在 tracing 程序和 socket filter 程序中使用
bpf_spin_lock
。
小結
使用起來比較簡單的東西,其背後往往比較複雜;spinlock
就是其中之一。
附:bpf_spin_lock()
與 bpf_spin_unlock()
的源代碼
// ${KERNEL}/kernel/bpf/helpers.c
#if defined(CONFIG_QUEUED_SPINLOCKS) || defined(CONFIG_BPF_ARCH_SPINLOCK)
static inline void __bpf_spin_lock(struct bpf_spin_lock *lock)
{
arch_spinlock_t *l = (void *)lock;
union {
__u32 val;
arch_spinlock_t lock;
} u = { .lock = __ARCH_SPIN_LOCK_UNLOCKED };
compiletime_assert(u.val == 0, "__ARCH_SPIN_LOCK_UNLOCKED not 0");
BUILD_BUG_ON(sizeof(*l) != sizeof(__u32));
BUILD_BUG_ON(sizeof(*lock) != sizeof(__u32));
arch_spin_lock(l);
}
static inline void __bpf_spin_unlock(struct bpf_spin_lock *lock)
{
arch_spinlock_t *l = (void *)lock;
arch_spin_unlock(l);
}
#else
static inline void __bpf_spin_lock(struct bpf_spin_lock *lock)
{
atomic_t *l = (void *)lock;
BUILD_BUG_ON(sizeof(*l) != sizeof(*lock));
do {
atomic_cond_read_relaxed(l, !VAL);
} while (atomic_xchg(l, 1));
}
static inline void __bpf_spin_unlock(struct bpf_spin_lock *lock)
{
atomic_t *l = (void *)lock;
atomic_set_release(l, 0);
}
#endif
static DEFINE_PER_CPU(unsigned long, irqsave_flags);
static inline void __bpf_spin_lock_irqsave(struct bpf_spin_lock *lock)
{
unsigned long flags;
local_irq_save(flags);
__bpf_spin_lock(lock);
__this_cpu_write(irqsave_flags, flags);
}
notrace BPF_CALL_1(bpf_spin_lock, struct bpf_spin_lock *, lock)
{
__bpf_spin_lock_irqsave(lock);
return 0;
}
const struct bpf_func_proto bpf_spin_lock_proto = {
.func = bpf_spin_lock,
.gpl_only = false,
.ret_type = RET_VOID,
.arg1_type = ARG_PTR_TO_SPIN_LOCK,
};
static inline void __bpf_spin_unlock_irqrestore(struct bpf_spin_lock *lock)
{
unsigned long flags;
flags = __this_cpu_read(irqsave_flags);
__bpf_spin_unlock(lock);
local_irq_restore(flags);
}
notrace BPF_CALL_1(bpf_spin_unlock, struct bpf_spin_lock *, lock)
{
__bpf_spin_unlock_irqrestore(lock);
return 0;
}
const struct bpf_func_proto bpf_spin_unlock_proto = {
.func = bpf_spin_unlock,
.gpl_only = false,
.ret_type = RET_VOID,
.arg1_type = ARG_PTR_TO_SPIN_LOCK,
};
參考資料
[1]
bpf: introduce bpf_spin_lock: https://github.com/torvalds/linux/commit/d83525ca62cf8ebe3271d14c36fb900c294274a2
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/P-m5edfYeIVEDsMUlo6cjQ