Linux cgroups development
 help / color / mirror / Atom feed
* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Yosry Ahmed @ 2026-06-08 22:27 UTC (permalink / raw)
  To: Shakeel Butt
  Cc: Hao Jia, Johannes Weiner, mhocko, tj, mkoutny, roman.gushchin,
	Nhat Pham, akpm, chengming.zhou, muchun.song, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia, youngjun.park
In-Reply-To: <aictKA0XWMWbxFdN@linux.dev>

> > > Youngjun is working on swap tiers. At the moment he is more interested in
> > > allowing a specific swap device to a memcg or not. I can imagine in future there
> > > will be use-cases where there will be a need to demote data on higher tier swap
> > > to lower tier swap. What would be the appropriate interface?
> > >
> > > BTW does zswap folks think of zswap as a top swap tier or something different?
> >
> > I haven't been following the swap tiers work closely, but personally I
> > do think of zswap as a top swap tier.
>
> Same for me though I imagine swap tiers would introduce some duplication i.e.
> different way (interface) to set limits for swap tiers for a given memcg.
>
> > Things will probably get more
> > blurry with memory tiers and compressed memory nodes though.
>
> I think there will still be distinction between byte addressable and fault on
> access devices.

Yeah, I think it makes sense to define "swap" as fault on access
(zswap, SSD, etc), and memory tiers as byte-addressable (even if you
put an SSD behind CXL and make it byte-addressable). But I also
remember seeing discussions about unifying memory tiers and swap in a
way, and it makes sense from a reclaim perspective (swap or demote
first?).

^ permalink raw reply

* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Shakeel Butt @ 2026-06-08 22:22 UTC (permalink / raw)
  To: Yosry Ahmed
  Cc: Hao Jia, Johannes Weiner, mhocko, tj, mkoutny, roman.gushchin,
	Nhat Pham, akpm, chengming.zhou, muchun.song, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia, youngjun.park
In-Reply-To: <CAO9r8zNBJ-BsXyKFveA92jbwMu63uFVTY5CuT4fRHTBVcOjhPw@mail.gmail.com>

On Mon, Jun 08, 2026 at 01:19:32PM -0700, Yosry Ahmed wrote:
> On Mon, Jun 8, 2026 at 12:50 PM Shakeel Butt <shakeel.butt@linux.dev> wrote:
> >
> > +Youngjun
> >
> > On Mon, Jun 08, 2026 at 11:30:30AM -0700, Shakeel Butt wrote:
> > > On Wed, Jun 03, 2026 at 10:36:07PM -0700, Yosry Ahmed wrote:
> > > > > >> But doesn't it make more sense to specify the compressed size, which is
> > > > > >> ultimately the amount of memory you actually want to reclaim.
> > > > > >>
> > > > > >
> > > > > > I personally prefer compressed size to pre-compressed size. That's
> > > > > > kinda what user cares about, no?
> > > > > >
> > > > > > One thing we can do is let users prescribe a compressed size, but
> > > > > > internally, we can multiply that by the average compression ratio.
> > > > > > That gives us a guesstimate of how many pages we need to reclaim, and
> > > > > > you can follow the rest of your implementation as is (perhaps with
> > > > > > short-circuit when we reach the goal with fewer pages reclaimed).
> > > > >
> > > > > Got it. I will change it to use the compressed size in the next version.
> > > > >
> > > > > Yosry, Nhat, should we continue using the zswap_writeback_only key to
> > > > > trigger proactive writeback?
> > > >
> > > > I *really* want the memcg maintainers to chime in here, it's
> > > > ultimately their call.
> > > >
> > > > Michal? Johannes? Shakeel? Roman? Anyone? :D
> > >
> > > Between the options of having an explicit interface (i.e.
> > > memory.zswap.writeback*) or a key (i.e. zswap_writeback_only) to memory.reclaim
> > > interface, I prefer the key option. I have not looked into how much proactively
> > > reclaiming zswap memory or proactively triggering zswap writeback makes sense
> > > but from the perspective of memcg interface, I think the key option would give a
> > > more clean solution if we decide in the future that this whole thing was a bad
> > > idea.
> > >
> > > Next regarding future proofing zswap writeback trigger, do we expect any
> > > potential additions/changes/new-features for this interface? For example do we
> > > expect in future we may want to trigger the zswap writeback only from a specific
> > > node or lowest memory tier?
> 
> The way I see it, zswap writeback is just a "special" type of
> proactive reclaim, but the goal is still proactively freeing cold
> memory. In that regard, I think it makes sense to have things like
> node-specific reclaim. Not sure about other extensions, but Hao
> initially suggested making this age-based, so I think the answer is
> yes.
> 
> For both of these examples (node-specific reclaim, age-based reclaim),
> I think the same semantics could apply to memory.reclaim in general,
> which is why I suggested making it a part of memory.reclaim. I also
> like the idea of having a single proactive reclaim interface in
> general, but maybe we don't want to overload it too much.

IMHO it is fine.

> 
> > Youngjun is working on swap tiers. At the moment he is more interested in
> > allowing a specific swap device to a memcg or not. I can imagine in future there
> > will be use-cases where there will be a need to demote data on higher tier swap
> > to lower tier swap. What would be the appropriate interface?
> >
> > BTW does zswap folks think of zswap as a top swap tier or something different?
> 
> I haven't been following the swap tiers work closely, but personally I
> do think of zswap as a top swap tier.

Same for me though I imagine swap tiers would introduce some duplication i.e.
different way (interface) to set limits for swap tiers for a given memcg.

> Things will probably get more
> blurry with memory tiers and compressed memory nodes though.

I think there will still be distinction between byte addressable and fault on
access devices.

^ permalink raw reply

* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Yosry Ahmed @ 2026-06-08 20:19 UTC (permalink / raw)
  To: Shakeel Butt
  Cc: Hao Jia, Johannes Weiner, mhocko, tj, mkoutny, roman.gushchin,
	Nhat Pham, akpm, chengming.zhou, muchun.song, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia, youngjun.park
In-Reply-To: <aicZ-5GX9De3MAU7@linux.dev>

On Mon, Jun 8, 2026 at 12:50 PM Shakeel Butt <shakeel.butt@linux.dev> wrote:
>
> +Youngjun
>
> On Mon, Jun 08, 2026 at 11:30:30AM -0700, Shakeel Butt wrote:
> > On Wed, Jun 03, 2026 at 10:36:07PM -0700, Yosry Ahmed wrote:
> > > > >> But doesn't it make more sense to specify the compressed size, which is
> > > > >> ultimately the amount of memory you actually want to reclaim.
> > > > >>
> > > > >
> > > > > I personally prefer compressed size to pre-compressed size. That's
> > > > > kinda what user cares about, no?
> > > > >
> > > > > One thing we can do is let users prescribe a compressed size, but
> > > > > internally, we can multiply that by the average compression ratio.
> > > > > That gives us a guesstimate of how many pages we need to reclaim, and
> > > > > you can follow the rest of your implementation as is (perhaps with
> > > > > short-circuit when we reach the goal with fewer pages reclaimed).
> > > >
> > > > Got it. I will change it to use the compressed size in the next version.
> > > >
> > > > Yosry, Nhat, should we continue using the zswap_writeback_only key to
> > > > trigger proactive writeback?
> > >
> > > I *really* want the memcg maintainers to chime in here, it's
> > > ultimately their call.
> > >
> > > Michal? Johannes? Shakeel? Roman? Anyone? :D
> >
> > Between the options of having an explicit interface (i.e.
> > memory.zswap.writeback*) or a key (i.e. zswap_writeback_only) to memory.reclaim
> > interface, I prefer the key option. I have not looked into how much proactively
> > reclaiming zswap memory or proactively triggering zswap writeback makes sense
> > but from the perspective of memcg interface, I think the key option would give a
> > more clean solution if we decide in the future that this whole thing was a bad
> > idea.
> >
> > Next regarding future proofing zswap writeback trigger, do we expect any
> > potential additions/changes/new-features for this interface? For example do we
> > expect in future we may want to trigger the zswap writeback only from a specific
> > node or lowest memory tier?

The way I see it, zswap writeback is just a "special" type of
proactive reclaim, but the goal is still proactively freeing cold
memory. In that regard, I think it makes sense to have things like
node-specific reclaim. Not sure about other extensions, but Hao
initially suggested making this age-based, so I think the answer is
yes.

For both of these examples (node-specific reclaim, age-based reclaim),
I think the same semantics could apply to memory.reclaim in general,
which is why I suggested making it a part of memory.reclaim. I also
like the idea of having a single proactive reclaim interface in
general, but maybe we don't want to overload it too much.

> Youngjun is working on swap tiers. At the moment he is more interested in
> allowing a specific swap device to a memcg or not. I can imagine in future there
> will be use-cases where there will be a need to demote data on higher tier swap
> to lower tier swap. What would be the appropriate interface?
>
> BTW does zswap folks think of zswap as a top swap tier or something different?

I haven't been following the swap tiers work closely, but personally I
do think of zswap as a top swap tier. Things will probably get more
blurry with memory tiers and compressed memory nodes though.

^ permalink raw reply

* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Shakeel Butt @ 2026-06-08 19:50 UTC (permalink / raw)
  To: Yosry Ahmed
  Cc: Hao Jia, Johannes Weiner, mhocko, tj, mkoutny, roman.gushchin,
	Nhat Pham, akpm, chengming.zhou, muchun.song, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia, youngjun.park
In-Reply-To: <aicJBVT4pBvmyooT@linux.dev>

+Youngjun

On Mon, Jun 08, 2026 at 11:30:30AM -0700, Shakeel Butt wrote:
> On Wed, Jun 03, 2026 at 10:36:07PM -0700, Yosry Ahmed wrote:
> > > >> But doesn't it make more sense to specify the compressed size, which is
> > > >> ultimately the amount of memory you actually want to reclaim.
> > > >>
> > > >
> > > > I personally prefer compressed size to pre-compressed size. That's
> > > > kinda what user cares about, no?
> > > >
> > > > One thing we can do is let users prescribe a compressed size, but
> > > > internally, we can multiply that by the average compression ratio.
> > > > That gives us a guesstimate of how many pages we need to reclaim, and
> > > > you can follow the rest of your implementation as is (perhaps with
> > > > short-circuit when we reach the goal with fewer pages reclaimed).
> > >
> > > Got it. I will change it to use the compressed size in the next version.
> > >
> > > Yosry, Nhat, should we continue using the zswap_writeback_only key to
> > > trigger proactive writeback?
> > 
> > I *really* want the memcg maintainers to chime in here, it's
> > ultimately their call.
> > 
> > Michal? Johannes? Shakeel? Roman? Anyone? :D
> 
> Between the options of having an explicit interface (i.e.
> memory.zswap.writeback*) or a key (i.e. zswap_writeback_only) to memory.reclaim
> interface, I prefer the key option. I have not looked into how much proactively
> reclaiming zswap memory or proactively triggering zswap writeback makes sense
> but from the perspective of memcg interface, I think the key option would give a
> more clean solution if we decide in the future that this whole thing was a bad
> idea.
> 
> Next regarding future proofing zswap writeback trigger, do we expect any
> potential additions/changes/new-features for this interface? For example do we
> expect in future we may want to trigger the zswap writeback only from a specific
> node or lowest memory tier?

Youngjun is working on swap tiers. At the moment he is more interested in
allowing a specific swap device to a memcg or not. I can imagine in future there
will be use-cases where there will be a need to demote data on higher tier swap
to lower tier swap. What would be the appropriate interface?

BTW does zswap folks think of zswap as a top swap tier or something different?

^ permalink raw reply

* Re: [PATCH] cgroup/cpuset: Support multiple source/destination cpusets using pids pattern
From: Waiman Long @ 2026-06-08 18:49 UTC (permalink / raw)
  To: Ridong Chen; +Cc: cgroups, Tejun Heo, Johannes Weiner, linux-kernel
In-Reply-To: <20260607031259.79541-1-ridong.chen@linux.dev>


