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。

返回

  • 成功时,指向新的 struct bpf_cpumask 实例的指针。

  • 如果 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() 释放。

返回

  • 传递给函数的 struct bpf_cpumask 指针。

__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 struct bpf_cpumask * 作为 kptr

如上所述和说明,这些 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 bpf_cpumask * 安全的,并且验证器会拒绝任何试图这样做的程序)。

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

3. cpumask kfuncs

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

3.1 修改 cpumask

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

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

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。

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

/**
 * 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() 是互补的 kfunc,允许调用者原子地测试和设置(或清除)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

正在设置和查询的包含 CPU 的 BPF cpumask。

返回

  • 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

正在清除和查询的包含 CPU 的 BPF cpumask。

返回

  • 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)

AND 两个 cpumask 并存储结果。

参数

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)

OR 两个 cpumask 并存储结果。

参数

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)

XOR 两个 cpumask 并存储结果。

参数

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 的内容复制到另一个 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 指针可以安全地传递给此函数。

返回

  • struct cpumask 中第一个非零位的索引。

__bpf_kfunc u32 bpf_cpumask_first_zero(const struct cpumask *cpumask)

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

参数

const struct cpumask *cpumask

正在查询的 cpumask。

说明

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

返回

  • struct 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

返回

  • 在两个 cpumask 实例中均为非零的第一个位的索引。

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

测试 CPU 是否在 cpumask 中设置。

参数

u32 cpu

正在查询的 CPU。

const struct cpumask *cpumask

正在查询的包含 CPU 的 cpumask。

返回

  • 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 是否是另一个 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


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

4. 添加 BPF cpumask kfuncs

支持的 BPF cpumask kfuncs 集合与 include/linux/cpumask.h 中的 cpumask 操作并非(尚未)完全匹配。 如果需要,这些 cpumask 操作中的任何一个都可以很容易地封装在一个新的 kfunc 中。 如果您想支持新的 cpumask 操作,请随时提交补丁。 如果您确实添加了新的 cpumask kfunc,请在此处记录它,并将任何相关的自测测试用例添加到 cpumask 自测套件中。