All of lore.kernel.org
 help / color / mirror / Atom feed
From: Tao Cui <cui.tao@linux.dev>
To: liuqiqi@kylinos.cn, linux-mm@kvack.org
Cc: cui.tao@linux.dev, tj@kernel.org, mkoutny@suse.com,
	hannes@cmpxchg.org, mhocko@kernel.org, roman.gushchin@linux.dev,
	shakeel.butt@linux.dev, muchun.song@linux.dev,
	akpm@linux-foundation.org, cgroups@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [RFC PATCH 7/8] mm/memcontrol: auto-derive tier high/max from memory.high/max
Date: Tue, 18 Aug 2026 12:46:35 +0800	[thread overview]
Message-ID: <749beeb5-cc97-42ab-b3a4-aeee378fbb12@linux.dev> (raw)
In-Reply-To: <20260818023121.100613-8-liuqiqi@kylinos.cn>



在 2026/8/18 10:31, liuqiqi@kylinos.cn 写道:
> From: Qiqi Liu <liuqiqi@kylinos.cn>
> 
> Per-tier limits are derived by default from the cgroup-wide memory.high
> and memory.max knobs based on the tier's capacity ratio:
> 
>   tierN.high = memory.high * tierN_capacity / total_capacity
>   tierN.max  = memory.max  * tierN_capacity / total_capacity
> 
> This provides zero-config tier partitioning. Setting memory.high or
> memory.max automatically splits the budget across tiers proportionally
> to their physical size. For instance, a 4G cgroup on a system with a
> 75%/25% DRAM/CXL split automatically receives ~3G DRAM and ~1G CXL.
> 
> The capacity cache and the derived limits are refreshed on node memory
> hotplug, both when node memory is added and when it is removed.
> 
> Writing an explicit value pins the limit and disables auto-derivation
> for that tier, decoupling it from subsequent memory.high/max updates.
> 
> Signed-off-by: Qiqi Liu <liuqiqi@kylinos.cn>
> ---
>  include/linux/memcontrol.h |   2 +
>  mm/memcontrol.c            | 162 +++++++++++++++++++++++++++++++++----
>  2 files changed, 150 insertions(+), 14 deletions(-)
> 
> diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> index fa944e4bc5ad..8848bc5eeb24 100644
> --- a/include/linux/memcontrol.h
> +++ b/include/linux/memcontrol.h
> @@ -205,6 +205,8 @@ struct memcg_tier_counter {
>  	int tier_id;
>  	struct list_head list;
>  	struct rcu_head rcu;
> +	bool max_derived;	/* true: derive from memory.max by capacity ratio */
> +	bool high_derived;	/* true: derive from memory.high by capacity ratio */
>  };
>  
>  struct mem_cgroup {
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index a6c6057f0e4f..f39a702d2301 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -2443,6 +2443,8 @@ static int memcg_tier_counter_create(struct mem_cgroup *memcg,
>  	page_counter_init(&new->counter, parent_cnt, false);
>  	page_counter_set_high(&new->counter, PAGE_COUNTER_MAX);
>  	INIT_LIST_HEAD(&new->list);
> +	new->max_derived = true;
> +	new->high_derived = true;
>  
>  	spin_lock(&memcg->tier_lock);
>  	if (memcg_tier_counter_find(memcg, tier_id)) {
> @@ -2514,6 +2516,115 @@ reclaim_tier(struct mem_cgroup *memcg, unsigned int nr_pages, gfp_t gfp_mask,
>  	return nr_reclaimed;
>  }
>  
> +/* Global per-tier capacity cache.
> + * Populated at boot and on node hotplug (walks online nodes only, no memcg touch).
> + * Stores (tier_id, capacity) pairs to support sparse tier IDs (e.g., 4, 22, 52)
> + * without requiring a dense index.
> + */
> +#define NR_TIER_CAP_ENTRIES 16
> +struct tier_cap_entry {
> +	int tier_id;
> +	unsigned long capacity;
> +};
> +static struct tier_cap_entry tier_cap_entries[NR_TIER_CAP_ENTRIES];
> +static int nr_tier_entries;
> +static unsigned long tier_total_capacity;
> +/* Guards the per-tier capacity cache. */
> +static DEFINE_SPINLOCK(tier_cap_lock);
> +

"Populated at boot" doesn't hold: the boot-time call sits in
mem_cgroup_init(), which runs from start_kernel() (init/main.c:1177),
before any initcall. Nodes are assigned tiers in
memory_tier_late_init() (late_initcall), which fires no node notifier.
So tier_total_capacity stays 0 and every memory.high/max write hits
the early return in tier_update_derived_limits(). The hotplug
callback is the only other refresh point - your QEMU setup likely
works because the CXL tier comes up through it.

Either hook a refresh into the tier-assignment path in memory-tiers,
or compute lazily when the cache is empty.

Thanks,
Tao 

> +static void update_tier_capacity_cache(void)
> +{
> +	int nid;
> +	int i;
> +
> +	spin_lock(&tier_cap_lock);
> +	nr_tier_entries = 0;
> +	tier_total_capacity = 0;
> +	for (i = 0; i < NR_TIER_CAP_ENTRIES; i++) {
> +		tier_cap_entries[i].tier_id = -1;
> +		tier_cap_entries[i].capacity = 0;
> +	}
> +
> +	for_each_online_node(nid) {
> +		int tid = node_to_tier_id(nid);
> +		unsigned long cap;
> +
> +		if (tid < 0)
> +			continue;
> +		cap = node_present_pages(nid);
> +		for (i = 0; i < nr_tier_entries; i++)
> +			if (tier_cap_entries[i].tier_id == tid) {
> +				tier_cap_entries[i].capacity += cap;
> +				tier_total_capacity += cap;
> +				break;
> +			}
> +		if (i == nr_tier_entries && nr_tier_entries < NR_TIER_CAP_ENTRIES) {
> +			tier_cap_entries[nr_tier_entries].tier_id = tid;
> +			tier_cap_entries[nr_tier_entries].capacity = cap;
> +			nr_tier_entries++;
> +			tier_total_capacity += cap;
> +		} else if (i == nr_tier_entries) {
> +			pr_warn_ratelimited("memcg: tier capacity cache full (>%d tiers), tier %d not tracked\n",
> +					    NR_TIER_CAP_ENTRIES, tid);
> +		}
> +	}
> +	spin_unlock(&tier_cap_lock);
> +}
> +
> +/* Push derived high/max limits to tier counters of @memcg.
> + * Invoked on memory.high/max writes and capacity changes (hotplug).
> + */
> +static void tier_update_derived_limits(struct mem_cgroup *memcg)
> +{
> +	struct memcg_tier_counter *tc;
> +	struct tier_cap_entry local_entries[NR_TIER_CAP_ENTRIES];
> +	unsigned long total;
> +	int i, nr_entries;
> +
> +	spin_lock(&tier_cap_lock);
> +	total = tier_total_capacity;
> +	nr_entries = nr_tier_entries;
> +	memcpy(local_entries, tier_cap_entries, sizeof(local_entries));
> +	spin_unlock(&tier_cap_lock);
> +
> +	if (total == 0)
> +		return;
> +
> +	/* Serialize flag check + limit store against memory.tier writes. */
> +	spin_lock(&memcg->tier_lock);
> +	list_for_each_entry_rcu(tc, &memcg->tier_counters, list,
> +				lockdep_is_held(&memcg->tier_lock)) {
> +		unsigned long cap = 0;
> +
> +		for (i = 0; i < nr_entries; i++)
> +			if (local_entries[i].tier_id == tc->tier_id) {
> +				cap = local_entries[i].capacity;
> +				break;
> +			}
> +		if (cap == 0)
> +			continue;
> +		if (READ_ONCE(tc->max_derived)) {
> +			unsigned long mem_max = READ_ONCE(memcg->memory.max);
> +
> +			if (mem_max == PAGE_COUNTER_MAX)
> +				xchg(&tc->counter.max, PAGE_COUNTER_MAX);
> +			else
> +				xchg(&tc->counter.max,
> +				     mul_u64_u64_div_u64(mem_max, cap, total));
> +		}
> +		if (READ_ONCE(tc->high_derived)) {
> +			unsigned long mem_high = READ_ONCE(memcg->memory.high);
> +
> +			if (mem_high == PAGE_COUNTER_MAX)
> +				xchg(&tc->counter.high, PAGE_COUNTER_MAX);
> +			else
> +				xchg(&tc->counter.high,
> +				     mul_u64_u64_div_u64(mem_high, cap, total));
> +		}
> +	}
> +	spin_unlock(&memcg->tier_lock);
> +}
> +
>  static void tier_high_work_func(struct work_struct *work)
>  {
>  	struct mem_cgroup *memcg;
> @@ -4841,6 +4952,8 @@ static void mem_cgroup_css_reset(struct cgroup_subsys_state *css)
>  	list_for_each_entry_rcu(tc, &memcg->tier_counters, list) {
>  		page_counter_set_max(&tc->counter, PAGE_COUNTER_MAX);
>  		page_counter_set_high(&tc->counter, PAGE_COUNTER_MAX);
> +		WRITE_ONCE(tc->max_derived, true);
> +		WRITE_ONCE(tc->high_derived, true);
>  	}
>  	rcu_read_unlock();
>  }
> @@ -5251,6 +5364,7 @@ static ssize_t memory_high_write(struct kernfs_open_file *of,
>  		return err;
>  
>  	page_counter_set_high(&memcg->memory, high);
> +	tier_update_derived_limits(memcg);
>  
>  	if (of->file->f_flags & O_NONBLOCK)
>  		goto out;
> @@ -5303,6 +5417,7 @@ static ssize_t memory_max_write(struct kernfs_open_file *of,
>  		return err;
>  
>  	xchg(&memcg->memory.max, max);
> +	tier_update_derived_limits(memcg);
>  
>  	if (of->file->f_flags & O_NONBLOCK)
>  		goto out;
> @@ -5390,10 +5505,17 @@ static ssize_t memory_tier_write(struct kernfs_open_file *of,
>  	if (!tier_id_to_nodemask(tier_id, &nodes) && !nodes_empty(nodes))
>  		nmp = &nodes;
>  
> -	if (!strcmp(knob, "high"))
> +	if (!strcmp(knob, "high")) {
> +		spin_lock(&memcg->tier_lock);
> +		WRITE_ONCE(tc->high_derived, false);
>  		page_counter_set_high(&tc->counter, val);
> -	else
> +		spin_unlock(&memcg->tier_lock);
> +	} else {
> +		spin_lock(&memcg->tier_lock);
> +		WRITE_ONCE(tc->max_derived, false);
>  		xchg(&tc->counter.max, val);
> +		spin_unlock(&memcg->tier_lock);
> +	}
>  
>  	if (of->file->f_flags & O_NONBLOCK)
>  		return nbytes;
> @@ -6145,18 +6267,28 @@ static int __meminit memcg_tier_hotplug_cb(struct notifier_block *self,
>  	struct mem_cgroup *memcg;
>  	int tid;
>  
> -	if (action != NODE_ADDED_FIRST_MEMORY)
> -		return notifier_from_errno(0);
> -
> -	tid = node_to_tier_id(nn->nid);
> -	if (tid < 0)
> -		return notifier_from_errno(0);
> -
> -	for_each_mem_cgroup(memcg) {
> -		if (!mem_cgroup_is_root(memcg) &&
> -		    memcg_tier_counter_create(memcg, parent_mem_cgroup(memcg), tid))
> -			pr_warn_ratelimited("memcg: tier %d counter alloc failed;"
> -					     " tier accounting degraded\n", tid);
> +	switch (action) {
> +	case NODE_ADDED_FIRST_MEMORY:
> +		tid = node_to_tier_id(nn->nid);
> +		if (tid < 0)
> +			return notifier_from_errno(0);
> +
> +		update_tier_capacity_cache();
> +		for_each_mem_cgroup(memcg) {
> +			if (!mem_cgroup_is_root(memcg) &&
> +			    memcg_tier_counter_create(memcg, parent_mem_cgroup(memcg), tid))
> +				pr_warn_ratelimited("memcg: tier %d counter alloc failed;"
> +						     " tier accounting degraded\n", tid);
> +		}
> +		for_each_mem_cgroup(memcg)
> +			tier_update_derived_limits(memcg);
> +		break;
> +	case NODE_REMOVED_LAST_MEMORY:
> +		/* Node memory removed: refresh capacity and derived limits. */
> +		update_tier_capacity_cache();
> +		for_each_mem_cgroup(memcg)
> +			tier_update_derived_limits(memcg);
> +		break;
>  	}
>  	return notifier_from_errno(0);
>  }
> @@ -6181,6 +6313,8 @@ int __init mem_cgroup_init(void)
>  	memcg_wq = alloc_workqueue("memcg", WQ_PERCPU, 0);
>  	WARN_ON(!memcg_wq);
>  
> +	update_tier_capacity_cache();
> +
>  #if defined(CONFIG_MEMORY_HOTPLUG) && defined(CONFIG_NUMA)
>  	hotplug_node_notifier(memcg_tier_hotplug_cb, MEMCG_TIER_NODE_PRI);
>  #endif


  reply	other threads:[~2026-08-18  4:46 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-18  2:31 [RFC PATCH 0/8] mm/memcontrol: introduce per-tier memory accounting and control liuqiqi
2026-08-18  2:31 ` [RFC PATCH 1/8] mm/memory-tiers: add node_to_tier_id and tier_id_to_nodemask liuqiqi
2026-08-18  2:31 ` [RFC PATCH 2/8] mm/vmscan: add try_to_free_mem_cgroup_pages_nodemask liuqiqi
2026-08-18  2:31 ` [RFC PATCH 3/8] mm/memcontrol: add per-tier page counter infrastructure and lifecycle liuqiqi
2026-08-18  2:31 ` [RFC PATCH 4/8] mm/memcontrol: add per-tier charge and uncharge liuqiqi
2026-08-18  4:32   ` Tao Cui
2026-08-18  2:31 ` [RFC PATCH 5/8] mm/memcontrol: add per-cpu stock for tier charge/uncharge liuqiqi
2026-08-18  2:31 ` [RFC PATCH 6/8] mm/memcontrol: add memory.tier control file liuqiqi
2026-08-18  2:31 ` [RFC PATCH 7/8] mm/memcontrol: auto-derive tier high/max from memory.high/max liuqiqi
2026-08-18  4:46   ` Tao Cui [this message]
2026-08-18  2:31 ` [RFC PATCH 8/8] cgroup: add memory_tiered_limits cgroup mount option liuqiqi
2026-08-18  4:56   ` Tao Cui
2026-08-18  8:12 ` [RFC PATCH 0/8] mm/memcontrol: introduce per-tier memory accounting and control Michal Hocko
2026-08-18  8:24 ` [syzbot ci] " syzbot ci

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=749beeb5-cc97-42ab-b3a4-aeee378fbb12@linux.dev \
    --to=cui.tao@linux.dev \
    --cc=akpm@linux-foundation.org \
    --cc=cgroups@vger.kernel.org \
    --cc=hannes@cmpxchg.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=liuqiqi@kylinos.cn \
    --cc=mhocko@kernel.org \
    --cc=mkoutny@suse.com \
    --cc=muchun.song@linux.dev \
    --cc=roman.gushchin@linux.dev \
    --cc=shakeel.butt@linux.dev \
    --cc=tj@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.