BPF cpumask kfuncs

1. 简介

struct cpumask 是内核中的位图数据结构,其索引反映系统上的 CPU。通常,cpumask 用于跟踪任务被分配到哪些 CPU,但它们也可以用于例如跟踪哪些核心与调度域相关联,机器上的哪些核心处于空闲状态等等。

BPF 为程序提供了一组 BPF 内核函数 (kfuncs),可用于分配、更改、查询和释放 cpumask。

2. BPF cpumask 对象

BPF 程序可以使用两种不同类型的 cpumask。

2.1 struct bpf_cpumask *

struct bpf_cpumask * 是由 BPF 代表 BPF 程序分配的 cpumask,其生命周期完全由 BPF 控制。这些 cpumask 受 RCU 保护,可以被修改,可以用作 kptr,并且可以安全地转换为 struct cpumask *

2.1.1 struct bpf_cpumask * 生命周期

使用以下函数分配、获取和释放 struct bpf_cpumask *

__bpf_kfunc struct bpf_cpumask *bpf_cpumask_create(void)

创建可变的 BPF cpumask。

参数

void

无参数

描述

分配一个可以由 BPF 程序查询、修改、获取和释放的 cpumask。此函数返回的 cpumask 必须以 kptr 的形式嵌入到 map 中,或者使用 bpf_cpumask_release() 释放。

bpf_cpumask_create() 使用 BPF 内存分配器分配内存,并且不会阻塞。如果没有可用内存,它可能会返回 NULL。

__bpf_kfunc struct bpf_cpumask *bpf_cpumask_acquire(struct bpf_cpumask *cpumask)

获取对 BPF cpumask 的引用。

参数

struct bpf_cpumask *cpumask

正在获取的 BPF cpumask。该 cpumask 必须是可信指针。

描述

获取对 BPF cpumask 的引用。此函数返回的 cpumask 必须以 kptr 的形式嵌入到 map 中,或者使用 bpf_cpumask_release() 释放。

__bpf_kfunc void bpf_cpumask_release(struct bpf_cpumask *cpumask)

释放先前获取的 BPF cpumask。

参数

struct bpf_cpumask *cpumask

正在释放的 cpumask。

描述

释放对 BPF cpumask 先前获取的引用。当 BPF cpumask 的最后一个引用被释放时,它随后在 BPF 内存分配器的 RCU 回调中被释放。

例如

struct cpumask_map_value {
        struct bpf_cpumask __kptr * cpumask;
};

struct array_map {
        __uint(type, BPF_MAP_TYPE_ARRAY);
        __type(key, int);
        __type(value, struct cpumask_map_value);
        __uint(max_entries, 65536);
} cpumask_map SEC(".maps");

static int cpumask_map_insert(struct bpf_cpumask *mask, u32 pid)
{
        struct cpumask_map_value local, *v;
        long status;
        struct bpf_cpumask *old;
        u32 key = pid;

        local.cpumask = NULL;
        status = bpf_map_update_elem(&cpumask_map, &key, &local, 0);
        if (status) {
                bpf_cpumask_release(mask);
                return status;
        }

        v = bpf_map_lookup_elem(&cpumask_map, &key);
        if (!v) {
                bpf_cpumask_release(mask);
                return -ENOENT;
        }

        old = bpf_kptr_xchg(&v->cpumask, mask);
        if (old)
                bpf_cpumask_release(old);

        return 0;
}

/**
 * A sample tracepoint showing how a task's cpumask can be queried and
 * recorded as a kptr.
 */
SEC("tp_btf/task_newtask")
int BPF_PROG(record_task_cpumask, struct task_struct *task, u64 clone_flags)
{
        struct bpf_cpumask *cpumask;
        int ret;

        cpumask = bpf_cpumask_create();
        if (!cpumask)
                return -ENOMEM;

        if (!bpf_cpumask_full(task->cpus_ptr))
                bpf_printk("task %s has CPU affinity", task->comm);

        bpf_cpumask_copy(cpumask, task->cpus_ptr);
        return cpumask_map_insert(cpumask, task->pid);
}

2.1.1 作为 kptr 的 struct bpf_cpumask *

如上所述并示出,这些 struct bpf_cpumask * 对象也可以存储在 map 中并用作 kptr。如果一个 struct bpf_cpumask * 在 map 中,则可以使用 bpf_kptr_xchg() 从 map 中删除引用,或者使用 RCU 有机会获取。

