memcg: nmi-safe kmem charging
Subsystems (7)
Shakeel Butt
maintainercover memcg: nmi-safe kmem charging
BPF programs can trigger memcg charged kernel allocations in nmi context. However memcg charging infra for kernel memory is not equipped to handle nmi context. This series adds support for kernel memory charging for nmi context.
The initial prototype tried to make memcg charging infra for kernel memory re-entrant against irq and nmi. However upon realizing that this_cpu_* operations are not safe on all architectures (Tejun), this series took a different approach targeting only nmi context. Since the number of stats that are updated in kernel memory charging path are 3, this series added special handling of those stats in nmi context rather than making all >100 memcg stats nmi safe.
There will be a followup series which will make kernel memory charging reentrant for irq and will be able to do without disabling irqs.
We ran network intensive workload on this series and have not seen any significant performance differences with and without the series.
Shakeel Butt (4):
memcg: add infra for nmi safe memcg stats
memcg: add nmi-safe update for MEMCG_KMEM
memcg: nmi-safe slab stats updates
memcg: make objcg charging nmi safe
include/linux/memcontrol.h | 6 +++ mm/memcontrol.c | 87 +++++++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 5 deletions(-)
Signature · 1 line
2.47.1
Shakeel Butt
maintainerPATCH 1/4 memcg: add infra for nmi safe memcg stats
BPF programs can trigger memcg charging in nmi context and at the moment memcg charging code path for kernel memory does not have support for nmi context. To support kernel memory charging for nmi support, we need to make objcg charging nmi safe and also memcg stats nmi.
At the moment, the memcg stats which get updated in the objcg charging path are MEMCG_KMEM, NR_SLAB_RECLAIMABLE_B & NR_SLAB_UNRECLAIMABLE_B. Rather than adding support for all memcg stats to be nmi safe, let's just add infra to make these three stats nmi safe which this patch is doing.
--- include/linux/memcontrol.h | 6 ++++++ mm/memcontrol.c | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+)
include/linux/memcontrol.h +6 -0
@@ -113,6 +113,9 @@ struct mem_cgroup_per_node {CACHELINE_PADDING (_pad2_);unsigned long lru_zone_size[MAX_NR_ZONES ][NR_LRU_LISTS ];struct mem_cgroup_reclaim_iter iter; +/* slab stats for nmi context */ +atomic64_t slab_reclaimable; +atomic64_t slab_unreclaimable; };struct mem_cgroup_threshold {
@@ -236,6 +239,9 @@ struct mem_cgroup {atomic_long_t memory_events[MEMCG_NR_MEMORY_EVENTS ];atomic_long_t memory_events_local[MEMCG_NR_MEMORY_EVENTS ]; +/* MEMCG_KMEM for nmi context */ +atomic64_t kmem_stat; +/* * Hint of reclaim pressure for socket memroy management. Note * that this indicator should NOT be used in legacy cgroup mode
mm/memcontrol.c +43 -0
55 lines not on this page · raw message
Signature · 1 line
2.47.1
Shakeel Butt
maintainerPATCH 2/4 memcg: add nmi-safe update for MEMCG_KMEM
In reply to Shakeel ButtThe objcg based kmem charging and uncharging code path needs to update MEMCG_KMEM appropriately. Let's add support to update MEMCG_KMEM in nmi-safe way for those code paths.
--- mm/memcontrol.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-)
mm/memcontrol.c +14 -2
@@ -2779,6 +2779,18 @@ struct obj_cgroup *get_obj_cgroup_from_folio(struct folio *folio)return objcg; } + +static inline void account_kmem_nmi_safe (struct mem_cgroup *memcg,int val) +{ +if (likely (!in_nmi ())) { +mod_memcg_state (memcg,MEMCG_KMEM , val); + }else { +/* TODO: add to cgroup update tree once it is nmi-safe. */ +atomic64_add (val, &memcg->kmem_stat); + } +} + +/* * obj_cgroup_uncharge_pages: uncharge a number of kernel pages from a objcg * @objcg: object cgroup to uncharge
@@ -2791,7 +2803,7 @@ static void obj_cgroup_uncharge_pages(struct obj_cgroup *objcg, memcg =get_mem_cgroup_from_objcg (objcg); -mod_memcg_state (memcg,MEMCG_KMEM , -nr_pages); +account_kmem_nmi_safe (memcg, -nr_pages);memcg1_account_kmem (memcg, -nr_pages);if (!mem_cgroup_is_root (memcg))refill_stock (memcg, nr_pages);
@@ -2819,7 +2831,7 @@ static int obj_cgroup_charge_pages(struct obj_cgroup *objcg, gfp_t gfp,if (ret) goto out; -mod_memcg_state (memcg,MEMCG_KMEM , nr_pages); +account_kmem_nmi_safe (memcg, nr_pages);memcg1_account_kmem (memcg, nr_pages); out:css_put (&memcg->css);
Signature · 1 line
2.47.1
Shakeel Butt
maintainerPATCH 3/4 memcg: nmi-safe slab stats updates
In reply to Shakeel ButtThe objcg based kmem [un]charging can be called in nmi context and it may need to update NR_SLAB_[UN]RECLAIMABLE_B stats. So, let's correctly handle the updates of these stats in the nmi context.
--- mm/memcontrol.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-)
mm/memcontrol.c +12 -2
@@ -2591,8 +2591,18 @@ static inline void __mod_objcg_mlstate(struct obj_cgroup *objcg,rcu_read_lock (); memcg =obj_cgroup_memcg (objcg); - lruvec =mem_cgroup_lruvec (memcg, pgdat); -__mod_memcg_lruvec_state (lruvec, idx, nr); +if (likely (!in_nmi ())) { + lruvec =mem_cgroup_lruvec (memcg, pgdat); +__mod_memcg_lruvec_state (lruvec, idx, nr); + }else { +struct mem_cgroup_per_node *pn = memcg->nodeinfo[pgdat->node_id]; + +/* TODO: add to cgroup update tree once it is nmi-safe. */ +if (idx ==NR_SLAB_RECLAIMABLE_B ) +atomic64_add (nr, &pn->slab_reclaimable); +else +atomic64_add (nr, &pn->slab_unreclaimable); + }rcu_read_unlock (); }
Signature · 1 line
2.47.1
Shakeel Butt
maintainerPATCH 4/4 memcg: make objcg charging nmi safe
In reply to Shakeel ButtTo enable memcg charged kernel memory allocations from nmi context, consume_obj_stock() and refill_obj_stock() needs to be nmi safe. With the simple in_nmi() check, take the slow path of the objcg charging which handles the charging and memcg stats updates correctly for the nmi context.
--- mm/memcontrol.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-)
mm/memcontrol.c +13 -1
@@ -2965,6 +2965,9 @@ static bool consume_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes,unsigned long flags;bool ret = false; +if (unlikely (in_nmi ())) +return ret; +local_lock_irqsave (&obj_stock.lock, flags); stock =this_cpu_ptr (&obj_stock);
@@ -3068,6 +3071,15 @@ static void refill_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes,unsigned long flags;unsigned int nr_pages =0 ; +if (unlikely (in_nmi ())) { +if (pgdat) +__mod_objcg_mlstate (objcg, pgdat, idx, nr_bytes); + nr_pages = nr_bytes >>PAGE_SHIFT ; + nr_bytes = nr_bytes & (PAGE_SIZE -1 ); +atomic_add (nr_bytes, &objcg->nr_charged_bytes); + goto out; + } +local_lock_irqsave (&obj_stock.lock, flags); stock =this_cpu_ptr (&obj_stock);
@@ -3091,7 +3103,7 @@ static void refill_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes, }local_unlock_irqrestore (&obj_stock.lock, flags); - +out:if (nr_pages)obj_cgroup_uncharge_pages (objcg, nr_pages); }
Signature · 1 line
2.47.1
Re: [PATCH 4/4] memcg: make objcg charging nmi safe
Quoted 28 lines · Shakeel Butt
To enable memcg charged kernel memory allocations from nmi context,
consume_obj_stock()andrefill_obj_stock()needs to be nmi safe. With the simplein_nmi()check, take the slow path of the objcg charging which handles the charging and memcg stats updates correctly for the nmi context.Signed-off-by Shakeel Butt <[email protected]>--- mm/memcontrol.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-)mm/memcontrol.c +3
-0@@ -2965,6 +2965,9 @@ static bool consume_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes,unsigned long flags;bool ret = false; +if (unlikely (in_nmi ())) +return ret; +local_lock_irqsave (&obj_stock.lock, flags); stock =this_cpu_ptr (&obj_stock);@@ -3068,6 +3071,15 @@ static void refill_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes,unsigned long flags;
mm/memcontrol.c static void refill_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes, · line 3081
Quoted 10 lines
mm/memcontrol.c +8
-0unsigned int nr_pages =0 ; +if (unlikely (in_nmi ())) { +if (pgdat) +__mod_objcg_mlstate (objcg, pgdat, idx, nr_bytes); + nr_pages = nr_bytes >>PAGE_SHIFT ; + nr_bytes = nr_bytes & (PAGE_SIZE -1 ); +atomic_add (nr_bytes, &objcg->nr_charged_bytes); + goto out; + }
Now I see what I did incorrectly in my series and how this patch 4 combined with patch 3 is doing accounting properly.
The only issue here and in other patches is that in_nmi() is an incomplete condition to check for.
The reentrance is possible through kprobe or tracepoint. In PREEMP_RT we will be fully preemptible, but obj_stock.lock will be already taken by the current task. To fix it you need to use local_lock_is_locked(&obj_stock.lock) instead of in_nmi() or use local_trylock_irqsave(&obj_stock.lock).
local_trylock_irqsave() is cleaner and works today,
while local_lock_is_locked() hasn't landed yet, but if we go is_locked route we can decouple reentrant obj_stock operation vs normal. Like the if (!local_lock_is_locked(&obj_stock.lock))
can be done much higher up the stack from __memcg_slab_post_alloc_hook() the way I did in my series, and if locked it can do atomic_add()-style charging.
So refill_obj_stock() and friends won't need to change.
Shakeel Butt
maintainerRe: [PATCH 4/4] memcg: make objcg charging nmi safe
Quoted 52 lines · Alexei Starovoitov
Quoted 28 lines · Shakeel Butt
To enable memcg charged kernel memory allocations from nmi context,
consume_obj_stock()andrefill_obj_stock()needs to be nmi safe. With the simplein_nmi()check, take the slow path of the objcg charging which handles the charging and memcg stats updates correctly for the nmi context.Signed-off-by Shakeel Butt <[email protected]>--- mm/memcontrol.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-)mm/memcontrol.c +3
-0@@ -2965,6 +2965,9 @@ static bool consume_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes,unsigned long flags;bool ret = false; +if (unlikely (in_nmi ())) +return ret; +local_lock_irqsave (&obj_stock.lock, flags); stock =this_cpu_ptr (&obj_stock);@@ -3068,6 +3071,15 @@ static void refill_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes,unsigned long flags;mm/memcontrol.c static void refill_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes, · line 3081
Quoted 10 lines
mm/memcontrol.c +8
-0unsigned int nr_pages =0 ; +if (unlikely (in_nmi ())) { +if (pgdat) +__mod_objcg_mlstate (objcg, pgdat, idx, nr_bytes); + nr_pages = nr_bytes >>PAGE_SHIFT ; + nr_bytes = nr_bytes & (PAGE_SIZE -1 ); +atomic_add (nr_bytes, &objcg->nr_charged_bytes); + goto out; + }Now I see what I did incorrectly in my series and how this patch 4 combined with patch 3 is doing accounting properly.
The only issue here and in other patches is that
in_nmi()is an incomplete condition to check for.
The reentrance is possible through kprobe or tracepoint. InPREEMP_RTwe will be fully preemptible, but obj_stock.lock will be already taken by the current task. To fix it you need to uselocal_lock_is_locked(&obj_stock.lock)instead ofin_nmi()or uselocal_trylock_irqsave(&obj_stock.lock).
Quoted 8 lines
local_trylock_irqsave()is cleaner and works today,
whilelocal_lock_is_locked()hasn't landed yet, but if we gois_lockedroute we can decouple reentrantobj_stockoperation vs normal. Like the if (!local_lock_is_locked(&obj_stock.lock))
can be done much higher up the stack from__memcg_slab_post_alloc_hook()the way I did in my series, and if locked it can do atomic_add()-style charging.
Sorefill_obj_stock()and friends won't need to change.
Thanks Alexei for taking a look. For now I am going with the trylock path and later will check if your suggested is_locked() makes things better.
Andrew Morton
maintainerQuoted 4 lines · On Fri, 9 May 2025 16:28:55 -0700 Shakeel Butt <[email protected]> wrote:
BPF programs can trigger memcg charged kernel allocations in nmi context. However memcg charging infra for kernel memory is not equipped to handle nmi context. This series adds support for kernel memory charging for nmi context.
The patchset adds quite a bit of material to core MM on behalf of a single caller. So can we please take a close look at why BPF is doing this?
What would be involved in changing BPF to avoid doing this, or of changing BPF to handle things locally? What would be the end-user impact of such an alteration? IOW, what is the value to our users of the present BPF behavior?
Shakeel Butt
maintainerHi Andrew,
Quoted 7 lines · Andrew Morton
Quoted 4 lines · On Fri, 9 May 2025 16:28:55 -0700 Shakeel Butt <[email protected]> wrote:
BPF programs can trigger memcg charged kernel allocations in nmi context. However memcg charging infra for kernel memory is not equipped to handle nmi context. This series adds support for kernel memory charging for nmi context.
Quoted 9 lines
The patchset adds quite a bit of material to core MM on behalf of a single caller. So can we please take a close look at why BPF is doing this?
What would be involved in changing BPF to avoid doing this, or of changing BPF to handle things locally? What would be the end-user impact of such an alteration? IOW, what is the value to our users of the present BPF behavior?
Before answering the questions, let me clarify that this series is continuation of the work which added similar support for page allocator and related memcg charging and now the work is happening for kmalloc/slab allocations. Alexei has a proposal on reentrant kmalloc and here I am providing how memcg charging for that (reentrant kmalloc) should work.
Next let me take a stab in answering the questions and BPF folks can correct me if I am wrong. From what I understand, users can attach BPF programs at almost any place in kernel and those BPF programs can do memory allocations. This line of work is to make those allocations work if the any such BPF attach point is triggered in mni context.
Before this line of work (reentrant page and slab allocators), I think BPF had its internal cache but it was very limited and can easily fail allocation requests (please BPF folks correct me if I am wrong). This was discussed in LSFMM this year as well.
Now regarding the impact to the users. First there will not be any negative impact on the non-users of this feature. For the value this feature will provide to users, I think this line of work will make BPF programs of the users more reliable with better allocation behavior. BPF folks, please add more comments for the value of these features.
thanks,
Shakeel
Quoted 38 lines · Shakeel Butt
Hi Andrew,
Quoted 7 lines · Andrew Morton
Quoted 4 lines · On Fri, 9 May 2025 16:28:55 -0700 Shakeel Butt <[email protected]> wrote:
BPF programs can trigger memcg charged kernel allocations in nmi context. However memcg charging infra for kernel memory is not equipped to handle nmi context. This series adds support for kernel memory charging for nmi context.
Quoted 9 lines
The patchset adds quite a bit of material to core MM on behalf of a single caller. So can we please take a close look at why BPF is doing this?
What would be involved in changing BPF to avoid doing this, or of changing BPF to handle things locally? What would be the end-user impact of such an alteration? IOW, what is the value to our users of the present BPF behavior?
Before answering the questions, let me clarify that this series is continuation of the work which added similar support for page allocator and related memcg charging and now the work is happening for kmalloc/slab allocations. Alexei has a proposal on reentrant kmalloc and here I am providing how memcg charging for that (reentrant kmalloc) should work.
Next let me take a stab in answering the questions and BPF folks can correct me if I am wrong. From what I understand, users can attach BPF programs at almost any place in kernel and those BPF programs can do memory allocations. This line of work is to make those allocations work if the any such BPF attach point is triggered in mni context.
Before this line of work (reentrant page and slab allocators), I think BPF had its internal cache but it was very limited and can easily fail allocation requests (please BPF folks correct me if I am wrong). This was discussed in LSFMM this year as well.
Quoted 5 lines
Now regarding the impact to the users. First there will not be any negative impact on the non-users of this feature. For the value this feature will provide to users, I think this line of work will make BPF programs of the users more reliable with better allocation behavior. BPF folks, please add more comments for the value of these features.
If kmalloc gains NMI-context support, preallocation would no longer be necessary, eliminating its memory overhead which has been observed to reach up to 1.5GB in Meta's fleet [1].
[1] https://lore.kernel.org/linux-mm/[email protected]
Signature · 2 lines
Cheers, Harry / Hyeonggon
Quoted 18 lines · Shakeel Butt
Before answering the questions, let me clarify that this series is continuation of the work which added similar support for page allocator and related memcg charging and now the work is happening for kmalloc/slab allocations. Alexei has a proposal on reentrant kmalloc and here I am providing how memcg charging for that (reentrant kmalloc) should work.
Next let me take a stab in answering the questions and BPF folks can correct me if I am wrong. From what I understand, users can attach BPF programs at almost any place in kernel and those BPF programs can do memory allocations. This line of work is to make those allocations work if the any such BPF attach point is triggered in mni context.
Before this line of work (reentrant page and slab allocators), I think BPF had its internal cache but it was very limited and can easily fail allocation requests (please BPF folks correct me if I am wrong). This was discussed in LSFMM this year as well.
Quoted 5 lines
Now regarding the impact to the users. First there will not be any negative impact on the non-users of this feature. For the value this feature will provide to users, I think this line of work will make BPF programs of the users more reliable with better allocation behavior. BPF folks, please add more comments for the value of these features.
Yes and I think this part of cover letter is also important:
Quoted 2 lines
There will be a followup series which will make kernel memory charging reentrant for irq and will be able to do without disabling irqs.
The "without disabling irqs" part will improve performance for all users.
Quoted 8 lines · Shakeel Butt
BPF programs can trigger memcg charged kernel allocations in nmi context. However memcg charging infra for kernel memory is not equipped to handle nmi context. This series adds support for kernel memory charging for nmi context.
The initial prototype tried to make memcg charging infra for kernel memory re-entrant against irq and nmi. However upon realizing that this_cpu_* operations are not safe on all architectures (Tejun), this
I assume it was an off-list discussion?
Could we avoid this for the architectures where these are safe, which should be the major ones I hope?
Quoted 4 lines
series took a different approach targeting only nmi context. Since the number of stats that are updated in kernel memory charging path are 3, this series added special handling of those stats in nmi context rather than making all >100 memcg stats nmi safe.
Hmm so from patches 2 and 3 I see this relies on atomic64_add(). But AFAIU lib/atomic64.c has the generic fallback implementation for architectures that don't know better, and that would be using the "void generic_atomic64_##op" macro, which AFAICS is doing:
local_irq_save(flags); \
arch_spin_lock(lock); \
v->counter c_op a; \
arch_spin_unlock(lock); \
local_irq_restore(flags); \so in case of a nmi hitting after the spin_lock this can still deadlock?
Hm or is there some assumption that we only use these paths when already in_nmi() and then another nmi can't come in that context?
But even then, flush_nmi_stats() in patch 1 isn't done in_nmi() and uses atomic64_xchg() which in generic_atomic64_xchg() implementation also has the irq_save+spin_lock. So can't we deadlock there?
Quoted 17 lines
There will be a followup series which will make kernel memory charging reentrant for irq and will be able to do without disabling irqs.
We ran network intensive workload on this series and have not seen any significant performance differences with and without the series.
Shakeel Butt (4):
memcg: add infra for nmi safe memcg stats
memcg: add nmi-safe update forMEMCG_KMEM
memcg: nmi-safe slab stats updates
memcg: make objcg charging nmi safeinclude/linux/memcontrol.h | 6 +++ mm/memcontrol.c | 87 +++++++++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 5 deletions(-)
Shakeel Butt
maintainerI forgot to CC Tejun, so doing it now.
Quoted 6 lines · Vlastimil Babka
Quoted 5 lines · Shakeel Butt
BPF programs can trigger memcg charged kernel allocations in nmi context. However memcg charging infra for kernel memory is not equipped to handle nmi context. This series adds support for kernel memory charging for nmi context.
Quoted 7 lines
Quoted 3 lines
The initial prototype tried to make memcg charging infra for kernel memory re-entrant against irq and nmi. However upon realizing that this_cpu_* operations are not safe on all architectures (Tejun), this
I assume it was an off-list discussion?
Could we avoid this for the architectures where these are safe, which should be the major ones I hope?
Yes it was an off-list discussion. The discussion was more about the this_cpu_* ops vs atomic_* ops as on x86 this_cpu_* does not have lock prefix and how I should prefer this_cpu_* over atomic_* for my series on objcg charging without disabling irqs. Tejun pointed out this_cpu_* are not nmi safe for some archs and it would be better to handle nmi context separately. So, I am not that worried about optimizing for NMI context but your next comment on generic_atomic64_* ops is giving me headache.
Quoted 17 lines
Quoted 5 lines
series took a different approach targeting only nmi context. Since the number of stats that are updated in kernel memory charging path are 3, this series added special handling of those stats in nmi context rather than making all >100 memcg stats nmi safe.
Hmm so from patches 2 and 3 I see this relies on
atomic64_add(). But AFAIUlib/atomic64.chas the generic fallback implementation for architectures that don't know better, and that would be using the "void generic_atomic64_##op" macro, which AFAICS is doing:local_irq_save(flags); \ arch_spin_lock(lock); \ v->counter c_op a; \ arch_spin_unlock(lock); \ local_irq_restore(flags); \
Quoted 8 lines
so in case of a nmi hitting after the
spin_lockthis can still deadlock?Hm or is there some assumption that we only use these paths when already
in_nmi()and then another nmi can't come in that context?But even then,
flush_nmi_stats()in patch 1 isn't donein_nmi()and usesatomic64_xchg()which ingeneric_atomic64_xchg()implementation also has the irq_save+spin_lock. So can't we deadlock there?
I was actually assuming that atomic_* ops are safe against nmis for all archs. I looked at atomic_* ops in include/asm-generic/atomic.h and it is using arch_cmpxchg() for CONFIG_SMP and it seems like for archs with cmpxchg should be fine against nmi. I am not sure why atomic64_* are not using arch_cmpxchg() instead. I will dig more.
I also have the followup series on objcg charging without irq almost ready. I will send it out as rfc soon.
Thanks a lot for awesome and insightful comments.
Shakeel
Quoted 17 lines · Shakeel Butt
I forgot to CC Tejun, so doing it now.
Quoted 6 lines · Vlastimil Babka
Quoted 5 lines · Shakeel Butt
BPF programs can trigger memcg charged kernel allocations in nmi context. However memcg charging infra for kernel memory is not equipped to handle nmi context. This series adds support for kernel memory charging for nmi context.
Quoted 7 lines
Quoted 3 lines
The initial prototype tried to make memcg charging infra for kernel memory re-entrant against irq and nmi. However upon realizing that this_cpu_* operations are not safe on all architectures (Tejun), this
I assume it was an off-list discussion?
Could we avoid this for the architectures where these are safe, which should be the major ones I hope?
Quoted 6 lines
Yes it was an off-list discussion. The discussion was more about the this_cpu_* ops vs atomic_* ops as on x86 this_cpu_* does not have lock prefix and how I should prefer this_cpu_* over atomic_* for my series on objcg charging without disabling irqs. Tejun pointed out this_cpu_* are not nmi safe for some archs and it would be better to handle nmi context separately. So, I am not that worried about optimizing for NMI context
Well, we're introducing in_nmi() check and different execution paths to all charging. This could be e.g. compiled out for architectures where this_cpu* is NMI safe or they don't have NMIs in the first place.
Quoted 28 lines
but your next comment on generic_atomic64_* ops is giving me headache.
Quoted 17 lines
Quoted 5 lines
series took a different approach targeting only nmi context. Since the number of stats that are updated in kernel memory charging path are 3, this series added special handling of those stats in nmi context rather than making all >100 memcg stats nmi safe.
Hmm so from patches 2 and 3 I see this relies on
atomic64_add(). But AFAIUlib/atomic64.chas the generic fallback implementation for architectures that don't know better, and that would be using the "void generic_atomic64_##op" macro, which AFAICS is doing:local_irq_save(flags); \ arch_spin_lock(lock); \ v->counter c_op a; \ arch_spin_unlock(lock); \ local_irq_restore(flags); \Quoted 8 lines
so in case of a nmi hitting after the
spin_lockthis can still deadlock?Hm or is there some assumption that we only use these paths when already
in_nmi()and then another nmi can't come in that context?But even then,
flush_nmi_stats()in patch 1 isn't donein_nmi()and usesatomic64_xchg()which ingeneric_atomic64_xchg()implementation also has the irq_save+spin_lock. So can't we deadlock there?
Quoted 5 lines
I was actually assuming that atomic_* ops are safe against nmis for all archs. I looked at atomic_* ops in
include/asm-generic/atomic.hand it is usingarch_cmpxchg()forCONFIG_SMPand it seems like for archs with cmpxchg should be fine against nmi. I am not sure why atomic64_* are not usingarch_cmpxchg()instead. I will dig more.
Yeah I've found https://docs.kernel.org/core-api/local_ops.html and since it listed Mathieu we discussed on IRC and he mentioned the same thing that atomic_ ops are fine, but the later added 64bit variant isn't, which PeterZ (who added it) acknowledged.
But there could be way out if we could somehow compile-time assert that either is true:
- CONFIG_HAVE_NMI=n - we can compile out all the nmi code
- this_cpu is safe on that arch - we can also compile out the nmi code
- (if the above leaves any 64bit arch) its 64bit atomics implementation is safe
- (if there are any 32bit applicable arch left) 32bit atomics should be enough for the nmi counters even with >4GB memory as we flush them? and we know the 32bit ops are safe
Quoted 5 lines
I also have the followup series on objcg charging without irq almost ready. I will send it out as rfc soon.
Thanks a lot for awesome and insightful comments.
Shakeel
Quoted 7 lines · Vlastimil Babka
Quoted 7 lines
Quoted 7 lines
Quoted 3 lines
The initial prototype tried to make memcg charging infra for kernel memory re-entrant against irq and nmi. However upon realizing that this_cpu_* operations are not safe on all architectures (Tejun), this
I assume it was an off-list discussion?
Could we avoid this for the architectures where these are safe, which should be the major ones I hope?
IIRC Power64 has issues here, 'funnily' their local_t is NMI safe. Perhaps we could do the same for their this_cpu_*(), but ideally someone with actual power hardware should do this ;-)
Quoted 10 lines
Quoted 6 lines
Yes it was an off-list discussion. The discussion was more about the this_cpu_* ops vs atomic_* ops as on x86 this_cpu_* does not have lock prefix and how I should prefer this_cpu_* over atomic_* for my series on objcg charging without disabling irqs. Tejun pointed out this_cpu_* are not nmi safe for some archs and it would be better to handle nmi context separately. So, I am not that worried about optimizing for NMI context
Well, we're introducing
in_nmi()check and different execution paths to all charging. This could be e.g. compiled out for architectures where this_cpu* is NMI safe or they don't have NMIs in the first place.
Very few architectures one would care about do not have NMIs. Risc-V seems to be the exception here ?!?
Quoted 24 lines
Quoted 24 lines
but your next comment on generic_atomic64_* ops is giving me headache.
Quoted 22 lines
Quoted 5 lines
series took a different approach targeting only nmi context. Since the number of stats that are updated in kernel memory charging path are 3, this series added special handling of those stats in nmi context rather than making all >100 memcg stats nmi safe.
Hmm so from patches 2 and 3 I see this relies on
atomic64_add(). But AFAIUlib/atomic64.chas the generic fallback implementation for architectures that don't know better, and that would be using the "void generic_atomic64_##op" macro, which AFAICS is doing:local_irq_save(flags); \ arch_spin_lock(lock); \ v->counter c_op a; \ arch_spin_unlock(lock); \ local_irq_restore(flags); \so in case of a nmi hitting after the
spin_lockthis can still deadlock?Hm or is there some assumption that we only use these paths when already
in_nmi()and then another nmi can't come in that context?
Quoted 6 lines
Quoted 6 lines
Quoted 3 lines
But even then,
flush_nmi_stats()in patch 1 isn't donein_nmi()and usesatomic64_xchg()which ingeneric_atomic64_xchg()implementation also has the irq_save+spin_lock. So can't we deadlock there?I was actually assuming that atomic_* ops are safe against nmis for all archs.
We have HAVE_NMI_SAFE_CMPXCHG for this -- there are architectures where this is not the case -- but again, those are typically oddball archs you don't much care about.
But yes, *64 on 32bit archs is generally not NMI safe.
Quoted 4 lines
I looked at atomic_* ops in
include/asm-generic/atomic.hand itQuoted 3 lines
is using
arch_cmpxchg()forCONFIG_SMPand it seems like for archs with cmpxchg should be fine against nmi. I am not sure why atomic64_* are not usingarch_cmpxchg()instead. I will dig more.
Not many 32bit architectures have 64bit cmpxchg. We're only now dropping support for x86 chips without CMPXCHG8b.
Quoted 8 lines
Yeah I've found https://docs.kernel.org/core-api/local_ops.html and since it listed Mathieu we discussed on IRC and he mentioned the same thing that
atomic_ops are fine, but the later added 64bit variant isn't, which PeterZ (who added it) acknowledged.But there could be way out if we could somehow compile-time assert that either is true:
-CONFIG_HAVE_NMI=n- we can compile out all the nmi code
Note that in_nmi() is not depending on HAVE_NMI -- nor can it be. Many architectures treat various traps as NMI-like, even though they might not have real NMIs.
Quoted 1 line
-
this_cpuis safe on that arch - we can also compile out the nmi code
There is no config symbol for this presently.
Quoted 1 line
- (if the above leaves any 64bit arch) its 64bit atomics implementation is safe
True, only because HPPA does not in fact have NMIs.
Quoted 3 lines
- (if there are any 32bit applicable arch left) 32bit atomics should be enough for the nmi counters even with >4GB memory as we flush them? and we know the 32bit ops are safe
Older ARM might qualify here.
Shakeel Butt
maintainerQuoted 6 lines · Peter Zijlstra
Quoted 4 lines · Vlastimil Babka
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
The initial prototype tried to make memcg charging infra for kernel memory re-entrant against irq and nmi. However upon realizing that this_cpu_* operations are not safe on all architectures (Tejun), this
Quoted 8 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
I assume it was an off-list discussion?
Could we avoid this for the architectures where these are safe, which should be the major ones I hope?IIRC Power64 has issues here, 'funnily' their
local_tis NMI safe. Perhaps we could do the same for their this_cpu_*(), but ideally someone with actual power hardware should do this ;-)
Is CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPS the right config to differentiate between such archs? I see Power64 does not have that enabled.
Quoted 51 lines
Quoted 10 lines
Quoted 6 lines
Yes it was an off-list discussion. The discussion was more about the this_cpu_* ops vs atomic_* ops as on x86 this_cpu_* does not have lock prefix and how I should prefer this_cpu_* over atomic_* for my series on objcg charging without disabling irqs. Tejun pointed out this_cpu_* are not nmi safe for some archs and it would be better to handle nmi context separately. So, I am not that worried about optimizing for NMI context
Well, we're introducing
in_nmi()check and different execution paths to all charging. This could be e.g. compiled out for architectures where this_cpu* is NMI safe or they don't have NMIs in the first place.Very few architectures one would care about do not have NMIs. Risc-V seems to be the exception here ?!?
Quoted 24 lines
Quoted 24 lines
but your next comment on generic_atomic64_* ops is giving me headache.
Quoted 22 lines
Quoted 5 lines
series took a different approach targeting only nmi context. Since the number of stats that are updated in kernel memory charging path are 3, this series added special handling of those stats in nmi context rather than making all >100 memcg stats nmi safe.
Hmm so from patches 2 and 3 I see this relies on
atomic64_add(). But AFAIUlib/atomic64.chas the generic fallback implementation for architectures that don't know better, and that would be using the "void generic_atomic64_##op" macro, which AFAICS is doing:local_irq_save(flags); \ arch_spin_lock(lock); \ v->counter c_op a; \ arch_spin_unlock(lock); \ local_irq_restore(flags); \so in case of a nmi hitting after the
spin_lockthis can still deadlock?Hm or is there some assumption that we only use these paths when already
in_nmi()and then another nmi can't come in that context?Quoted 6 lines
Quoted 6 lines
Quoted 3 lines
But even then,
flush_nmi_stats()in patch 1 isn't donein_nmi()and usesatomic64_xchg()which ingeneric_atomic64_xchg()implementation also has the irq_save+spin_lock. So can't we deadlock there?I was actually assuming that atomic_* ops are safe against nmis for all archs.
We have
HAVE_NMI_SAFE_CMPXCHGfor this -- there are architectures where this is not the case -- but again, those are typically oddball archs you don't much care about.But yes, *64 on 32bit archs is generally not NMI safe.
Quoted 8 lines
Quoted 4 lines
I looked at atomic_* ops in
include/asm-generic/atomic.hand itQuoted 3 lines
is using
arch_cmpxchg()forCONFIG_SMPand it seems like for archs with cmpxchg should be fine against nmi. I am not sure why atomic64_* are not usingarch_cmpxchg()instead. I will dig more.Not many 32bit architectures have 64bit cmpxchg. We're only now dropping support for x86 chips without CMPXCHG8b.
As Vlastimil pointed out (last point), I don't think I will need 64bit cmpxchg, 32bit cmpxchg will be fine.
Quoted 9 lines
Quoted 8 lines
Yeah I've found https://docs.kernel.org/core-api/local_ops.html and since it listed Mathieu we discussed on IRC and he mentioned the same thing that
atomic_ops are fine, but the later added 64bit variant isn't, which PeterZ (who added it) acknowledged.But there could be way out if we could somehow compile-time assert that either is true:
-CONFIG_HAVE_NMI=n- we can compile out all the nmi code
Quoted 7 lines
Note that
in_nmi()is not depending onHAVE_NMI-- nor can it be. Many architectures treat various traps as NMI-like, even though they might not have real NMIs.Quoted 1 line
-
this_cpuis safe on that arch - we can also compile out the nmi codeThere is no config symbol for this presently.
Hmm what about CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPS?
Quoted 4 lines
Quoted 2 lines
- (if the above leaves any 64bit arch) its 64bit atomics implementation is safe
True, only because HPPA does not in fact have NMIs.
What is HPPA?
Quoted 6 lines
Quoted 4 lines
- (if there are any 32bit applicable arch left) 32bit atomics should be enough for the nmi counters even with >4GB memory as we flush them? and we know the 32bit ops are safe
Older ARM might qualify here.
Thanks a lot Vlastimil & Peter for the suggestions. Let me summarize what I plan to do and please point out if I am doing something wrong:
#if defined(CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPS) || !defined(CONFIG_HAVE_NMI)
// Do normal this_cpu* ops
#elif defined(ARCH_HAVE_NMI_SAFE_CMPXCHG)
// Do 32 bit atomic ops with in_nmi() checks
#else
// Build error or ignore nmi stats??
#endif
WDYT?
(BTW Vlastimil, I might rebase send out the irq-safe series before this nmi one.)
thanks,
Shakeel
Quoted 5 lines · Shakeel Butt
Quoted 4 lines
IIRC Power64 has issues here, 'funnily' their
local_tis NMI safe. Perhaps we could do the same for their this_cpu_*(), but ideally someone with actual power hardware should do this ;-)
Quoted 7 lines
Is
CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPSthe right config to differentiate between such archs? I see Power64 does not have that enabled.Quoted 1 line
There is no config symbol for this presently.
Hmm what about
CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPS?
Hmm, I didn't know about that one, and it escaped my grep yesterday.
Anyway, PPC is fixable, just not sure its worth it for them.
Quoted 6 lines
Quoted 4 lines
Quoted 2 lines
- (if the above leaves any 64bit arch) its 64bit atomics implementation is safe
True, only because HPPA does not in fact have NMIs.
What is HPPA?
arch/parisc the worst 64bit arch ever.
They saw sparc32-smp and thought that was a great idea, or something along those lines. Both are quite terrible. Sparc64 realized the mistake and fixed it -- it has cmpxchg.
Nick, is this something that's useful for you guys?
---
arch/powerpc/include/asm/percpu.h +5 -0
@@ -25,6 +25,11 @@ DECLARE_STATIC_KEY_FALSE(__percpu_first_chunk_is_paged);#define percpu_first_chunk_is_paged false#endif +#ifdef CONFIG_PPC_BOOK3S_64 +#define __pcpu_local_irq_save (f) powerpc_local_irq_pmu_save(f) +#define __pcpu_local_irq_restore (s) powerpc_local_irq_pmu_restore(f) +#endif +#include <asm-generic/percpu.h> #include <asm/paca.h>
include/asm-generic/percpu.h +17 -12
@@ -75,6 +75,11 @@ extern void setup_per_cpu_areas(void);#define PER_CPU_ATTRIBUTES #endif +#ifndef __pcpu_local_irq_save +#define __pcpu_local_irq_save (x) raw_local_irq_save(x) +#define __pcpu_local_irq_restore (x) raw_local_irq_restore(x) +#endif +#define raw_cpu_generic_read (pcp) \ ({ \ *raw_cpu_ptr(&(pcp)); \
@@ -146,9 +151,9 @@ do { \ ({ \TYPEOF_UNQUAL (pcp) ___ret; \unsigned long ___flags; \ -raw_local_irq_save (___flags); \ +__pcpu_local_irq_save (___flags); \ ___ret =raw_cpu_generic_read (pcp); \ -raw_local_irq_restore (___flags); \ +__pcpu_local_irq_restore (___flags); \ ___ret; \ })
@@ -165,9 +170,9 @@ do { \#define this_cpu_generic_to_op (pcp, val, op) \ do { \ unsigned long __flags; \ - raw_local_irq_save(__flags); \ + __pcpu_local_irq_save(__flags); \ raw_cpu_generic_to_op(pcp, val, op); \ - raw_local_irq_restore(__flags); \ + __pcpu_local_irq_restore(__flags); \ } while (0)
@@ -175,9 +180,9 @@ do { \ ({ \TYPEOF_UNQUAL (pcp) __ret; \unsigned long __flags; \ -raw_local_irq_save (__flags); \ +__pcpu_local_irq_save (__flags); \ __ret =raw_cpu_generic_add_return (pcp, val); \ -raw_local_irq_restore (__flags); \ +__pcpu_local_irq_restore (__flags); \ __ret; \ })
@@ -185,9 +190,9 @@ do { \ ({ \TYPEOF_UNQUAL (pcp) __ret; \unsigned long __flags; \ -raw_local_irq_save (__flags); \ +__pcpu_local_irq_save (__flags); \ __ret =raw_cpu_generic_xchg (pcp, nval); \ -raw_local_irq_restore (__flags); \ +__pcpu_local_irq_restore (__flags); \ __ret; \ })
@@ -195,9 +200,9 @@ do { \ ({ \bool __ret; \unsigned long __flags; \ -raw_local_irq_save (__flags); \ +__pcpu_local_irq_save (__flags); \ __ret =raw_cpu_generic_try_cmpxchg (pcp, ovalp, nval); \ -raw_local_irq_restore (__flags); \ +__pcpu_local_irq_restore (__flags); \ __ret; \ })
@@ -205,9 +210,9 @@ do { \ ({ \TYPEOF_UNQUAL (pcp) __ret; \unsigned long __flags; \ -raw_local_irq_save (__flags); \ +__pcpu_local_irq_save (__flags); \ __ret =raw_cpu_generic_cmpxchg (pcp, oval, nval); \ -raw_local_irq_restore (__flags); \ +__pcpu_local_irq_restore (__flags); \ __ret; \ })
Shakeel Butt
maintainerOn Tue, May 13, 2025 at 03:17:00PM -0700, Shakeel Butt wrote:
[...]
Quoted 11 lines
Thanks a lot Vlastimil & Peter for the suggestions. Let me summarize what I plan to do and please point out if I am doing something wrong:
#if
defined(CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPS)|| !defined(CONFIG_HAVE_NMI)// Do normal this_cpu* ops
#elif
defined(ARCH_HAVE_NMI_SAFE_CMPXCHG)
Quoted 9 lines
// Do 32 bit atomic ops with
in_nmi()checks#else
// Build error or ignore nmi stats??
#endif
Just wanted to circle back on the updated plan:
#if defined(CONFIG_ARCH_HAS_NMI_SAFE_THIS_CPU_OPS) || !defined(CONFIG_HAVE_NMI)
- Allow memcg charging in nmi context
- Use this_cpu_ops for memcg stats even in nmi context
#elif defined(ARCH_HAVE_NMI_SAFE_CMPXCHG)
- Allow memcg charging in nmi context
- Use atomic* ops for selected memcg stats in nmi context
#else
- Do not allow memcg charging in nmi context
#endif