From: JP Kobryn <jp.kobryn@linux.dev>
To: Shakeel Butt <shakeel.butt@linux.dev>,
Andrew Morton <akpm@linux-foundation.org>,
Alexei Starovoitov <ast@kernel.org>
Cc: Johannes Weiner <hannes@cmpxchg.org>,
Michal Hocko <mhocko@kernel.org>,
Roman Gushchin <roman.gushchin@linux.dev>,
Muchun Song <muchun.song@linux.dev>, Tejun Heo <tj@kernel.org>,
Michal Koutny <mkoutny@suse.com>,
Amery Hung <ameryhung@gmail.com>,
Daniel Borkmann <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Martin KaFai Lau <martin.lau@linux.dev>,
Song Liu <song@kernel.org>,
Yonghong Song <yonghong.song@linux.dev>,
Emil Tsalapatis <emil@etsalapatis.com>,
Jiri Olsa <jolsa@kernel.org>,
Ihor Solodrai <ihor.solodrai@linux.dev>,
John Fastabend <john.fastabend@gmail.com>,
Jiayuan Chen <jiayuan.chen@linux.dev>,
hui.zhu@linux.dev, Donet Tom <donettom@linux.ibm.com>,
Greg Thelen <gthelen@google.com>,
Meta kernel team <kernel-team@meta.com>,
linux-mm@kvack.org, bpf@vger.kernel.org, cgroups@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [RFC PATCH 3/4] memcg_ext: allow BPF to defer memory.high enforcement
Date: Thu, 24 Sep 2026 13:14:59 -0700 [thread overview]
Message-ID: <c8b8504c-8e54-479f-a372-3d2755edaf88@linux.dev> (raw)
In-Reply-To: <20260921192559.2619635-4-shakeel.butt@linux.dev>
On 9/21/26 12:25 PM, Shakeel Butt wrote:
> Before it returns, try_charge_memcg() calls __mem_cgroup_handle_over_high(),
> which reclaims and can throttle the task. That happens wherever the charge
> happens, so a task holding a kernel lock can be stuck there, and everything
> waiting on the lock is stuck behind it.
>
> Let's add high_policy ops through which a program gets a read-only snapshot
> of the charge and returns a request. There is one so far:
> BPF_MEMCG_HIGH_DEFER_INLINE skips the inline call.
>
> A charge runs the policies of its cgroup and of every ancestor.
> task_struct::in_bpf_memcg stops a program that allocates from re-entering
> the charge path and the dispatcher with it.
>
> A memcg outlives its cgroup while it has charges, and cgroup_bpf_release()
> frees the arrays when the cgroup goes. Take the reference for the walk.
>
> Signed-off-by: Shakeel Butt <shakeel.butt@linux.dev>
> ---
> MAINTAINERS | 1 +
> include/linux/bpf-cgroup.h | 3 ++
> include/linux/bpf_memcontrol.h | 53 +++++++++++++++++++++-
> include/linux/cgroup.h | 7 +++
> include/linux/sched.h | 4 ++
> mm/bpf_memcontrol.c | 82 +++++++++++++++++++++++++++++++++-
> mm/memcontrol.c | 31 ++++++++++++-
> 7 files changed, 176 insertions(+), 5 deletions(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6215fcb07770..0c84beab396f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5032,6 +5032,7 @@ L: bpf@vger.kernel.org
> L: linux-mm@kvack.org
> S: Maintained
> F: mm/bpf_memcontrol.c
> +F: include/linux/bpf_memcontrol.h
>
> BPF [MISC]
> L: bpf@vger.kernel.org
> diff --git a/include/linux/bpf-cgroup.h b/include/linux/bpf-cgroup.h
> index 4e8150848bd2..e19cf83e58f3 100644
> --- a/include/linux/bpf-cgroup.h
> +++ b/include/linux/bpf-cgroup.h
> @@ -517,6 +517,9 @@ static inline int cgroup_bpf_struct_ops_attach(struct bpf_map *map,
>
> #define cgroup_bpf_enabled(atype) (0)
> #define cgroup_bpf_enabled_runtime(atype) (0)
> +/* Nothing can be attached, so the walk has nothing to walk. */
> +#define bpf_cgroup_struct_ops_foreach(var, item, cgrp, atype) \
> + for ((void)(cgrp), (item) = NULL, (var) = NULL; 0; )
> #define BPF_CGROUP_RUN_SA_PROG_LOCK(sk, uaddr, uaddrlen, atype, t_ctx) ({ 0; })
> #define BPF_CGROUP_RUN_SA_PROG(sk, uaddr, uaddrlen, atype) ({ 0; })
> #define BPF_CGROUP_PRE_CONNECT_ENABLED(sk) (0)
> diff --git a/include/linux/bpf_memcontrol.h b/include/linux/bpf_memcontrol.h
> index 8204d894761e..76ea5d1c3d32 100644
> --- a/include/linux/bpf_memcontrol.h
> +++ b/include/linux/bpf_memcontrol.h
> @@ -5,13 +5,62 @@
> * A bpf_memcg_ops is attached to a cgroup. A charge runs the policies of
> * that cgroup and of every ancestor, and the kernel combines what they
> * return. BPF only picks between things the kernel already does.
> - *
> - * The type has no members yet; they come with the policies that use them.
> */
> #ifndef _LINUX_BPF_MEMCONTROL_H
> #define _LINUX_BPF_MEMCONTROL_H
>
> +#include <linux/types.h>
> +#include <linux/gfp_types.h>
> +
> +struct mem_cgroup;
> +struct task_struct;
> +
> +/*
> + * What a policy can ask for when a cgroup is over memory.high. The kernel
> + * ORs them, so one policy cannot undo another.
> + */
> +enum bpf_memcg_high_request {
> + BPF_MEMCG_HIGH_NO_OPINION = 0,
> + /*
> + * Skip the inline reclaim and throttle. The debt is kept and paid on
> + * the way back to userspace, where no kernel locks are held.
> + */
> + BPF_MEMCG_HIGH_DEFER_INLINE = 1U << 0,
> +};
> +
> +#define BPF_MEMCG_HIGH_VALID_MASK BPF_MEMCG_HIGH_DEFER_INLINE
> +
> +/* Read-only snapshot. Only values the caller already has. */
> +struct bpf_memcg_ctx {
> + struct mem_cgroup *memcg; /* charged memcg */
> + struct mem_cgroup *memcg_over_limit; /* NULL if none found */
> + struct task_struct *task; /* current */
> + u64 cgroup_id;
> + u64 over_limit_cgroup_id; /* 0 if none */
> + u64 nr_pages_over_high;
> + u32 gfp_flags;
> +};
> +
> struct bpf_memcg_ops {
> + /**
> + * high_policy - say where memory.high should be enforced
> + * @ctx: snapshot of the charge
> + *
> + * Return: bits from enum bpf_memcg_high_request, or 0. Other bits
> + * are dropped.
> + */
> + u32 (*high_policy)(const struct bpf_memcg_ctx *ctx);
> };
>
> +/*
> + * Run every high_policy on @memcg's cgroup and its ancestors, and return the
> + * combined request for the caller to act on.
> + *
> + * @memcg: the memcg being charged, never NULL
> + * @over_limit: first memcg found over memory.high or swap.high, or NULL
> + * @gfp_mask: the charge's gfp mask
> + */
> +u32 bpf_memcg_high_policy(struct mem_cgroup *memcg,
> + struct mem_cgroup *over_limit, gfp_t gfp_mask);
> +
> #endif /* _LINUX_BPF_MEMCONTROL_H */
> diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h
> index 5dfa915a630e..cf92b6cec819 100644
> --- a/include/linux/cgroup.h
> +++ b/include/linux/cgroup.h
> @@ -959,10 +959,17 @@ static inline void cgroup_bpf_put(struct cgroup *cgrp)
> percpu_ref_put(&cgrp->bpf.refcnt);
> }
>
> +/* Fails once the cgroup is gone and its bpf state has been freed. */
> +static inline bool cgroup_bpf_tryget_live(struct cgroup *cgrp)
> +{
> + return percpu_ref_tryget_live_rcu(&cgrp->bpf.refcnt);
> +}
> +
> #else /* CONFIG_CGROUP_BPF */
>
> static inline void cgroup_bpf_get(struct cgroup *cgrp) {}
> static inline void cgroup_bpf_put(struct cgroup *cgrp) {}
> +static inline bool cgroup_bpf_tryget_live(struct cgroup *cgrp) { return false; }
>
> #endif /* CONFIG_CGROUP_BPF */
>
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 8b3d47a325cc..4b20a346aaa8 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -1031,6 +1031,10 @@ struct task_struct {
> #ifdef CONFIG_MEMCG_V1
> unsigned in_user_fault:1;
> #endif
> +#ifdef CONFIG_MEMCG
> + /* A bpf_memcg_ops program is running; do not recurse into policy */
> + unsigned in_bpf_memcg:1;
> +#endif
> #ifdef CONFIG_LRU_GEN
> /* whether the LRU algorithm may apply to this access */
> unsigned in_lru_fault:1;
> diff --git a/mm/bpf_memcontrol.c b/mm/bpf_memcontrol.c
> index fd6dff150f01..cfd0f1d443c9 100644
> --- a/mm/bpf_memcontrol.c
> +++ b/mm/bpf_memcontrol.c
> @@ -246,8 +246,17 @@ static const struct btf_kfunc_id_set bpf_memcontrol_reclaim_kfunc_set = {
> * request and the kernel acts on it. Nothing here reclaims or sleeps.
> */
>
> -/* CFI stubs. A slot points at these while its policy is being detached. */
> +/*
> + * CFI stubs. These really run: a slot points at them while its policy is
> + * being detached. Return 0, the identity for the kernel's OR.
> + */
> +static u32 high_policy_stub(const struct bpf_memcg_ctx *ctx)
> +{
> + return BPF_MEMCG_HIGH_NO_OPINION;
> +}
> +
> static struct bpf_memcg_ops __bpf_memcg_ops = {
> + .high_policy = high_policy_stub,
> };
>
> static const struct bpf_func_proto *
> @@ -326,6 +335,77 @@ static struct bpf_struct_ops bpf_memcg_ops_desc = {
> */
> };
>
> +static void bpf_memcg_ctx_init(struct bpf_memcg_ctx *ctx,
> + struct mem_cgroup *memcg,
> + struct mem_cgroup *over_limit, gfp_t gfp_mask)
> +{
> + ctx->memcg = memcg;
> + ctx->memcg_over_limit = over_limit;
> + ctx->task = current;
> + ctx->cgroup_id = cgroup_id(memcg->css.cgroup);
> + ctx->over_limit_cgroup_id = over_limit ?
> + cgroup_id(over_limit->css.cgroup) : 0;
> + ctx->nr_pages_over_high = current->memcg_nr_pages_over_high;
> + ctx->gfp_flags = (__force u32)gfp_mask;
> +}
> +
> +u32 bpf_memcg_high_policy(struct mem_cgroup *memcg,
> + struct mem_cgroup *over_limit, gfp_t gfp_mask)
> +{
> + const struct bpf_prog_array_item *item;
> + const struct bpf_memcg_ops *ops;
> + struct bpf_memcg_ctx ctx;
> + u32 acc = BPF_MEMCG_HIGH_NO_OPINION;
> + struct cgroup *cgrp;
> +
> + if (!cgroup_bpf_enabled(CGROUP_MEMCG_OPS))
> + return acc;
> +
> + /*
> + * Only the default hierarchy has a cgroup_bpf, and the static key is
> + * global, so one policy anywhere turns this on for v1 memcgs too. A
> + * v1 memcg still cannot get here, because memory.high and swap.high
> + * are both v2-only and so it never builds the debt that leads to this
> + * call. A hook on a path v1 can reach needs its own cgroup_on_dfl()
> + * test: a v1 cgroup has no effective array and an uninitialised
> + * cgrp->bpf.refcnt.
> + */
> + cgrp = memcg->css.cgroup;
> +
> + /*
> + * A program can allocate and re-enter the charge path. Skip the
> + * nested call. This guards the callbacks only.
> + */
> + if (current->in_bpf_memcg)
> + return acc;
> + current->in_bpf_memcg = 1;
> +
> + rcu_read_lock_dont_migrate();
> +
> + /*
> + * A memcg outlives its cgroup while it has charges, and
> + * cgroup_bpf_release() frees the arrays when the cgroup goes.
> + */
> + if (!cgroup_bpf_tryget_live(cgrp))
> + goto out;
> +
> + bpf_memcg_ctx_init(&ctx, memcg, over_limit, gfp_mask);
> +
> + bpf_cgroup_struct_ops_foreach(ops, item, cgrp, CGROUP_MEMCG_OPS) {
> + if (ops->high_policy)
> + acc |= ops->high_policy(&ctx) &
> + BPF_MEMCG_HIGH_VALID_MASK;
> + }
If I'm reading correctly, the gfp_mask at this point doesn't account for
task restrictions, so the BPF program may see __GFP_FS, __GFP_IO, etc
which may later be cleared when setting up the scan_control instance.
next prev parent reply other threads:[~2026-09-24 20:15 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-21 19:25 [RFC PATCH 0/4] memcg_ext: memcg policy through cgroup-attached struct_ops Shakeel Butt
2026-09-21 19:25 ` [RFC PATCH 1/4] bpf, cgroup: fix cgroup struct_ops query for a second attach type Shakeel Butt
2026-09-21 20:19 ` bot+bpf-ci
2026-09-29 11:49 ` Yafang Shao
2026-09-21 19:25 ` [RFC PATCH 2/4] memcg_ext: add cgroup-attached bpf_memcg_ops Shakeel Butt
2026-09-21 19:41 ` sashiko-bot
2026-09-21 19:25 ` [RFC PATCH 3/4] memcg_ext: allow BPF to defer memory.high enforcement Shakeel Butt
2026-09-24 20:14 ` JP Kobryn [this message]
2026-09-24 21:19 ` Shakeel Butt
2026-09-21 19:25 ` [RFC PATCH 4/4] selftests/bpf: add a cgroupfs lock-holder bpf_memcg_ops sample Shakeel Butt
2026-09-23 13:07 ` [RFC PATCH 0/4] memcg_ext: memcg policy through cgroup-attached struct_ops Yafang Shao
2026-09-23 15:47 ` Shakeel Butt
2026-09-24 10:01 ` Yafang Shao
2026-09-24 20:42 ` Shakeel Butt
2026-09-28 3:23 ` Yafang Shao
2026-09-28 20:40 ` Tejun Heo
2026-09-30 13:28 ` Shakeel Butt
2026-09-30 23:35 ` Tejun Heo
2026-10-01 1:00 ` Shakeel Butt
2026-10-01 18:42 ` Tejun Heo
2026-10-01 22:56 ` Shakeel Butt
2026-10-02 16:25 ` Tejun Heo
2026-10-02 22:19 ` Shakeel Butt
2026-10-03 7:17 ` Tejun Heo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=c8b8504c-8e54-479f-a372-3d2755edaf88@linux.dev \
--to=jp.kobryn@linux.dev \
--cc=akpm@linux-foundation.org \
--cc=ameryhung@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=cgroups@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=donettom@linux.ibm.com \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=gthelen@google.com \
--cc=hannes@cmpxchg.org \
--cc=hui.zhu@linux.dev \
--cc=ihor.solodrai@linux.dev \
--cc=jiayuan.chen@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=kernel-team@meta.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=mhocko@kernel.org \
--cc=mkoutny@suse.com \
--cc=muchun.song@linux.dev \
--cc=roman.gushchin@linux.dev \
--cc=shakeel.butt@linux.dev \
--cc=song@kernel.org \
--cc=tj@kernel.org \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.