/* struct containing the struct bpf_cpumask kptr which is stored in the map. */
struct cpumasks_kfunc_map_value {
        struct bpf_cpumask __kptr * bpf_cpumask;
};

/* The map containing struct cpumasks_kfunc_map_value entries. */
struct {
        __uint(type, BPF_MAP_TYPE_ARRAY);
        __type(key, int);
        __type(value, struct cpumasks_kfunc_map_value);
        __uint(max_entries, 1);
} cpumasks_kfunc_map SEC(".maps");

/* ... */

/**
 * A simple example tracepoint program showing how a
 * struct bpf_cpumask * kptr that is stored in a map can
 * be passed to kfuncs using RCU protection.
 */
SEC("tp_btf/cgroup_mkdir")
int BPF_PROG(cgrp_ancestor_example, struct cgroup *cgrp, const char *path)
{
        struct bpf_cpumask *kptr;
        struct cpumasks_kfunc_map_value *v;
        u32 key = 0;

        /* Assume a bpf_cpumask * kptr was previously stored in the map. */
        v = bpf_map_lookup_elem(&cpumasks_kfunc_map, &key);
        if (!v)
                return -ENOENT;

        bpf_rcu_read_lock();
        /* Acquire a reference to the bpf_cpumask * kptr that's already stored in the map. */
        kptr = v->cpumask;
        if (!kptr) {
                /* If no bpf_cpumask was present in the map, it's because
                 * we're racing with another CPU that removed it with
                 * bpf_kptr_xchg() between the bpf_map_lookup_elem()
                 * above, and our load of the pointer from the map.
                 */
                bpf_rcu_read_unlock();
                return -EBUSY;
        }

        bpf_cpumask_setall(kptr);
        bpf_rcu_read_unlock();

        return 0;
}

2.2 struct cpumask

struct cpumask 是实际包含被查询、修改等的 cpumask 位图的对象。struct bpf_cpumask 包装一个 struct cpumask,这就是为什么将其转换为 struct cpumask 是安全的原因(但请注意,将 struct cpumask * 转换为 struct bpf_cpumask * 是**不**安全的,并且验证器将拒绝任何尝试这样做的程序)。

正如我们将在下面看到的,任何修改其 cpumask 参数的 kfunc 都将使用 struct bpf_cpumask * 作为该参数。任何只查询 cpumask 的参数都将使用 struct cpumask *

3. cpumask kfuncs

上面,我们描述了可用于分配、获取、释放等的 kfuncs struct bpf_cpumask *。本文档的这一部分将描述用于修改和查询 cpumask 的 kfuncs。

3.1 修改 cpumask

一些 cpumask kfuncs 是“只读”的,因为它们不修改其任何参数,而另一些则修改至少一个参数(这意味着该参数必须是 struct bpf_cpumask *,如上所述)。

本节将描述修改至少一个参数的所有 cpumask kfuncs。下面的 3.2 查询 cpumask 描述了只读 kfuncs。

3.1.1 设置和清除 CPU

bpf_cpumask_set_cpu()bpf_cpumask_clear_cpu() 可用于分别设置和清除 struct bpf_cpumask 中的 CPU

__bpf_kfunc void bpf_cpumask_set_cpu(u32 cpu, struct bpf_cpumask *cpumask)

在 BPF cpumask 中为 CPU 设置一位。

参数

u32 cpu

要在 cpumask 中设置的 CPU。

struct bpf_cpumask *cpumask

要在其中设置一位的 BPF cpumask。

__bpf_kfunc void bpf_cpumask_clear_cpu(u32 cpu, struct bpf_cpumask *cpumask)

在 BPF cpumask 中为 CPU 清除一位。

参数

u32 cpu

要从 cpumask 中清除的 CPU。

struct bpf_cpumask *cpumask

要在其中清除一位的 BPF cpumask。

这些 kfuncs 非常简单,可以按如下方式使用,例如

/**
 * A sample tracepoint showing how a cpumask can be queried.
 */
