mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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.

  reply	other threads:[~2026-09-24 20:15 UTC|newest]

Thread overview: 12+ 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-21 19:25 ` [RFC PATCH 2/4] memcg_ext: add cgroup-attached bpf_memcg_ops Shakeel Butt
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

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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®