On 6/6/26 11:12 PM, Ridong Chen wrote:
> When the cpuset controller is enabled/disabled in a parent cgroup, tasks
> from multiple child cpusets need to be migrated. The current code only
> handles a single source/destination pair.
>
> Support multiple source/destination cpusets by adopting the per-task
> processing pattern similar to the pids controller:
>
> 1) Perform per-task DL bandwidth reservation (dl_bw_alloc) directly in
>     cpuset_can_attach() instead of batching into sum_migrate_dl_bw. This
>     eliminates the sum_migrate_dl_bw and dl_bw_cpu fields from the cpuset
>     struct.
>
> 2) Track attach_in_progress per-task per-destination cpuset to properly
>     guard all involved cpusets from having their cpus/mems zeroed.
>
> 3) Use a shared cpuset_undo_attach() helper for both rollback-on-error
>     in cpuset_can_attach() and for cpuset_cancel_attach().
>
> 4) Detect many-source migrations and force cpus_updated/mems_updated
>     to true so all tasks get properly updated during attach.
>
> 5) Defer nr_deadline_tasks updates to cpuset_attach() (after migration
>     is committed) to avoid a race with dl_rebuild_rd_accounting() that
>     could see inconsistent values between can_attach and attach.
>
> Signed-off-by: Ridong Chen <ridong.chen@linux.dev>
> ---
>   kernel/cgroup/cpuset-internal.h |   7 --
>   kernel/cgroup/cpuset.c          | 167 ++++++++++++++++----------------
>   2 files changed, 84 insertions(+), 90 deletions(-)
>
> diff --git a/kernel/cgroup/cpuset-internal.h b/kernel/cgroup/cpuset-internal.h
> index f7aaf01f7cd5..8f32cb97eb94 100644
> --- a/kernel/cgroup/cpuset-internal.h
> +++ b/kernel/cgroup/cpuset-internal.h
> @@ -167,13 +167,6 @@ struct cpuset {
>   	 */
>   	int nr_deadline_tasks;
>   	int nr_migrate_dl_tasks;
> -	/* DL bandwidth that needs destination reservation for this attach. */
> -	u64 sum_migrate_dl_bw;
> -	/*
> -	 * CPU used for temporary DL bandwidth allocation during attach;
> -	 * -1 if no DL bandwidth was allocated in the current attach.
> -	 */
> -	int dl_bw_cpu;
>   
>   	/* Invalid partition error code, not lock protected */
>   	enum prs_errcode prs_err;
> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
> index e52a5a40d607..a6d96a39cdb1 100644
> --- a/kernel/cgroup/cpuset.c
> +++ b/kernel/cgroup/cpuset.c
> @@ -288,7 +288,6 @@ struct cpuset top_cpuset = {
>   	.flags = BIT(CS_CPU_EXCLUSIVE) |
>   		 BIT(CS_MEM_EXCLUSIVE) | BIT(CS_SCHED_LOAD_BALANCE),
>   	.partition_root_state = PRS_ROOT,
> -	.dl_bw_cpu = -1,
>   };
>   
>   /**
> @@ -580,8 +579,6 @@ static struct cpuset *dup_or_alloc_cpuset(struct cpuset *cs)
>   	if (!trial)
>   		return NULL;
>   
> -	trial->dl_bw_cpu = -1;
> -
>   	/* Setup cpumask pointer array */
>   	cpumask_var_t *pmask[4] = {
>   		&trial->cpus_allowed,
> @@ -3026,31 +3023,36 @@ static int cpuset_can_attach_check(struct cpuset *cs, struct cpuset *oldcs,
>   	return 0;
>   }
>   
> -static int cpuset_reserve_dl_bw(struct cpuset *cs)
> +/*
> + * Undo DL bandwidth reservations and attach_in_progress increments done
> + * in cpuset_can_attach(). Used for both rollback on error and cancel_attach.
> + * If @stop_at is non-NULL, undo only for tasks before @stop_at in the tset.
> + */
> +static void cpuset_undo_attach(struct cgroup_taskset *tset,
> +			       struct task_struct *stop_at)
>   {
> -	int cpu, ret;
> -
> -	if (!cs->sum_migrate_dl_bw)
> -		return 0;
> +	struct cgroup_subsys_state *css;
> +	struct task_struct *task;
>   
> -	cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus);
> -	if (unlikely(cpu >= nr_cpu_ids))
> -		return -EINVAL;
> +	cgroup_taskset_for_each(task, css, tset) {
> +		struct cpuset *cs = css_cs(css);
>   
> -	ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw);
> -	if (ret)
> -		return ret;
> +		if (task == stop_at)
> +			break;
>   
> -	cs->dl_bw_cpu = cpu;
> -	return 0;
> +		if (dl_task(task)) {
> +			cs->nr_migrate_dl_tasks--;
> +			if (dl_task_needs_bw_move(task, cs->effective_cpus)) {
> +				int cpu = cpumask_any_and(cpu_active_mask,
> +							 cs->effective_cpus);
> +				dl_bw_free(cpu, task->dl.dl_bw);
> +			}
> +		}
> +		dec_attach_in_progress_locked(cs);
> +	}
>   }
>   
> -static void reset_migrate_dl_data(struct cpuset *cs)
> -{
> -	cs->nr_migrate_dl_tasks = 0;
> -	cs->sum_migrate_dl_bw = 0;
> -	cs->dl_bw_cpu = -1;
> -}
> +static bool attach_many_sources;
>   
>   /* Called by cgroups to determine if a cpuset is usable; cpuset_mutex held */
>   static int cpuset_can_attach(struct cgroup_taskset *tset)
> @@ -3067,90 +3069,73 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
>   	cs = css_cs(css);
>   
>   	mutex_lock(&cpuset_mutex);
> +	attach_many_sources = false;
>   
>   	/* Check to see if task is allowed in the cpuset */
>   	ret = cpuset_can_attach_check(cs, oldcs, &setsched_check);
>   	if (ret)
>   		goto out_unlock;
>   
> -	/*
> -	 * The cpuset_attach_old_cs is used mainly by cpuset_migrate_mm() to get
> -	 * the old_mems_allowed value. There are two ways that many-to-one
> -	 * cpuset migration can happen:
> -	 * 1) A multithread application with threads in different cpusets is
> -	 *    wholely migrated to a new cpuset.
> -	 * 2) Disabling v2 cpuset controller will move all the tasks in child
> -	 *    cpusets to the parent cpuset.
> -	 *
> -	 * In the former case, it is the mm setting of the group leader that
> -	 * really matters. So cpuset_attach_old_cs should track the oldcs of the
> -	 * group leader. It falls back to the oldcs of the first task if there
> -	 * is no group leader in the taskset. In the latter case, effective_mems
> -	 * of child cpusets must always be a subset of the parent. So no real
> -	 * page migration will be necessary no matter which child cpuset is
> -	 * selected as cpuset_attach_old_cs.
> -	 */
>   	cgroup_taskset_for_each(task, css, tset) {
> +		struct cpuset *newcs = css_cs(css);
> +		struct cpuset *new_oldcs = task_cs(task);
> +
> +		if (newcs != cs || new_oldcs != oldcs) {
> +			if (new_oldcs != oldcs)
> +				attach_many_sources = true;
> +			cs = newcs;
> +			oldcs = new_oldcs;
> +			ret = cpuset_can_attach_check(cs, oldcs,
> +						      &setsched_check);
> +			if (ret)
> +				goto out_rollback;
> +		}
> +
>   		ret = task_can_attach(task);
>   		if (ret)
> -			goto out_unlock;
> +			goto out_rollback;
>   
> -		/* Update cpuset_attach_old_cs to the latest group leader */
>   		if (task == task->group_leader)
>   			cpuset_attach_old_cs = task_cs(task);
>   
>   		if (setsched_check) {
>   			ret = security_task_setscheduler(task);
>   			if (ret)
> -				goto out_unlock;
> +				goto out_rollback;
>   		}
>   
>   		if (dl_task(task)) {
> -			/*
> -			 * Count all migrating DL tasks for cpuset task accounting.
> -			 * Only tasks that need a root-domain bandwidth move
> -			 * contribute to sum_migrate_dl_bw.
> -			 */
>   			cs->nr_migrate_dl_tasks++;
> -			if (dl_task_needs_bw_move(task, cs->effective_cpus))
> -				cs->sum_migrate_dl_bw += task->dl.dl_bw;
> +			if (dl_task_needs_bw_move(task, cs->effective_cpus)) {
> +				int cpu = cpumask_any_and(cpu_active_mask,
> +							 cs->effective_cpus);
> +				if (unlikely(cpu >= nr_cpu_ids)) {
> +					ret = -EINVAL;
> +					goto out_rollback;
> +				}
> +				ret = dl_bw_alloc(cpu, task->dl.dl_bw);
> +				if (ret)
> +					goto out_rollback;
> +			}
>   		}
> -	}
> -
> -	ret = cpuset_reserve_dl_bw(cs);
>   
> -out_unlock:
> -	if (ret) {
> -		reset_migrate_dl_data(cs);
> -	} else {
> -		/*
> -		 * Mark attach is in progress.  This makes validate_change() fail
> -		 * changes which zero cpus/mems_allowed.
> -		 */
>   		cs->attach_in_progress++;
>   	}
>   
> +	goto out_unlock;
> +
> +out_rollback:
> +	cpuset_undo_attach(tset, task);
> +
> +out_unlock:
>   	mutex_unlock(&cpuset_mutex);
>   	return ret;
>   }
>   
>   static void cpuset_cancel_attach(struct cgroup_taskset *tset)
>   {
> -	struct cgroup_subsys_state *css;
> -	struct cpuset *cs;
> -
> -	cgroup_taskset_first(tset, &css);
> -	cs = css_cs(css);
> -
>   	mutex_lock(&cpuset_mutex);
> -	dec_attach_in_progress_locked(cs);
> -
> -	if (cs->dl_bw_cpu >= 0)
> -		dl_bw_free(cs->dl_bw_cpu, cs->sum_migrate_dl_bw);
> -
> -	if (cs->nr_migrate_dl_tasks)
> -		reset_migrate_dl_data(cs);
> -
> +	cpuset_undo_attach(tset, NULL);
>   	mutex_unlock(&cpuset_mutex);
>   }
>   
> @@ -3232,8 +3217,15 @@ static void cpuset_attach(struct cgroup_taskset *tset)
>   	mutex_lock(&cpuset_mutex);
>   	queue_task_work = false;
>   
> -	attach_cpus_updated = !cpumask_equal(cs->effective_cpus, oldcs->effective_cpus);
> -	attach_mems_updated = !nodes_equal(cs->effective_mems, oldcs->effective_mems);
> +	if (attach_many_sources) {
> +		attach_cpus_updated = true;
> +		attach_mems_updated = true;
> +	} else {
> +		attach_cpus_updated = !cpumask_equal(cs->effective_cpus,
> +						    oldcs->effective_cpus);
> +		attach_mems_updated = !nodes_equal(cs->effective_mems,
> +						   oldcs->effective_mems);
> +	}
>   
>   	/*
>   	 * In the default hierarchy, enabling cpuset in the child cgroups
> @@ -3250,20 +3242,29 @@ static void cpuset_attach(struct cgroup_taskset *tset)
>   	}
>   
>   	cgroup_taskset_for_each(task, css, tset)
> -		cpuset_attach_task(cs, task);
> +		cpuset_attach_task(css_cs(css), task);
>   
>   out:
>   	if (queue_task_work)
>   		schedule_flush_migrate_mm();
>   	cs->old_mems_allowed = cpuset_attach_nodemask_to;
>   
> -	if (cs->nr_migrate_dl_tasks) {
> -		cs->nr_deadline_tasks += cs->nr_migrate_dl_tasks;
> -		oldcs->nr_deadline_tasks -= cs->nr_migrate_dl_tasks;
> -		reset_migrate_dl_data(cs);
> -	}
> +	/*
> +	 * Update nr_deadline_tasks now that migration is committed.
> +	 * nr_migrate_dl_tasks was accumulated per-dst in can_attach but
> +	 * nr_deadline_tasks is deferred to here to avoid a race with
> +	 * dl_rebuild_rd_accounting() between can_attach and attach.
> +	 */
> +	cgroup_taskset_for_each(task, css, tset) {
> +		struct cpuset *dst_cs = css_cs(css);
>   
> -	dec_attach_in_progress_locked(cs);
> +		if (dst_cs->nr_migrate_dl_tasks) {
> +			dst_cs->nr_deadline_tasks += dst_cs->nr_migrate_dl_tasks;
> +			oldcs->nr_deadline_tasks -= dst_cs->nr_migrate_dl_tasks;
> +			dst_cs->nr_migrate_dl_tasks = 0;
> +		}
> +		dec_attach_in_progress_locked(dst_cs);
> +	}

You are assuming that there is only one source cpuset. That may not be 
true. I suppose that we may not track the set of destination cpusets and 
they will be hit during task iteration. We will still need to track all 
the source cpusets.

Cheers,
Longman

>   
>   	mutex_unlock(&cpuset_mutex);
>   }


^ permalink raw reply

* Re: [PATCH v3 2/4] mm/zswap: Implement proactive writeback
From: Shakeel Butt @ 2026-06-08 18:30 UTC (permalink / raw)
  To: Yosry Ahmed
  Cc: Hao Jia, Johannes Weiner, mhocko, tj, mkoutny, roman.gushchin,
	Nhat Pham, akpm, chengming.zhou, muchun.song, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAO9r8zM4SDdTgz9L2s1VfXL8K2VBjMD9ej2BTDxaGge1t2+quA@mail.gmail.com>

On Wed, Jun 03, 2026 at 10:36:07PM -0700, Yosry Ahmed wrote:
> > >> But doesn't it make more sense to specify the compressed size, which is
> > >> ultimately the amount of memory you actually want to reclaim.
> > >>
> > >
> > > I personally prefer compressed size to pre-compressed size. That's
> > > kinda what user cares about, no?
> > >
> > > One thing we can do is let users prescribe a compressed size, but
> > > internally, we can multiply that by the average compression ratio.
> > > That gives us a guesstimate of how many pages we need to reclaim, and
> > > you can follow the rest of your implementation as is (perhaps with
> > > short-circuit when we reach the goal with fewer pages reclaimed).
> >
> > Got it. I will change it to use the compressed size in the next version.
> >
> > Yosry, Nhat, should we continue using the zswap_writeback_only key to
> > trigger proactive writeback?
> 
> I *really* want the memcg maintainers to chime in here, it's
> ultimately their call.
> 
> Michal? Johannes? Shakeel? Roman? Anyone? :D

Between the options of having an explicit interface (i.e.
memory.zswap.writeback*) or a key (i.e. zswap_writeback_only) to memory.reclaim
interface, I prefer the key option. I have not looked into how much proactively
reclaiming zswap memory or proactively triggering zswap writeback makes sense
but from the perspective of memcg interface, I think the key option would give a
more clean solution if we decide in the future that this whole thing was a bad
idea.

Next regarding future proofing zswap writeback trigger, do we expect any
potential additions/changes/new-features for this interface? For example do we
expect in future we may want to trigger the zswap writeback only from a specific
node or lowest memory tier?

^ permalink raw reply

* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Nhat Pham @ 2026-06-08 18:01 UTC (permalink / raw)
  To: Yosry Ahmed
  Cc: Hao Jia, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
	chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAO9r8zN6VVZz7dpjNrh8n7wbLkqcrsROPm70MQQxO49HJSmMFw@mail.gmail.com>

On Mon, Jun 8, 2026 at 9:48 AM Yosry Ahmed <yosry@kernel.org> wrote:
>
> > But OTOH, this does seem like a recipe for inefficient reclaim. We
> > might exhaust hotter memory of a cgroup while sparing colder memory of
> > another cgroup... But maybe if they're all cold anyway, then who
> > cares, and eventually you'll get to the cold stuff of other child?
>
> Forgot to respond to this part, the unfairness is limited to the batch
> size per-invocation, so it should be fine as long as you don't divide
> the amount over 100 iterations for some reason. Also yes, all memory
> in zswap is cold, the relative coldness is not that important (e.g.
> compared to relative coldness during reclaim).

Ok then yeah, I think we should shelve per-memcg cursor for the next
version. Down the line, if we have more data that unfairness is an
issue, we can always fix it. One step at a time :)

^ permalink raw reply