SEC("tp_btf/task_newtask")
int BPF_PROG(test_set_clear_cpu, struct task_struct *task, u64 clone_flags)
{
        struct bpf_cpumask *cpumask;

        cpumask = bpf_cpumask_create();
        if (!cpumask)
                return -ENOMEM;

        bpf_cpumask_set_cpu(0, cpumask);
        if (!bpf_cpumask_test_cpu(0, cast(cpumask)))
                /* Should never happen. */
                goto release_exit;

        bpf_cpumask_clear_cpu(0, cpumask);
        if (bpf_cpumask_test_cpu(0, cast(cpumask)))
                /* Should never happen. */
                goto release_exit;

        /* struct cpumask * pointers such as task->cpus_ptr can also be queried. */
        if (bpf_cpumask_test_cpu(0, task->cpus_ptr))
                bpf_printk("task %s can use CPU %d", task->comm, 0);

release_exit:
        bpf_cpumask_release(cpumask);
        return 0;
}

bpf_cpumask_test_and_set_cpu()bpf_cpumask_test_and_clear_cpu() 是互补的 kfuncs,允许调用者以原子方式测试和设置(或清除)CPU

__bpf_kfunc bool bpf_cpumask_test_and_set_cpu(u32 cpu, struct bpf_cpumask *cpumask)

原子地测试并设置 BPF cpumask 中的一个 CPU。

参数

u32 cpu

正在设置和查询的 CPU。

struct bpf_cpumask *cpumask

正在设置和查询的 BPF cpumask,其中包含一个 CPU。

返回值

  • true - cpu 已在 cpumask 中设置

  • false - cpu 未在 cpumask 中设置,或 cpu 无效。

__bpf_kfunc bool bpf_cpumask_test_and_clear_cpu(u32 cpu, struct bpf_cpumask *cpumask)

原子地测试并清除 BPF cpumask 中的一个 CPU。

参数

u32 cpu

正在清除和查询的 CPU。

struct bpf_cpumask *cpumask

正在清除和查询的 BPF cpumask,其中包含一个 CPU。

返回值

  • true - cpu 已在 cpumask 中设置

  • false - cpu 未在 cpumask 中设置,或 cpu 无效。


我们还可以使用 bpf_cpumask_setall()bpf_cpumask_clear() 在一次操作中设置和清除整个 struct bpf_cpumask * 对象。

__bpf_kfunc void bpf_cpumask_setall(struct bpf_cpumask *cpumask)

设置 BPF cpumask 中的所有位。

参数

struct bpf_cpumask *cpumask

所有位都被设置的 BPF cpumask。

__bpf_kfunc void bpf_cpumask_clear(struct bpf_cpumask *cpumask)

清除 BPF cpumask 中的所有位。

参数

struct bpf_cpumask *cpumask

正在被清除的 BPF cpumask。

3.1.2 cpumask 之间的操作

除了在单个 cpumask 中设置和清除单个 CPU 之外,调用者还可以使用 bpf_cpumask_and()bpf_cpumask_or()bpf_cpumask_xor() 在多个 cpumask 之间执行按位操作。

__bpf_kfunc bool bpf_cpumask_and(struct bpf_cpumask *dst, const struct cpumask *src1, const struct cpumask *src2)

对两个 cpumask 执行 AND 操作并存储结果。

参数

struct bpf_cpumask *dst

存储结果的 BPF cpumask。

const struct cpumask *src1

第一个输入。

const struct cpumask *src2

第二个输入。

返回值

  • true - dst 在操作后至少设置了一位

  • false - dst 在操作后为空

描述

struct bpf_cpumask 指针可以安全地传递给 src1src2

__bpf_kfunc void bpf_cpumask_or(struct bpf_cpumask *dst, const struct cpumask *src1, const struct cpumask *src2)

对两个 cpumask 执行 OR 操作并存储结果。

参数

struct bpf_cpumask *dst

存储结果的 BPF cpumask。

const struct cpumask *src1

第一个输入。

const struct cpumask *src2

第二个输入。

描述

struct bpf_cpumask 指针可以安全地传递给 src1src2

__bpf_kfunc void bpf_cpumask_xor(struct bpf_cpumask *dst, const struct cpumask *src1, const struct cpumask *src2)

对两个 cpumask 执行 XOR 操作并存储结果。

参数

struct bpf_cpumask *dst

存储结果的 BPF cpumask。

const struct cpumask *src1

第一个输入。

const struct cpumask *src2

第二个输入。

描述

struct bpf_cpumask 指针可以安全地传递给 src1src2

