The Linux Kernel Mailing List
 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: 12+ 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

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