mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-27 06:50:37 +00:00

BPF_MAP_TYPE_LRU_HASH can recycle most recent elements well before the map is full, due to percpu reservations and force shrink before neighbor stealing. Once a CPU is unable to borrow from the global map, it will once steal one elem from a neighbor and after that each time flush this one element to the global list and immediately recycle it. Batch value LOCAL_FREE_TARGET (128) will exhaust a 10K element map with 79 CPUs. CPU 79 will observe this behavior even while its neighbors hold 78 * 127 + 1 * 15 == 9921 free elements (99%). CPUs need not be active concurrently. The issue can appear with affinity migration, e.g., irqbalance. Each CPU can reserve and then hold onto its 128 elements indefinitely. Avoid global list exhaustion by limiting aggregate percpu caches to half of map size, by adjusting LOCAL_FREE_TARGET based on cpu count. This change has no effect on sufficiently large tables. Similar to LOCAL_NR_SCANS and lru->nr_scans, introduce a map variable lru->free_target. The extra field fits in a hole in struct bpf_lru. The cacheline is already warm where read in the hot path. The field is only accessed with the lru lock held. Tested-by: Anton Protopopov <a.s.protopopov@gmail.com> Signed-off-by: Willem de Bruijn <willemb@google.com> Acked-by: Stanislav Fomichev <sdf@fomichev.me> Link: https://lore.kernel.org/r/20250618215803.3587312-1-willemdebruijn.kernel@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
266 lines
9.4 KiB
ReStructuredText
266 lines
9.4 KiB
ReStructuredText
.. SPDX-License-Identifier: GPL-2.0-only
|
|
.. Copyright (C) 2022 Red Hat, Inc.
|
|
.. Copyright (C) 2022-2023 Isovalent, Inc.
|
|
|
|
===============================================
|
|
BPF_MAP_TYPE_HASH, with PERCPU and LRU Variants
|
|
===============================================
|
|
|
|
.. note::
|
|
- ``BPF_MAP_TYPE_HASH`` was introduced in kernel version 3.19
|
|
- ``BPF_MAP_TYPE_PERCPU_HASH`` was introduced in version 4.6
|
|
- Both ``BPF_MAP_TYPE_LRU_HASH`` and ``BPF_MAP_TYPE_LRU_PERCPU_HASH``
|
|
were introduced in version 4.10
|
|
|
|
``BPF_MAP_TYPE_HASH`` and ``BPF_MAP_TYPE_PERCPU_HASH`` provide general
|
|
purpose hash map storage. Both the key and the value can be structs,
|
|
allowing for composite keys and values.
|
|
|
|
The kernel is responsible for allocating and freeing key/value pairs, up
|
|
to the max_entries limit that you specify. Hash maps use pre-allocation
|
|
of hash table elements by default. The ``BPF_F_NO_PREALLOC`` flag can be
|
|
used to disable pre-allocation when it is too memory expensive.
|
|
|
|
``BPF_MAP_TYPE_PERCPU_HASH`` provides a separate value slot per
|
|
CPU. The per-cpu values are stored internally in an array.
|
|
|
|
The ``BPF_MAP_TYPE_LRU_HASH`` and ``BPF_MAP_TYPE_LRU_PERCPU_HASH``
|
|
variants add LRU semantics to their respective hash tables. An LRU hash
|
|
will automatically evict the least recently used entries when the hash
|
|
table reaches capacity. An LRU hash maintains an internal LRU list that
|
|
is used to select elements for eviction. This internal LRU list is
|
|
shared across CPUs but it is possible to request a per CPU LRU list with
|
|
the ``BPF_F_NO_COMMON_LRU`` flag when calling ``bpf_map_create``. The
|
|
following table outlines the properties of LRU maps depending on the a
|
|
map type and the flags used to create the map.
|
|
|
|
======================== ========================= ================================
|
|
Flag ``BPF_MAP_TYPE_LRU_HASH`` ``BPF_MAP_TYPE_LRU_PERCPU_HASH``
|
|
======================== ========================= ================================
|
|
**BPF_F_NO_COMMON_LRU** Per-CPU LRU, global map Per-CPU LRU, per-cpu map
|
|
**!BPF_F_NO_COMMON_LRU** Global LRU, global map Global LRU, per-cpu map
|
|
======================== ========================= ================================
|
|
|
|
Usage
|
|
=====
|
|
|
|
Kernel BPF
|
|
----------
|
|
|
|
bpf_map_update_elem()
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: c
|
|
|
|
long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)
|
|
|
|
Hash entries can be added or updated using the ``bpf_map_update_elem()``
|
|
helper. This helper replaces existing elements atomically. The ``flags``
|
|
parameter can be used to control the update behaviour:
|
|
|
|
- ``BPF_ANY`` will create a new element or update an existing element
|
|
- ``BPF_NOEXIST`` will create a new element only if one did not already
|
|
exist
|
|
- ``BPF_EXIST`` will update an existing element
|
|
|
|
``bpf_map_update_elem()`` returns 0 on success, or negative error in
|
|
case of failure.
|
|
|
|
bpf_map_lookup_elem()
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: c
|
|
|
|
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
|
|
|
|
Hash entries can be retrieved using the ``bpf_map_lookup_elem()``
|
|
helper. This helper returns a pointer to the value associated with
|
|
``key``, or ``NULL`` if no entry was found.
|
|
|
|
bpf_map_delete_elem()
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: c
|
|
|
|
long bpf_map_delete_elem(struct bpf_map *map, const void *key)
|
|
|
|
Hash entries can be deleted using the ``bpf_map_delete_elem()``
|
|
helper. This helper will return 0 on success, or negative error in case
|
|
of failure.
|
|
|
|
Per CPU Hashes
|
|
--------------
|
|
|
|
For ``BPF_MAP_TYPE_PERCPU_HASH`` and ``BPF_MAP_TYPE_LRU_PERCPU_HASH``
|
|
the ``bpf_map_update_elem()`` and ``bpf_map_lookup_elem()`` helpers
|
|
automatically access the hash slot for the current CPU.
|
|
|
|
bpf_map_lookup_percpu_elem()
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: c
|
|
|
|
void *bpf_map_lookup_percpu_elem(struct bpf_map *map, const void *key, u32 cpu)
|
|
|
|
The ``bpf_map_lookup_percpu_elem()`` helper can be used to lookup the
|
|
value in the hash slot for a specific CPU. Returns value associated with
|
|
``key`` on ``cpu`` , or ``NULL`` if no entry was found or ``cpu`` is
|
|
invalid.
|
|
|
|
Concurrency
|
|
-----------
|
|
|
|
Values stored in ``BPF_MAP_TYPE_HASH`` can be accessed concurrently by
|
|
programs running on different CPUs. Since Kernel version 5.1, the BPF
|
|
infrastructure provides ``struct bpf_spin_lock`` to synchronise access.
|
|
See ``tools/testing/selftests/bpf/progs/test_spin_lock.c``.
|
|
|
|
Userspace
|
|
---------
|
|
|
|
bpf_map_get_next_key()
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
.. code-block:: c
|
|
|
|
int bpf_map_get_next_key(int fd, const void *cur_key, void *next_key)
|
|
|
|
In userspace, it is possible to iterate through the keys of a hash using
|
|
libbpf's ``bpf_map_get_next_key()`` function. The first key can be fetched by
|
|
calling ``bpf_map_get_next_key()`` with ``cur_key`` set to
|
|
``NULL``. Subsequent calls will fetch the next key that follows the
|
|
current key. ``bpf_map_get_next_key()`` returns 0 on success, -ENOENT if
|
|
cur_key is the last key in the hash, or negative error in case of
|
|
failure.
|
|
|
|
Note that if ``cur_key`` gets deleted then ``bpf_map_get_next_key()``
|
|
will instead return the *first* key in the hash table which is
|
|
undesirable. It is recommended to use batched lookup if there is going
|
|
to be key deletion intermixed with ``bpf_map_get_next_key()``.
|
|
|
|
Examples
|
|
========
|
|
|
|
Please see the ``tools/testing/selftests/bpf`` directory for functional
|
|
examples. The code snippets below demonstrates API usage.
|
|
|
|
This example shows how to declare an LRU Hash with a struct key and a
|
|
struct value.
|
|
|
|
.. code-block:: c
|
|
|
|
#include <linux/bpf.h>
|
|
#include <bpf/bpf_helpers.h>
|
|
|
|
struct key {
|
|
__u32 srcip;
|
|
};
|
|
|
|
struct value {
|
|
__u64 packets;
|
|
__u64 bytes;
|
|
};
|
|
|
|
struct {
|
|
__uint(type, BPF_MAP_TYPE_LRU_HASH);
|
|
__uint(max_entries, 32);
|
|
__type(key, struct key);
|
|
__type(value, struct value);
|
|
} packet_stats SEC(".maps");
|
|
|
|
This example shows how to create or update hash values using atomic
|
|
instructions:
|
|
|
|
.. code-block:: c
|
|
|
|
static void update_stats(__u32 srcip, int bytes)
|
|
{
|
|
struct key key = {
|
|
.srcip = srcip,
|
|
};
|
|
struct value *value = bpf_map_lookup_elem(&packet_stats, &key);
|
|
|
|
if (value) {
|
|
__sync_fetch_and_add(&value->packets, 1);
|
|
__sync_fetch_and_add(&value->bytes, bytes);
|
|
} else {
|
|
struct value newval = { 1, bytes };
|
|
|
|
bpf_map_update_elem(&packet_stats, &key, &newval, BPF_NOEXIST);
|
|
}
|
|
}
|
|
|
|
Userspace walking the map elements from the map declared above:
|
|
|
|
.. code-block:: c
|
|
|
|
#include <bpf/libbpf.h>
|
|
#include <bpf/bpf.h>
|
|
|
|
static void walk_hash_elements(int map_fd)
|
|
{
|
|
struct key *cur_key = NULL;
|
|
struct key next_key;
|
|
struct value value;
|
|
int err;
|
|
|
|
for (;;) {
|
|
err = bpf_map_get_next_key(map_fd, cur_key, &next_key);
|
|
if (err)
|
|
break;
|
|
|
|
bpf_map_lookup_elem(map_fd, &next_key, &value);
|
|
|
|
// Use key and value here
|
|
|
|
cur_key = &next_key;
|
|
}
|
|
}
|
|
|
|
Internals
|
|
=========
|
|
|
|
This section of the document is targeted at Linux developers and describes
|
|
aspects of the map implementations that are not considered stable ABI. The
|
|
following details are subject to change in future versions of the kernel.
|
|
|
|
``BPF_MAP_TYPE_LRU_HASH`` and variants
|
|
--------------------------------------
|
|
|
|
Updating elements in LRU maps may trigger eviction behaviour when the capacity
|
|
of the map is reached. There are various steps that the update algorithm
|
|
attempts in order to enforce the LRU property which have increasing impacts on
|
|
other CPUs involved in the following operation attempts:
|
|
|
|
- Attempt to use CPU-local state to batch operations
|
|
- Attempt to fetch ``target_free`` free nodes from global lists
|
|
- Attempt to pull any node from a global list and remove it from the hashmap
|
|
- Attempt to pull any node from any CPU's list and remove it from the hashmap
|
|
|
|
The number of nodes to borrow from the global list in a batch, ``target_free``,
|
|
depends on the size of the map. Larger batch size reduces lock contention, but
|
|
may also exhaust the global structure. The value is computed at map init to
|
|
avoid exhaustion, by limiting aggregate reservation by all CPUs to half the map
|
|
size. With a minimum of a single element and maximum budget of 128 at a time.
|
|
|
|
This algorithm is described visually in the following diagram. See the
|
|
description in commit 3a08c2fd7634 ("bpf: LRU List") for a full explanation of
|
|
the corresponding operations:
|
|
|
|
.. kernel-figure:: map_lru_hash_update.dot
|
|
:alt: Diagram outlining the LRU eviction steps taken during map update.
|
|
|
|
LRU hash eviction during map update for ``BPF_MAP_TYPE_LRU_HASH`` and
|
|
variants. See the dot file source for kernel function name code references.
|
|
|
|
Map updates start from the oval in the top right "begin ``bpf_map_update()``"
|
|
and progress through the graph towards the bottom where the result may be
|
|
either a successful update or a failure with various error codes. The key in
|
|
the top right provides indicators for which locks may be involved in specific
|
|
operations. This is intended as a visual hint for reasoning about how map
|
|
contention may impact update operations, though the map type and flags may
|
|
impact the actual contention on those locks, based on the logic described in
|
|
the table above. For instance, if the map is created with type
|
|
``BPF_MAP_TYPE_LRU_PERCPU_HASH`` and flags ``BPF_F_NO_COMMON_LRU`` then all map
|
|
properties would be per-cpu.
|