以下是如何使用它们的示例。请注意,此示例中显示的一些 kfunc 将在下面更详细地介绍。

/**
 * A sample tracepoint showing how a cpumask can be mutated using
   bitwise operators (and queried).
 */
SEC("tp_btf/task_newtask")
int BPF_PROG(test_and_or_xor, struct task_struct *task, u64 clone_flags)
{
        struct bpf_cpumask *mask1, *mask2, *dst1, *dst2;

        mask1 = bpf_cpumask_create();
        if (!mask1)
                return -ENOMEM;

        mask2 = bpf_cpumask_create();
        if (!mask2) {
                bpf_cpumask_release(mask1);
                return -ENOMEM;
        }

        // ...Safely create the other two masks... */

        bpf_cpumask_set_cpu(0, mask1);
        bpf_cpumask_set_cpu(1, mask2);
        bpf_cpumask_and(dst1, (const struct cpumask *)mask1, (const struct cpumask *)mask2);
        if (!bpf_cpumask_empty((const struct cpumask *)dst1))
                /* Should never happen. */
                goto release_exit;

        bpf_cpumask_or(dst1, (const struct cpumask *)mask1, (const struct cpumask *)mask2);
        if (!bpf_cpumask_test_cpu(0, (const struct cpumask *)dst1))
                /* Should never happen. */
                goto release_exit;

        if (!bpf_cpumask_test_cpu(1, (const struct cpumask *)dst1))
                /* Should never happen. */
                goto release_exit;

        bpf_cpumask_xor(dst2, (const struct cpumask *)mask1, (const struct cpumask *)mask2);
        if (!bpf_cpumask_equal((const struct cpumask *)dst1,
                               (const struct cpumask *)dst2))
                /* Should never happen. */
                goto release_exit;

 release_exit:
        bpf_cpumask_release(mask1);
        bpf_cpumask_release(mask2);
        bpf_cpumask_release(dst1);
        bpf_cpumask_release(dst2);
        return 0;
}

可以使用 bpf_cpumask_copy() 将整个 cpumask 的内容复制到另一个。

__bpf_kfunc void bpf_cpumask_copy(struct bpf_cpumask *dst, const struct cpumask *src)

将 cpumask 的内容复制到 BPF cpumask 中。

参数

struct bpf_cpumask *dst

正在复制到的 BPF cpumask。

const struct cpumask *src

正在复制的 cpumask。

描述

struct bpf_cpumask 指针可以安全地传递给 src


3.2 查询 cpumask

除了上述 kfunc 之外,还有一组只读 kfunc,可用于查询 cpumask 的内容。

__bpf_kfunc u32 bpf_cpumask_first(const struct cpumask *cpumask)

获取 cpumask 中第一个非零位的索引。

参数

const struct cpumask *cpumask

正在查询的 cpumask。

描述

查找 cpumask 的第一个非零位的索引。 struct bpf_cpumask 指针可以安全地传递给此函数。

__bpf_kfunc u32 bpf_cpumask_first_zero(const struct cpumask *cpumask)

获取 cpumask 中第一个未设置位的索引。

参数

const struct cpumask *cpumask

正在查询的 cpumask。

描述

查找 cpumask 的第一个未设置位的索引。 struct bpf_cpumask 指针可以安全地传递给此函数。

__bpf_kfunc u32 bpf_cpumask_first_and(const struct cpumask *src1, const struct cpumask *src2)

返回两个 cpumask 的 AND 运算中第一个非零位的索引。

参数

const struct cpumask *src1

第一个 cpumask。

const struct cpumask *src2

第二个 cpumask。

描述

查找两个 cpumask 的 AND 运算的第一个非零位的索引。 struct bpf_cpumask 指针可以安全地传递给 src1src2

__bpf_kfunc bool bpf_cpumask_test_cpu(u32 cpu, const struct cpumask *cpumask)

测试是否在 cpumask 中设置了 CPU。

参数

u32 cpu

正在查询的 CPU。

const struct cpumask *cpumask

正在查询的 cpumask,以确定是否包含 CPU。

返回值

  • true - cpu 已在 cpumask 中设置

  • false - cpu 未在 cpumask 中设置,或 cpu 是无效的 cpu。

__bpf_kfunc u32 bpf_cpumask_weight(const struct cpumask *cpumask)