* Re: [PATCH v2] cgroup/dmem: accept only one region per limit write
From: Natalie Vock @ 2026-06-08 16:51 UTC (permalink / raw)
  To: Eric Chanudet, Maarten Lankhorst, Maxime Ripard, Tejun Heo,
	Johannes Weiner, Michal Koutný
  Cc: cgroups, dri-devel, linux-kernel, Albert Esteve
In-Reply-To: <20260608-cgroup-dmem-write-single-region-v2-1-b0cd6c4ccf1b@redhat.com>

On 6/8/26 17:53, Eric Chanudet wrote:
> Accept only one "region value" pair entry for the dmem.max, dmem.min,
> dmem.low files.
> 
> This changes the UAPI that otherwise accepted multiple lines for setting
> multiple entries in one write. No existing user is known to rely on
> writing multiple regions in a single write.
> 
> Processing multiple regions in dmemcg_limit_write() could quietly change
> first limits before failing on a later one and returning an error to the
> writer, with no indication some changes occurred.
> 
> Acked-by: Tejun Heo <tj@kernel.org>
> Signed-off-by: Eric Chanudet <echanude@redhat.com>

Reviewed-by: Natalie Vock <natalie.vock@gmx.de>

Thanks,
Natalie

^ permalink raw reply

* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Yosry Ahmed @ 2026-06-08 16:48 UTC (permalink / raw)
  To: Nhat Pham
  Cc: Hao Jia, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
	chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAKEwX=PF9hfERC_QMq+rjkSc-BsJyawMgTe+EhwR_86HiQKm=Q@mail.gmail.com>

> But OTOH, this does seem like a recipe for inefficient reclaim. We
> might exhaust hotter memory of a cgroup while sparing colder memory of
> another cgroup... But maybe if they're all cold anyway, then who
> cares, and eventually you'll get to the cold stuff of other child?

Forgot to respond to this part, the unfairness is limited to the batch
size per-invocation, so it should be fine as long as you don't divide
the amount over 100 iterations for some reason. Also yes, all memory
in zswap is cold, the relative coldness is not that important (e.g.
compared to relative coldness during reclaim).

^ permalink raw reply

* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Yosry Ahmed @ 2026-06-08 16:44 UTC (permalink / raw)
  To: Nhat Pham
  Cc: Hao Jia, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
	chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAKEwX=PF9hfERC_QMq+rjkSc-BsJyawMgTe+EhwR_86HiQKm=Q@mail.gmail.com>

On Mon, Jun 8, 2026 at 9:23 AM Nhat Pham <nphamcs@gmail.com> wrote:
>
> On Mon, Jun 8, 2026 at 5:50 AM Hao Jia <jiahao.kernel@gmail.com> wrote:
> > On 2026/6/5 01:23, Nhat Pham wrote:
> > >
> >
> > Thanks for the suggestion!
> >
> > I ran some tests and found that neither the per-memcg cursor nor
> > different batch sizes have a significant impact on proactive writeback
> > performance. However, exactly as we suspected, without the per-memcg
> > cursor, the writeback distribution among child memcgs is highly unfair.
> >
> > Test Setup:
> >
> >    zswap config: 18G capacity, LZ4 compression.
> >    cgroup hierarchy: 1 parent test memcg with 10 child memcgs.
> >    Allocation: Allocated 1600MB of anonymous pages in each child memcg.
> > To ensure compressibility, the first half of each page was filled with
> > random data and the second half with zeros.
> >    Force to zswap: Ran echo "1600M" > memory.reclaim on each child memcg
> > to squeeze all their memory into zswap.
> >    Trigger writeback: Ran echo "<size> zswap_writeback_only" >
> > memory.reclaim on the parent cgroup 200 times, with a 2-second interval
> > between each run.
> >    Metric: Monitored the zswpwb_proactive metric in memory.stat to
> > observe the writeback volume.
> >    **Note**: The size here refers to the uncompressed memory size. Also,
> > since the second-chance algorithm would cause many writebacks to fall
> > short of the target size, I **bypassed** it during these tests to avoid
> > interference.
> >
> > Without cursor (size: 1M, batch: 32)
> >    child        wb_pages        wb_MB     share%
> >    child0           6368        24.88      12.50
> >    child1           6368        24.88      12.50
> >    child2           6368        24.88      12.50
> >    child3           6368        24.88      12.50
> >    child4           6368        24.88      12.50
> >    child5           6368        24.88      12.50
> >    child6           6368        24.88      12.50
> >    child7           6368        24.88      12.50
> >    child8              0         0.00       0.00
> >    child9              0         0.00       0.00
> > Without cursor (size: 1M, batch: 128)
> >    child        wb_pages        wb_MB     share%
> >    child0          25472        99.50      50.00
> >    child1          25472        99.50      50.00
> >    child2              0         0.00       0.00
> >    child3              0         0.00       0.00
> >    child4              0         0.00       0.00
> >    child5              0         0.00       0.00
> >    child6              0         0.00       0.00
> >    child7              0         0.00       0.00
> >    child8              0         0.00       0.00
> >    child9              0         0.00       0.00
> > Without cursor (size: 6M, batch: 128)
> >    child        wb_pages        wb_MB     share%
> >    child0          51200       200.00      16.67
> >    child1          51200       200.00      16.67
> >    child2          25600       100.00       8.33
> >    child3          25600       100.00       8.33
> >    child4          25600       100.00       8.33
> >    child5          25600       100.00       8.33
> >    child6          25600       100.00       8.33
> >    child7          25600       100.00       8.33
> >    child8          25600       100.00       8.33
> >    child9          25600       100.00       8.33
> >
> >
> > With cursor (size: 1M, batch: 32)
> >    child        wb_pages        wb_MB     share%
> >    child0           5120        20.00      10.00
> >    child1           5120        20.00      10.00
> >    child2           5120        20.00      10.00
> >    child3           5120        20.00      10.00
> >    child4           5120        20.00      10.00
> >    child5           5120        20.00      10.00
> >    child6           5120        20.00      10.00
> >    child7           5120        20.00      10.00
> >    child8           5120        20.00      10.00
> >    child9           5120        20.00      10.00
> > With cursor (size: 1M, batch: 128)
> >    child        wb_pages        wb_MB     share%
> >    child0           5120        20.00      10.00
> >    child1           5120        20.00      10.00
> >    child2           5120        20.00      10.00
> >    child3           5120        20.00      10.00
> >    child4           5120        20.00      10.00
> >    child5           5120        20.00      10.00
> >    child6           5120        20.00      10.00
> >    child7           5120        20.00      10.00
> >    child8           5120        20.00      10.00
> >    child9           5120        20.00      10.00

Yes, the per-memcg cursor is more fair, and you can synthesize
scenarios that show that. However, I don't think this is a problem in
practice:

1. The unfairness is limited to the batch size per-invocation. If the
batch size is 128 pages (your highest one here), that's 0.5 MB (on
x86), which is fairly low? If the batch size is 32, it's even less.

2. Realistically, if you have a parent cgroup with with >10G of
memory, you wouldn't be reclaiming in steps of 1M. If you want to
reclaim 200MB, why are you doing it over 200 invocations? If you do it
in a single one (or over a few retries) the shares should become much
more even.

We're trying to fix a practical use case, not finding reasons why a
simple implementation won't work -- right?

