요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
.. _kfuncs-header-label:
=============================
BPF Kernel Functions (kfuncs)
=============================
1. Introduction
===============
BPF Kernel Functions or more commonly known as kfuncs are functions in the Linux
kernel which are exposed for use by BPF programs. Unlike normal BPF helpers,
kfuncs do not have a stable interface and can change from one kernel release to
another. Hence, BPF programs need to be updated in response to changes in the
kernel. See :ref:`BPF_kfunc_lifecycle_expectations` for more information.
2. Defining a kfunc
===================
There are two ways to expose a kernel function to BPF programs, either make an
existing function in the kernel visible, or add a new wrapper for BPF. In both
cases, care must be taken that BPF program can only call such function in a
valid context. To enforce this, visibility of a kfunc can be per program type.
If you are not creating a BPF wrapper for existing kernel function, skip ahead
to :ref:`BPF_kfunc_nodef`.
2.1 Creating a wrapper kfunc
----------------------------
When defining a wrapper kfunc, the wrapper function should have extern linkage.
This prevents the compiler from optimizing away dead code, as this wrapper kfunc
is not invoked anywhere in the kernel itself. It is not necessary to provide a
prototype in a header for the wrapper kfunc.
An example is given below::
/* Disables missing prototype warnings */
__bpf_kfunc_start_defs();
__bpf_kfunc struct task_struct *bpf_find_get_task_by_vpid(pid_t nr)
{
return find_get_task_by_vpid(nr);
}
__bpf_kfunc_end_defs();
A wrapper kfunc is often needed when we need to annotate parameters of the
kfunc. Otherwise one may directly make the kfunc visible to the BPF program by
registering it with the BPF subsystem. See :ref:`BPF_kfunc_nodef`.
2.2 Annotating kfunc parameters
-------------------------------
Similar to BPF helpers, there is sometime need for additional context required
by the verifier to make the usage of kernel functions safer and more useful.
Hence, we can annotate a parameter by suffixing the name of the argument of the
kfunc with a __tag, where tag may be one of the supported annotations.
2.2.1 __sz Annotation
---------------------
This annotation is used to indicate a memory and size pair in the argument list.
An example is given below::
__bpf_kfunc void bpf_memzero(void *mem, int mem__sz)
{
...
}
Here, the verifier will treat first argument as a PTR_TO_MEM, and second
argument as its size. By default, without __sz annotation, the size of the type
of the pointer is used. Without __sz annotation, a kfunc cannot accept a void
pointer.
2.2.2 __k Annotation
--------------------
This annotation is only understood for scalar arguments, where it indicates that
the verifier must check the scalar argument to be a known constant, which does
not indicate a size parameter, and the value of the constant is relevant to the
safety of the program.
An example is given below::
__bpf_kfunc void *bpf_obj_new(u32 local_type_id__k, ...)
{
...
}
Here, bpf_obj_new uses local_type_id argument to find out the size of that type
ID in program's BTF and return a sized pointer to it. Each type ID will have a
distinct size, hence it is crucial to treat each such call as distinct when
values don't match during verifier state pruning checks.
Hence, whenever a constant scalar argument is accepted by a kfunc which is not a
size parameter, and the value of the constant matters for program safety, __k
suffix should be used.
2.2.3 __uninit Annotation
-------------------------
This annotation is used to indicate that the argument will be treated as
uninitialized.
An example is given below::
__bpf_kfunc int bpf_dynptr_from_skb(..., struct bpf_dynptr_kern *ptr__uninit)
{
...
}
Here, the dynptr will be treated as an uninitialized dynptr. Without this
annotation, the verifier will reject the program if the dynptr passed in is
not initialized.
2.2.4 __opt Annotation
-------------------------
This annotation is used to indicate that the buffer associated with an __sz or __szk
argument may be null. If the function is passed a nullptr in place of the buffer,
the verifier will not check that length is appropriate for the buffer. The kfunc is
responsible for checking if this buffer is null before using it.
An example is given below::
__bpf_kfunc void *bpf_dynptr_slice(..., void *buffer__opt, u32 buffer__szk)
{
...
}
Here, the buffer may be null. If buffer is not null, it at least of size buffer_szk.
Either way, the returned buffer is either NULL, or of size buffer_szk. Without this
annotation, the verifier will reject the program if a null pointer is passed in with
a nonzero size.
2.2.5 __str Annotation
----------------------------
This annotation is used to indicate that the argument is a constant string.
An example is given below::
__bpf_kfunc bpf_get_file_xattr(..., const char *name__str, ...)
{
...
}
In this case, ``bpf_get_file_xattr()`` can be called as::
bpf_get_file_xattr(..., "xattr_name", ...);
Or::
const char name[] = "xattr_name"; /* This need to be global */
int BPF_PROG(...)
{
...
bpf_get_file_xattr(..., name, ...);
...
}
2.2.6 __prog Annotation
---------------------------
This annotation is used to indicate that the argument needs to be fixed up to
the bpf_prog_aux of the caller BPF program. Any value passed into this argument
is ignored, and rewritten by the verifier.
An example is given below::
__bpf_kfunc int bpf_wq_set_callback_impl(struct bpf_wq *wq,
int (callback_fn)(void *map, int *key, void *value),
unsigned int flags,
void *aux__prog)
{
struct bpf_prog_aux *aux = aux__prog;
...
}
.. _BPF_kfunc_nodef:
2.3 Using an existing kernel function
-------------------------------------
When an existing function in the kernel is fit for consumption by BPF programs,
it can be directly registered with the BPF subsystem. However, care must still
be taken to review the context in which it will be invoked by the BPF program
and whether it is safe to do so.
2.4 Annotating kfuncs
---------------------
In addition to kfuncs' arguments, verifier may need more information about the
type of kfunc(s) being registered with the BPF subsystem. To do so, we define
flags on a set of kfuncs as follows::
BTF_KFUNCS_START(bpf_task_set)
BTF_ID_FLAGS(func, bpf_get_task_pid, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_put_pid, KF_RELEASE)
BTF_KFUNCS_END(bpf_task_set)
This set encodes the BTF ID of each kfunc listed above, and encodes the flags
along with it. Ofcourse, it is also allowed to specify no flags.
kfunc definitions should also always be annotated with the ``__bpf_kfunc``
macro. This prevents issues such as the compiler inlining the kfunc if it's a
static kernel function, or the function being elided in an LTO build as it's
not used in the rest of the kernel. Developers should not manually add
annotations to their kfunc to prevent these issues. If an annotation is
required to prevent such an issue with your kfunc, it is a bug and should be
added to the definition of the macro so that other kfuncs are similarly
protected. An example is given below::
__bpf_kfunc struct task_struct *bpf_get_task_pid(s32 pid)
{
...
}
2.4.1 KF_ACQUIRE flag
---------------------
The KF_ACQUIRE flag is used to indicate that the kfunc returns a pointer to a
refcounted object. The verifier will then ensure that the pointer to the object
is eventually released using a release kfunc, or transferred to a map using a
referenced kptr (by invoking bpf_kptr_xchg). If not, the verifier fails the
loading of the BPF program until no lingering references remain in all possible
explored states of the program.
2.4.2 KF_RET_NULL flag
----------------------
The KF_RET_NULL flag is used to indicate that the pointer returned by the kfunc
may be NULL. Hence, it forces the user to do a NULL check on the pointer
returned from the kfunc before making use of it (dereferencing or passing to
another helper). This flag is often used in pairing with KF_ACQUIRE flag, but
both are orthogonal to each other.
2.4.3 KF_RELEASE flag
---------------------
The KF_RELEASE flag is used to indicate that the kfunc releases the pointer
passed in to it. There can be only one referenced pointer that can be passed
in. All copies of the pointer being released are invalidated as a result of
invoking kfunc with this flag. KF_RELEASE kfuncs automatically receive the
protection afforded by the KF_TRUSTED_ARGS flag described below.
2.4.4 KF_TRUSTED_ARGS flag
--------------------------
The KF_TRUSTED_ARGS flag is used for kfuncs taking pointer arguments. It
indicates that the all pointer arguments are valid, and that all pointers to
BTF objects have been passed in their unmodified form (that is, at a zero
offset, and without having been obtained from walking another pointer, with one
exception described below).
There are two types of pointers to kernel objects which are considered "valid":
1. Pointers which are passed as tracepoint or struct_ops callback arguments.
2. Pointers which were returned from a KF_ACQUIRE kfunc.
Pointers to non-BTF objects (e.g. scalar pointers) may also be passed to
KF_TRUSTED_ARGS kfuncs, and may have a non-zero offset.
The definition of "valid" pointers is subject to change at any time, and has
absolutely no ABI stability guarantees.
As mentioned above, a nested pointer obtained from walking a trusted pointer is
no longer trusted, with one exception. If a struct type has a field that is
guaranteed to be valid (trusted or rcu, as in KF_RCU description below) as long
as its parent pointer is valid, the following macros can be used to express
that to the verifier:
* ``BTF_TYPE_SAFE_TRUSTED``
* ``BTF_TYPE_SAFE_RCU``
* ``BTF_TYPE_SAFE_RCU_OR_NULL``
For example,
.. code-block:: c
BTF_TYPE_SAFE_TRUSTED(struct socket) {
struct sock *sk;
};
or
.. code-block:: c
BTF_TYPE_SAFE_RCU(struct task_struct) {
const cpumask_t *cpus_ptr;
struct css_set __rcu *cgroups;
struct task_struct __rcu *real_parent;
struct task_struct *group_leader;
};
In other words, you must:
1. Wrap the valid pointer type in a ``BTF_TYPE_SAFE_*`` macro.
2. Specify the type and name of the valid nested field. This field must match
the field in the original type definition exactly.
A new type declared by a ``BTF_TYPE_SAFE_*`` macro also needs to be emitted so
that it appears in BTF. For example, ``BTF_TYPE_SAFE_TRUSTED(struct socket)``
is emitted in the ``type_is_trusted()`` function as follows:
.. code-block:: c
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct socket));
2.4.5 KF_SLEEPABLE flag
-----------------------
The KF_SLEEPABLE flag is used for kfuncs that may sleep. Such kfuncs can only
be called by sleepable BPF programs (BPF_F_SLEEPABLE).
2.4.6 KF_DESTRUCTIVE flag
--------------------------
The KF_DESTRUCTIVE flag is used to indicate functions calling which is
destructive to the system. For example such a call can result in system
rebooting or panicking. Due to this additional restrictions apply to these
calls. At the moment they only require CAP_SYS_BOOT capability, but more can be
added later.
2.4.7 KF_RCU flag
-----------------
The KF_RCU flag is a weaker version of KF_TRUSTED_ARGS. The kfuncs marked with
KF_RCU expect either PTR_TRUSTED or MEM_RCU arguments. The verifier guarantees
that the objects are valid and there is no use-after-free. The pointers are not
NULL, but the object's refcount could have reached zero. The kfuncs need to
consider doing refcnt != 0 check, especially when returning a KF_ACQUIRE
pointer. Note as well that a KF_ACQUIRE kfunc that is KF_RCU should very likely
also be KF_RET_NULL.
2.4.8 KF_RCU_PROTECTED flag
---------------------------
The KF_RCU_PROTECTED flag is used to indicate that the kfunc must be invoked in
an RCU critical section. This is assumed by default in non-sleepable programs,
and must be explicitly ensured by calling ``bpf_rcu_read_lock`` for sleepable
ones.
If the kfunc returns a pointer value, this flag also enforces that the returned
pointer is RCU protected, and can only be used while the RCU critical section is
active.
The flag is distinct from the ``KF_RCU`` flag, which only ensures that its
arguments are at least RCU protected pointers. This may transitively imply that
RCU protection is ensured, but it does not work in cases of kfuncs which require
RCU protection but do not take RCU protected arguments.
.. _KF_deprecated_flag:
2.4.9 KF_DEPRECATED flag
------------------------
The KF_DEPRECATED flag is used for kfuncs which are scheduled to be
changed or removed in a subsequent kernel release. A kfunc that is
marked with KF_DEPRECATED should also have any relevant information
captured in its kernel doc. Such information typically includes the
kfunc's expected remaining lifespan, a recommendation for new
functionality that can replace it if any is available, and possibly a
rationale for why it is being removed.
Note that while on some occasions, a KF_DEPRECATED kfunc may continue to be
supported and have its KF_DEPRECATED flag removed, it is likely to be far more
difficult to remove a KF_DEPRECATED flag after it's been added than it is to
prevent it from being added in the first place. As described in
:ref:`BPF_kfunc_lifecycle_expectations`, users that rely on specific kfuncs are
encouraged to make their use-cases known as early as possible, and participate
in upstream discussions regarding whether to keep, change, deprecate, or remove
those kfuncs if and when such discussions occur.
2.5 Registering the kfuncs
--------------------------
Once the kfunc is prepared for use, the final step to making it visible is
registering it with the BPF subsystem. Registration is done per BPF program
type. An example is shown below::
BTF_KFUNCS_START(bpf_task_set)
BTF_ID_FLAGS(func, bpf_get_task_pid, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_put_pid, KF_RELEASE)
BTF_KFUNCS_END(bpf_task_set)
static const struct btf_kfunc_id_set bpf_task_kfunc_set = {
.owner = THIS_MODULE,
.set = &bpf_task_set,
};
static int init_subsystem(void)
{
return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_task_kfunc_set);
}
late_initcall(init_subsystem);
2.6 Specifying no-cast aliases with ___init
--------------------------------------------
The verifier will always enforce that the BTF type of a pointer passed to a
kfunc by a BPF program, matches the type of pointer specified in the kfunc
definition. The verifier, does, however, allow types that are equivalent
according to the C standard to be passed to the same kfunc arg, even if their
BTF_IDs differ.
For example, for the following type definition:
.. code-block:: c
struct bpf_cpumask {
cpumask_t cpumask;
refcount_t usage;
};
The verifier would allow a ``struct bpf_cpumask *`` to be passed to a kfunc
taking a ``cpumask_t *`` (which is a typedef of ``struct cpumask *``). For
instance, both ``struct cpumask *`` and ``struct bpf_cpmuask *`` can be passed
to bpf_cpumask_test_cpu().
In some cases, this type-aliasing behavior is not desired. ``struct
nf_conn___init`` is one such example:
.. code-block:: c
struct nf_conn___init {
struct nf_conn ct;
};
The C standard would consider these types to be equivalent, but it would not
always be safe to pass either type to a trusted kfunc. ``struct
nf_conn___init`` represents an allocated ``struct nf_conn`` object that has
*not yet been initialized*, so it would therefore be unsafe to pass a ``struct
nf_conn___init *`` to a kfunc that's expecting a fully initialized ``struct
nf_conn *`` (e.g. ``bpf_ct_change_timeout()``).
In order to accommodate such requirements, the verifier will enforce strict
PTR_TO_BTF_ID type matching if two types have the exact same name, with one
being suffixed with ``___init``.
.. _BPF_kfunc_lifecycle_expectations:
3. kfunc lifecycle expectations
===============================
kfuncs provide a kernel <-> kernel API, and thus are not bound by any of the
strict stability restrictions associated with kernel <-> user UAPIs. This means
they can be thought of as similar to EXPORT_SYMBOL_GPL, and can therefore be
modified or removed by a maintainer of the subsystem they're defined in when
it's deemed necessary.
Like any other change to the kernel, maintainers will not change or remove a
kfunc without having a reasonable justification. Whether or not they'll choose
to change a kfunc will ultimately depend on a variety of factors, such as how
widely used the kfunc is, how long the kfunc has been in the kernel, whether an
alternative kfunc exists, what the norm is in terms of stability for the
subsystem in question, and of course what the technical cost is of continuing
to support the kfunc.
There are several implications of this:
a) kfuncs that are widely used or have been in the kernel for a long time will
be more difficult to justify being changed or removed by a maintainer. In
other words, kfuncs that are known to have a lot of users and provide
significant value provide stronger incentives for maintainers to invest the
time and complexity in supporting them. It is therefore important for
developers that are using kfuncs in their BPF programs to communicate and
explain how and why those kfuncs are being used, and to participate in
discussions regarding those kfuncs when they occur upstream.
b) Unlike regular kernel symbols marked with EXPORT_SYMBOL_GPL, BPF programs
that call kfuncs are generally not part of the kernel tree. This means that
refactoring cannot typically change callers in-place when a kfunc changes,
as is done for e.g. an upstreamed driver being updated in place when a
kernel symbol is changed.
Unlike with regular kernel symbols, this is expected behavior for BPF
symbols, and out-of-tree BPF programs that use kfuncs should be considered
relevant to discussions and decisions around modifying and removing those
kfuncs. The BPF community will take an active role in participating in
upstream discussions when necessary to ensure that the perspectives of such
users are taken into account.
c) A kfunc will never have any hard stability guarantees. BPF APIs cannot and
will not ever hard-block a change in the kernel purely for stability
reasons. That being said, kfuncs are features that are meant to solve
problems and provide value to users. The decision of whether to change or
remove a kfunc is a multivariate technical decision that is made on a
case-by-case basis, and which is informed by data points such as those
mentioned above. It is expected that a kfunc being removed or changed with
no warning will not be a common occurrence or take place without sound
justification, but it is a possibility that must be accepted if one is to
use kfuncs.
3.1 kfunc deprecation
---------------------
As described above, while sometimes a maintainer may find that a kfunc must be
changed or removed immediately to accommodate some changes in their subsystem,
usually kfuncs will be able to accommodate a longer and more measured
deprecation process. For example, if a new kfunc comes along which provides
superior functionality to an existing kfunc, the existing kfunc may be
deprecated for some period of time to allow users to migrate their BPF programs
to use the new one. Or, if a kfunc has no known users, a decision may be made
to remove the kfunc (without providing an alternative API) after some
deprecation period so as to provide users with a window to notify the kfunc
maintainer if it turns out that the kfunc is actually being used.
It's expected that the common case will be that kfuncs will go through a
deprecation period rather than being changed or removed without warning. As
described in :ref:`KF_deprecated_flag`, the kfunc framework provides the
KF_DEPRECATED flag to kfunc developers to signal to users that a kfunc has been
deprecated. Once a kfunc has been marked with KF_DEPRECATED, the following
procedure is followed for removal:
1. Any relevant information for deprecated kfuncs is documented in the kfunc's
kernel docs. This documentation will typically include the kfunc's expected
remaining lifespan, a recommendation for new functionality that can replace
the usage of the deprecated function (or an explanation as to why no such
replacement exists), etc.
2. The deprecated kfunc is kept in the kernel for some period of time after it
was first marked as deprecated. This time period will be chosen on a
case-by-case basis, and will typically depend on how widespread the use of
the kfunc is, how long it has been in the kernel, and how hard it is to move
to alternatives. This deprecation time period is "best effort", and as
described :ref:`above<BPF_kfunc_lifecycle_expectations>`, circumstances may
sometimes dictate that the kfunc be removed before the full intended
deprecation period has elapsed.
3. After the deprecation period the kfunc will be removed. At this point, BPF
programs calling the kfunc will be rejected by the verifier.
4. Core kfuncs
==============
The BPF subsystem provides a number of "core" kfuncs that are potentially
applicable to a wide variety of different possible use cases and programs.
Those kfuncs are documented here.
4.1 struct task_struct * kfuncs
-------------------------------
There are a number of kfuncs that allow ``struct task_struct *`` objects to be
used as kptrs:
.. kernel-doc:: kernel/bpf/helpers.c
:identifiers: bpf_task_acquire bpf_task_release
These kfuncs are useful when you want to acquire or release a reference to a
``struct task_struct *`` that was passed as e.g. a tracepoint arg, or a
struct_ops callback arg. For example:
.. code-block:: c
/**
* A trivial example tracepoint program that shows how to
* acquire and release a struct task_struct * pointer.
*/
SEC("tp_btf/task_newtask")
int BPF_PROG(task_acquire_release_example, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired;
acquired = bpf_task_acquire(task);
if (acquired)
/*
* In a typical program you'd do something like store
* the task in a map, and the map will automatically
* release it later. Here, we release it manually.
*/
bpf_task_release(acquired);
return 0;
}
References acquired on ``struct task_struct *`` objects are RCU protected.
Therefore, when in an RCU read region, you can obtain a pointer to a task
embedded in a map value without having to acquire a reference:
.. code-block:: c
#define private(name) SEC(".data." #name) __hidden __attribute__((aligned(8)))
private(TASK) static struct task_struct *global;
/**
* A trivial example showing how to access a task stored
* in a map using RCU.
*/
SEC("tp_btf/task_newtask")
int BPF_PROG(task_rcu_read_example, struct task_struct *task, u64 clone_flags)
{
struct task_struct *local_copy;
bpf_rcu_read_lock();
local_copy = global;
if (local_copy)
/*
* We could also pass local_copy to kfuncs or helper functions here,
* as we're guaranteed that local_copy will be valid until we exit
* the RCU read region below.
*/
bpf_printk("Global task %s is valid", local_copy->comm);
else
bpf_printk("No global task found");
bpf_rcu_read_unlock();
/* At this point we can no longer reference local_copy. */
return 0;
}
----
A BPF program can also look up a task from a pid. This can be useful if the
caller doesn't have a trusted pointer to a ``struct task_struct *`` object that
it can acquire a reference on with bpf_task_acquire().
.. kernel-doc:: kernel/bpf/helpers.c
:identifiers: bpf_task_from_pid
Here is an example of it being used:
.. code-block:: c
SEC("tp_btf/task_newtask")
int BPF_PROG(task_get_pid_example, struct task_struct *task, u64 clone_flags)
{
struct task_struct *lookup;
lookup = bpf_task_from_pid(task->pid);
if (!lookup)
/* A task should always be found, as %task is a tracepoint arg. */
return -ENOENT;
if (lookup->pid != task->pid) {
/* bpf_task_from_pid() looks up the task via its
* globally-unique pid from the init_pid_ns. Thus,
* the pid of the lookup task should always be the
* same as the input task.
*/
bpf_task_release(lookup);
return -EINVAL;
}
/* bpf_task_from_pid() returns an acquired reference,
* so it must be dropped before returning from the
* tracepoint handler.
*/
bpf_task_release(lookup);
return 0;
}
4.2 struct cgroup * kfuncs
--------------------------
``struct cgroup *`` objects also have acquire and release functions:
.. kernel-doc:: kernel/bpf/helpers.c
:identifiers: bpf_cgroup_acquire bpf_cgroup_release
These kfuncs are used in exactly the same manner as bpf_task_acquire() and
bpf_task_release() respectively, so we won't provide examples for them.
----
Other kfuncs available for interacting with ``struct cgroup *`` objects are
bpf_cgroup_ancestor() and bpf_cgroup_from_id(), allowing callers to access
the ancestor of a cgroup and find a cgroup by its ID, respectively. Both
return a cgroup kptr.
.. kernel-doc:: kernel/bpf/helpers.c
:identifiers: bpf_cgroup_ancestor
.. kernel-doc:: kernel/bpf/helpers.c
:identifiers: bpf_cgroup_from_id
Eventually, BPF should be updated to allow this to happen with a normal memory
load in the program itself. This is currently not possible without more work in
the verifier. bpf_cgroup_ancestor() can be used as follows:
.. code-block:: c
/**
* Simple tracepoint example that illustrates how a cgroup's
* ancestor can be accessed using bpf_cgroup_ancestor().
*/
SEC("tp_btf/cgroup_mkdir")
int BPF_PROG(cgrp_ancestor_example, struct cgroup *cgrp, const char *path)
{
struct cgroup *parent;
/* The parent cgroup resides at the level before the current cgroup's level. */
parent = bpf_cgroup_ancestor(cgrp, cgrp->level - 1);
if (!parent)
return -ENOENT;
bpf_printk("Parent id is %d", parent->self.id);
/* Return the parent cgroup that was acquired above. */
bpf_cgroup_release(parent);
return 0;
}
4.3 struct cpumask * kfuncs
---------------------------
BPF provides a set of kfuncs that can be used to query, allocate, mutate, and
destroy struct cpumask * objects. Please refer to :ref:`cpumasks-header-label`
for more details.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
BPF kernel function 소개
1-17이 문서는 `GPL-2.0` license를 따르며 `kfuncs-header-label` anchor에서 BPF Kernel Functions (kfuncs)를 설명합니다.
BPF Kernel Function, 더 흔히 kfunc라고 부르는 function은 BPF program이 사용할 수 있도록 노출한 Linux kernel function입니다.
일반 BPF helper와 달리 kfunc는 stable interface가 아니며 kernel release 사이에 바뀔 수 있습니다. 따라서 kernel 변경에 맞춰 BPF program도 갱신해야 합니다. 자세한 내용은 `BPF_kfunc_lifecycle_expectations`를 참조합니다.
Kfunc를 정의하는 두 방법
18-28Kernel function을 BPF program에 노출하는 방법은 기존 kernel function을 visible하게 만드는 방법과 BPF용 새 wrapper를 추가하는 방법 두 가지입니다.
어느 경우든 BPF program이 valid context에서만 function을 호출하도록 주의해야 합니다. 이를 강제하기 위해 kfunc visibility를 program type별로 지정할 수 있습니다.
기존 kernel function의 BPF wrapper를 만들지 않는다면 `BPF_kfunc_nodef` 절로 건너갑니다.
Wrapper kfunc 작성
29-52Wrapper kfunc를 정의할 때 wrapper function은 extern linkage를 가져야 합니다. Kernel 자체에서는 이 wrapper kfunc를 호출하지 않으므로, 이렇게 해야 compiler가 dead code로 최적화해 없애지 않습니다. Wrapper kfunc prototype을 header에 둘 필요는 없습니다.
/* Disables missing prototype warnings */
__bpf_kfunc_start_defs();
__bpf_kfunc struct task_struct *bpf_find_get_task_by_vpid(pid_t nr)
{
return find_get_task_by_vpid(nr);
}
__bpf_kfunc_end_defs();
Kfunc parameter에 annotation이 필요할 때 wrapper kfunc가 흔히 필요합니다. 그렇지 않다면 BPF subsystem에 등록해 kfunc를 BPF program에 직접 노출할 수 있습니다. `BPF_kfunc_nodef` 절을 참조합니다.
Kfunc parameter annotation
53-60BPF helper와 마찬가지로 verifier가 kernel function을 더 안전하고 유용하게 사용하도록 추가 context가 필요한 경우가 있습니다.
Kfunc argument 이름 끝에 `__tag`를 붙여 parameter를 annotate할 수 있으며, tag에는 지원되는 annotation 중 하나를 사용합니다.
__sz annotation
61-76`__sz` annotation은 argument list의 memory와 size pair를 나타냅니다.
__bpf_kfunc void bpf_memzero(void *mem, int mem__sz)
{
...
}
Verifier는 첫 argument를 `PTR_TO_MEM`으로, 둘째 argument를 그 memory의 size로 취급합니다. 기본적으로 `__sz` annotation이 없으면 pointer가 가리키는 type의 size를 사용합니다. `__sz`가 없으면 kfunc는 void pointer를 받을 수 없습니다.
__k annotation
77-100`__k` annotation은 scalar argument에만 적용됩니다. 이는 verifier가 해당 scalar를 알려진 constant인지 확인해야 하고, 그 argument가 size parameter는 아니지만 constant value가 program safety에 중요함을 나타냅니다.
__bpf_kfunc void *bpf_obj_new(u32 local_type_id__k, ...)
{
...
}
`bpf_obj_new`는 `local_type_id` argument로 program BTF에서 해당 type ID의 size를 알아낸 뒤 그 size를 가진 pointer를 반환합니다. Type ID마다 size가 다르므로 verifier state pruning에서 value가 일치하지 않을 때 각 call을 별개의 call로 취급하는 것이 중요합니다.
따라서 kfunc가 size parameter가 아닌 constant scalar argument를 받고 그 constant value가 program safety에 중요하면 `__k` suffix를 사용해야 합니다.
__uninit annotation
101-117`__uninit` annotation은 argument를 uninitialized 상태로 취급함을 나타냅니다.
__bpf_kfunc int bpf_dynptr_from_skb(..., struct bpf_dynptr_kern *ptr__uninit)
{
...
}
이 예제에서는 dynptr를 uninitialized dynptr로 취급합니다. 이 annotation이 없으면 전달된 dynptr가 initialize되지 않았을 때 verifier가 program을 reject합니다.
__opt annotation
118-137`__opt` annotation은 `__sz` 또는 `__szk` argument와 연결된 buffer가 null일 수 있음을 나타냅니다. Buffer 대신 nullptr를 전달하면 verifier는 length가 buffer에 적절한지 확인하지 않습니다. Kfunc는 buffer를 사용하기 전에 null인지 직접 확인해야 합니다.
__bpf_kfunc void *bpf_dynptr_slice(..., void *buffer__opt, u32 buffer__szk)
{
...
}
이 예제에서 buffer는 null일 수 있습니다. Null이 아니면 적어도 `buffer__szk` size입니다. 반환된 buffer도 NULL이거나 `buffer__szk` size입니다. 이 annotation이 없으면 nonzero size와 함께 null pointer를 전달한 program을 verifier가 reject합니다.
__str annotation
138-162`__str` annotation은 argument가 constant string임을 나타냅니다.
__bpf_kfunc bpf_get_file_xattr(..., const char *name__str, ...)
{
...
}
이 경우 `bpf_get_file_xattr()`는 string literal로 호출할 수 있습니다.
bpf_get_file_xattr(..., "xattr_name", ...);
또는 global이어야 하는 constant character array를 정의해 전달할 수 있습니다.
const char name[] = "xattr_name"; /* This need to be global */
int BPF_PROG(...)
{
...
bpf_get_file_xattr(..., name, ...);
...
}
__prog annotation
163-179`__prog` annotation은 argument를 caller BPF program의 `bpf_prog_aux`로 fix up해야 함을 나타냅니다. 이 argument에 전달한 값은 무시되고 verifier가 다시 씁니다.
__bpf_kfunc int bpf_wq_set_callback_impl(struct bpf_wq *wq,
int (callback_fn)(void *map, int *key, void *value),
unsigned int flags,
void *aux__prog)
{
struct bpf_prog_aux *aux = aux__prog;
...
}
기존 kernel function 사용
180-189`BPF_kfunc_nodef` anchor 절입니다. 기존 kernel function이 BPF program에서 사용하기 적합하면 BPF subsystem에 직접 등록할 수 있습니다.
그래도 BPF program이 어떤 context에서 호출할지와 그 호출이 안전한지는 반드시 검토해야 합니다.
Kfunc flag annotation
190-218Verifier는 kfunc argument 외에도 BPF subsystem에 등록하는 kfunc type에 관한 정보가 더 필요할 수 있습니다. 이를 위해 kfunc set에 flag를 정의합니다.
BTF_KFUNCS_START(bpf_task_set)
BTF_ID_FLAGS(func, bpf_get_task_pid, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_put_pid, KF_RELEASE)
BTF_KFUNCS_END(bpf_task_set)
이 set은 나열한 각 kfunc의 BTF ID와 flag를 함께 encode합니다. Flag를 지정하지 않아도 됩니다.
Kfunc definition에는 항상 `__bpf_kfunc` macro도 붙여야 합니다. 이는 static kernel function인 kfunc를 compiler가 inline하거나, kernel의 다른 곳에서 사용되지 않는 function을 LTO build가 제거하는 문제를 막습니다.
Developer가 이 문제를 막으려고 kfunc에 annotation을 수동으로 추가해서는 안 됩니다. 특정 annotation이 필요하다면 그것은 bug이며 다른 kfunc도 보호받도록 macro definition에 추가해야 합니다.
__bpf_kfunc struct task_struct *bpf_get_task_pid(s32 pid)
{
...
}
KF_ACQUIRE flag
219-228`KF_ACQUIRE`는 kfunc가 refcounted object pointer를 반환함을 나타냅니다. Verifier는 release kfunc로 pointer를 최종 release하거나 `bpf_kptr_xchg`를 호출해 referenced kptr로 map에 이전하도록 강제합니다.
Program의 가능한 모든 explored state에 남은 reference가 없어질 때까지 이 조건을 만족하지 않으면 verifier가 BPF program load를 실패시킵니다.
KF_RET_NULL flag
229-237`KF_RET_NULL`은 kfunc가 반환한 pointer가 NULL일 수 있음을 나타냅니다. 따라서 user는 pointer를 dereference하거나 다른 helper에 넘기기 전에 NULL check를 해야 합니다.
이 flag는 흔히 `KF_ACQUIRE`와 함께 사용하지만 두 flag는 서로 orthogonal합니다.
KF_RELEASE flag
238-246`KF_RELEASE`는 kfunc가 전달받은 pointer를 release함을 나타냅니다. Referenced pointer는 하나만 전달할 수 있고, 이 flag를 가진 kfunc를 호출하면 release된 pointer의 모든 copy가 invalidate됩니다.
`KF_RELEASE` kfunc는 아래에서 설명하는 `KF_TRUSTED_ARGS` flag의 보호를 자동으로 받습니다.
KF_TRUSTED_ARGS와 valid pointer
247-278`KF_TRUSTED_ARGS`는 pointer argument를 받는 kfunc에 사용합니다. 모든 pointer argument가 valid하고, BTF object pointer는 수정되지 않은 형태, 즉 zero offset이고 다른 pointer를 따라가 얻은 것이 아닌 형태로 전달됐음을 나타냅니다. 아래에 한 가지 예외가 있습니다.
Valid한 kernel object pointer는 두 종류입니다.
- Tracepoint 또는 `struct_ops` callback argument로 전달된 pointer
- `KF_ACQUIRE` kfunc가 반환한 pointer
Non-BTF object pointer, 예를 들어 scalar pointer도 `KF_TRUSTED_ARGS` kfunc에 전달할 수 있으며 non-zero offset을 가질 수 있습니다.
"Valid" pointer의 정의는 언제든 바뀔 수 있고 ABI stability를 전혀 보장하지 않습니다.
Trusted pointer를 따라가 얻은 nested pointer는 더 이상 trusted하지 않지만 예외가 하나 있습니다. Parent pointer가 valid한 동안 valid(trusted 또는 아래 `KF_RCU`에서 말하는 rcu)하다고 보장되는 field가 struct type에 있다면 다음 macro로 verifier에 이를 표현할 수 있습니다.
- `BTF_TYPE_SAFE_TRUSTED`
- `BTF_TYPE_SAFE_RCU`
- `BTF_TYPE_SAFE_RCU_OR_NULL`
안전한 nested field type 선언
279-311Trusted nested field 선언 예제입니다.
BTF_TYPE_SAFE_TRUSTED(struct socket) {
struct sock *sk;
};
RCU-protected nested field 선언 예제입니다.
BTF_TYPE_SAFE_RCU(struct task_struct) {
const cpumask_t *cpus_ptr;
struct css_set __rcu *cgroups;
struct task_struct __rcu *real_parent;
struct task_struct *group_leader;
};
즉 다음 두 조건을 지켜야 합니다.
- Valid pointer type을 `BTF_TYPE_SAFE_*` macro로 감쌉니다.
- Valid nested field의 type과 name을 지정합니다. 이 field는 original type definition의 field와 정확히 일치해야 합니다.
`BTF_TYPE_SAFE_*` macro가 선언한 새 type은 BTF에 나타나도록 emit해야 합니다. 예를 들어 `BTF_TYPE_SAFE_TRUSTED(struct socket)`은 `type_is_trusted()` function에서 다음과 같이 emit합니다.
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct socket));
KF_SLEEPABLE과 KF_DESTRUCTIVE
312-326`KF_SLEEPABLE`은 sleep할 수 있는 kfunc에 사용합니다. 이런 kfunc는 sleepable BPF program(`BPF_F_SLEEPABLE`)에서만 호출할 수 있습니다.
`KF_DESTRUCTIVE`는 호출하면 system에 파괴적인 영향을 주는 function을 나타냅니다. 예를 들어 system reboot나 panic을 일으킬 수 있으므로 추가 제약을 적용합니다. 현재는 `CAP_SYS_BOOT` capability만 요구하지만 이후 더 추가될 수 있습니다.
KF_RCU와 KF_RCU_PROTECTED
327-354`KF_RCU`는 `KF_TRUSTED_ARGS`보다 약한 형태입니다. `KF_RCU` kfunc는 `PTR_TRUSTED` 또는 `MEM_RCU` argument를 기대합니다. Verifier는 object가 valid하고 use-after-free가 없음을 보장합니다.
Pointer는 NULL이 아니지만 object refcount는 0에 도달했을 수 있습니다. 특히 `KF_ACQUIRE` pointer를 반환할 때 kfunc는 `refcnt != 0` check를 고려해야 합니다. `KF_ACQUIRE`이면서 `KF_RCU`인 kfunc는 거의 항상 `KF_RET_NULL`도 함께 가져야 합니다.
`KF_RCU_PROTECTED`는 kfunc를 RCU critical section 안에서 호출해야 함을 나타냅니다. Non-sleepable program에서는 기본적으로 이를 가정하고, sleepable program에서는 `bpf_rcu_read_lock`을 호출해 명시적으로 보장해야 합니다.
Kfunc가 pointer를 반환하면 이 flag는 반환 pointer도 RCU-protected임을 강제하며 RCU critical section이 active한 동안에만 사용할 수 있습니다.
이 flag는 argument가 적어도 RCU-protected pointer임만 보장하는 `KF_RCU`와 구별됩니다. Argument 특성상 RCU protection을 transitive하게 암시할 수 있지만, RCU-protected argument를 받지 않으면서 RCU protection을 요구하는 kfunc에는 적용되지 않습니다.
KF_DEPRECATED flag
355-376`KF_deprecated_flag` anchor 절입니다. `KF_DEPRECATED`는 다음 kernel release에서 변경하거나 제거할 예정인 kfunc에 사용합니다.
이 flag를 붙인 kfunc의 kernel doc에는 예상되는 남은 lifespan, 사용 가능한 대체 기능에 대한 권고, 제거 이유 같은 관련 정보를 담아야 합니다.
때로는 deprecated kfunc 지원을 계속하고 flag를 제거할 수도 있지만, 한번 추가한 `KF_DEPRECATED`를 제거하기는 처음부터 붙이지 않는 것보다 훨씬 어렵습니다.
`BPF_kfunc_lifecycle_expectations`에서 설명하듯 특정 kfunc에 의존하는 user는 use case를 가능한 한 일찍 알리고, 해당 kfunc의 유지·변경·deprecate·제거를 논의하는 upstream discussion에 참여하는 것이 좋습니다.
Kfunc set 등록
377-399Kfunc를 사용할 준비가 끝나면 마지막 단계는 BPF subsystem에 등록해 visible하게 만드는 것입니다. 등록은 BPF program type별로 수행합니다.
BTF_KFUNCS_START(bpf_task_set)
BTF_ID_FLAGS(func, bpf_get_task_pid, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_put_pid, KF_RELEASE)
BTF_KFUNCS_END(bpf_task_set)
static const struct btf_kfunc_id_set bpf_task_kfunc_set = {
.owner = THIS_MODULE,
.set = &bpf_task_set,
};
static int init_subsystem(void)
{
return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_task_kfunc_set);
}
late_initcall(init_subsystem);
예제는 `bpf_task_set`에 acquire/release flag를 encode하고 `btf_kfunc_id_set`의 owner와 set을 지정한 뒤 `register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, ...)`로 tracing program type에 등록합니다.
___init no-cast alias
400-442Verifier는 BPF program이 kfunc에 전달한 pointer의 BTF type이 kfunc definition의 pointer type과 일치하도록 항상 강제합니다. 다만 BTF ID가 달라도 C standard상 equivalent한 type은 같은 kfunc argument로 허용합니다.
다음 type definition을 예로 듭니다.
struct bpf_cpumask {
cpumask_t cpumask;
refcount_t usage;
};
Verifier는 `struct bpf_cpumask *`를 `cpumask_t *`(`struct cpumask *` typedef)를 받는 kfunc에 전달하도록 허용합니다. 예를 들어 `struct cpumask *`와 `struct bpf_cpmuask *` 모두 `bpf_cpumask_test_cpu()`에 전달할 수 있습니다.
이 type aliasing 동작을 원하지 않는 경우도 있습니다. `struct nf_conn___init`가 그런 예입니다.
struct nf_conn___init {
struct nf_conn ct;
};
C standard는 이 type들을 equivalent하게 보지만 trusted kfunc에 둘 중 어느 type이나 전달하는 것이 항상 안전하지는 않습니다. `struct nf_conn___init`는 allocate됐지만 아직 initialize되지 않은 `struct nf_conn` object를 나타냅니다.
따라서 완전히 initialize된 `struct nf_conn *`을 기대하는 `bpf_ct_change_timeout()` 같은 kfunc에 `struct nf_conn___init *`을 넘기면 안전하지 않습니다.
이 요구를 지원하기 위해 이름이 정확히 같고 한쪽에 `___init` suffix가 붙은 두 type에는 verifier가 strict `PTR_TO_BTF_ID` type matching을 강제합니다.
Kfunc lifecycle과 안정성 기대
443-461`BPF_kfunc_lifecycle_expectations` anchor 절입니다. Kfunc는 kernel과 kernel 사이의 API이므로 kernel과 user 사이 UAPI에 적용되는 엄격한 stability 제약을 받지 않습니다.
따라서 `EXPORT_SYMBOL_GPL`과 비슷하게 생각할 수 있으며, 필요하다고 판단하면 kfunc가 정의된 subsystem maintainer가 수정하거나 제거할 수 있습니다.
다른 kernel 변경과 마찬가지로 maintainer는 합리적 정당화 없이 kfunc를 바꾸거나 제거하지 않습니다. 결정은 사용 범위, kernel에 존재한 기간, 대체 kfunc 유무, 해당 subsystem의 stability 관행, 계속 지원하는 기술적 비용 등 여러 요인에 좌우됩니다.
사용자가 많은 kfunc와 upstream 소통
462-472a) 널리 사용되거나 kernel에 오래 존재한 kfunc는 maintainer가 변경이나 제거를 정당화하기 더 어렵습니다. User가 많고 큰 가치를 제공한다고 알려진 kfunc는 maintainer가 지원에 시간과 복잡성을 투자할 동기를 강화합니다.
따라서 BPF program에서 kfunc를 사용하는 developer는 그 kfunc를 어떻게, 왜 사용하는지 알리고 설명하며 관련 upstream discussion에 참여하는 것이 중요합니다.
Out-of-tree BPF caller 고려
473-485b) `EXPORT_SYMBOL_GPL`로 표시한 일반 kernel symbol과 달리 kfunc를 호출하는 BPF program은 대개 kernel tree 밖에 있습니다. 따라서 kfunc가 바뀔 때 upstream driver caller를 함께 고치듯 refactoring으로 caller를 제자리에서 일괄 변경할 수 없습니다.
이는 BPF symbol에서 예상되는 동작입니다. Kfunc를 사용하는 out-of-tree BPF program도 kfunc 수정·제거 discussion과 decision에서 관련 user로 간주해야 합니다. BPF community는 필요할 때 upstream discussion에 적극 참여해 이 user들의 관점을 고려하도록 합니다.
Hard stability guarantee는 없음
486-496c) Kfunc에는 hard stability guarantee가 결코 없습니다. BPF API는 순전히 stability를 이유로 kernel change를 hard-block할 수 없고 앞으로도 그러지 않습니다.
그렇더라도 kfunc는 문제를 해결하고 user에게 가치를 제공하려는 feature입니다. 변경·제거 여부는 앞의 data point를 고려한 다변수 기술 decision이며 case-by-case로 결정합니다.
경고 없이 kfunc를 제거하거나 변경하는 일이 흔하거나 충분한 정당화 없이 일어나지는 않을 것으로 기대하지만, kfunc를 사용하려면 그런 가능성을 받아들여야 합니다.
Kfunc deprecation 과정
497-517Subsystem 변경 때문에 kfunc를 즉시 바꾸거나 제거해야 할 때도 있지만, 보통은 더 길고 신중한 deprecation process를 적용할 수 있습니다.
새 kfunc가 기존 kfunc보다 우수한 기능을 제공하면 user가 BPF program을 새 API로 migrate할 기간을 주기 위해 기존 kfunc를 일정 기간 deprecate할 수 있습니다.
알려진 user가 없는 kfunc라면 일정 deprecation period 후 대체 API 없이 제거하기로 결정할 수도 있습니다. 실제 user가 있다면 그 기간에 maintainer에게 알릴 수 있습니다.
일반적으로 kfunc는 경고 없이 변경·제거되기보다 deprecation period를 거칠 것으로 기대합니다. `KF_deprecated_flag` 절의 `KF_DEPRECATED` flag로 developer가 deprecated 상태를 알리며, 그 뒤 다음 제거 절차를 따릅니다.
Deprecated kfunc 제거 절차
518-535- 1. Deprecated kfunc 관련 정보를 kernel docs에 기록합니다. 일반적으로 예상되는 남은 lifespan, deprecated function을 대신할 새 기능에 대한 권고 또는 대체가 없는 이유 등을 포함합니다.
- 2. 처음 deprecated로 표시한 뒤 일정 기간 kernel에 유지합니다. 기간은 case-by-case로 정하며 보통 사용 범위, kernel에 존재한 기간, 대체 기능으로 이동하기 어려운 정도에 따라 달라집니다. 이 기간은 best effort이므로 상황에 따라 의도한 기간이 모두 지나기 전에 제거할 수도 있습니다.
- 3. Deprecation period가 끝나면 kfunc를 제거합니다. 이때 해당 kfunc를 호출하는 BPF program은 verifier가 reject합니다.
Core kfunc
536-542BPF subsystem은 매우 다양한 use case와 program에 적용할 수 있는 여러 "core" kfunc를 제공합니다. 이 절에서 그 kfunc를 설명합니다.
struct task_struct reference acquire와 release
543-578`struct task_struct *` object를 kptr로 사용할 수 있게 하는 여러 kfunc가 있습니다. `kernel/bpf/helpers.c`의 kernel-doc에서 `bpf_task_acquire`와 `bpf_task_release`를 가져옵니다.
Tracepoint argument나 `struct_ops` callback argument로 받은 `struct task_struct *` reference를 acquire하거나 release할 때 유용합니다.
/**
* A trivial example tracepoint program that shows how to
* acquire and release a struct task_struct * pointer.
*/
SEC("tp_btf/task_newtask")
int BPF_PROG(task_acquire_release_example, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired;
acquired = bpf_task_acquire(task);
if (acquired)
/*
* In a typical program you'd do something like store
* the task in a map, and the map will automatically
* release it later. Here, we release it manually.
*/
bpf_task_release(acquired);
return 0;
}
RCU로 map의 task pointer 접근
579-616`struct task_struct *` object에서 acquire한 reference는 RCU-protected입니다. 따라서 RCU read region 안에서는 reference를 acquire하지 않고도 map value에 embedded된 task pointer를 얻을 수 있습니다.
#define private(name) SEC(".data." #name) __hidden __attribute__((aligned(8)))
private(TASK) static struct task_struct *global;
/**
* A trivial example showing how to access a task stored
* in a map using RCU.
*/
SEC("tp_btf/task_newtask")
int BPF_PROG(task_rcu_read_example, struct task_struct *task, u64 clone_flags)
{
struct task_struct *local_copy;
bpf_rcu_read_lock();
local_copy = global;
if (local_copy)
/*
* We could also pass local_copy to kfuncs or helper functions here,
* as we're guaranteed that local_copy will be valid until we exit
* the RCU read region below.
*/
bpf_printk("Global task %s is valid", local_copy->comm);
else
bpf_printk("No global task found");
bpf_rcu_read_unlock();
/* At this point we can no longer reference local_copy. */
return 0;
}
예제는 `bpf_rcu_read_lock()`과 `bpf_rcu_read_unlock()` 사이에서 global task pointer를 local copy로 읽고 사용합니다. RCU read region을 벗어난 뒤에는 `local_copy`를 더 이상 참조할 수 없습니다.
PID로 task lookup
617-655BPF program은 PID로 task를 lookup할 수도 있습니다. Caller에게 `bpf_task_acquire()`로 reference를 acquire할 trusted `struct task_struct *` pointer가 없을 때 유용합니다.
`kernel/bpf/helpers.c`의 kernel-doc에서 `bpf_task_from_pid`를 가져옵니다.
SEC("tp_btf/task_newtask")
int BPF_PROG(task_get_pid_example, struct task_struct *task, u64 clone_flags)
{
struct task_struct *lookup;
lookup = bpf_task_from_pid(task->pid);
if (!lookup)
/* A task should always be found, as %task is a tracepoint arg. */
return -ENOENT;
if (lookup->pid != task->pid) {
/* bpf_task_from_pid() looks up the task via its
* globally-unique pid from the init_pid_ns. Thus,
* the pid of the lookup task should always be the
* same as the input task.
*/
bpf_task_release(lookup);
return -EINVAL;
}
/* bpf_task_from_pid() returns an acquired reference,
* so it must be dropped before returning from the
* tracepoint handler.
*/
bpf_task_release(lookup);
return 0;
}
`bpf_task_from_pid()`는 init PID namespace의 globally unique PID로 task를 찾고 acquired reference를 반환하므로 tracepoint handler가 return하기 전에 `bpf_task_release()`로 반드시 drop해야 합니다.
struct cgroup acquire와 release
656-668`struct cgroup *` object에도 acquire와 release function이 있습니다. `kernel/bpf/helpers.c`의 kernel-doc에서 `bpf_cgroup_acquire`와 `bpf_cgroup_release`를 가져옵니다.
이 kfunc는 각각 `bpf_task_acquire()`와 `bpf_task_release()`와 정확히 같은 방식으로 사용하므로 별도 예제를 제공하지 않습니다.
Cgroup ancestor와 ID lookup
669-706`struct cgroup *` object와 상호 작용하는 다른 kfunc에는 `bpf_cgroup_ancestor()`와 `bpf_cgroup_from_id()`가 있습니다. 각각 cgroup ancestor에 접근하고 ID로 cgroup을 찾으며 둘 다 cgroup kptr를 반환합니다.
`kernel/bpf/helpers.c`의 kernel-doc에서 `bpf_cgroup_ancestor`와 `bpf_cgroup_from_id`를 가져옵니다.
궁극적으로 BPF program 자체의 일반 memory load로 이 작업을 허용해야 하지만 현재는 verifier에 추가 작업이 필요합니다. `bpf_cgroup_ancestor()` 사용 예제는 다음과 같습니다.
/**
* Simple tracepoint example that illustrates how a cgroup's
* ancestor can be accessed using bpf_cgroup_ancestor().
*/
SEC("tp_btf/cgroup_mkdir")
int BPF_PROG(cgrp_ancestor_example, struct cgroup *cgrp, const char *path)
{
struct cgroup *parent;
/* The parent cgroup resides at the level before the current cgroup's level. */
parent = bpf_cgroup_ancestor(cgrp, cgrp->level - 1);
if (!parent)
return -ENOENT;
bpf_printk("Parent id is %d", parent->self.id);
/* Return the parent cgroup that was acquired above. */
bpf_cgroup_release(parent);
return 0;
}
struct cpumask kfunc
707-712BPF는 `struct cpumask *` object를 query, allocate, mutate, destroy하는 kfunc 집합을 제공합니다. 자세한 내용은 `cpumasks-header-label` 절을 참조합니다.
요약과 해설
kfuncs.rst:1-712Kfunc는 kernel function을 BPF program에 노출하는 유연한 kernel 내부 API입니다. Wrapper의 extern linkage와 `__bpf_kfunc`, `__sz`·`__k`·`__uninit`·`__opt`·`__str`·`__prog` parameter annotation으로 verifier가 call contract를 이해하게 합니다.
`KF_ACQUIRE`, `KF_RELEASE`, `KF_RET_NULL`, `KF_TRUSTED_ARGS`, `KF_RCU`, `KF_RCU_PROTECTED` 등은 reference lifetime과 pointer validity를 강제합니다. Kfunc set은 BTF ID와 flag를 묶어 BPF program type별로 등록합니다.
Kfunc는 UAPI와 달리 hard stability guarantee가 없습니다. 일반적으로 `KF_DEPRECATED`와 kernel-doc을 통한 migration 기간을 제공하지만 subsystem maintainer는 기술적으로 필요하면 변경하거나 제거할 수 있으므로 out-of-tree user의 upstream 소통이 중요합니다.
Core kfunc 예제는 `task_struct`와 `cgroup` reference의 acquire·release, RCU read-side 접근, PID·ID lookup, cgroup ancestor 조회를 실제 BPF code로 보여 줍니다.