返回 cpumask 中设置的位数。

参数

const struct cpumask *cpumask

正在查询的 cpumask。

描述

计算给定 cpumask 中已设置的位数。

__bpf_kfunc bool bpf_cpumask_equal(const struct cpumask *src1, const struct cpumask *src2)

检查两个 cpumask 是否相等。

参数

const struct cpumask *src1

第一个输入。

const struct cpumask *src2

第二个输入。

返回值

  • true - src1src2 中设置的位相同。

  • false - src1src2 至少在一个位上不同。

描述

struct bpf_cpumask 指针可以安全地传递给 src1src2

__bpf_kfunc bool bpf_cpumask_intersects(const struct cpumask *src1, const struct cpumask *src2)

检查两个 cpumask 是否重叠。

参数

const struct cpumask *src1

第一个输入。

const struct cpumask *src2

第二个输入。

返回值

  • true - src1src2 至少有一个相同的位被设置。

  • false - src1src2 没有设置任何相同的位。

描述

struct bpf_cpumask 指针可以安全地传递给 src1src2

__bpf_kfunc bool bpf_cpumask_subset(const struct cpumask *src1, const struct cpumask *src2)

检查一个 cpumask 是否是另一个的子集。

参数

const struct cpumask *src1

第一个被检查的 cpumask 作为子集。

const struct cpumask *src2

第二个被检查的 cpumask 作为超集。

返回值

  • true - src1 的所有位都在 src2 中设置。

  • false - src1 中至少有一位没有在 src2 中设置。

描述

struct bpf_cpumask 指针可以安全地传递给 src1src2

__bpf_kfunc bool bpf_cpumask_empty(const struct cpumask *cpumask)

检查一个 cpumask 是否为空。

参数

const struct cpumask *cpumask

被检查的 cpumask。

返回值

  • true - cpumask 中没有设置任何位。

  • false - cpumask 中至少设置了一位。

描述

一个 struct bpf_cpumask 指针可以安全地传递给 cpumask

__bpf_kfunc bool bpf_cpumask_full(const struct cpumask *cpumask)

检查一个 cpumask 是否所有位都被设置。

参数

const struct cpumask *cpumask

被检查的 cpumask。

返回值

  • true - cpumask 中的所有位都被设置。

  • false - cpumask 中至少有一位被清除。

描述

一个 struct bpf_cpumask 指针可以安全地传递给 cpumask

__bpf_kfunc u32 bpf_cpumask_any_distribute(const struct cpumask *cpumask)

从 cpumask 返回一个随机设置的 CPU。

参数

const struct cpumask *cpumask

正在查询的 cpumask。

返回值

  • 如果至少设置了一位,则在 [0, num_cpus) 范围内返回一个随机设置的位。

  • 如果未设置任何位,则返回 >= num_cpus。

描述

struct bpf_cpumask 指针可以安全地传递给 src

__bpf_kfunc u32 bpf_cpumask_any_and_distribute(const struct cpumask *src1, const struct cpumask *src2)

从两个 cpumask 的 AND 运算结果中返回一个随机设置的 CPU。

参数

const struct cpumask *src1

第一个 cpumask。

const struct cpumask *src2

第二个 cpumask。

返回值

  • 如果至少设置了一位,则从两个 cpumask 的 AND 运算结果中,在 [0, num_cpus) 范围内返回一个随机设置的位。

  • 如果未设置任何位,则返回 >= num_cpus。

描述

struct bpf_cpumask 指针可以安全地传递给 src1src2


上面展示了这些查询 kfunc 的一些示例用法。我们不会在这里重复这些示例。但请注意,所有上述 kfunc 都在 tools/testing/selftests/bpf/progs/cpumask_success.c 中进行了测试,因此如果您想了解更多关于如何使用它们的示例,请查看那里。

4. 添加 BPF cpumask kfunc

目前支持的 BPF cpumask kfunc 集合与 include/linux/cpumask.h 中的 cpumask 操作不是(还不是)一一对应的。如果需要,可以将任何这些 cpumask 操作轻松地封装在一个新的 kfunc 中。如果您想支持新的 cpumask 操作,请随时提交补丁。如果您添加了新的 cpumask kfunc,请在此处记录它,并将任何相关的自测试用例添加到 cpumask 自测试套件中。