More below (to Nhat's point).

>
> Yeah OTOH, we don't really make fairness an API contract here. When
> you set up a proactive reclaim scheme, if you decide to target a
> cgroup (and not its children separately), everything underneath it is
> fair game to the kernel in any split that we fancy. If you want true
> fairness or a desired split, you have to treat them as independent
> memory domains and set up proactive reclaim to hit each child cgroup
> separately (i.e one "echo > memory.reclaim" for each of them). This is
> necessary for example if each child represents a separate, isolated
> service/container/tenant. And maybe this is actually what you really
> want - hit the ancestor cgroup very lightly for the stuff it owns, but
> then dedidcate most of the reclaim effort at the leaf cgroups
> independently?

I would go a bit farther and claim that ideally fairness shouldn't
even be a factor. If you invoke proactive reclaim on a parent cgroup
with 100MB, you want to reclaim the coldest 100MB in that parent, no
matter what child they reside in. If one child cgroup has 100% hot
memory and one child cgroup has 100% cold memory, ideally you'd
reclaim all the cold memory from the second child.

However, the implementation of the LRUs and the coldness tracking
doesn't allow for doing this, so we "fallback" to reclaiming in
batches from each child because we don't really know where the coldest
pages overall are. If that changes in the future (somehow), I argue
that the correct thing to do is reclaim the absolute coldest memory at
the parent level.

If you want to reclaim evenly among the children, you can do that and
directly reclaim from the children.

>
> But OTOH, this does seem like a recipe for inefficient reclaim. We
> might exhaust hotter memory of a cgroup while sparing colder memory of
> another cgroup... But maybe if they're all cold anyway, then who
> cares, and eventually you'll get to the cold stuff of other child?
>
> Yosry, what's the concern here? Is it space overhead, or overall code
> complexity?

Mostly the complexity (e.g. the zombie memcg cleanup) and a tiny bit
the unnecessary space (8 bytes is not a lot, but these things add up).

^ permalink raw reply

* Re: [PATCH v2] cgroup/dmem: accept only one region per limit write
From: Maxime Ripard @ 2026-06-08 16:37 UTC (permalink / raw)
  To: Eric Chanudet
  Cc: cgroups, dri-devel, linux-kernel, Albert Esteve, Johannes Weiner,
	Maarten Lankhorst, Maxime Ripard, Michal Koutný,
	Natalie Vock, Tejun Heo
In-Reply-To: <20260608-cgroup-dmem-write-single-region-v2-1-b0cd6c4ccf1b@redhat.com>

On Mon, 8 Jun 2026 11:53:51 -0400, Eric Chanudet wrote:
> Accept only one "region value" pair entry for the dmem.max, dmem.min,
> dmem.low files.
> 
> This changes the UAPI that otherwise accepted multiple lines for setting
> multiple entries in one write. No existing user is known to rely on
> 
> [ ... ]

Reviewed-by: Maxime Ripard <mripard@kernel.org>

Thanks!
Maxime

^ permalink raw reply

* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Nhat Pham @ 2026-06-08 16:23 UTC (permalink / raw)
  To: Hao Jia
  Cc: Yosry Ahmed, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
	chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia
In-Reply-To: <90730fa7-62e7-d5f4-b638-23b22a8509f2@gmail.com>

On Mon, Jun 8, 2026 at 5:50 AM Hao Jia <jiahao.kernel@gmail.com> wrote:
> On 2026/6/5 01:23, Nhat Pham wrote:
> >
>
> Thanks for the suggestion!
>
> I ran some tests and found that neither the per-memcg cursor nor
> different batch sizes have a significant impact on proactive writeback
> performance. However, exactly as we suspected, without the per-memcg
> cursor, the writeback distribution among child memcgs is highly unfair.
>
> Test Setup:
>
>    zswap config: 18G capacity, LZ4 compression.
>    cgroup hierarchy: 1 parent test memcg with 10 child memcgs.
>    Allocation: Allocated 1600MB of anonymous pages in each child memcg.
> To ensure compressibility, the first half of each page was filled with
> random data and the second half with zeros.
>    Force to zswap: Ran echo "1600M" > memory.reclaim on each child memcg
> to squeeze all their memory into zswap.
>    Trigger writeback: Ran echo "<size> zswap_writeback_only" >
> memory.reclaim on the parent cgroup 200 times, with a 2-second interval
> between each run.
>    Metric: Monitored the zswpwb_proactive metric in memory.stat to
> observe the writeback volume.
>    **Note**: The size here refers to the uncompressed memory size. Also,
> since the second-chance algorithm would cause many writebacks to fall
> short of the target size, I **bypassed** it during these tests to avoid
> interference.
>
> Without cursor (size: 1M, batch: 32)
>    child        wb_pages        wb_MB     share%
>    child0           6368        24.88      12.50
>    child1           6368        24.88      12.50
>    child2           6368        24.88      12.50
>    child3           6368        24.88      12.50
>    child4           6368        24.88      12.50
>    child5           6368        24.88      12.50
>    child6           6368        24.88      12.50
>    child7           6368        24.88      12.50
>    child8              0         0.00       0.00
>    child9              0         0.00       0.00
> Without cursor (size: 1M, batch: 128)
>    child        wb_pages        wb_MB     share%
>    child0          25472        99.50      50.00
>    child1          25472        99.50      50.00
>    child2              0         0.00       0.00
>    child3              0         0.00       0.00
>    child4              0         0.00       0.00
>    child5              0         0.00       0.00
>    child6              0         0.00       0.00
>    child7              0         0.00       0.00
>    child8              0         0.00       0.00
>    child9              0         0.00       0.00
> Without cursor (size: 6M, batch: 128)
>    child        wb_pages        wb_MB     share%
>    child0          51200       200.00      16.67
>    child1          51200       200.00      16.67
>    child2          25600       100.00       8.33
>    child3          25600       100.00       8.33
>    child4          25600       100.00       8.33
>    child5          25600       100.00       8.33
>    child6          25600       100.00       8.33
>    child7          25600       100.00       8.33
>    child8          25600       100.00       8.33
>    child9          25600       100.00       8.33
>
>
> With cursor (size: 1M, batch: 32)
>    child        wb_pages        wb_MB     share%
>    child0           5120        20.00      10.00
>    child1           5120        20.00      10.00
>    child2           5120        20.00      10.00
>    child3           5120        20.00      10.00
>    child4           5120        20.00      10.00
>    child5           5120        20.00      10.00
>    child6           5120        20.00      10.00
>    child7           5120        20.00      10.00
>    child8           5120        20.00      10.00
>    child9           5120        20.00      10.00
> With cursor (size: 1M, batch: 128)
>    child        wb_pages        wb_MB     share%
>    child0           5120        20.00      10.00
>    child1           5120        20.00      10.00
>    child2           5120        20.00      10.00
>    child3           5120        20.00      10.00
>    child4           5120        20.00      10.00
>    child5           5120        20.00      10.00
>    child6           5120        20.00      10.00
>    child7           5120        20.00      10.00
>    child8           5120        20.00      10.00
>    child9           5120        20.00      10.00
>

Yeah OTOH, we don't really make fairness an API contract here. When
you set up a proactive reclaim scheme, if you decide to target a
cgroup (and not its children separately), everything underneath it is
fair game to the kernel in any split that we fancy. If you want true
fairness or a desired split, you have to treat them as independent
memory domains and set up proactive reclaim to hit each child cgroup
separately (i.e one "echo > memory.reclaim" for each of them). This is
necessary for example if each child represents a separate, isolated
service/container/tenant. And maybe this is actually what you really
want - hit the ancestor cgroup very lightly for the stuff it owns, but
then dedidcate most of the reclaim effort at the leaf cgroups
independently?

But OTOH, this does seem like a recipe for inefficient reclaim. We
might exhaust hotter memory of a cgroup while sparing colder memory of
another cgroup... But maybe if they're all cold anyway, then who
cares, and eventually you'll get to the cold stuff of other child?

Yosry, what's the concern here? Is it space overhead, or overall code
complexity?

^ permalink raw reply

* [PATCH v2] cgroup/dmem: accept only one region per limit write
From: Eric Chanudet @ 2026-06-08 15:53 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Natalie Vock, Tejun Heo,
	Johannes Weiner, Michal Koutný
  Cc: cgroups, dri-devel, linux-kernel, Albert Esteve, Eric Chanudet

Accept only one "region value" pair entry for the dmem.max, dmem.min,
dmem.low files.

This changes the UAPI that otherwise accepted multiple lines for setting
multiple entries in one write. No existing user is known to rely on
writing multiple regions in a single write.

Processing multiple regions in dmemcg_limit_write() could quietly change
first limits before failing on a later one and returning an error to the
writer, with no indication some changes occurred.

Acked-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Eric Chanudet <echanude@redhat.com>
---
Follow up from discussions on a previous thread[1].
If Albert's series[2] lands, I can cleanup and prepare some kunits for
these as well.
[1] https://lore.kernel.org/all/158bc103-7f99-4df4-8d3b-2da9b04ac0ed@lankhorst.se/
[2] https://lore.kernel.org/all/20260519-kunit_cgroups-v4-1-f6c2f498fae4@redhat.com/
---
Changes in v2:
- Handle buf == NULL by testing !buf first after strsep (Natalie)
- Don't allow extra spaces to separate key and value (Natalie)
  Other cgroup files don't (rdma, misc), so stay consistent.
- Link to v1: https://lore.kernel.org/r/20260605-cgroup-dmem-write-single-region-v1-1-9137f296579c@redhat.com
---
 kernel/cgroup/dmem.c | 69 +++++++++++++++++++---------------------------------
 1 file changed, 25 insertions(+), 44 deletions(-)

diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c
index 6430c7ce1e0372f59f1313163fb7630ce49ac1ef..39930c59cb769a505a5852a5644a371fd5596f59 100644
--- a/kernel/cgroup/dmem.c
+++ b/kernel/cgroup/dmem.c
@@ -734,57 +734,38 @@ static ssize_t dmemcg_limit_write(struct kernfs_open_file *of,
 				 void (*apply)(struct dmem_cgroup_pool_state *, u64))
 {
 	struct dmemcg_state *dmemcs = css_to_dmemcs(of_css(of));
-	int err = 0;
-
-	while (buf && !err) {
-		struct dmem_cgroup_pool_state *pool = NULL;
-		char *options, *region_name;
-		struct dmem_cgroup_region *region;
-		u64 new_limit;
-
-		options = buf;
-		buf = strchr(buf, '\n');
-		if (buf)
-			*buf++ = '\0';
-
-		options = strstrip(options);
-
-		/* eat empty lines */
-		if (!options[0])
-			continue;
-
-		region_name = strsep(&options, " \t");
-		if (!region_name[0])
-			continue;
-
-		if (!options || !*options)
-			return -EINVAL;
+	struct dmem_cgroup_pool_state *pool;
+	struct dmem_cgroup_region *region;
+	char *region_name;
+	u64 new_limit;
+	int err;
 
-		rcu_read_lock();
-		region = dmemcg_get_region_by_name(region_name);
-		rcu_read_unlock();
+	buf = strstrip(buf);
+	region_name = strsep(&buf, " \t");
+	if (!buf || !region_name[0])
+		return -EINVAL;
 
-		if (!region)
-			return -EINVAL;
+	rcu_read_lock();
+	region = dmemcg_get_region_by_name(region_name);
+	rcu_read_unlock();
+	if (!region)
+		return -EINVAL;
 
-		err = dmemcg_parse_limit(options, &new_limit);
-		if (err < 0)
-			goto out_put;
+	err = dmemcg_parse_limit(buf, &new_limit);
+	if (err < 0)
+		goto out_put;
 
-		pool = get_cg_pool_unlocked(dmemcs, region);
-		if (IS_ERR(pool)) {
-			err = PTR_ERR(pool);
-			goto out_put;
-		}
+	pool = get_cg_pool_unlocked(dmemcs, region);
+	if (IS_ERR(pool)) {
+		err = PTR_ERR(pool);
+		goto out_put;
+	}
 
-		/* And commit */
-		apply(pool, new_limit);
-		dmemcg_pool_put(pool);
+	apply(pool, new_limit);
+	dmemcg_pool_put(pool);
 
 out_put:
-		kref_put(&region->ref, dmemcg_free_region);
-	}
-
+	kref_put(&region->ref, dmemcg_free_region);
 
 	return err ?: nbytes;
 }

---
base-commit: 640c57d6ca1346a1c2363a3f473b405af979e046
change-id: 20260605-cgroup-dmem-write-single-region-9bf05b6d995d

Best regards,
-- 
Eric Chanudet <echanude@redhat.com>


^ permalink raw reply related

* Re: [PATCH] cgroup/dmem: accept only one region per limit write
From: Maarten Lankhorst @ 2026-06-08 15:42 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Natalie Vock, Eric Chanudet, Tejun Heo, Johannes Weiner,
	Michal Koutný, cgroups, dri-devel, linux-kernel,
	Albert Esteve
In-Reply-To: <20260608-rational-cuscus-of-enhancement-eecbfa@houat>

Hello,

On 6/8/26 16:04, Maxime Ripard wrote:
> On Mon, Jun 08, 2026 at 03:13:56PM +0200, Maarten Lankhorst wrote:
>> Hey,
>>
>> On 6/8/26 14:42, Maxime Ripard wrote:
>>> On Sat, Jun 06, 2026 at 11:44:10PM +0200, Maarten Lankhorst wrote:
>>>> Hey,
>>>>
>>>> On 6/6/26 18:31, Natalie Vock wrote:
>>>>> On 6/6/26 00:44, Eric Chanudet wrote:
>>>>>> Accept only one "region value" pair entry for the dmem.max, dmem.min,
>>>>>> dmem.low files.>
>>>>>> This changes the UAPI that otherwise accepted multiple lines for setting
>>>>>> multiple entries in one write. No existing user is known to rely on
>>>>>> writing multiple regions in a single write.
>>>>>
>>>>> Ugh, shoot.
>>>>>
>>>>> For dmem.low specifically, there already are some userspace thingies floating around that may write more than one region/value pairs.
>>>>>
>>>>> These thingies all depend on that one patchset for dmemcg protection that I should really get around to merging[1]. Since the userspace utilities depend on not-yet-merged patches, they sort of have to expect stuff changing under their belts, so I wouldn't really consider those users a blocker by necessity.
>>>>>
>>>>> As I see it, we could go down one of two paths:
>>>>> 1. We go ahead with the patch as proposed, and I make sure that the users I know of adapt. Could be a bit icky wrt. "do not break userspace" rules, but since the already use non-merged UAPIs in one place, you can argue that these users kind of have to expect breakage.
>>>>> 2. We use the old handling allowing multiple lines for dmem.min and dmem.low only. This preserves compatibility but uglifies the code by quite a bit.
>>>>>
>>>>> All things considered, I think I personally would prefer going with 1. and taking the patch as proposed and just having one codepath handling every limit file. Just highlighting this so we don't do it on accident.
>>>>>
>>>>> [1] https://patchwork.freedesktop.org/series/163183/
>>>>>
>>>>
>>>> I prefer option 1 as well, but would like an ack from one of the core cgroup maintainers too,
>>>> and what Maxime's opinion on this as well.
>>>
>>> Option 1 works for me too if doable
>>>
>>> Maxime
>>
>>
>> I see this as an acked-by?
>>
>> I'll commit this patch to drm-misc-next if so.
>>
>> Fortunately it may not even break those scripts in the typical case
>> where only 1 region is registered, eg the most common laptop/desktop
>> case.
> 
> Natalie had a bunch of comments afaik, so I was expecting a v2, but if
> you intend to merge it as is, you can add it if you want.

Yeah forgot about that for a second, will wait for v2!

Kind regards,
~Maarten Lankhorst

^ permalink raw reply

* Re: [PATCH] cgroup/dmem: accept only one region per limit write
From: Maxime Ripard @ 2026-06-08 14:04 UTC (permalink / raw)
  To: Maarten Lankhorst
  Cc: Natalie Vock, Eric Chanudet, Tejun Heo, Johannes Weiner,
	Michal Koutný, cgroups, dri-devel, linux-kernel,
	Albert Esteve
In-Reply-To: <15bab7c9-0857-40b9-963b-9923c0e7392c@lankhorst.se>

[-- Attachment #1: Type: text/plain, Size: 2514 bytes --]

On Mon, Jun 08, 2026 at 03:13:56PM +0200, Maarten Lankhorst wrote:
> Hey,
> 
> On 6/8/26 14:42, Maxime Ripard wrote:
> > On Sat, Jun 06, 2026 at 11:44:10PM +0200, Maarten Lankhorst wrote:
> >> Hey,
> >>
> >> On 6/6/26 18:31, Natalie Vock wrote:
> >>> On 6/6/26 00:44, Eric Chanudet wrote:
> >>>> Accept only one "region value" pair entry for the dmem.max, dmem.min,
> >>>> dmem.low files.>
> >>>> This changes the UAPI that otherwise accepted multiple lines for setting
> >>>> multiple entries in one write. No existing user is known to rely on
> >>>> writing multiple regions in a single write.
> >>>
> >>> Ugh, shoot.
> >>>
> >>> For dmem.low specifically, there already are some userspace thingies floating around that may write more than one region/value pairs.
> >>>
> >>> These thingies all depend on that one patchset for dmemcg protection that I should really get around to merging[1]. Since the userspace utilities depend on not-yet-merged patches, they sort of have to expect stuff changing under their belts, so I wouldn't really consider those users a blocker by necessity.
> >>>
> >>> As I see it, we could go down one of two paths:
> >>> 1. We go ahead with the patch as proposed, and I make sure that the users I know of adapt. Could be a bit icky wrt. "do not break userspace" rules, but since the already use non-merged UAPIs in one place, you can argue that these users kind of have to expect breakage.
> >>> 2. We use the old handling allowing multiple lines for dmem.min and dmem.low only. This preserves compatibility but uglifies the code by quite a bit.
> >>>
> >>> All things considered, I think I personally would prefer going with 1. and taking the patch as proposed and just having one codepath handling every limit file. Just highlighting this so we don't do it on accident.
> >>>
> >>> [1] https://patchwork.freedesktop.org/series/163183/
> >>>
> >>
> >> I prefer option 1 as well, but would like an ack from one of the core cgroup maintainers too,
> >> and what Maxime's opinion on this as well.
> > 
> > Option 1 works for me too if doable
> > 
> > Maxime
> 
> 
> I see this as an acked-by?
> 
> I'll commit this patch to drm-misc-next if so.
> 
> Fortunately it may not even break those scripts in the typical case
> where only 1 region is registered, eg the most common laptop/desktop
> case.

Natalie had a bunch of comments afaik, so I was expecting a v2, but if
you intend to merge it as is, you can add it if you want.

Maxime

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 273 bytes --]

^ permalink raw reply

* Re: [PATCH] cgroup/dmem: accept only one region per limit write
From: Eric Chanudet @ 2026-06-08 13:30 UTC (permalink / raw)
  To: Natalie Vock
  Cc: Maarten Lankhorst, Maxime Ripard, Tejun Heo, Johannes Weiner,
	Michal Koutný, cgroups, dri-devel, linux-kernel,
	Albert Esteve
In-Reply-To: <271b1c16-3c3c-4a1e-b09e-c4361c63814c@gmx.de>

On Sat, Jun 06, 2026 at 06:31:53PM +0200, Natalie Vock wrote:
> On 6/6/26 00:44, Eric Chanudet wrote:
> > Accept only one "region value" pair entry for the dmem.max, dmem.min,
> > dmem.low files.>
> > This changes the UAPI that otherwise accepted multiple lines for setting
> > multiple entries in one write. No existing user is known to rely on
> > writing multiple regions in a single write.
> 
> Ugh, shoot.
> 
> For dmem.low specifically, there already are some userspace thingies
> floating around that may write more than one region/value pairs.
> 
> These thingies all depend on that one patchset for dmemcg protection that I
> should really get around to merging[1]. Since the userspace utilities depend
> on not-yet-merged patches, they sort of have to expect stuff changing under
> their belts, so I wouldn't really consider those users a blocker by
> necessity.
> 
> As I see it, we could go down one of two paths:
> 1. We go ahead with the patch as proposed, and I make sure that the users I
> know of adapt. Could be a bit icky wrt. "do not break userspace" rules, but
> since the already use non-merged UAPIs in one place, you can argue that
> these users kind of have to expect breakage.
> 2. We use the old handling allowing multiple lines for dmem.min and dmem.low
> only. This preserves compatibility but uglifies the code by quite a bit.
> 
> All things considered, I think I personally would prefer going with 1. and
> taking the patch as proposed and just having one codepath handling every
> limit file. Just highlighting this so we don't do it on accident.
> 
> [1] https://patchwork.freedesktop.org/series/163183/
> 
> Some more review comments inline.
> 
> > 
> > Processing multiple regions in dmemcg_limit_write() could quietly change
> > first limits before failing on a later one and returning an error to the
> > writer, with no indication some changes occurred.
> > 
> > Signed-off-by: Eric Chanudet <echanude@redhat.com>
> > ---
> > Follow up from discussions on a previous thread[1].
> > If Albert's series[2] lands, I can cleanup and prepare some kunits for
> > these as well.
> > [1] https://lore.kernel.org/all/158bc103-7f99-4df4-8d3b-2da9b04ac0ed@lankhorst.se/
> > [2] https://lore.kernel.org/all/20260519-kunit_cgroups-v4-1-f6c2f498fae4@redhat.com/
> > ---
> >   kernel/cgroup/dmem.c | 70 +++++++++++++++++++---------------------------------
> >   1 file changed, 26 insertions(+), 44 deletions(-)
> > 
> > diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c
> > index 6430c7ce1e0372f59f1313163fb7630ce49ac1ef..113ee88e276296bccb4def546adf5cc175d7f0be 100644
> > --- a/kernel/cgroup/dmem.c
> > +++ b/kernel/cgroup/dmem.c
> > @@ -734,57 +734,39 @@ static ssize_t dmemcg_limit_write(struct kernfs_open_file *of,
> >   				 void (*apply)(struct dmem_cgroup_pool_state *, u64))
> >   {
> >   	struct dmemcg_state *dmemcs = css_to_dmemcs(of_css(of));
> > -	int err = 0;
> > -
> > -	while (buf && !err) {
> > -		struct dmem_cgroup_pool_state *pool = NULL;
> > -		char *options, *region_name;
> > -		struct dmem_cgroup_region *region;
> > -		u64 new_limit;
> > -
> > -		options = buf;
> > -		buf = strchr(buf, '\n');
> > -		if (buf)
> > -			*buf++ = '\0';
> > -
> > -		options = strstrip(options);
> > -
> > -		/* eat empty lines */
> > -		if (!options[0])
> > -			continue;
> > -
> > -		region_name = strsep(&options, " \t");
> > -		if (!region_name[0])
> > -			continue;
> > -
> > -		if (!options || !*options)
> > -			return -EINVAL;
> > +	struct dmem_cgroup_pool_state *pool;
> > +	struct dmem_cgroup_region *region;
> > +	char *region_name;
> > +	u64 new_limit;
> > +	int err;
> > -		rcu_read_lock();
> > -		region = dmemcg_get_region_by_name(region_name);
> > -		rcu_read_unlock();
> > +	buf = strstrip(buf);
> > +	region_name = strsep(&buf, " \t");
> > +	if (!region_name[0] || !buf)
> 
> If buf is NULL, isn't strsep(&buf, ...) also NULL? region_name[0] would
> therefore be a NULL pointer deref. Flipping the order of the logical or
> should be enough to prevent this.
> 

I can do a v2 with that today.

I added it if there are no delimiter found (e.g, if only the region name
is passed and strstrip() ate any trailing space). Although, buf can't be
NULL in the write callback iirc, it's either pre-allocated or
kmalloc'ed.

> > +		return -EINVAL;
> > -		if (!region)
> > -			return -EINVAL;
> > +	rcu_read_lock();
> > +	region = dmemcg_get_region_by_name(region_name);
> > +	rcu_read_unlock();
> > +	if (!region)
> > +		return -EINVAL;
> > -		err = dmemcg_parse_limit(options, &new_limit);
> > -		if (err < 0)
> > -			goto out_put;
> > +	buf = strstrip(buf);
> 
> Do we start allowing extra spaces between region and limit as well? Would
> also be fine by me, it doesn't break anything, just highlighting that it's a
> change in behavior. Should perhaps be documented in the commit message, too.
> 
> Also, you should be able to use skip_spaces() here for an equivalent result.
> I'm not strongly opinionated on either way, but using skip_spaces()
> indicates more clearly that this can only remove spaces at the start.

Same I can add to v2, I failed to notice it wasn't allowed in the
original logic.

Thank you for the review.
Best,

> 
> Best,
> Natalie
> 
> > +	err = dmemcg_parse_limit(buf, &new_limit);
> > +	if (err < 0)
> > +		goto out_put;
> > -		pool = get_cg_pool_unlocked(dmemcs, region);
> > -		if (IS_ERR(pool)) {
> > -			err = PTR_ERR(pool);
> > -			goto out_put;
> > -		}
> > +	pool = get_cg_pool_unlocked(dmemcs, region);
> > +	if (IS_ERR(pool)) {
> > +		err = PTR_ERR(pool);
> > +		goto out_put;
> > +	}
> > -		/* And commit */
> > -		apply(pool, new_limit);
> > -		dmemcg_pool_put(pool);
> > +	apply(pool, new_limit);
> > +	dmemcg_pool_put(pool);
> >   out_put:
> > -		kref_put(&region->ref, dmemcg_free_region);
> > -	}
> > -
> > +	kref_put(&region->ref, dmemcg_free_region);
> >   	return err ?: nbytes;
> >   }
> > 
> > ---
> > base-commit: 640c57d6ca1346a1c2363a3f473b405af979e046
> > change-id: 20260605-cgroup-dmem-write-single-region-9bf05b6d995d
> > 
> > Best regards,
> 

-- 
Eric Chanudet


^ permalink raw reply

* Re: [PATCH] mm: memcontrol-v1: use nofail allocations for soft limit trees
From: Michal Hocko @ 2026-06-08 13:29 UTC (permalink / raw)
  To: Ruoyu Wang
  Cc: Johannes Weiner, Roman Gushchin, Shakeel Butt, Muchun Song,
	Andrew Morton, cgroups, linux-mm, linux-kernel
In-Reply-To: <CAK_7xqyyDqNW1+puMSp2LzxmOKxFUx-UO9uGiDKoL7ZTJ8+3ZQ@mail.gmail.com>

On Mon 08-06-26 16:34:48, Ruoyu Wang wrote:
> No, I have not observed this allocation failing in practice.
> 
> This was found by static analysis and then checked by reading the code:
> memcg1_init() dereferences rtpn unconditionally after kzalloc_node(). I
> treated the soft-limit tree as mandatory memcg v1 init state and used
> __GFP_NOFAIL because continuing without it would not be useful.

This should have been part of the changelog because it provides an
insight into how have you reached your conclusion.

> I agree this is early boot init code, and I do not have a
> runtime failure report or fault-injection reproduction for it. If such
> allocations are considered not worth handling in this path, feel
> free to drop the patch.

Yes, there is simply no point in handling these failures because early
allocation failure like this one would very likely lead to massive
failure before userspace is brought up anyway so there is no practical
way to trigger the NULL ptr.

-- 
Michal Hocko
SUSE Labs

^ permalink raw reply

* Re: [PATCH] cgroup/dmem: accept only one region per limit write
From: Maarten Lankhorst @ 2026-06-08 13:13 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Natalie Vock, Eric Chanudet, Tejun Heo, Johannes Weiner,
	Michal Koutný, cgroups, dri-devel, linux-kernel,
	Albert Esteve
In-Reply-To: <20260608-glorious-fluorescent-salamander-93daec@houat>

Hey,

On 6/8/26 14:42, Maxime Ripard wrote:
> On Sat, Jun 06, 2026 at 11:44:10PM +0200, Maarten Lankhorst wrote:
>> Hey,
>>
>> On 6/6/26 18:31, Natalie Vock wrote:
>>> On 6/6/26 00:44, Eric Chanudet wrote:
>>>> Accept only one "region value" pair entry for the dmem.max, dmem.min,
>>>> dmem.low files.>
>>>> This changes the UAPI that otherwise accepted multiple lines for setting
>>>> multiple entries in one write. No existing user is known to rely on
>>>> writing multiple regions in a single write.
>>>
>>> Ugh, shoot.
>>>
>>> For dmem.low specifically, there already are some userspace thingies floating around that may write more than one region/value pairs.
>>>
>>> These thingies all depend on that one patchset for dmemcg protection that I should really get around to merging[1]. Since the userspace utilities depend on not-yet-merged patches, they sort of have to expect stuff changing under their belts, so I wouldn't really consider those users a blocker by necessity.
>>>
>>> As I see it, we could go down one of two paths:
>>> 1. We go ahead with the patch as proposed, and I make sure that the users I know of adapt. Could be a bit icky wrt. "do not break userspace" rules, but since the already use non-merged UAPIs in one place, you can argue that these users kind of have to expect breakage.
>>> 2. We use the old handling allowing multiple lines for dmem.min and dmem.low only. This preserves compatibility but uglifies the code by quite a bit.
>>>
>>> All things considered, I think I personally would prefer going with 1. and taking the patch as proposed and just having one codepath handling every limit file. Just highlighting this so we don't do it on accident.
>>>
>>> [1] https://patchwork.freedesktop.org/series/163183/
>>>
>>
>> I prefer option 1 as well, but would like an ack from one of the core cgroup maintainers too,
>> and what Maxime's opinion on this as well.
> 
> Option 1 works for me too if doable
> 
> Maxime


I see this as an acked-by?

I'll commit this patch to drm-misc-next if so.

Fortunately it may not even break those scripts in the typical case where only 1 region is registered, eg the most common laptop/desktop case.

Kind regards,
~Maarten Lankhorst

^ permalink raw reply

* Re: [PATCH v3 1/4] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Hao Jia @ 2026-06-08 12:50 UTC (permalink / raw)
  To: Nhat Pham
  Cc: Yosry Ahmed, akpm, tj, hannes, shakeel.butt, mhocko, mkoutny,
	chengming.zhou, muchun.song, roman.gushchin, cgroups, linux-mm,
	linux-kernel, linux-doc, Hao Jia
In-Reply-To: <CAKEwX=MQ3xXBAY-2H8vA+XSX5GHNBubJ2GCYAXGD+Hra++ZM7A@mail.gmail.com>



On 2026/6/5 01:23, Nhat Pham wrote:
> On Thu, Jun 4, 2026 at 6:06 AM Hao Jia <jiahao.kernel@gmail.com> wrote:
>>
>>
>>
>> On 2026/6/4 13:34, Yosry Ahmed wrote:
>>>>>> For instance, suppose a parent memcg has two children, memcg1 and memcg2,
>>>>>> each with 200MB of zswap (100MB inactive). Triggering proactive writeback on
>>>>>> the parent memcg will exhaust memcg1's inactive zswap pages. After that,
>>>>>> even though memcg2 still has plenty of inactive zswap pages, it will
>>>>>> continue to write back memcg1's active zswap pages. Writing back active
>>>>>> zswap pages causes the user-space agent to prematurely abort the writeback
>>>>>> because it detects that certain memcg metrics have exceeded predefined
>>>>>> thresholds.
>>>>>
>>>>> This will only happen if the reclaim size is smaller than the batch
>>>>> size, right? Otherwise the kernel should reclaim more or less equally
>>>>> from both memcgs?
>>>>>
>>>>
>>>> I gave it some thought. Not using a cursor could lead to unfairness
>>>> issues with certain writeback sizes:
>>>>
>>>>     - If the writeback size is an odd multiple of WB_BATCH (e.g.,
>>>> triggering a writeback of 3 * WB_BATCH), with 2 child cgroups, the
>>>> writeback ratio might end up being 2:1.
>>>>     - If a memcg has 5 child cgroups and a writeback of 2 * WB_BATCH is
>>>> triggered, it might repeatedly write back from only the first 2 child
>>>> cgroups.
>>>>
>>>> Although setting a smaller WB_BATCH might mitigate this unfairness, it
>>>> could hurt writeback efficiency. Let's just use per-memcg cursors to
>>>> completely fix these corner cases.
>>>
>>> Exactly, the batch size should be small enough that any unfairness is
>>> not a problem. I would honestly just do batching without a per-memcg
>>> cursor, unless we have numbers to prove that the efficiency is
>>> affected when we use a small batch size. Let's only introduce
>>> complexity when needed please.
> 
> I'm impartial towards the complexity of per-memcg cursor. I don't
> think it's that big of a deal, but only if it's warranted.
> 
> Hao, if you're convinced that doing small batch is not efficient,
> could you run some experiments to show the improvement bigger batchign
> and fairness? Maybe implement a small batch, no-memcg cursor first.
> Then implement a patch on top of it to add per-memcg cursor, and show
> how much performance win we can get from that patch on top of the
> patch series?
> 

Thanks for the suggestion!

I ran some tests and found that neither the per-memcg cursor nor 
different batch sizes have a significant impact on proactive writeback 
performance. However, exactly as we suspected, without the per-memcg 
cursor, the writeback distribution among child memcgs is highly unfair.

Test Setup:

   zswap config: 18G capacity, LZ4 compression.
   cgroup hierarchy: 1 parent test memcg with 10 child memcgs.
   Allocation: Allocated 1600MB of anonymous pages in each child memcg. 
To ensure compressibility, the first half of each page was filled with 
random data and the second half with zeros.
   Force to zswap: Ran echo "1600M" > memory.reclaim on each child memcg 
to squeeze all their memory into zswap.
   Trigger writeback: Ran echo "<size> zswap_writeback_only" > 
memory.reclaim on the parent cgroup 200 times, with a 2-second interval 
between each run.
   Metric: Monitored the zswpwb_proactive metric in memory.stat to 
observe the writeback volume.
   **Note**: The size here refers to the uncompressed memory size. Also, 
since the second-chance algorithm would cause many writebacks to fall 
short of the target size, I **bypassed** it during these tests to avoid 
interference.

Without cursor (size: 1M, batch: 32)
   child        wb_pages        wb_MB     share%
   child0           6368        24.88      12.50
   child1           6368        24.88      12.50
   child2           6368        24.88      12.50
   child3           6368        24.88      12.50
   child4           6368        24.88      12.50
   child5           6368        24.88      12.50
   child6           6368        24.88      12.50
   child7           6368        24.88      12.50
   child8              0         0.00       0.00
   child9              0         0.00       0.00
Without cursor (size: 1M, batch: 128)
   child        wb_pages        wb_MB     share%
   child0          25472        99.50      50.00
   child1          25472        99.50      50.00
   child2              0         0.00       0.00
   child3              0         0.00       0.00
   child4              0         0.00       0.00
   child5              0         0.00       0.00
   child6              0         0.00       0.00
   child7              0         0.00       0.00
   child8              0         0.00       0.00
   child9              0         0.00       0.00
Without cursor (size: 6M, batch: 128)
   child        wb_pages        wb_MB     share%
   child0          51200       200.00      16.67
   child1          51200       200.00      16.67
   child2          25600       100.00       8.33
   child3          25600       100.00       8.33
   child4          25600       100.00       8.33
   child5          25600       100.00       8.33
   child6          25600       100.00       8.33
   child7          25600       100.00       8.33
   child8          25600       100.00       8.33
   child9          25600       100.00       8.33


With cursor (size: 1M, batch: 32)
   child        wb_pages        wb_MB     share%
   child0           5120        20.00      10.00
   child1           5120        20.00      10.00
   child2           5120        20.00      10.00
   child3           5120        20.00      10.00
   child4           5120        20.00      10.00
   child5           5120        20.00      10.00
   child6           5120        20.00      10.00
   child7           5120        20.00      10.00
   child8           5120        20.00      10.00
   child9           5120        20.00      10.00
With cursor (size: 1M, batch: 128)
   child        wb_pages        wb_MB     share%
   child0           5120        20.00      10.00
   child1           5120        20.00      10.00
   child2           5120        20.00      10.00
   child3           5120        20.00      10.00
   child4           5120        20.00      10.00
   child5           5120        20.00      10.00
   child6           5120        20.00      10.00
   child7           5120        20.00      10.00
   child8           5120        20.00      10.00
   child9           5120        20.00      10.00

Thakns,
Hao

^ permalink raw reply

* Re: [PATCH] cgroup/dmem: accept only one region per limit write
From: Maxime Ripard @ 2026-06-08 12:42 UTC (permalink / raw)
  To: Maarten Lankhorst
  Cc: Natalie Vock, Eric Chanudet, Tejun Heo, Johannes Weiner,
	Michal Koutný, cgroups, dri-devel, linux-kernel,
	Albert Esteve
In-Reply-To: <f00e7771-cd70-4c86-9fac-149897e02b12@lankhorst.se>

[-- Attachment #1: Type: text/plain, Size: 1931 bytes --]

On Sat, Jun 06, 2026 at 11:44:10PM +0200, Maarten Lankhorst wrote:
> Hey,
> 
> On 6/6/26 18:31, Natalie Vock wrote:
> > On 6/6/26 00:44, Eric Chanudet wrote:
> >> Accept only one "region value" pair entry for the dmem.max, dmem.min,
> >> dmem.low files.>
> >> This changes the UAPI that otherwise accepted multiple lines for setting
> >> multiple entries in one write. No existing user is known to rely on
> >> writing multiple regions in a single write.
> > 
> > Ugh, shoot.
> > 
> > For dmem.low specifically, there already are some userspace thingies floating around that may write more than one region/value pairs.
> > 
> > These thingies all depend on that one patchset for dmemcg protection that I should really get around to merging[1]. Since the userspace utilities depend on not-yet-merged patches, they sort of have to expect stuff changing under their belts, so I wouldn't really consider those users a blocker by necessity.
> > 
> > As I see it, we could go down one of two paths:
> > 1. We go ahead with the patch as proposed, and I make sure that the users I know of adapt. Could be a bit icky wrt. "do not break userspace" rules, but since the already use non-merged UAPIs in one place, you can argue that these users kind of have to expect breakage.
> > 2. We use the old handling allowing multiple lines for dmem.min and dmem.low only. This preserves compatibility but uglifies the code by quite a bit.
> > 
> > All things considered, I think I personally would prefer going with 1. and taking the patch as proposed and just having one codepath handling every limit file. Just highlighting this so we don't do it on accident.
> > 
> > [1] https://patchwork.freedesktop.org/series/163183/
> > 
> 
> I prefer option 1 as well, but would like an ack from one of the core cgroup maintainers too,
> and what Maxime's opinion on this as well.

Option 1 works for me too if doable

Maxime

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 273 bytes --]

^ permalink raw reply

* [RFC PATCH v6 25/25] Documentation: Update documentation for real-time cgroups
From: Yuri Andriaccio @ 2026-06-08 12:15 UTC (permalink / raw)
  To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, Tejun Heo, Johannes Weiner,
	Michal Koutný
  Cc: cgroups, linux-kernel, Luca Abeni, Yuri Andriaccio
In-Reply-To: <20260608121546.69910-1-yurand2000@gmail.com>

Update the RT_GROUP_SCHED specific documentation. Give a brief theoretical
background for Hierarchical Constant Bandwidth Server (HCBS). Document how
the HCBS is implemented in the kernel and how the RT_GROUP_SCHED behaves
now compared to the version which this patchset replaces.

Signed-off-by: Yuri Andriaccio <yurand2000@gmail.com>
---
 Documentation/scheduler/sched-rt-group.rst | 470 +++++++++++++++++----
 1 file changed, 393 insertions(+), 77 deletions(-)

diff --git a/Documentation/scheduler/sched-rt-group.rst b/Documentation/scheduler/sched-rt-group.rst
index ab464335d320..f00bec718d67 100644
--- a/Documentation/scheduler/sched-rt-group.rst
+++ b/Documentation/scheduler/sched-rt-group.rst
@@ -53,9 +53,12 @@ CPU time is divided by means of specifying how much time can be spent running
 in a given period. We allocate this "run time" for each real-time group which
 the other real-time groups will not be permitted to use.

-Any time not allocated to a real-time group will be used to run normal priority
-tasks (SCHED_OTHER). Any allocated run time not used will also be picked up by
-SCHED_OTHER.
+Each real-time group runs at the same priority as SCHED_DEADLINE, thus they
+share and contend the SCHED_DEADLINE allowed bandwidth. Any time not allocated
+to a real-time group (and SCHED_DEADLINE tasks) will be used to run both
+SCHED_FIFO/SCHED_RR, normal priority tasks (SCHED_OTHER), and SCHED_EXT tasks,
+following the usual priorities. Any allocated run time not used will also be
+picked up by the other scheduling classes, in the same order as before.

 Let's consider an example: a frame fixed real-time renderer must deliver 25
 frames a second, which yields a period of 0.04s per frame. Now say it will also
@@ -73,10 +76,6 @@ The remaining CPU time will be used for user input and other tasks. Because
 real-time tasks have explicitly allocated the CPU time they need to perform
 their tasks, buffer underruns in the graphics or audio can be eliminated.

-NOTE: the above example is not fully implemented yet. We still
-lack an EDF scheduler to make non-uniform periods usable.
-
-
 2. The Interface
 ================

@@ -86,105 +85,422 @@ lack an EDF scheduler to make non-uniform periods usable.

 The system wide settings are configured under the /proc virtual file system:

-/proc/sys/kernel/sched_rt_period_us:
+``/proc/sys/kernel/sched_rt_period_us``:
   The scheduling period that is equivalent to 100% CPU bandwidth.

-/proc/sys/kernel/sched_rt_runtime_us:
-  A global limit on how much time real-time scheduling may use. This is always
-  less or equal to the period_us, as it denotes the time allocated from the
-  period_us for the real-time tasks. Without CONFIG_RT_GROUP_SCHED enabled,
-  this only serves for admission control of deadline tasks. With
-  CONFIG_RT_GROUP_SCHED=y it also signifies the total bandwidth available to
-  all real-time groups.
+``/proc/sys/kernel/sched_rt_runtime_us``:
+  A global limit on how much time real-time scheduling may use (SCHED_DEADLINE
+  tasks + real-time groups). This is always less or equal to the period_us, as
+  it denotes the time allocated from the period_us for the real-time tasks.
+  Without **CONFIG_RT_GROUP_SCHED** enabled, this only serves for admission
+  control of deadline tasks. With **CONFIG_RT_GROUP_SCHED=y** it also signifies
+  the total bandwidth available to both real-time groups and deadline tasks.

   * Time is specified in us because the interface is s32. This gives an
     operating range from 1us to about 35 minutes.
-  * sched_rt_period_us takes values from 1 to INT_MAX.
-  * sched_rt_runtime_us takes values from -1 to sched_rt_period_us.
-  * A run time of -1 specifies runtime == period, ie. no limit.
-  * sched_rt_runtime_us/sched_rt_period_us > 0.05 inorder to preserve
-    bandwidth for fair dl_server. For accurate value check average of
-    runtime/period in /sys/kernel/debug/sched/fair_server/cpuX/
+  * ``sched_rt_period_us`` takes values from 1 to INT_MAX.
+  * ``sched_rt_runtime_us`` takes values from -1 to ``sched_rt_period_us``.
+  * A run time of -1 specifies runtime == period, i.e., no limit, but also
+    disables admission tests for SCHED_DEADLINE.
+
+The default value for both ``sched_rt_period_us`` and ``sched_rt_runtime_us`` is
+1000000 (or 1s), while fair-servers and ext-servers have a default runtime of
+50ms and default period of 1s, giving a minimum of 0.05s to be used by
+SCHED_FIFO/SCHED_RR and non-RT tasks (SCHED_OTHER, SCHED_EXT), while 0.95s are
+the maximum to be used by SCHED_DEADLINE, and rt-cgroups if enabled.
+
+2.2 Cgroup settings
+-------------------
+
+Enabling **CONFIG_RT_GROUP_SCHED** lets you explicitly allocate real CPU
+bandwidth to task groups.
+
+ .. warning::
+  Real Time Cgroups are only available for cgroups-v2.
+ ..
+
+This uses the cgroup virtual file system and the CPU controller for cgroups.
+Enabling the controller for the hierarchy creates two files:
+
+* ``<cgroup>/cpu.rt.max``, which specifies the runtime and period of the group.
+  The file also accepts a runtime of 'max', specifying that its tasks must be
+  scheduled using the nearest configured ancestor (or the root cgroup if it is
+  the nearest non-max ancestor).
+* ``<cgroup>/cpu.rt.internal``, read-only, returns the runtime and period
+  actually allocated to the group, excluding that of its children.
+
+ .. tip::
+  For more information on working with control groups, you should read
+  *Documentation/admin-guide/cgroup-v2.rst*.
+ ..
+
+By default the root cgroup has the same period of
+``/proc/sys/kernel/sched_rt_period_us``, which is 1s, and a runtime of zero, so
+that rt-cgroup is *soft-disabled* by default, and all the runtime is available
+for SCHED_DEADLINE tasks only. New groups instead get a period of zero and
+runtime of 'max' (essentially delegating their tasks' scheduling to the nearest
+configured ancestor).
+
+3. Theoretical Background
+=========================
+
+
+ ..  BIG FAT WARNING ******************************************************
+
+ .. warning::
+
+   This section contains a (not-thorough) summary on deadline/hierarchical
+   scheduling theory, and how it applies to real-time control groups.
+   The reader can "safely" skip to Section 4 if only interested in seeing
+   how the scheduling policy can be used. Anyway, we strongly recommend
+   to come back here and continue reading (once the urge for testing is
+   satisfied :P) to be sure of fully understanding all technical details.
+
+ .. ************************************************************************
+
+The real-time cgroup scheduler is based upon the **Hierarchical Constant
+Bandwidth Server** (HCBS) [1] *Compositional Scheduling Framework* (CSF). A
+**CSF** is a framework where global (system-level) timing properties can be
+established by composing independently (specified and) analyzed local
+(component-level) timing properties [5].
+
+For HCBS (related to the Linux kernel), the compositional framework consists of
+two parts:
+
+* The *scheduling components*, which are the basic units of the scheduling. In
+  the kernel these are the single cgroups along with the tasks that must be run
+  inside.
+
+* The *scheduling resources*, which are the CPUs of the machine.
+
+HCBS is a *hierarchical scheduling framework*, where the scheduling components
+form a hierarchy and resources are allocated from parent components to its child
+components in the hierarchy.
+
+The Chapter is organized as follows: **Section 3.1** gives basic real-time
+theory definitions that are used throughout the whole section. **Section 3.2**
+talks about the HCBS framework, giving a general idea on how this is structured.
+**Section 3.3** introduces the MPR model, one of the many models which may be
+used for the analysis of the scheduling components and the computation of the
+minimum required scheduling resources for a given component. **Section 3.4**
+shows the schedulability test for MPR on the HCBS framework. **Section 3.5**
+shows how to convert a MPR interface to a HCBS compatible resource reservation
+for a component. Finally, **Section 3.6** lists other interesting models which
+could be used for the component analysis in HCBS.
+
+3.1 Basic Definitions
+---------------------

+*We borrow the same definitions given in the* ``sched_deadline`` *document, which
+are very briefly summarized here, and new ones, needed by the following content,
+are added.*
+
+A typical real-time task is composed of a repetition of computation phases (task
+instances, or jobs) which are activated on a periodic or sporadic fashion. For
+our purposes, real-time tasks are characterized by three parameters:
+
+* Worst Case Execution Time (WCET): the maximum execution time among all jobs.
+* Relative Deadline (D): the maximum time each job must be completed, relative
+  to the release time of the job.
+* Inter-Arrival Period (P): the exact/minimum (for periodic/sporadic tasks) time
+  between each consecutive job.
+
+3.2 Hierarchical Constant Bandwidth Server (HCBS) [1]
+-----------------------------------------------------
+
+As mentioned, HCBS is a *hierarchical scheduling framework*:
+
+* The framework hierarchy follows the same hierarchy of cgroups. Cgroups may
+  have two roles, either bandwidth reservation for children cgroups, or they may
+  be *live*, i.e. run tasks (but not both). The root cgroup, for the kernel's
+  implementation of HCBS, acts only as bandwidth reservation (but as written in
+  this document it has also different uses outside of the hierarchical
+  framework).
+* The cgroup tree is internally flattened, for ease of scheduling, to a
+  two-level hierarchy, since only the *live* groups are of interest and all the
+  necessary information for their scheduling lies in their interface (there is
+  no need for the reservation components).
+* The hierarchical framework, now on two levels, consists then of a first level
+  of cgroups, and a second level of tasks that are run inside these groups.
+* The scheduling of components is performed using global Earliest Deadline First
+  (gEDF), SCHED_DEADLINE in the kernel, following the bandwidth reservation of
+  each group.
+* Whenever a component is scheduled, a local scheduler picks which of the tasks
+  of the cgroup to run. The scheduling policy is global Fixed Priority (gFP),
+  SCHED_FIFO/SCHED_RR in the kernel.

-2.2 Default behaviour
----------------------
+3.3 Multiprocessor Periodic Resource (MPR) model
+------------------------------------------------
+
+A Multiprocessor Periodic Resource (MPR) model [2] **u = <Pi, Theta, m'>**
+specifies that an identical, unit-capacity multiprocessor platform collectively
+provides **Theta** units of resource every **Pi** time units, where the
+**Theta** time units are supplied with concurrency at most **m'**.
+
+This theoretical model is one of the many models that can abstract the
+interface of our real-time cgroups: let **m'** be the number of CPUs of the
+machine, let **Theta** be **m' * <cgroup>/cpu.rt_runtime_us** and **Pi** be
+**<cgroup>/cpu.rt_period_us**.
+
+Let's introduce the concept of Supply Bound Function (SBF). A SBF is a function
+which outputs a lower bound for the processor supply provided in a given time
+interval, given a resource supply model. For a completely dedicated CPU, the SBF
+function is simply the identity function, as it will always provide **t** units
+of computation for an interval of length **t**. The situation gets slightly more
+complicated for the MPR model or any of the other model listed in section 3.6.
+
+The **SBF(t)** for a MPR model **u = <Pi, Theta, m'>** is::
+
+             | 0                                       if t' < 0
+             |
+  SBF_u(t) = | floor(t' / PI) * Theta
+             |   + max(0, m' * x - (m' * Pi - Theta)   if t' >= 0 and 1 <= x <= y
+             |
+             | floor(t' / PI) * Theta
+             |   + max(0, m' * x - (m' * Pi - Theta)   else
+             |   - (m' - beta)
+
+where::
+
+  alpha = floor(Theta / m')
+  beta = Theta - m' * alpha
+  t' = t - (Pi - ceil(Theta / m'))
+  x  = t' - (Pi * floor(t' / Pi))
+  y  = Pi - floor(Theta / m')
+
+Briefly, this function models that the server's bandwidth is given as late as
+possible, so describing the worst case possible for the supplied bandwidth.
+
+3.4 Schedulability for MPR on global Fixed-Priority
+---------------------------------------------------
+
+Let's introduce the concept of Demand Bound Function (DBF). A DBF is a function
+that, given a taskset, a scheduling algorithm and an interval of time, outputs
+the worst resource demand for that interval of time.
+
+It is easy to see that, given a DBF and a SBF, we can deem a component/taskset
+schedulable if, for every time interval t >= 0, it is possible to demonstrate
+that:
+
+  DBF(t) <= SBF(t)
+
+We have the Supply Bound Function for our given MPR model, so we are missing the
+Demand Bound Function for a given taskset that is being scheduled using global
+Fixed Priority.
+
+3.4.1 Schedulability Analysis for global Fixed Priority
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Bertogna, Cirinei and Lipari [6] have derived a schedulability test for global
+Fixed Priority (gFP) on multi-processor platforms. In this test (called
+*BCL_gFP* test) we can consider all the CPUs to be dedicated to the scheduling.
+
+  A taskset **Tau** is schedulable with gFP on a multiprocessor platform
+  composed of **m'** identical processors if for each task **tau_k in Tau**:
+
+    Sum(for i < k)( min(W_i(D_k), D_k - C_k + 1) ) < m' * (D_k - C_k + 1)
+
+  where **W_i(t)** is the workload of task **tau_i** over a time interval **t**:
+
+    W_i(t) = N_i(t) * C_i + min(C_i, t + D_i - C_i - N_i(t) * P_i)

-The default values for sched_rt_period_us (1000000 or 1s) and
-sched_rt_runtime_us (950000 or 0.95s).  This gives 0.05s to be used by
-SCHED_OTHER (non-RT tasks). These defaults were chosen so that a run-away
-real-time tasks will not lock up the machine but leave a little time to recover
-it.  By setting runtime to -1 you'd get the old behaviour back.
+  and **N_i(t)** is the number of activations of task **tau_i** that complete in
+  a time interval **t**:

-By default all bandwidth is assigned to the root group and new groups get the
-period from /proc/sys/kernel/sched_rt_period_us and a run time of 0. If you
-want to assign bandwidth to another group, reduce the root group's bandwidth
-and assign some or all of the difference to another group.
+    N_i(t) = floor( (t + D_i - C_i) / P_i )

-Real-time group scheduling means you have to assign a portion of total CPU
-bandwidth to the group before it will accept real-time tasks. Therefore you will
-not be able to run real-time tasks as any user other than root until you have
-done that, even if the user has the rights to run processes with real-time
-priority!
+  while the **min** term is the contribution of the carried-out job in the
+  interval **t**, i.e. that job that does not completely fit in the interval
+  **t**, but starts inside the interval after all the jobs that complete.
+
+3.4.2 From BCL_gFP to the Demand Bound Function
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We can then derive the DBF from this test:
+
+  DBF_gFP(tau_k) = Sum(for i < k)( min(W_i(D_k), D_k - C_k + 1) ) + m' * (C_k - 1)
+
+Briefly, the first sum component, the same in the BCL_gFP test, describes the
+maximum interference that higher priority task give to the analysed task. The
+workload is upperbounded by ``(D_k - C_K + 1)`` because we are only interested
+in the interference in the slack time, while for the ``C_k`` time we are
+requiring that all the CPUs are fully available, as the single job needs `C_k`
+(non overlapping) time units to run.
+
+The demand bound function from Bertogna et al. is only defined on a single time
+(i.e. the deadline of the task in analysis) instead of all possible times as
+this is the minimum argument to demonstrate schedulability on global Fixed
+Priority.
+
+3.4.3 Putting it all togheter
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A component **C**, on **m'** processors, running a taskset **Tau = { tau_1 =
+(C_1, D_1, P_1), ..., tau_n = (C_n, D_n, P_n) }** of **n** sporadic tasks, is
+schedulable under gFP using an MPR model **u = <Pi, Theta, m'>**, if for all
+tasks **tau_k in Tau**:
+
+  DBF_gFP(tau_k) <= SBF_u(D_K)
+
+3.5 From MPR to deadline servers
+--------------------------------
+
+Since there exist no algorithm to schedule MPR interfaces, a tecnique was
+developed to transform MPR interfaces into periodic tasks, so that a
+number of periodic servers which respect the tasks requirements can be used for
+the scheduling of the MPR interface and associated tasks.
+
+Let **u = <Pi, Theta, m>** be a MPR interface, let **a = Theta - m * floor(Theta
+/ m)**, let **k = floor(a)**. Define a transformation from **u** to a periodic
+taskset **Tau_u = { tau_1 = (C_1, D_1, P_1), ..., tau_m' = (C_m', D_m', P_m')
+}**, where:
+
+  **tau_1 = ... = tau_k = (floor(Theta / m') + 1, Pi, Pi)**
+
+  **tau_k+1 = (floor(Theta / m') + a - k * floor(a/k), Pi, Pi)**
+
+  **tau_k+2 = ... = tau_m' = (floor(Theta / m'), Pi, Pi)**
+
+This periodic taskset of servers **Tau_u** can be scheduled on any number of
+processors with concurrency at most **m'**.
+
+For real-time control groups, it is possible to just consider a slightly more
+demanding taskset **Tau_u'**, where each task **tau_i** is defined as follows:
+
+  **tau_i = (ceil(Theta / m'), Pi, Pi)**
+
+3.6 Other models
+----------------
+
+There exist many other theoretical models in literature which are used to
+describe a hierarchical scheduling framework on multi-core architectures.
+Notable examples are the Multi Supply Function (MSF) abstraction [3], the
+Parallel Supply Function (PSF) abstraction [4] and the Bounded Delay
+Multipartition (BDM) [7].
+
+3.7 References
+--------------
+  1 - L. Abeni, A. Balsini, and T. Cucinotta, “Container-based real-time
+      scheduling in the Linux kernel,” SIGBED Rev., vol. 16, no. 3, pp. 33-38,
+      Nov. 2019, doi: 10.1145/3373400.3373405.
+  2 - A. Easwaran, I. Shin, and I. Lee, “Optimal virtual cluster-based
+      multiprocessor scheduling,” Real-Time Syst, vol. 43, no. 1, pp. 25-59,
+      Sept. 2009, doi: 10.1007/s11241-009-9073-x.
+  3 - E. Bini, G. Buttazzo, and M. Bertogna, “The Multi Supply Function
+      Abstraction for Multiprocessors,” in 2009 15th IEEE International
+      Conference on Embedded and Real-Time Computing Systems and Applications,
+      Aug. 2009, pp. 294-302. doi: 10.1109/RTCSA.2009.39.
+  4 - E. Bini, B. Marko, and S. K. Baruah, “The Parallel Supply Function
+      Abstraction for a Virtual Multiprocessor,” in Scheduling, S. Albers, S. K.
+      Baruah, R. H. Möhring, and K. Pruhs, Eds., in Dagstuhl Seminar Proceedings
+      (DagSemProc), vol. 10071. Dagstuhl, Germany: Schloss Dagstuhl -
+      Leibniz-Zentrum für Informatik, 2010, pp. 1-14. doi:
+      10.4230/DagSemProc.10071.14.
+  5 - I. Shin and I. Lee, “Compositional real-time scheduling framework,” in
+      25th IEEE International Real-Time Systems Symposium, Dec. 2004, pp. 57-67.
+      doi: 10.1109/REAL.2004.15.
+  6 - M. Bertogna, M. Cirinei, and G. Lipari, “Schedulability Analysis of Global
+      Scheduling Algorithms on Multiprocessor Platforms,” IEEE Transactions on
+      Parallel and Distributed Systems, vol. 20, no. 4, pp. 553-566, Apr. 2009,
+      doi: 10.1109/TPDS.2008.129.
+  7 - G. Lipari and E. Bini, “A Framework for Hierarchical Scheduling on
+      Multiprocessors: From Application Requirements to Run-Time Allocation,” in
+      2010 31st IEEE Real-Time Systems Symposium, Nov. 2010, pp. 249-258. doi:
+      10.1109/RTSS.2010.12.
+
+
+4. Using Real-Time cgroups
+==========================
+
+4.1 CGroup Setup
+----------------

+The following is a brief guide to the use of Real-Time Control Groups.

-2.3 Basis for grouping tasks
-----------------------------
+Of course, real-time control groups require mounting of the cgroup file system.
+We have decided to only support cgroups v2, so make sure you mount the v2
+controller for the cgroup hierarchy.

-Enabling CONFIG_RT_GROUP_SCHED lets you explicitly allocate real
-CPU bandwidth to task groups.
+Additionally the real-time cgroups require the CPU controller for the cgroups to
+be enabled::

-This uses the cgroup virtual file system and "<cgroup>/cpu.rt_runtime_us"
-to control the CPU time reserved for each control group.
+  # Assume the cgroup file system is mounted at /sys/fs/cgroup
+  > echo "+cpu" > /sys/fs/cgroup/cgroup.subtree_control

-For more information on working with control groups, you should read
-Documentation/admin-guide/cgroup-v1/cgroups.rst as well.
+The CPU controller can only be mounted if there is no SCHED_FIFO/SCHED_RR task
+scheduled in any cgroup other than the root control group.

-Group settings are checked against the following limits in order to keep the
-configuration schedulable:
+The root control group has no bandwidth allocated by default, so make sure to
+allocate some bandwidth so that it can be used by the other cgroups. More on
+that in the following section...

-   \Sum_{i} runtime_{i} / global_period <= global_runtime / global_period
+4.2 Bandwidth Allocation for groups
+-----------------------------------

-For now, this can be simplified to just the following (but see Future plans):
+Allocating bandwidth to a cgroup is a fundamental step to run real-time
+workload. The cgroup filesystem exposes two files:

-   \Sum_{i} runtime_{i} <= global_runtime
+* ``<cgroup>/cpu.rt.max``: which specifies the cgroups' runtime and period in
+  microseconds.
+* ``<cgroup>/cpu.rt.internal``: read-only, get the cgroups' actualy runtime and
+  period in microseconds, without its children's bandwidth.

+By definition, the specified runtime must be always less than or equal to the
+period. Additionally, an admission test checks if the bandwidth invariant is
+respected (i.e. sum of children's bandwidth <= parent's bandwidth).

-3. Future plans
-===============
+The root control group files instead control and reserve the SCHED_DEADLINE
+bandwidth allocated to real-time cgroups, since real-time groups compete and
+share the same bandwidth allocated to SCHED_DEADLINE tasks.

-There is work in progress to make the scheduling period for each group
-("<cgroup>/cpu.rt_period_us") configurable as well.
+4.3 Running real-time tasks in groups
+-------------------------------------

-The constraint on the period is that a subgroup must have a smaller or
-equal period to its parent. But realistically its not very useful _yet_
-as its prone to starvation without deadline scheduling.
+To run tasks in real-time groups it is just necessary to change a tasks
+scheduling policy to SCHED_FIFO/SCHED_RR and migrate it into the group. If the
+group is not allowed to run real-time tasks because of incorrect configuration,
+either migrating a SCHED_FIFO/SCHED_RR task into the group or changing
+scheduling policy to a task already inside the group will fail::

-Consider two sibling groups A and B; both have 50% bandwidth, but A's
-period is twice the length of B's.
+ # assume there is a task of PID 42 running
+ # change its scheduling policy to SCHED_FIFO, priority 99
+ > chrt -f -p 99 42

-* group A: period=100000us, runtime=50000us
+ # migrate the task to a cgroup
+ > echo 42 > /sys/fs/cgroup/<my-cgroup>/cgroup.procs

-	- this runs for 0.05s once every 0.1s
+4.4 Special case: the root control group
+----------------------------------------

-* group B: period= 50000us, runtime=25000us
+The root cgroup is special, compared to the other cgroups, as its tasks are not
+managed by the HCBS algorithm, rather they just use the original
+SCHED_FIFO/SCHED_RR policies (as if CONFIG_RT_GROUP_SCHED was disabled). As
+mentioned, its bandwidth files are just used to control how much of the
+SCHED_DEADLINE bandwidth is allocated to cgroups.

-	- this runs for 0.025s twice every 0.1s (or once every 0.05 sec).
+Any non-root cgroup configured as 'max' that has the root cgroup as its nearest
+non-max ancestor will run its tasks in the root runqueue.

-This means that currently a while (1) loop in A will run for the full period of
-B and can starve B's tasks (assuming they are of lower priority) for a whole
-period.
+4.5 Guarantees and Special Behaviours
+-------------------------------------

-The next project will be SCHED_EDF (Earliest Deadline First scheduling) to bring
-full deadline scheduling to the linux kernel. Deadline scheduling the above
-groups and treating end of the period as a deadline will ensure that they both
-get their allocated time.
+Real-time cgroups are run at the same priority level of SCHED_DEADLINE tasks.
+Since this is the highest priority scheduling policy, and since the Constant
+Bandwidth Server (CBS) enforces that the specified bandwidth requirements for
+both groups and tasks cannot be overrun, real-time groups have the same
+guarantees that SCHED_DEADLINE tasks have, i.e. they will be necessarily
+supplied by the amount of bandwidth requested (whenever the admission tests
+pass).

-Implementing SCHED_EDF might take a while to complete. Priority Inheritance is
-the biggest challenge as the current linux PI infrastructure is geared towards
-the limited static priority levels 0-99. With deadline scheduling you need to
-do deadline inheritance (since priority is inversely proportional to the
-deadline delta (deadline - now)).
+This means that, since SCHED_FIFO/SCHED_RR tasks (scheduled in the root control
+group) are not subject to bandwidth controls, they are run at a lower priority
+than the cgroups' counterparts. Nonetheless, a minimum amount of bandwidth, if
+reserved, will always be available to run SCHED_FIFO/SCHED_RR workloads in the
+root cgroup, while they will be able to use more runtime if any of the
+SCHED_DEADLINE tasks or servers use less than their specified amount of
+bandwidth. SCHED_OTHER tasks are instead scheduled as normal, at lower priority
+than real-time workloads.

-This means the whole PI machinery will have to be reworked - and that is one of
-the most complex pieces of code we have.
+The aforementioned behaviour differs from the preceding RT_GROUP_SCHED
+implementation, but this is necessary to give actual guarantees to the amount of
+bandwidth given to rt-cgroups.
\ No newline at end of file
--
2.54.0


^ permalink raw reply related

* [RFC PATCH v6 24/25] sched/core: Execute enqueued balance callbacks after migrate_disable_switch
From: Yuri Andriaccio @ 2026-06-08 12:15 UTC (permalink / raw)
  To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, Tejun Heo, Johannes Weiner,
	Michal Koutný
  Cc: cgroups, linux-kernel, Luca Abeni, Yuri Andriaccio
In-Reply-To: <20260608121546.69910-1-yurand2000@gmail.com>

Execute balance callbacks after migrate_disable_switch.
  Balancing may be requested on the __schedule path, in migrate_disable_switch,
  when the running task is throttled and then pushed away from its runqueue.

Signed-off-by: Yuri Andriaccio <yurand2000@gmail.com>
---
 kernel/sched/core.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 9e337f0090b3..1d458638aab9 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2410,6 +2410,9 @@ do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx);

 static void migrate_disable_switch(struct rq *rq, struct task_struct *p)
 {
+	struct rq_flags rf;
+	struct balance_callback *head;
+
 	struct affinity_context ac = {
 		.new_mask  = cpumask_of(rq->cpu),
 		.flags     = SCA_MIGRATE_DISABLE,
@@ -2421,8 +2424,13 @@ static void migrate_disable_switch(struct rq *rq, struct task_struct *p)
 	if (p->cpus_ptr != &p->cpus_mask)
 		return;

-	scoped_guard (task_rq_lock, p)
-		do_set_cpus_allowed(p, &ac);
+	rq = task_rq_lock(p, &rf);
+
+	do_set_cpus_allowed(p, &ac);
+
+	head = splice_balance_callbacks(rq);
+	task_rq_unlock(rq, p, &rf);
+	balance_callbacks(rq, head);
 }

 void ___migrate_enable(void)
--
2.54.0


^ permalink raw reply related

* [RFC PATCH v6 23/25] sched/rt: Try pull task on empty server pick.
From: Yuri Andriaccio @ 2026-06-08 12:15 UTC (permalink / raw)
  To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, Tejun Heo, Johannes Weiner,
	Michal Koutný
  Cc: cgroups, linux-kernel, Luca Abeni, Yuri Andriaccio
In-Reply-To: <20260608121546.69910-1-yurand2000@gmail.com>

Try to pull task on a server with an empty runqueue before returning NULL (and
thus shutting down).

---

When all the servers of a cgroup are throttled, work is pending, and any one of
the servers is replenished, it may happen that the runqueue is empty and thus
the replenished server is immediately shut down.

The server may try to pull a task so that the cgroup could consume its
allocated runtime as soon as it is replenished.

Signed-off-by: Yuri Andriaccio <yurand2000@gmail.com>
---
 kernel/sched/rt.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index 964704d88ba1..f672ef17e5d1 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -285,14 +285,22 @@ int alloc_rt_sched_group(struct task_group *tg, struct task_group *parent)
 }
 
 static struct sched_rt_entity *pick_next_rt_entity(struct rt_rq *rt_rq);
+static void pull_rt_task(struct rq *);
 
 static struct task_struct *rt_server_pick(struct sched_dl_entity *dl_se, struct rq_flags *rf)
 {
 	struct rt_rq *rt_rq = &dl_se->my_q->rt;
+	struct rq *global_rq = global_rq_of_rt_rq(rt_rq);
 	struct task_struct *p;
 
-	if (!sched_rt_runnable(dl_se->my_q))
-		return NULL;
+	if (!sched_rt_runnable(dl_se->my_q)) {
+		rq_unpin_lock(global_rq, rf);
+		pull_rt_task(rq_of_rt_rq(rt_rq));
+		rq_repin_lock(global_rq, rf);
+
+		if (!sched_rt_runnable(dl_se->my_q))
+			return NULL;
+	}
 
 	p = rt_task_of(pick_next_rt_entity(rt_rq));
 
-- 
2.54.0


^ permalink raw reply related

* [RFC PATCH v6 22/25] sched/core: Execute enqueued balance callbacks when changing allowed CPUs
From: Yuri Andriaccio @ 2026-06-08 12:15 UTC (permalink / raw)
  To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, Tejun Heo, Johannes Weiner,
	Michal Koutný
  Cc: cgroups, linux-kernel, Luca Abeni, Yuri Andriaccio
In-Reply-To: <20260608121546.69910-1-yurand2000@gmail.com>

From: luca abeni <luca.abeni@santannapisa.it>

Execute balancing callbacks when setting the affinity of a task, since
the HCBS scheduler may request balancing of throttled dl_servers to fully
utilize the server's bandwidth.

Signed-off-by: luca abeni <luca.abeni@santannapisa.it>
Signed-off-by: Yuri Andriaccio <yurand2000@gmail.com>
---
 kernel/sched/core.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 1ad1efe1dca7..9e337f0090b3 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2933,6 +2933,7 @@ static int affine_move_task(struct rq *rq, struct task_struct *p, struct rq_flag
 	if (cpumask_test_cpu(task_cpu(p), &p->cpus_mask) ||
 	    (task_current_donor(rq, p) && !task_current(rq, p))) {
 		struct task_struct *push_task = NULL;
+		struct balance_callback *head;

 		if ((flags & SCA_MIGRATE_ENABLE) &&
 		    (p->migration_flags & MDF_PUSH) && !rq->push_busy) {
@@ -2951,11 +2952,13 @@ static int affine_move_task(struct rq *rq, struct task_struct *p, struct rq_flag
 		}

 		preempt_disable();
+		head = splice_balance_callbacks(rq);
 		task_rq_unlock(rq, p, rf);
 		if (push_task) {
 			stop_one_cpu_nowait(rq->cpu, push_cpu_stop,
 					    p, &rq->push_work);
 		}
+		balance_callbacks(rq, head);
 		preempt_enable();

 		if (complete)
@@ -3010,6 +3013,8 @@ static int affine_move_task(struct rq *rq, struct task_struct *p, struct rq_flag
 	}

 	if (task_on_cpu(rq, p) || READ_ONCE(p->__state) == TASK_WAKING) {
+		struct balance_callback *head;
+
 		/*
 		 * MIGRATE_ENABLE gets here because 'p == current', but for
 		 * anything else we cannot do is_migration_disabled(), punt
@@ -3023,16 +3028,19 @@ static int affine_move_task(struct rq *rq, struct task_struct *p, struct rq_flag
 			p->migration_flags &= ~MDF_PUSH;

 		preempt_disable();
+		head = splice_balance_callbacks(rq);
 		task_rq_unlock(rq, p, rf);
 		if (!stop_pending) {
 			stop_one_cpu_nowait(cpu_of(rq), migration_cpu_stop,
 					    &pending->arg, &pending->stop_work);
 		}
+		balance_callbacks(rq, head);
 		preempt_enable();

 		if (flags & SCA_MIGRATE_ENABLE)
 			return 0;
 	} else {
+		struct balance_callback *head;

 		if (!is_migration_disabled(p)) {
 			if (task_on_rq_queued(p))
@@ -3043,7 +3051,12 @@ static int affine_move_task(struct rq *rq, struct task_struct *p, struct rq_flag
 				complete = true;
 			}
 		}
+
+		preempt_disable();
+		head = splice_balance_callbacks(rq);
 		task_rq_unlock(rq, p, rf);
+		balance_callbacks(rq, head);
+		preempt_enable();

 		if (complete)
 			complete_all(&pending->done);
--
2.54.0


^ permalink raw reply related

* [RFC PATCH v6 21/25] sched/rt: Hook HCBS migration functions
From: Yuri Andriaccio @ 2026-06-08 12:15 UTC (permalink / raw)
  To: Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
	Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
	Valentin Schneider, Tejun Heo, Johannes Weiner,
	Michal Koutný
  Cc: cgroups, linux-kernel, Luca Abeni, Yuri Andriaccio
In-Reply-To: <20260608121546.69910-1-yurand2000@gmail.com>

Hook rt-cgroup migration functions:

- select_task_rt_rq
  Always return the cpu where the task is scheduled.
- balance_rt
- put_prev_task_rt
  If a server is throttled, put_prev_task_rt is invoked and a push is
  necessary so that the task can keep running on another server if possible.
- switched_to_rt
  Keep track of the deadline server that is assigned to the task switching
  to FIFO/RR priority.

Co-developed-by: Alessio Balsini <a.balsini@sssup.it>
Signed-off-by: Alessio Balsini <a.balsini@sssup.it>
Co-developed-by: Andrea Parri <parri.andrea@gmail.com>
Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
Co-developed-by: luca abeni <luca.abeni@santannapisa.it>
Signed-off-by: luca abeni <luca.abeni@santannapisa.it>
Signed-off-by: Yuri Andriaccio <yurand2000@gmail.com>
---
 kernel/sched/rt.c | 38 +++++++++++++++++++++++++++++++-------
 1 file changed, 31 insertions(+), 7 deletions(-)

diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index 276eebe8d0a9..964704d88ba1 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -976,6 +976,10 @@ select_task_rq_rt(struct task_struct *p, int cpu, int flags)
 	struct rq *rq;
 	bool test;
 
+	/* Just return the task_cpu for processes inside task groups */
+	if (is_dl_group(rt_rq_of_se(&p->rt)))
+		goto out;
+
 	/* For anything but wake ups, just return the task_cpu */
 	if (!(flags & (WF_TTWU | WF_FORK)))
 		goto out;
@@ -1065,21 +1069,25 @@ static void check_preempt_equal_prio(struct rq *rq, struct task_struct *p)
 	resched_curr(rq);
 }
 
-static int balance_rt(struct rq *rq, struct task_struct *p, struct rq_flags *rf)
+static int balance_rt(struct rq *global_rq, struct task_struct *p, struct rq_flags *rf)
 {
-	if (!on_rt_rq(&p->rt) && need_pull_rt_task(rq, p)) {
+	struct rt_rq *rt_rq = rt_rq_of_se(&p->rt);
+
+	if (!on_rt_rq(&p->rt) && need_pull_rt_task(rq_of_rt_rq(rt_rq), p)) {
 		/*
 		 * This is OK, because current is on_cpu, which avoids it being
 		 * picked for load-balance and preemption/IRQs are still
 		 * disabled avoiding further scheduler activity on it and we've
 		 * not yet started the picking loop.
 		 */
-		rq_unpin_lock(rq, rf);
-		pull_rt_rq_task(&rq->rt);
-		rq_repin_lock(rq, rf);
+		rq_unpin_lock(global_rq, rf);
+		pull_rt_rq_task(rt_rq);
+		rq_repin_lock(global_rq, rf);
 	}
 
-	return sched_stop_runnable(rq) || sched_dl_runnable(rq) || sched_rt_runnable(rq);
+	return sched_stop_runnable(global_rq) ||
+	       sched_dl_runnable(global_rq) ||
+	       sched_rt_runnable(global_rq);
 }
 
 /*
@@ -1241,6 +1249,13 @@ static void put_prev_task_rt(struct rq *rq, struct task_struct *p, struct task_s
 	 */
 	if (on_rt_rq(&p->rt) && p->nr_cpus_allowed > 1)
 		enqueue_pushable_task(rt_rq, p);
+
+	if (is_dl_group(rt_rq)) {
+		struct sched_dl_entity *dl_se = dl_group_of(rt_rq);
+
+		if (dl_se->dl_throttled)
+			rt_queue_push_tasks(rt_rq);
+	}
 }
 
 /* Only try algorithms three times */
@@ -2050,12 +2065,21 @@ static void switching_to_rt(struct rq *rq, struct task_struct *p) {}
  */
 static void switched_to_rt(struct rq *rq, struct task_struct *p)
 {
+	struct rt_rq *rt_rq = rt_rq_of_se(&p->rt);
+
 	/*
 	 * If we are running, update the avg_rt tracking, as the running time
 	 * will now on be accounted into the latter.
 	 */
 	if (task_current(rq, p)) {
 		update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 0);
+
+		if (is_dl_group(rt_rq)) {
+			struct sched_dl_entity *dl_se = dl_group_of(rt_rq);
+
+			p->dl_server = dl_se;
+		}
+
 		return;
 	}
 
@@ -2066,7 +2090,7 @@ static void switched_to_rt(struct rq *rq, struct task_struct *p)
 	 */
 	if (task_on_rq_queued(p)) {
 		if (p->nr_cpus_allowed > 1 && rq->rt.overloaded)
-			rt_queue_push_tasks(rt_rq_of_se(&p->rt));
+			rt_queue_push_tasks(rt_rq);
 
 		if (p->prio < rq->donor->prio && cpu_online(cpu_of(rq)))
 			resched_curr(rq);
-- 
2.54.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox