eBPF Talk: spinlock 詳解

spinlock 指的是 eBPF 代碼裏使用的 struct bpf_spin_lock

eBPF Talk: 正確地進行統計 裏學習到可以使用 spinlock 對統計進行保護。

spinlock 的用法就是那麼簡單。

但是,spinlock 真的那麼簡單嗎?

  1. 禁止讀寫 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;
        }
    }

    // ...
}
  1. 不使用 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;
    }

    // ...
}
  1. 是否可以在 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;
    }

    // ...
}
  1. kprobe 裏可以使用 spinlock 嗎?

不可以。

因爲無法充分檢查 kprobe bpf prog 的搶佔情況,所以目前不允許在 kprobe 裏使用 spinlock

當前,以下 bpf prog 類型都不允許使用 spinlock

// ${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。

小結

使用起來比較簡單的東西,其背後往往比較複雜;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