BPF_MAP_TYPE_ARRAY 和 BPF_MAP_TYPE_PERCPU_ARRAY

注意

  • BPF_MAP_TYPE_ARRAY 在核心版本 3.19 中引入

  • BPF_MAP_TYPE_PERCPU_ARRAY 在版本 4.6 中引入

BPF_MAP_TYPE_ARRAYBPF_MAP_TYPE_PERCPU_ARRAY 提供通用的陣列儲存。鍵型別是一個無符號 32 位整數(4 位元組),對映是恆定大小的。陣列的大小在建立時透過 max_entries 定義。所有陣列元素在建立時預先分配並零初始化。BPF_MAP_TYPE_PERCPU_ARRAY 為每個 CPU 使用不同的記憶體區域,而 BPF_MAP_TYPE_ARRAY 使用相同的記憶體區域。儲存的值可以是任何大小,但是,所有陣列元素都對齊到 8 位元組。

自核心 5.5 版本起,透過設定標誌 BPF_F_MMAPABLE,可以為 BPF_MAP_TYPE_ARRAY 啟用記憶體對映。對映定義是頁對齊的,並從第一頁開始。分配足夠的頁大小和頁對齊的記憶體塊以儲存所有陣列值,從第二頁開始,這在某些情況下會導致記憶體的過度分配。使用此功能的優點是提高了效能和易用性,因為使用者空間程式不需要使用輔助函式來訪問和修改資料。

用法

核心 BPF

bpf_map_lookup_elem()

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)

可以使用 bpf_map_lookup_elem() 輔助函式檢索陣列元素。此輔助函式返回指向陣列元素的指標,因此為了避免與使用者空間讀取值時發生資料競爭,使用者在原地更新值時必須使用 __sync_fetch_and_add() 等原語。

bpf_map_update_elem()

long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)

可以使用 bpf_map_update_elem() 輔助函式更新陣列元素。

bpf_map_update_elem() 成功時返回 0,失敗時返回負錯誤碼。

由於陣列是固定大小的,因此不支援 bpf_map_delete_elem()。要清除陣列元素,可以使用 bpf_map_update_elem() 向該索引插入一個零值。

Per CPU 陣列

儲存在 BPF_MAP_TYPE_ARRAY 中的值可以被不同 CPU 上的多個程式訪問。要將儲存限制在單個 CPU,可以使用 BPF_MAP_TYPE_PERCPU_ARRAY

使用 BPF_MAP_TYPE_PERCPU_ARRAY 時,bpf_map_update_elem()bpf_map_lookup_elem() 輔助函式會自動訪問當前 CPU 的槽位。

bpf_map_lookup_percpu_elem()

void *bpf_map_lookup_percpu_elem(struct bpf_map *map, const void *key, u32 cpu)

可以使用 bpf_map_lookup_percpu_elem() 輔助函式查詢特定 CPU 的陣列值。成功時返回值,如果未找到條目或 cpu 無效,則返回 NULL

併發

自核心版本 5.1 起,BPF 基礎設施提供了 struct bpf_spin_lock 用於同步訪問。

使用者空間

從使用者空間訪問使用與上述同名的 libbpf API,對映透過其 fd 標識。

示例

有關功能示例,請參閱 tools/testing/selftests/bpf 目錄。以下程式碼示例演示了 API 的用法。

核心 BPF

此程式碼片段展示瞭如何在 BPF 程式中宣告一個數組。

struct {
        __uint(type, BPF_MAP_TYPE_ARRAY);
        __type(key, u32);
        __type(value, long);
        __uint(max_entries, 256);
} my_map SEC(".maps");

此示例 BPF 程式展示瞭如何訪問陣列元素。

int bpf_prog(struct __sk_buff *skb)
{
        struct iphdr ip;
        int index;
        long *value;

        if (bpf_skb_load_bytes(skb, ETH_HLEN, &ip, sizeof(ip)) < 0)
                return 0;

        index = ip.protocol;
        value = bpf_map_lookup_elem(&my_map, &index);
        if (value)
                __sync_fetch_and_add(value, skb->len);

        return 0;
}

使用者空間

BPF_MAP_TYPE_ARRAY

此程式碼片段展示瞭如何使用 bpf_map_create_opts 設定標誌來建立陣列。

#include <bpf/libbpf.h>
#include <bpf/bpf.h>

int create_array()
{
        int fd;
        LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = BPF_F_MMAPABLE);

        fd = bpf_map_create(BPF_MAP_TYPE_ARRAY,
                            "example_array",       /* name */
                            sizeof(__u32),         /* key size */
                            sizeof(long),          /* value size */
                            256,                   /* max entries */
                            &opts);                /* create opts */
        return fd;
}

此程式碼片段展示瞭如何初始化陣列的元素。

int initialize_array(int fd)
{
        __u32 i;
        long value;
        int ret;

        for (i = 0; i < 256; i++) {
                value = i;
                ret = bpf_map_update_elem(fd, &i, &value, BPF_ANY);
                if (ret < 0)
                        return ret;
        }

        return ret;
}

此程式碼片段展示瞭如何從陣列中檢索元素值。

int lookup(int fd)
{
        __u32 index = 42;
        long value;
        int ret;

        ret = bpf_map_lookup_elem(fd, &index, &value);
        if (ret < 0)
                return ret;

        /* use value here */
        assert(value == 42);

        return ret;
}

BPF_MAP_TYPE_PERCPU_ARRAY

此程式碼片段展示瞭如何初始化每 CPU 陣列的元素。

int initialize_array(int fd)
{
        int ncpus = libbpf_num_possible_cpus();
        long values[ncpus];
        __u32 i, j;
        int ret;

        for (i = 0; i < 256 ; i++) {
                for (j = 0; j < ncpus; j++)
                        values[j] = i;
                ret = bpf_map_update_elem(fd, &i, &values, BPF_ANY);
                if (ret < 0)
                        return ret;
        }

        return ret;
}

此程式碼片段展示瞭如何訪問陣列值的每 CPU 元素。

int lookup(int fd)
{
        int ncpus = libbpf_num_possible_cpus();
        __u32 index = 42, j;
        long values[ncpus];
        int ret;

        ret = bpf_map_lookup_elem(fd, &index, &values);
        if (ret < 0)
                return ret;

        for (j = 0; j < ncpus; j++) {
                /* Use per CPU value here */
                assert(values[j] == 42);
        }

        return ret;
}

語義

如上例所示,在使用者空間中訪問 BPF_MAP_TYPE_PERCPU_ARRAY 時,每個值都是一個包含 ncpus 元素的陣列。

呼叫 bpf_map_update_elem() 時,這些對映不能使用標誌 BPF_NOEXIST