Use local_lock for pcp protection and reduce stat overhead
Subsystems (8)
v2 cover Use local_lock for pcp protection and reduce stat overhead
For MM people, the whole series is relevant but patch 3 needs particular attention for memory hotremove as I had problems testing it because full zone removal always failed for me. For RT people, the most interesting patches are 2, 9 and 10 with 2 being the most important.
This series requires patches in Andrew's tree so for convenience, it's also available at
git://git.kernel.org/pub/scm/linux/kernel/git/mel/linux.git mm-percpu-local_lock-v2r10
The PCP (per-cpu page allocator in page_alloc.c) shares locking requirements with vmstat and the zone lock which is inconvenient and causes some issues. For example, the PCP list and vmstat share the same per-cpu space meaning that it's possible that vmstat updates dirty cache lines holding per-cpu lists across CPUs unless padding is used. Second, PREEMPT_RT does not want IRQs disabled in the page allocator because it's too long for IRQs to be disabled unnecesarily.
This series splits the locking requirements and uses locks types more suitable for PREEMPT_RT, reduces the time when special locking is required for stats and reduces the time when IRQs need to be disabled on !PREEMPT_RT kernels.
Why local_lock? PREEMPT_RT considers the following sequence to be unsafe as documented in Documentation/locking/locktypes.rst
local_irq_disable(); raw_spin_lock(&lock);
The page allocator does not use raw_spin_lock but using local_irq_safe is undesirable on PREEMPT_RT as it leaves IRQs disabled for an excessive length of time. By converting to local_lock which disables migration on PREEMPT_RT, the locking requirements can be separated and start moving the protections for PCP, stats and the zone lock to PREEMPT_RT-safe equivalent locking. As a bonus, local_lock also means that PROVE_LOCKING does something useful.
After that, it was very obvious that zone_statistics in particular has way too much overhead and leaves IRQs disabled for longer than necessary on !PREEMPT_RT kernels. zone_statistics uses perfectly accurate counters requiring IRQs be disabled for parallel RMW sequences when inaccurate ones like vm_events would do. The series makes the NUMA statistics (NUMA_HIT and friends) inaccurate counters that then require no special protection on !PREEMPT_RT.
The bulk page allocator can then do stat updates in bulk with IRQs enabled which should improve the efficiency. Technically, this could have been done without the local_lock and vmstat conversion work and the order simply reflects the timing of when different series were implemented.
Finally, there are places where we conflate IRQs being disabled for the PCP with the IRQ-safe zone spinlock. The remainder of the series reduces the scope of what is protected by disabled IRQs on !PREEMPT_RT kernels. By the end of the series, page_alloc.c does not call local_irq_save so the locking scope is a bit clearer. The one exception is that modifying NR_FREE_PAGES still happens in places where it's known the IRQs are disabled as it's harmless for PREEMPT_RT and would be expensive to split the locking there.
No performance data is included because despite the overhead of the stats, it's within the noise for most workloads on !PREEMPT_RT. However, Jesper Dangaard Brouer ran a page allocation microbenchmark on a E5-1650 v4 @ 3.60GHz CPU on the first version of this series. Focusing on the array variant of the bulk page allocator reveals the following.
(CPU: Intel(R) Xeon(R) CPU E5-1650 v4 @ 3.60GHz) ARRAY variant: time_bulk_page_alloc_free_array: step=bulk size
Baseline Patched 1 56.383 54.225 (+3.83%) 2 40.047 35.492 (+11.38%) 3 37.339 32.643 (+12.58%) 4 35.578 30.992 (+12.89%) 8 33.592 29.606 (+11.87%) 16 32.362 28.532 (+11.85%) 32 31.476 27.728 (+11.91%) 64 30.633 27.252 (+11.04%) 128 30.596 27.090 (+11.46%)
While this is a positive outcome, the series is more likely to be interesting to the RT people in terms of getting parts of the PREEMPT_RT tree into mainline.
drivers/base/node.c | 18 +-- include/linux/mmzone.h | 29 ++-- include/linux/vmstat.h | 65 +++++---- mm/internal.h | 2 +- mm/memory_hotplug.c | 10 +- mm/mempolicy.c | 2 +- mm/page_alloc.c | 297 ++++++++++++++++++++++++----------------- mm/vmstat.c | 250 ++++++++++++---------------------- 8 files changed, 339 insertions(+), 334 deletions(-)
Signature · 1 line
2.26.2
PATCH v2 1/11 mm/page_alloc: Split per cpu page lists and zone stats
The per-cpu page allocator lists and the per-cpu vmstat deltas are stored in the same struct per_cpu_pages even though vmstats have no direct impact on the per-cpu page lists. This is inconsistent because the vmstats for a node are stored on a dedicated structure. The bigger issue is that the per_cpu_pages structure is not cache-aligned and stat updates either cache conflict with adjacent per-cpu lists incurring a runtime cost or padding is required incurring a memory cost.
This patch splits the per-cpu pagelists and the vmstat deltas into separate structures. It's mostly a mechanical conversion but some variable renaming is done to clearly distinguish the per-cpu pages structure (pcp) from the vmstats (pzstats).
Superficially, this appears to increase the size of the per_cpu_pages structure but the movement of expire fills a structure hole so there is no impact overall.
--- include/linux/mmzone.h | 18 ++++---- include/linux/vmstat.h | 8 ++-- mm/page_alloc.c | 84 +++++++++++++++++++----------------- mm/vmstat.c | 96 ++++++++++++++++++++++-------------------- 4 files changed, 110 insertions(+), 96 deletions(-)
include/linux/mmzone.h +10 -8
@@ -341,20 +341,21 @@ struct per_cpu_pages {int count;/* number of pages in the list */ int high;/* high watermark, emptying needed */ int batch;/* chunk size for buddy add/remove */ +#ifdef CONFIG_NUMA +int expire;/* When 0, remote pagesets are drained */ +#endif /* Lists of pages, one per migrate type stored on the pcp-lists */ struct list_head lists[MIGRATE_PCPTYPES ]; }; -struct per_cpu_pageset { -struct per_cpu_pages pcp; -#ifdef CONFIG_NUMA -s8 expire; -u16 vm_numa_stat_diff[NR_VM_NUMA_STAT_ITEMS ]; -#endif +struct per_cpu_zonestat {#ifdef CONFIG_SMP -s8 stat_threshold;s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS ]; +s8 stat_threshold; +#endif +#ifdef CONFIG_NUMA +u16 vm_numa_stat_diff[NR_VM_NUMA_STAT_ITEMS ];#endif };
@@ -470,7 +471,8 @@ struct zone {int node;#endif struct pglist_data *zone_pgdat; -struct per_cpu_pageset __percpu *pageset; +struct per_cpu_pages __percpu *per_cpu_pageset; +struct per_cpu_zonestat __percpu *per_cpu_zonestats;/* * the high and batch values are copied to individual pagesets for * faster access
include/linux/vmstat.h +4 -4
@@ -163,7 +163,7 @@ static inline unsigned long zone_numa_state_snapshot(struct zone *zone,int cpu;for_each_online_cpu (cpu) - x +=per_cpu_ptr (zone->pageset, cpu)->vm_numa_stat_diff[item]; + x +=per_cpu_ptr (zone->per_cpu_zonestats, cpu)->vm_numa_stat_diff[item];return x; }
@@ -236,7 +236,7 @@ static inline unsigned long zone_page_state_snapshot(struct zone *zone,#ifdef CONFIG_SMP int cpu;for_each_online_cpu (cpu) - x +=per_cpu_ptr (zone->pageset, cpu)->vm_stat_diff[item]; + x +=per_cpu_ptr (zone->per_cpu_zonestats, cpu)->vm_stat_diff[item];if (x <0 ) x =0 ;
@@ -291,7 +291,7 @@ struct ctl_table;int vmstat_refresh (struct ctl_table *,int write,void *buffer,size_t *lenp,loff_t *ppos); -void drain_zonestat (struct zone *zone,struct per_cpu_pageset *); +void drain_zonestat (struct zone *zone,struct per_cpu_zonestat *);int calculate_pressure_threshold (struct zone *zone);int calculate_normal_threshold (struct zone *zone);
@@ -399,7 +399,7 @@ static inline void cpu_vm_stats_fold(int cpu) { }static inline void quiet_vmstat (void ) { }static inline void drain_zonestat (struct zone *zone, -struct per_cpu_pageset *pset) { } +struct per_cpu_zonestat *pzstats) { }#endif /* CONFIG_SMP */ static inline void __mod_zone_freepage_state(struct zone *zone, int nr_pages,
mm/page_alloc.c +46 -38
213 lines not on this page · raw message
mm/vmstat.c +50 -46
250 lines not on this page · raw message
Signature · 1 line
2.26.2
Re: [PATCH 01/11] mm/page_alloc: Split per cpu page lists and zone stats
mm/page_alloc.c static __meminit void zone_pcp_init(struct zone *zone) · line 6700
Quoted 6 lines · Mel Gorman
mm/page_alloc.c +1
-1@@ -6691,7 +6697,7 @@ static __meminit void zone_pcp_init(struct zone *zone)* relies on the ability of the linker to provide the * offset of a (static) per cpu variable into the per cpu area. */ - zone->pageset = &boot_pageset; + zone->per_cpu_pageset = &boot_pageset;
I don't see any &boot_zonestats assignment here in zone_pcp_init() or its caller(s), which seems strange, as zone_pcp_reset() does it.
Quoted 16 lines
mm/page_alloc.c +2
-3zone->pageset_high =BOOT_PAGESET_HIGH ; zone->pageset_batch =BOOT_PAGESET_BATCH ;@@ -8954,17 +8960,19 @@ void zone_pcp_reset(struct zone *zone) {unsigned long flags;int cpu; -struct per_cpu_pageset *pset; +struct per_cpu_zonestat *pzstats;/* avoid races with drain_pages() */ local_irq_save (flags); -if (zone->pageset != &boot_pageset) { +if (zone->per_cpu_pageset != &boot_pageset) {for_each_online_cpu (cpu) { - pset =per_cpu_ptr (zone->pageset, cpu);
mm/page_alloc.c void zone_pcp_reset(struct zone *zone) · line 8975
Quoted 10 lines
mm/page_alloc.c +6
-3-drain_zonestat (zone, pset); + pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); +drain_zonestat (zone, pzstats); } -free_percpu (zone->pageset); - zone->pageset = &boot_pageset; +free_percpu (zone->per_cpu_pageset); +free_percpu (zone->per_cpu_zonestats); + zone->per_cpu_pageset = &boot_pageset; + zone->per_cpu_zonestats = &boot_zonestats;
^ here
Quoted 3 lines
mm/page_alloc.c +0
-0}local_irq_restore (flags); }
Re: [PATCH 01/11] mm/page_alloc: Split per cpu page lists and zone stats
Quoted 11 lines · Vlastimil Babka
mm/page_alloc.c static __meminit void zone_pcp_init(struct zone *zone) · line 6700
Quoted 6 lines · Mel Gorman
mm/page_alloc.c +1
-1@@ -6691,7 +6697,7 @@ static __meminit void zone_pcp_init(struct zone *zone)* relies on the ability of the linker to provide the * offset of a (static) per cpu variable into the per cpu area. */ - zone->pageset = &boot_pageset; + zone->per_cpu_pageset = &boot_pageset;I don't see any
&boot_zonestatsassignment here inzone_pcp_init()or its caller(s), which seems strange, aszone_pcp_reset()does it.
Yes, it's required, well spotted!
Signature · 2 lines
Mel Gorman SUSE Labs
PATCH v2 2/11 mm/page_alloc: Convert per-cpu list protection to local_lock
In reply to Mel GormanThere is a lack of clarity of what exactly local_irq_save/local_irq_restore protects in page_alloc.c . It conflates the protection of per-cpu page allocation structures with per-cpu vmstat deltas.
This patch protects the PCP structure using local_lock which for most configurations is identical to IRQ enabling/disabling. The scope of the lock is still wider than it should be but this is decreased laster.
--- include/linux/mmzone.h | 2 ++ mm/page_alloc.c | 50 +++++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 15 deletions(-)
include/linux/mmzone.h +2 -0
@@ -20,6 +20,7 @@#include <linux/atomic.h> #include <linux/mm_types.h> #include <linux/page-flags.h> +#include <linux/local_lock.h> #include <asm/page.h> /* Free memory management - zoned buddy allocator. */
@@ -337,6 +338,7 @@ enum zone_watermarks {#define high_wmark_pages (z) (z->_watermark[WMARK_HIGH] + z->watermark_boost)#define wmark_pages (z, i) (z->_watermark[i] + z->watermark_boost) +/* Fields and list protected by pagesets local_lock in page_alloc.c */ struct per_cpu_pages {int count;/* number of pages in the list */ int high;/* high watermark, emptying needed */
mm/page_alloc.c +35 -15
145 lines not on this page · raw message
Signature · 1 line
2.26.2
Peter Zijlstra
maintainerRe: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 27 lines · Mel Gorman
There is a lack of clarity of what exactly local_irq_save/local_irq_restore protects in
page_alloc.c. It conflates the protection of per-cpu page allocation structures with per-cpu vmstat deltas.This patch protects the PCP structure using
local_lockwhich for most configurations is identical to IRQ enabling/disabling. The scope of the lock is still wider than it should be but this is decreased laster.include/linux/mmzone.h +1
-0@@ -337,6 +338,7 @@ enum zone_watermarks {#define high_wmark_pages (z) (z->_watermark[WMARK_HIGH] + z->watermark_boost)#define wmark_pages (z, i) (z->_watermark[i] + z->watermark_boost) +/* Fields and list protected by pagesets local_lock in page_alloc.c */ struct per_cpu_pages {int count;/* number of pages in the list */ int high;/* high watermark, emptying needed */ mm/page_alloc.c +0
-0
mm/page_alloc.c typedef int __bitwise fpi_t; · line 120
Quoted 9 lines
mm/page_alloc.c +6
-0@@ -112,6 +112,13 @@ typedef int __bitwise fpi_t;static DEFINE_MUTEX (pcp_batch_high_lock);#define MIN_PERCPU_PAGELIST_FRACTION (8) +struct pagesets { +local_lock_t lock; +}; +static DEFINE_PER_CPU(struct pagesets, pagesets) = { + .lock =INIT_LOCAL_LOCK (lock), +};
So why isn't the local_lock_t in struct per_cpu_pages ? That seems to be the actual object that is protected by it and is already per-cpu.
Is that because you want to avoid the duplication across zones? Is that worth the effort?
Re: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 18 lines · Peter Zijlstra
Quoted 5 lines
mm/page_alloc.c +0
-0mm/page_alloc.c typedef int __bitwise fpi_t; · line 120
Quoted 9 lines
mm/page_alloc.c +6
-0@@ -112,6 +112,13 @@ typedef int __bitwise fpi_t;static DEFINE_MUTEX (pcp_batch_high_lock);#define MIN_PERCPU_PAGELIST_FRACTION (8) +struct pagesets { +local_lock_t lock; +}; +static DEFINE_PER_CPU(struct pagesets, pagesets) = { + .lock =INIT_LOCAL_LOCK (lock), +};So why isn't the
local_lock_tin structper_cpu_pages? That seems to be the actual object that is protected by it and is already per-cpu.
Quoted 2 lines
Is that because you want to avoid the duplication across zones? Is that worth the effort?
When I wrote the patch, the problem was that zone_pcp_reset freed the per_cpu_pages structure and it was "protected" by local_irq_save(). If that was converted to local_lock_irq then the structure containing the lock is freed before it is released which is obviously bad.
Much later when trying to make the allocator RT-safe in general, I realised that locking was broken and fixed it in patch 3 of this series. With that, the local_lock could potentially be embedded within per_cpu_pages safely at the end of this series.
Signature · 2 lines
Mel Gorman SUSE Labs
Peter Zijlstra
maintainerRe: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 27 lines · Mel Gorman
Quoted 18 lines · Peter Zijlstra
Quoted 5 lines
mm/page_alloc.c +0
-0mm/page_alloc.c typedef int __bitwise fpi_t; · line 120
Quoted 9 lines
mm/page_alloc.c +6
-0@@ -112,6 +112,13 @@ typedef int __bitwise fpi_t;static DEFINE_MUTEX (pcp_batch_high_lock);#define MIN_PERCPU_PAGELIST_FRACTION (8) +struct pagesets { +local_lock_t lock; +}; +static DEFINE_PER_CPU(struct pagesets, pagesets) = { + .lock =INIT_LOCAL_LOCK (lock), +};So why isn't the
local_lock_tin structper_cpu_pages? That seems to be the actual object that is protected by it and is already per-cpu.Quoted 2 lines
Is that because you want to avoid the duplication across zones? Is that worth the effort?
When I wrote the patch, the problem was that
zone_pcp_resetfreed theper_cpu_pagesstructure and it was "protected" bylocal_irq_save(). If that was converted tolocal_lock_irqthen the structure containing the lock is freed before it is released which is obviously bad.
Quoted 4 lines
Much later when trying to make the allocator RT-safe in general, I realised that locking was broken and fixed it in patch 3 of this series. With that, the
local_lockcould potentially be embedded withinper_cpu_pagessafely at the end of this series.
Fair enough; I was just wondering why the obvious solution wasn't chosen and neither changelog nor comment explain, so I had to ask :-)
Re: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 28 lines · Peter Zijlstra
Quoted 27 lines · Mel Gorman
Quoted 18 lines · Peter Zijlstra
Quoted 5 lines
mm/page_alloc.c +0
-0mm/page_alloc.c typedef int __bitwise fpi_t; · line 120
Quoted 9 lines
mm/page_alloc.c +6
-0@@ -112,6 +112,13 @@ typedef int __bitwise fpi_t;static DEFINE_MUTEX (pcp_batch_high_lock);#define MIN_PERCPU_PAGELIST_FRACTION (8) +struct pagesets { +local_lock_t lock; +}; +static DEFINE_PER_CPU(struct pagesets, pagesets) = { + .lock =INIT_LOCAL_LOCK (lock), +};So why isn't the
local_lock_tin structper_cpu_pages? That seems to be the actual object that is protected by it and is already per-cpu.Quoted 2 lines
Is that because you want to avoid the duplication across zones? Is that worth the effort?
When I wrote the patch, the problem was that
zone_pcp_resetfreed theper_cpu_pagesstructure and it was "protected" bylocal_irq_save(). If that was converted tolocal_lock_irqthen the structure containing the lock is freed before it is released which is obviously bad.
Quoted 7 lines
Quoted 4 lines
Much later when trying to make the allocator RT-safe in general, I realised that locking was broken and fixed it in patch 3 of this series. With that, the
local_lockcould potentially be embedded withinper_cpu_pagessafely at the end of this series.Fair enough; I was just wondering why the obvious solution wasn't chosen and neither changelog nor comment explain, so I had to ask :-)
It's a fair question and it was my first approach before I hit problems. Thinking again this morning, I remembered that another problem I hit was patterns like this
local_lock_irqsave(&pagesets.lock, flags);
pcp = per_cpu_ptr(zone->per_cpu_pageset, cpu);turning into
cpu = get_cpu();
pcp = per_cpu_ptr(zone->per_cpu_pageset, cpu);
local_lock_irqsave(&pcp->lock, flags);That has its own problems if zone->lock was acquired within the local_lock_irqsave section (Section "spinlock_t and rwlock_t" in Documentation/locking/locktypes.rst) so it has to turn into
migrate_disable();
pcp = this_cpu_ptr(zone->per_cpu_pageset);
local_lock_irqsave(&pcp->lock, flags);I did not want to start adding migrate_disable() in multiple places like this because I'm guessing that new users of migrate_disable() need strong justification and adding such code in page_alloc.c might cause cargo-cult copy&paste in the future. Maybe it could be addressed with a helper like this_cpu_local_lock or this_cpu_local_lock_irq but that means in some cases that the PCP structure is looked up twice with patterns like this one
local_lock_irqsave(&pagesets.lock, flags);
free_unref_page_commit(page, pfn, migratetype);
local_unlock_irqrestore(&pagesets.lock, flags);To get around multiple lookups the helper becomes something that disables migration, looks up the PCP structure, locks it and returns it with pcp then passed around as appropriate. Not sure what I would call that helper :P
In the end I just gave up and kept it simple as there is no benefit to !PREEMPT_RT which just disables IRQs. Maybe it'll be worth considering when PREEMPT_RT is upstream and can be enabled. The series was functionally tested on the PREEMPT_RT tree by reverting the page_alloc.c patch and applies this series and all of its prerequisites on top.
Signature · 2 lines
Mel Gorman SUSE Labs
Peter Zijlstra
maintainerRe: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 5 lines · Mel Gorman
In the end I just gave up and kept it simple as there is no benefit to !PREEMPT_RT which just disables IRQs. Maybe it'll be worth considering when
PREEMPT_RTis upstream and can be enabled. The series was functionally tested on thePREEMPT_RTtree by reverting thepage_alloc.cpatch and applies this series and all of its prerequisites on top.
Right, I see the problem. Fair enough; perhaps ammend the changelog to include some of that so that we can 'remember' in a few months why the code is 'funneh'.
Re: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 11 lines · Peter Zijlstra
Quoted 5 lines · Mel Gorman
In the end I just gave up and kept it simple as there is no benefit to !PREEMPT_RT which just disables IRQs. Maybe it'll be worth considering when
PREEMPT_RTis upstream and can be enabled. The series was functionally tested on thePREEMPT_RTtree by reverting thepage_alloc.cpatch and applies this series and all of its prerequisites on top.Right, I see the problem. Fair enough; perhaps ammend the changelog to include some of that so that we can 'remember' in a few months why the code is 'funneh'.
I updated the changelog and also added a comment above the declaration. That said, there are some curious users already. fs/squashfs/decompressor_multi_percpu.c looks like it always uses the local_lock in CPU 0's per-cpu structure instead of stabilising a per-cpu pointer. drivers/block/zram/zcomp.c appears to do the same although for at least one of the zcomp_stream_get() callers, the CPU is pinned for other reasons (bit spin lock held). I think it happens to work anyway but it's weird and I'm not a fan.
Anyway, new version looks like is below.
Signature · 22 lines
[PATCH] mm/page_alloc: Convert per-cpu list protection to local_lock There is a lack of clarity of what exactly local_irq_save/local_irq_restore protects in page_alloc.c . It conflates the protection of per-cpu page allocation structures with per-cpu vmstat deltas. This patch protects the PCP structure using local_lock which for most configurations is identical to IRQ enabling/disabling. The scope of the lock is still wider than it should be but this is decreased later. local_lock is declared statically instead of placing it within a structure and this is deliberate. Placing it in the zone offers limited benefit and confuses what the lock is protecting -- struct per_cpu_pages. However, putting it in per_cpu_pages is problematic because the task is not guaranteed to be pinned to the CPU yet so looking up a per-cpu structure is unsafe. [[email protected]: Make pagesets static] Signed-off-by: Mel Gorman <[email protected]> --- include/linux/mmzone.h | 2 ++ mm/page_alloc.c | 67 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 54 insertions(+), 15 deletions(-)
include/linux/mmzone.h +2 -0
@@ -20,6 +20,7 @@#include <linux/atomic.h> #include <linux/mm_types.h> #include <linux/page-flags.h> +#include <linux/local_lock.h> #include <asm/page.h> /* Free memory management - zoned buddy allocator. */
@@ -337,6 +338,7 @@ enum zone_watermarks {#define high_wmark_pages (z) (z->_watermark[WMARK_HIGH] + z->watermark_boost)#define wmark_pages (z, i) (z->_watermark[i] + z->watermark_boost) +/* Fields and list protected by pagesets local_lock in page_alloc.c */ struct per_cpu_pages {int count;/* number of pages in the list */ int high;/* high watermark, emptying needed */
mm/page_alloc.c +52 -15
162 lines not on this page · raw message
Peter Zijlstra
maintainerRe: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 4 lines · Mel Gorman
That said, there are some curious users already.
fs/squashfs/decompressor_multi_percpu.clooks like it always uses thelocal_lockin CPU 0's per-cpu structure instead of stabilising a per-cpu pointer.
I'm not sure how you read that.
You're talking about this:
local_lock(&msblk->stream->lock);
right? Note that msblk->stream is a per-cpu pointer, so &msblk->stream->lock is that same per-cpu pointer with an offset on.
The whole think relies on:
&per_cpu_ptr(msblk->stream, cpu)->lock == per_cpu_ptr(&msblk->stream->lock, cpu)
Which is true because the lhs:
(local_lock_t *)((msblk->stream + per_cpu_offset(cpu)) + offsetof(struct squashfs_stream, lock))
and the rhs:
(local_lock_t *)((msblk->stream + offsetof(struct squashfs_stream, lock)) + per_cpu_offset(cpu))
are identical, because addition is associative.
Quoted 4 lines
drivers/block/zram/zcomp.cappears to do the same although for at least one of thezcomp_stream_get()callers, the CPU is pinned for other reasons (bit spin lock held). I think it happens to work anyway but it's weird and I'm not a fan.
Same thing.
Re: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 21 lines · Peter Zijlstra
Quoted 4 lines · Mel Gorman
That said, there are some curious users already.
fs/squashfs/decompressor_multi_percpu.clooks like it always uses thelocal_lockin CPU 0's per-cpu structure instead of stabilising a per-cpu pointer.I'm not sure how you read that.
You're talking about this:
local_lock(&msblk->stream->lock);right? Note that
msblk->streamis a per-cpu pointer, so&msblk->stream->lockis that same per-cpu pointer with an offset on.The whole think relies on:
&per_cpu_ptr(msblk->stream, cpu)->lock == per_cpu_ptr(&msblk->stream->lock, cpu)Which is true because the lhs:
Quoted 8 lines
(local_lock_t *)((msblk->stream + per_cpu_offset(cpu)) + offsetof(struct squashfs_stream, lock))and the rhs:
(local_lock_t *)((msblk->stream + offsetof(struct squashfs_stream, lock)) + per_cpu_offset(cpu))are identical, because addition is associative.
Ok, I think I see and understand now, I didn't follow far enough down into the macro magic and missed this observation so thanks for your patience. The page allocator still incurs a double lookup of the per cpu offsets but it should work for both the current local_lock_irq implementation and the one in preempt-rt because the task will be pinned to the CPU by either preempt_disable, migrate_disable or IRQ disable depending on the local_lock implementation and kernel configuration.
I'll update the changelog and comment accordingly. I'll decide later whether to leave it or move the location of the lock at the end of the series. If the patch is added, it'll either incur the double lookup (not that expensive, might be optimised by the compiler) or come up with a helper that takes the lock and returns the per-cpu structure. The double lookup probably makes more sense initially because there are multiple potential users of a helper that says "pin to CPU, lookup, lock and return a per-cpu structure" for both IRQ-safe and IRQ-unsafe variants with the associated expansion of the local_lock API. It might be better to introduce such a helper with multiple users converted at the same time and there are other local_lock users in preempt-rt that could do with upstreaming first.
Quoted 6 lines
Quoted 4 lines
drivers/block/zram/zcomp.cappears to do the same although for at least one of thezcomp_stream_get()callers, the CPU is pinned for other reasons (bit spin lock held). I think it happens to work anyway but it's weird and I'm not a fan.Same thing.
Yep.
Signature · 2 lines
Mel Gorman SUSE Labs
Re: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 4 lines · Mel Gorman
On Fri, Apr 09, 2021 at 08:55:39PM +0200, Peter Zijlstra wrote:
I'll update the changelog and comment accordingly. I'll decide later whether to leave it or move the location of the lock at the end of the series. If the patch is added, it'll either incur the double lookup (not
Quoted 8 lines
that expensive, might be optimised by the compiler) or come up with a helper that takes the lock and returns the per-cpu structure. The double lookup probably makes more sense initially because there are multiple potential users of a helper that says "pin to CPU, lookup, lock and return a per-cpu structure" for both IRQ-safe and IRQ-unsafe variants with the associated expansion of the
local_lockAPI. It might be better to introduce such a helper with multiple users converted at the same time and there are otherlocal_lockusers in preempt-rt that could do with upstreaming first.
We had such helpers in RT a while ago but it turned into an helper explosion pretty fast. But that was one of the early versions of local locks which could not be embedded into a per CPU data structure due to raisins (my stupidity).
But with the more thought out approach of today we can have (+/- the obligatory naming bikeshedding):
include/linux/local_lock.h +31 -0
@@ -51,4 +51,35 @@#define local_unlock_irqrestore (lock, flags) \ __local_unlock_irqrestore(lock, flags) +/** +* local_lock_get_cpu_ptr - Acquire a per CPU local lock and return +* a pointer to the per CPU data which +* contains the local lock. +* @pcp: Per CPU data structure +* @lock: The local lock member of @pcp +*/ +#define local_lock_get_cpu_ptr (pcp, lock) \ + __local_lock_get_cpu_ptr(pcp, typeof(*(pcp)), lock) + +/** +* local_lock_irq_get_cpu_ptr - Acquire a per CPU local lock, disable +* interrupts and return a pointer to the +* per CPU data which contains the local lock. +* @pcp: Per CPU data structure +* @lock: The local lock member of @pcp +*/ +#define local_lock_irq_get_cpu_ptr (pcp, lock) \ + __local_lock_irq_get_cpu_ptr(pcp, typeof(*(pcp)), lock) + +/** +* local_lock_irqsave_get_cpu_ptr - Acquire a per CPU local lock, save and +* disable interrupts and return a pointer to +* the CPU data which contains the local lock. +* @pcp: Per CPU data structure +* @lock: The local lock member of @pcp +* @flags: Storage for interrupt flags +*/ +#define local_lock_irqsave_get_cpu_ptr (pcp, lock, flags) \ + __local_lock_irqsave_get_cpu_ptr(pcp, typeof(*(pcp)), lock, flags) +#endif
include/linux/local_lock_internal.h +30 -0
@@ -91,3 +91,33 @@ static inline void local_lock_release(lolocal_lock_release (this_cpu_ptr (lock)); \local_irq_restore (flags); \ } while (0) + +#define __local_lock_get_cpu_ptr (pcp, type, lock) \ + ({ \ + type *__pcp; \ + \ + preempt_disable(); \ + __pcp = this_cpu_ptr(pcp); \ + local_lock_acquire(&__pcp->lock); \ + __pcp; \ + }) + +#define __local_lock_irq_get_cpu_ptr (pcp, type, lock) \ + ({ \ + type *__pcp; \ + \ + local_irq_disable(); \ + __pcp = this_cpu_ptr(pcp); \ + local_lock_acquire(&__pcp->lock); \ + __pcp; \ + }) + +#define __local_lock_irqsave_get_cpu_ptr (pcp, type, lock, flags)\ + ({ \ + type *__pcp; \ + \ + local_irq_save(flags); \ + __pcp = this_cpu_ptr(pcp); \ + local_lock_acquire(&__pcp->lock); \ + __pcp; \ + })
and RT will then change that to:
include/linux/local_lock_internal.h +3 -3
@@ -96,7 +96,7 @@ static inline void local_lock_release(lo ({ \ type *__pcp; \ \ - preempt_disable(); \ + ll_preempt_disable(); \ __pcp = this_cpu_ptr(pcp); \ local_lock_acquire(&__pcp->lock); \ __pcp; \
@@ -106,7 +106,7 @@ static inline void local_lock_release(lo ({ \ type *__pcp; \ \ - local_irq_disable(); \ + ll_local_irq_disable(); \ __pcp = this_cpu_ptr(pcp); \ local_lock_acquire(&__pcp->lock); \ __pcp; \
@@ -116,7 +116,7 @@ static inline void local_lock_release(lo ({ \ type *__pcp; \ \ - local_irq_save(flags); \ + ll_local_irq_save(flags); \ __pcp = this_cpu_ptr(pcp); \ local_lock_acquire(&__pcp->lock); \ __pcp; \
where ll_xxx is defined as xxx for non-RT and on RT all of them get mapped to migrate_disable().
Thoughts?
Thanks,
tglx
Re: [PATCH 02/11] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 19 lines · Thomas Gleixner
Quoted 4 lines · Mel Gorman
On Fri, Apr 09, 2021 at 08:55:39PM +0200, Peter Zijlstra wrote:
I'll update the changelog and comment accordingly. I'll decide later whether to leave it or move the location of the lock at the end of the series. If the patch is added, it'll either incur the double lookup (notQuoted 8 lines
that expensive, might be optimised by the compiler) or come up with a helper that takes the lock and returns the per-cpu structure. The double lookup probably makes more sense initially because there are multiple potential users of a helper that says "pin to CPU, lookup, lock and return a per-cpu structure" for both IRQ-safe and IRQ-unsafe variants with the associated expansion of the
local_lockAPI. It might be better to introduce such a helper with multiple users converted at the same time and there are otherlocal_lockusers in preempt-rt that could do with upstreaming first.We had such helpers in RT a while ago but it turned into an helper explosion pretty fast. But that was one of the early versions of local locks which could not be embedded into a per CPU data structure due to raisins (my stupidity).
Quoted 4 lines
But with the more thought out approach of today we can have (+/- the obligatory naming bikeshedding):
<SNIP>
I don't have strong opinions on the name -- it's long but it's clear. The overhead of local_lock_get_cpu_ptr has similar weight to get_cpu_ptr in terms of the cost of preempt_disable. The helper also means that new users of a local_lock embedded within a per-cpu structure do not have to figure out if it's safe from scratch.
If the page allocator embeds local_lock within struct per_cpu_pages then the conversion to the helper is at the end of the mail. The messiest part is free_unref_page_commit and that is a mess because free_unref_page_list has to check if a new lock is required in case a list of pages is from different zones.
Quoted 35 lines
<SNIP>
and RT will then change that to:
include/linux/local_lock_internal.h +3
-3@@ -96,7 +96,7 @@ static inline void local_lock_release(lo ({ \ type *__pcp; \ \ - preempt_disable(); \ + ll_preempt_disable(); \ __pcp = this_cpu_ptr(pcp); \ local_lock_acquire(&__pcp->lock); \ __pcp; \@@ -106,7 +106,7 @@ static inline void local_lock_release(lo ({ \ type *__pcp; \ \ - local_irq_disable(); \ + ll_local_irq_disable(); \ __pcp = this_cpu_ptr(pcp); \ local_lock_acquire(&__pcp->lock); \ __pcp; \@@ -116,7 +116,7 @@ static inline void local_lock_release(lo ({ \ type *__pcp; \ \ - local_irq_save(flags); \ + ll_local_irq_save(flags); \ __pcp = this_cpu_ptr(pcp); \ local_lock_acquire(&__pcp->lock); \ __pcp; \
Quoted 5 lines
where
ll_xxxis defined as xxx for non-RT and on RT all of them get mapped tomigrate_disable().Thoughts?
I think that works. I created the obvious definitions of ll_* and rebased on top of preempt-rt to see. I'll see if it boots :P
Page allocator conversion to helper looks like
mm/page_alloc.c +11 -14
@@ -3008,9 +3008,7 @@ static void drain_pages_zone(unsigned int cpu, struct zone *zone)unsigned long flags;struct per_cpu_pages *pcp; -local_lock_irqsave (&zone->per_cpu_pageset->lock, flags); - - pcp =per_cpu_ptr (zone->per_cpu_pageset, cpu); + pcp =local_lock_irqsave_get_cpu_ptr (zone->per_cpu_pageset, lock, flags);if (pcp->count)free_pcppages_bulk (zone, pcp->count, pcp);
@@ -3235,12 +3233,10 @@ static bool free_unref_page_prepare(struct page *page, unsigned long pfn) }static void free_unref_page_commit (struct page *page,struct zone *zone, -unsigned long pfn,int migratetype) +struct per_cpu_pages *pcp,unsigned long pfn, +int migratetype) { -struct per_cpu_pages *pcp; -__count_vm_event (PGFREE ); - pcp =this_cpu_ptr (zone->per_cpu_pageset);list_add (&page->lru, &pcp->lists[migratetype]); pcp->count++;if (pcp->count >=READ_ONCE (pcp->high))
@@ -3252,6 +3248,7 @@ static void free_unref_page_commit(struct page *page, struct zone *zone,*/ void free_unref_page (struct page *page) { +struct per_cpu_pages *pcp;struct zone *zone;unsigned long flags;unsigned long pfn =page_to_pfn (page);
@@ -3277,8 +3274,8 @@ void free_unref_page(struct page *page) } zone =page_zone (page); -local_lock_irqsave (&zone->per_cpu_pageset->lock, flags); -free_unref_page_commit (page, zone, pfn, migratetype); + pcp =local_lock_irqsave_get_cpu_ptr (zone->per_cpu_pageset, lock, flags); +free_unref_page_commit (page, zone, pcp, pfn, migratetype);local_unlock_irqrestore (&zone->per_cpu_pageset->lock, flags); }
@@ -3287,6 +3284,7 @@ void free_unref_page(struct page *page)*/ void free_unref_page_list (struct list_head *list) { +struct per_cpu_pages *pcp;struct zone *locked_zone;struct page *page, *next;unsigned long flags, pfn;
@@ -3320,7 +3318,7 @@ void free_unref_page_list(struct list_head *list)/* Acquire the lock required for the first page. */ page = list_first_entry(list, struct page, lru); locked_zone =page_zone (page); -local_lock_irqsave (&locked_zone->per_cpu_pageset->lock, flags); + pcp =local_lock_irqsave_get_cpu_ptr (locked_zone->per_cpu_pageset, lock, flags);list_for_each_entry_safe (page, next, list, lru) {struct zone *zone =page_zone (page);
@@ -3342,12 +3340,12 @@ void free_unref_page_list(struct list_head *list)#if defined(CONFIG_PREEMPT_RT ) || defined(CONFIG_DEBUG_LOCK_ALLOC )if (locked_zone != zone) {local_unlock_irqrestore (&locked_zone->per_cpu_pageset->lock, flags); -local_lock_irqsave (&zone->per_cpu_pageset->lock, flags); + pcp =local_lock_irqsave_get_cpu_ptr (zone->per_cpu_pageset, lock, flags); locked_zone = zone; }#endif -free_unref_page_commit (page, zone, pfn, migratetype); +free_unref_page_commit (page, zone, pcp, pfn, migratetype);/* * Guard against excessive IRQ disabled times when we get
@@ -3517,8 +3515,7 @@ static struct page *rmqueue_pcplist(struct zone *preferred_zone,struct page *page;unsigned long flags; -local_lock_irqsave (&zone->per_cpu_pageset->lock, flags); - pcp =this_cpu_ptr (zone->per_cpu_pageset); + pcp =local_lock_irqsave_get_cpu_ptr (zone->per_cpu_pageset, lock, flags); list = &pcp->lists[migratetype]; page =__rmqueue_pcplist (zone, migratetype, alloc_flags, pcp, list);local_unlock_irqrestore (&zone->per_cpu_pageset->lock, flags);
Signature · 2 lines
Mel Gorman SUSE Labs
PATCH v2 3/11 mm/memory_hotplug: Make unpopulated zones PCP structures unreachable during hot remove
In reply to Mel Gormanzone_pcp_reset allegedly protects against a race with drain_pages using local_irq_save but this is bogus. local_irq_save only operates on the local CPU. If memory hotplug is running on CPU A and drain_pages is running on CPU B, disabling IRQs on CPU A does not affect CPU B and offers no protection.
This patch reorders memory hotremove such that the PCP structures relevant to the zone are no longer reachable by the time the structures are freed. With this reordering, no protection is required to prevent a use-after-free and the IRQs can be left enabled. zone_pcp_reset is renamed to zone_pcp_destroy to make it clear that the per-cpu structures are deleted when the function returns.
--- mm/internal.h | 2 +- mm/memory_hotplug.c | 10 +++++++--- mm/page_alloc.c | 22 ++++++++++++++++------ 3 files changed, 24 insertions(+), 10 deletions(-)
mm/internal.h +1 -1
@@ -203,7 +203,7 @@ extern void free_unref_page(struct page *page);extern void free_unref_page_list (struct list_head *list);extern void zone_pcp_update (struct zone *zone); -extern void zone_pcp_reset (struct zone *zone); +extern void zone_pcp_destroy (struct zone *zone);extern void zone_pcp_disable (struct zone *zone);extern void zone_pcp_enable (struct zone *zone);
mm/memory_hotplug.c +7 -3
@@ -1687,12 +1687,16 @@ int __ref offline_pages(unsigned long start_pfn, unsigned long nr_pages) zone->nr_isolate_pageblock -= nr_pages / pageblock_nr_pages;spin_unlock_irqrestore (&zone->lock, flags); -zone_pcp_enable (zone); -/* removal success */ adjust_managed_page_count (pfn_to_page (start_pfn), -nr_pages); zone->present_pages -= nr_pages; +/* +* Restore PCP after managed pages has been updated. Unpopulated +* zones PCP structures will remain unusable. +*/ +zone_pcp_enable (zone); +pgdat_resize_lock (zone->zone_pgdat, &flags); zone->zone_pgdat->node_present_pages -= nr_pages;pgdat_resize_unlock (zone->zone_pgdat, &flags);
@@ -1700,8 +1704,8 @@ int __ref offline_pages(unsigned long start_pfn, unsigned long nr_pages)init_per_zone_wmark_min ();if (!populated_zone (zone)) { -zone_pcp_reset (zone);build_all_zonelists (NULL ); +zone_pcp_destroy (zone); }else zone_pcp_update (zone);
mm/page_alloc.c +16 -6
@@ -8972,18 +8972,29 @@ void zone_pcp_disable(struct zone *zone)void zone_pcp_enable (struct zone *zone) { -__zone_set_pageset_high_and_batch (zone, zone->pageset_high, zone->pageset_batch); +/* +* If the zone is populated, restore the high and batch counts. +* If unpopulated, leave the high and batch count as 0 and 1 +* respectively as done by zone_pcp_disable. The per-cpu +* structures will later be freed by zone_pcp_destroy. +*/ +if (populated_zone (zone)) +__zone_set_pageset_high_and_batch (zone, zone->pageset_high, zone->pageset_batch); +mutex_unlock (&pcp_batch_high_lock); } -void zone_pcp_reset (struct zone *zone) +/* +* Called when a zone has been hot-removed. At this point, the PCP has been +* drained, disabled and the zone is removed from the zonelists so the +* structures are no longer in use. PCP was disabled/drained by +* zone_pcp_disable. This function will drain any remaining vmstat deltas. +*/ +void zone_pcp_destroy (struct zone *zone) { -unsigned long flags;int cpu;struct per_cpu_zonestat *pzstats; -/* avoid races with drain_pages() */ -local_irq_save (flags);if (zone->per_cpu_pageset != &boot_pageset) {for_each_online_cpu (cpu) { pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu);
@@ -8994,7 +9005,6 @@ void zone_pcp_reset(struct zone *zone) zone->per_cpu_pageset = &boot_pageset; zone->per_cpu_zonestats = &boot_zonestats; } -local_irq_restore (flags); }#ifdef CONFIG_MEMORY_HOTREMOVE
Signature · 1 line
2.26.2
PATCH v2 4/11 mm/vmstat: Convert NUMA statistics to basic NUMA counters
In reply to Mel GormanNUMA statistics are maintained on the zone level for hits, misses, foreign etc but nothing relies on them being perfectly accurate for functional correctness. The counters are used by userspace to get a general overview of a workloads NUMA behaviour but the page allocator incurs a high cost to maintain perfect accuracy similar to what is required for a vmstat like NR_FREE_PAGES. There even is a sysctl vm.numa_stat to allow userspace to turn off the collection of NUMA statistics like NUMA_HIT.
This patch converts NUMA_HIT and friends to be NUMA events with similar accuracy to VM events. There is a possibility that slight errors will be introduced but the overall trend as seen by userspace will be similar. Note that while these counters could be maintained at the node level that it would have a user-visible impact.
--- drivers/base/node.c | 18 +++-- include/linux/mmzone.h | 11 ++- include/linux/vmstat.h | 42 +++++----- mm/mempolicy.c | 2 +- mm/page_alloc.c | 12 +-- mm/vmstat.c | 175 ++++++++++++----------------------------- 6 files changed, 93 insertions(+), 167 deletions(-)
drivers/base/node.c +10 -8
@@ -484,6 +484,7 @@ static DEVICE_ATTR(meminfo, 0444, node_read_meminfo, NULL);static ssize_t node_read_numastat (struct device *dev,struct device_attribute *attr,char *buf) { +fold_vm_numa_events ();return sysfs_emit (buf,"numa_hit %lu\n" "numa_miss %lu\n"
@@ -491,12 +492,12 @@ static ssize_t node_read_numastat(struct device *dev,"interleave_hit %lu\n" "local_node %lu\n" "other_node %lu\n" , -sum_zone_numa_state (dev->id,NUMA_HIT ), -sum_zone_numa_state (dev->id,NUMA_MISS ), -sum_zone_numa_state (dev->id,NUMA_FOREIGN ), -sum_zone_numa_state (dev->id,NUMA_INTERLEAVE_HIT ), -sum_zone_numa_state (dev->id,NUMA_LOCAL ), - sum_zone_numa_state(dev->id, NUMA_OTHER)); +sum_zone_numa_event_state (dev->id,NUMA_HIT ), +sum_zone_numa_event_state (dev->id,NUMA_MISS ), +sum_zone_numa_event_state (dev->id,NUMA_FOREIGN ), +sum_zone_numa_event_state (dev->id,NUMA_INTERLEAVE_HIT ), +sum_zone_numa_event_state (dev->id,NUMA_LOCAL ), + sum_zone_numa_event_state(dev->id, NUMA_OTHER)); } static DEVICE_ATTR(numastat, 0444, node_read_numastat, NULL);
@@ -514,10 +515,11 @@ static ssize_t node_read_vmstat(struct device *dev, sum_zone_node_page_state(nid, i));#ifdef CONFIG_NUMA -for (i =0 ; i <NR_VM_NUMA_STAT_ITEMS ; i++) +fold_vm_numa_events (); +for (i =0 ; i <NR_VM_NUMA_EVENT_ITEMS ; i++) len +=sysfs_emit_at (buf, len,"%s %lu\n" ,numa_stat_name (i), -sum_zone_numa_state (nid, i)); +sum_zone_numa_event_state (nid, i));#endif for (i =0 ; i <NR_VM_NODE_STAT_ITEMS ; i++) {
include/linux/mmzone.h +7 -4
@@ -135,10 +135,10 @@ enum numa_stat_item {NUMA_INTERLEAVE_HIT ,/* interleaver preferred this zone */ NUMA_LOCAL ,/* allocation from local node */ NUMA_OTHER ,/* allocation from other node */ -NR_VM_NUMA_STAT_ITEMS +NR_VM_NUMA_EVENT_ITEMS };#else -#define NR_VM_NUMA_STAT_ITEMS 0 +#define NR_VM_NUMA_EVENT_ITEMS 0#endif enum zone_stat_item {
@@ -357,7 +357,10 @@ struct per_cpu_zonestat {s8 stat_threshold;#endif #ifdef CONFIG_NUMA -u16 vm_numa_stat_diff[NR_VM_NUMA_STAT_ITEMS ]; +u16 vm_numa_stat_diff[NR_VM_NUMA_EVENT_ITEMS ]; +#endif +#ifdef CONFIG_NUMA +unsigned long vm_numa_event[NR_VM_NUMA_EVENT_ITEMS ];#endif };
@@ -609,7 +612,7 @@ struct zone {ZONE_PADDING (_pad3_)/* Zone statistics */ atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]; -atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS ]; +atomic_long_t vm_numa_events[NR_VM_NUMA_EVENT_ITEMS ]; }____cacheline_internodealigned_in_smp ;enum pgdat_flags {
include/linux/vmstat.h +19 -23
93 lines not on this page · raw message
mm/mempolicy.c +1 -1
@@ -2146,7 +2146,7 @@ static struct page *alloc_page_interleave(gfp_t gfp, unsigned order,return page;if (page &&page_to_nid (page) == nid) {preempt_disable (); -__inc_numa_state (page_zone (page),NUMA_INTERLEAVE_HIT ); +__count_numa_event (page_zone (page),NUMA_INTERLEAVE_HIT );preempt_enable (); }return page;
mm/page_alloc.c +6 -6
@@ -3424,12 +3424,12 @@ static inline void zone_statistics(struct zone *preferred_zone, struct zone *z) local_stat =NUMA_OTHER ;if (zone_to_nid (z) ==zone_to_nid (preferred_zone)) -__inc_numa_state (z,NUMA_HIT ); +__count_numa_event (z,NUMA_HIT );else { -__inc_numa_state (z,NUMA_MISS ); -__inc_numa_state (preferred_zone,NUMA_FOREIGN ); +__count_numa_event (z,NUMA_MISS ); +__count_numa_event (preferred_zone,NUMA_FOREIGN ); } -__inc_numa_state (z, local_stat); +__count_numa_event (z, local_stat);#endif }
@@ -6700,8 +6700,8 @@ void __init setup_per_cpu_pageset(void)*/ for_each_possible_cpu (cpu) {struct per_cpu_zonestat *pzstats = &per_cpu (boot_zonestats, cpu); -memset (pzstats->vm_numa_stat_diff,0 , -sizeof (pzstats->vm_numa_stat_diff)); +memset (pzstats->vm_numa_event,0 , +sizeof (pzstats->vm_numa_event)); }#endif
mm/vmstat.c +50 -125
338 lines not on this page · raw message
Signature · 1 line
2.26.2
Re: [PATCH 04/11] mm/vmstat: Convert NUMA statistics to basic NUMA counters
Quoted 8 lines · Mel Gorman
NUMA statistics are maintained on the zone level for hits, misses, foreign etc but nothing relies on them being perfectly accurate for functional correctness. The counters are used by userspace to get a general overview of a workloads NUMA behaviour but the page allocator incurs a high cost to maintain perfect accuracy similar to what is required for a vmstat like
NR_FREE_PAGES. There even is a sysctl vm.numa_stat to allow userspace to turn off the collection of NUMA statistics likeNUMA_HIT.
Quoted 5 lines
This patch converts
NUMA_HITand friends to be NUMA events with similar accuracy to VM events. There is a possibility that slight errors will be introduced but the overall trend as seen by userspace will be similar. Note that while these counters could be maintained at the node level that it would have a user-visible impact.
I guess this kind of inaccuracy is fine. I just don't like much fold_vm_zone_numa_events() which seems to calculate sums of percpu counters and then assign the result to zone counters for immediate consumption, which differs from other kinds of folds in vmstat that reset the percpu counters to 0 as they are treated as diffs to the global counters.
So it seems that this intermediate assignment to zone counters (using atomic_long_set() even) is unnecessary and this could mimic sum_vm_events() that just does the summation on a local array?
And probably a bit more serious is that vm_events have vm_events_fold_cpu() to deal with a cpu going away, but after your patch the stats counted on a cpu just disapepar from the sums as it goes offline as there's no such thing for the numa counters.
Thanks,
Vlastimil
Re: [PATCH 04/11] mm/vmstat: Convert NUMA statistics to basic NUMA counters
Quoted 15 lines · Vlastimil Babka
Quoted 8 lines · Mel Gorman
NUMA statistics are maintained on the zone level for hits, misses, foreign etc but nothing relies on them being perfectly accurate for functional correctness. The counters are used by userspace to get a general overview of a workloads NUMA behaviour but the page allocator incurs a high cost to maintain perfect accuracy similar to what is required for a vmstat like
NR_FREE_PAGES. There even is a sysctl vm.numa_stat to allow userspace to turn off the collection of NUMA statistics likeNUMA_HIT.Quoted 5 lines
This patch converts
NUMA_HITand friends to be NUMA events with similar accuracy to VM events. There is a possibility that slight errors will be introduced but the overall trend as seen by userspace will be similar. Note that while these counters could be maintained at the node level that it would have a user-visible impact.
Quoted 6 lines
I guess this kind of inaccuracy is fine. I just don't like much
fold_vm_zone_numa_events()which seems to calculate sums of percpu counters and then assign the result to zone counters for immediate consumption, which differs from other kinds of folds in vmstat that reset the percpu counters to 0 as they are treated as diffs to the global counters.
The counters that are diffs fit inside an s8 and they are kept limited because their "true" value is sometimes critical -- e.g. NR_FREE_PAGES for watermark checking. So the level of drift has to be controlled and the drift should not exist potentially forever so it gets updated periodically.
The inaccurate counters are only exported to userspace. There is no need to update them every few seconds so fold_vm_zone_numa_events() is only called when a user cares but you raise a raise a valid below.
Quoted 4 lines
So it seems that this intermediate assignment to zone counters (using
atomic_long_set()even) is unnecessary and this could mimicsum_vm_events()that just does the summation on a local array?
The atomic is unnecessary for sure but using a local array is problematic because of your next point.
Quoted 5 lines
And probably a bit more serious is that
vm_eventshavevm_events_fold_cpu()to deal with a cpu going away, but after your patch the stats counted on a cpu just disapepar from the sums as it goes offline as there's no such thing for the numa counters.
That is a problem I missed. Even if zonestats was preserved on hot-remove, fold_vm_zone_numa_events would not be reading the CPU so hotplug events jump all over the place.
So some periodic folding is necessary. I would still prefer not to do it by time but it could be done only on overflow or when a file like /proc/vmstat is read. I'll think about it a bit more and see what I come up with.
Thanks!
Signature · 2 lines
Mel Gorman SUSE Labs
Re: [PATCH 04/11] mm/vmstat: Convert NUMA statistics to basic NUMA counters
Quoted 8 lines · Mel Gorman
Quoted 4 lines · Vlastimil Babka
So it seems that this intermediate assignment to zone counters (using
atomic_long_set()even) is unnecessary and this could mimicsum_vm_events()that just does the summation on a local array?The atomic is unnecessary for sure but using a local array is problematic because of your next point.
IIUC vm_events seems to do fine without a centralized array and handling CPU hot remove at the sime time ...
Quoted 6 lines
Quoted 5 lines
And probably a bit more serious is that
vm_eventshavevm_events_fold_cpu()to deal with a cpu going away, but after your patch the stats counted on a cpu just disapepar from the sums as it goes offline as there's no such thing for the numa counters.
Quoted 8 lines
That is a problem I missed. Even if zonestats was preserved on hot-remove,
fold_vm_zone_numa_eventswould not be reading the CPU so hotplug events jump all over the place.So some periodic folding is necessary. I would still prefer not to do it by time but it could be done only on overflow or when a file like
/proc/vmstatis read. I'll think about it a bit more and see what I come up with.
... because vm_events_fold_cpu() seems to simply move the stats from the CPU being offlined to the current one. So the same approach should be enough for NUMA stats?
Quoted 2 lines
Thanks!
Re: [PATCH 04/11] mm/vmstat: Convert NUMA statistics to basic NUMA counters
Quoted 7 lines · Vlastimil Babka
Quoted 6 lines · Mel Gorman
Quoted 4 lines · Vlastimil Babka
So it seems that this intermediate assignment to zone counters (using
atomic_long_set()even) is unnecessary and this could mimicsum_vm_events()that just does the summation on a local array?
Quoted 6 lines
Quoted 2 lines
The atomic is unnecessary for sure but using a local array is problematic because of your next point.
IIUC
vm_eventsseems to do fine without a centralized array and handling CPU hot remove at the sime time ...
The vm_events are more global in nature. They are not reported to userspace on a per-zone (/proc/zoneinfo) basis or per-node (/sys/devices/system/node/node*/numastat) basis so they are not equivalent.
Quoted 10 lines
Quoted 10 lines
Quoted 5 lines
And probably a bit more serious is that
vm_eventshavevm_events_fold_cpu()to deal with a cpu going away, but after your patch the stats counted on a cpu just disapepar from the sums as it goes offline as there's no such thing for the numa counters.That is a problem I missed. Even if zonestats was preserved on hot-remove,
fold_vm_zone_numa_eventswould not be reading the CPU so hotplug events jump all over the place.
Quoted 9 lines
Quoted 4 lines
So some periodic folding is necessary. I would still prefer not to do it by time but it could be done only on overflow or when a file like
/proc/vmstatis read. I'll think about it a bit more and see what I come up with.... because
vm_events_fold_cpu()seems to simply move the stats from the CPU being offlined to the current one. So the same approach should be enough for NUMA stats?
Yes, or at least very similar.
Signature · 2 lines
Mel Gorman SUSE Labs
PATCH v2 5/11 mm/vmstat: Inline NUMA event counter updates
In reply to Mel Gorman__count_numa_event is small enough to be treated similarly to __count_vm_event so inline it.
--- include/linux/vmstat.h | 9 +++++++++ mm/vmstat.c | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-)
include/linux/vmstat.h +9 -0
@@ -237,6 +237,15 @@ static inline unsigned long zone_page_state_snapshot(struct zone *zone, }#ifdef CONFIG_NUMA +/* See __count_vm_event comment on why raw_cpu_inc is used. */ +static inline void +__count_numa_event (struct zone *zone,enum numa_stat_item item) +{ +struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats; + +raw_cpu_inc (pzstats->vm_numa_event[item]); +} +extern void __count_numa_event (struct zone *zone,enum numa_stat_item item);extern unsigned long sum_zone_node_page_state (int node,enum zone_stat_item item);
mm/vmstat.c +0 -9
@@ -902,15 +902,6 @@ void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats)#endif #ifdef CONFIG_NUMA -/* See __count_vm_event comment on why raw_cpu_inc is used. */ -void __count_numa_event (struct zone *zone, -enum numa_stat_item item) -{ -struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats; - -raw_cpu_inc (pzstats->vm_numa_event[item]); -} -/* * Determine the per node value of a stat item. This function * is called frequently in a NUMA machine, so try to be as
Signature · 1 line
2.26.2
PATCH v2 6/11 mm/page_alloc: Batch the accounting updates in the bulk allocator
In reply to Mel GormanNow that the zone_statistics are simple counters that do not require special protection, the bulk allocator accounting updates can be batch updated without adding too much complexity with protected RMW updates or using xchg.
--- include/linux/vmstat.h | 8 ++++++++ mm/page_alloc.c | 30 +++++++++++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-)
include/linux/vmstat.h +8 -0
@@ -246,6 +246,14 @@ __count_numa_event(struct zone *zone, enum numa_stat_item item)raw_cpu_inc (pzstats->vm_numa_event[item]); } +static inline void +__count_numa_events (struct zone *zone,enum numa_stat_item item,long delta) +{ +struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats; + +raw_cpu_add (pzstats->vm_numa_event[item], delta); +} +extern void __count_numa_event (struct zone *zone,enum numa_stat_item item);extern unsigned long sum_zone_node_page_state (int node,enum zone_stat_item item);
mm/page_alloc.c +13 -17
@@ -3411,7 +3411,8 @@ void __putback_isolated_page(struct page *page, unsigned int order, int mt)* * Must be called with interrupts disabled. */ -static inline void zone_statistics (struct zone *preferred_zone,struct zone *z) +static inline void zone_statistics (struct zone *preferred_zone,struct zone *z, +long nr_account) {#ifdef CONFIG_NUMA enum numa_stat_item local_stat =NUMA_LOCAL ;
@@ -3424,12 +3425,12 @@ static inline void zone_statistics(struct zone *preferred_zone, struct zone *z) local_stat =NUMA_OTHER ;if (zone_to_nid (z) ==zone_to_nid (preferred_zone)) -__count_numa_event (z,NUMA_HIT ); +__count_numa_events (z,NUMA_HIT , nr_account);else { -__count_numa_event (z,NUMA_MISS ); -__count_numa_event (preferred_zone,NUMA_FOREIGN ); +__count_numa_events (z,NUMA_MISS , nr_account); +__count_numa_events (preferred_zone,NUMA_FOREIGN , nr_account); } -__count_numa_event (z, local_stat); +__count_numa_events (z, local_stat, nr_account);#endif }
@@ -3475,7 +3476,7 @@ static struct page *rmqueue_pcplist(struct zone *preferred_zone, page =__rmqueue_pcplist (zone, migratetype, alloc_flags, pcp, list);if (page) {__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 ); -zone_statistics (preferred_zone, zone); +zone_statistics (preferred_zone, zone,1 ); }local_unlock_irqrestore (&pagesets.lock, flags);return page;
@@ -3536,7 +3537,7 @@ struct page *rmqueue(struct zone *preferred_zone, get_pcppage_migratetype(page));__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 << order); -zone_statistics (preferred_zone, zone); +zone_statistics (preferred_zone, zone,1 );local_irq_restore (flags); out:
@@ -5019,7 +5020,7 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid,struct alloc_context ac;gfp_t alloc_gfp;unsigned int alloc_flags =ALLOC_WMARK_LOW ; -int nr_populated =0 ; +int nr_populated =0 , nr_account =0 ;if (unlikely (nr_pages <=0 ))return 0 ;
@@ -5092,15 +5093,7 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid, goto failed_irq;break ; } - -/* -* Ideally this would be batched but the best way to do -* that cheaply is to first convert zone_statistics to -* be inaccurate per-cpu counter like vm_events to avoid -* a RMW cycle then do the accounting with IRQs enabled. -*/ -__count_zid_vm_events (PGALLOC ,zone_idx (zone),1 ); -zone_statistics (ac.preferred_zoneref->zone, zone); + nr_account++;prep_new_page (page,0 , gfp,0 );if (page_list)
@@ -5110,6 +5103,9 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid, nr_populated++; } +__count_zid_vm_events (PGALLOC ,zone_idx (zone), nr_account); +zone_statistics (ac.preferred_zoneref->zone, zone, nr_account); +local_unlock_irqrestore (&pagesets.lock, flags);return nr_populated;
Signature · 1 line
2.26.2
PATCH v2 7/11 mm/page_alloc: Reduce duration that IRQs are disabled for VM counters
In reply to Mel GormanIRQs are left disabled for the zone and node VM event counters. This is unnecessary as the affected counters are allowed to race for preemmption and IRQs.
This patch reduces the scope of IRQs being disabled
via local_[lock|unlock]_irq on !PREEMPT_RT kernels. One __mod_zone_freepage_state is still called with IRQs disabled. While this could be moved out, it's not free on all architectures as some require IRQs to be disabled for mod_zone_page_state on !PREEMPT_RTkernels.
--- mm/page_alloc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
mm/page_alloc.c +6 -6
@@ -3474,11 +3474,11 @@ static struct page *rmqueue_pcplist(struct zone *preferred_zone, pcp =this_cpu_ptr (zone->per_cpu_pageset); list = &pcp->lists[migratetype]; page =__rmqueue_pcplist (zone, migratetype, alloc_flags, pcp, list); +local_unlock_irqrestore (&pagesets.lock, flags);if (page) {__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 );zone_statistics (preferred_zone, zone,1 ); } -local_unlock_irqrestore (&pagesets.lock, flags);return page; }
@@ -3530,15 +3530,15 @@ struct page *rmqueue(struct zone *preferred_zone,if (!page) page =__rmqueue (zone, order, migratetype, alloc_flags); }while (page &&check_new_pages (page, order)); -spin_unlock (&zone->lock);if (!page) goto failed; +__mod_zone_freepage_state (zone, -(1 << order),get_pcppage_migratetype (page)); +spin_unlock_irqrestore (&zone->lock, flags);__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 << order);zone_statistics (preferred_zone, zone,1 ); -local_irq_restore (flags); out:/* Separate test+clear to avoid unnecessary atomics */
@@ -3551,7 +3551,7 @@ struct page *rmqueue(struct zone *preferred_zone,return page; failed: -local_irq_restore (flags); +spin_unlock_irqrestore (&zone->lock, flags);return NULL ; }
@@ -5103,11 +5103,11 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid, nr_populated++; } +local_unlock_irqrestore (&pagesets.lock, flags); +__count_zid_vm_events (PGALLOC ,zone_idx (zone), nr_account);zone_statistics (ac.preferred_zoneref->zone, zone, nr_account); -local_unlock_irqrestore (&pagesets.lock, flags); -return nr_populated; failed_irq:
Signature · 1 line
2.26.2
PATCH v2 8/11 mm/page_alloc: Remove duplicate checks if migratetype should be isolated
In reply to Mel GormanBoth free_pcppages_bulk() and free_one_page() have very similar checks about whether a pages migratetype has changed under the zone lock. Use a common helper.
--- mm/page_alloc.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-)
mm/page_alloc.c +22 -10
@@ -1354,6 +1354,23 @@ static inline void prefetch_buddy(struct page *page)prefetch (buddy); } +/* +* The migratetype of a page may have changed due to isolation so check. +* Assumes the caller holds the zone->lock to serialise against page +* isolation. +*/ +static inline int +check_migratetype_isolated (struct zone *zone,struct page *page,unsigned long pfn,int migratetype) +{ +/* If isolating, check if the migratetype has changed */ +if (unlikely (has_isolate_pageblock (zone) || +is_migrate_isolate (migratetype))) { + migratetype =get_pfnblock_migratetype (page, pfn); + } + +return migratetype; +} +/* * Frees a number of pages from the PCP lists * Assumes all pages on list are in same zone, and of same order.
@@ -1371,7 +1388,6 @@ static void free_pcppages_bulk(struct zone *zone, int count,int migratetype =0 ;int batch_free =0 ;int prefetch_nr =READ_ONCE (pcp->batch); -bool isolated_pageblocks;struct page *page, *tmp;LIST_HEAD (head);
@@ -1433,21 +1449,20 @@ static void free_pcppages_bulk(struct zone *zone, int count,* both PREEMPT_RT and non-PREEMPT_RT configurations. */ spin_lock (&zone->lock); - isolated_pageblocks =has_isolate_pageblock (zone);/* * Use safe version since after __free_one_page(), * page->lru.next will not point to original list. */ list_for_each_entry_safe (page, tmp, &head, lru) { +unsigned long pfn =page_to_pfn (page);int mt =get_pcppage_migratetype (page); +/* MIGRATE_ISOLATE page should not go to pcplists */ VM_BUG_ON_PAGE (is_migrate_isolate (mt), page); -/* Pageblock could have been isolated meanwhile */ -if (unlikely (isolated_pageblocks)) - mt =get_pageblock_migratetype (page); -__free_one_page (page,page_to_pfn (page), zone,0 , mt,FPI_NONE ); + mt =check_migratetype_isolated (zone, page, pfn, mt); +__free_one_page (page, pfn, zone,0 , mt,FPI_NONE );trace_mm_page_pcpu_drain (page,0 , mt); }spin_unlock (&zone->lock);
@@ -1459,10 +1474,7 @@ static void free_one_page(struct zone *zone, int migratetype, fpi_t fpi_flags) {spin_lock (&zone->lock); -if (unlikely (has_isolate_pageblock (zone) || -is_migrate_isolate (migratetype))) { - migratetype =get_pfnblock_migratetype (page, pfn); - } + migratetype =check_migratetype_isolated (zone, page, pfn, migratetype);__free_one_page (page, pfn, zone, order, migratetype, fpi_flags);spin_unlock (&zone->lock); }
Signature · 1 line
2.26.2
PATCH v2 9/11 mm/page_alloc: Explicitly acquire the zone lock in __free_pages_ok
In reply to Mel Gorman__free_pages_ok() disables IRQs before calling a common helper free_one_page() that acquires the zone lock. While this is safe, it unnecessarily disables IRQs on PREEMPT_RT kernels.
This patch explicitly acquires the lock with spin_lock_irqsave instead of relying on a helper. This removes the last instance of local_irq_save() in page_alloc.c.
--- mm/page_alloc.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-)
mm/page_alloc.c +5 -8
@@ -1559,21 +1559,18 @@ static void __free_pages_ok(struct page *page, unsigned int order,unsigned long flags;int migratetype;unsigned long pfn =page_to_pfn (page); +struct zone *zone =page_zone (page);if (!free_pages_prepare (page, order, true))return ; migratetype =get_pfnblock_migratetype (page, pfn); -/* -* TODO FIX: Disable IRQs before acquiring IRQ-safe zone->lock -* and protect vmstat updates. -*/ -local_irq_save (flags); +spin_lock_irqsave (&zone->lock, flags);__count_vm_events (PGFREE ,1 << order); -free_one_page (page_zone (page), page, pfn, order, migratetype, - fpi_flags); -local_irq_restore (flags); + migratetype =check_migratetype_isolated (zone, page, pfn, migratetype); +__free_one_page (page, pfn, zone, order, migratetype, fpi_flags); +spin_unlock_irqrestore (&zone->lock, flags); }void __free_pages_core (struct page *page,unsigned int order)
Signature · 1 line
2.26.2
PATCH v2 10/11 mm/page_alloc: Avoid conflating IRQs disabled with zone->lock
In reply to Mel GormanHistorically when freeing pages, free_one_page() assumed that callers had IRQs disabled and the zone->lock could be acquired with spin_lock(). This confuses the scope of what local_lock_irq is protecting and what zone->lock is protecting in free_unref_page_list in particular.
This patch uses spin_lock_irqsave() for the zone->lock in free_one_page() instead of relying on callers to have disabled IRQs. free_unref_page_commit() is changed to only deal with PCP pages protected by the local lock. free_unref_page_list() then first frees isolated pages to the buddy lists with free_one_page() and frees the rest of the pages to the PCP via free_unref_page_commit(). The end result is that free_one_page() is no longer depending on side-effects of local_lock to be correct.
Note that this may incur a performance penalty while memory hot-remove is running but that is not a common operation.
--- mm/page_alloc.c | 67 ++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 26 deletions(-)
mm/page_alloc.c +41 -26
@@ -1473,10 +1473,12 @@ static void free_one_page(struct zone *zone,unsigned int order, int migratetype, fpi_t fpi_flags) { - spin_lock(&zone->lock); + unsigned long flags; + +spin_lock_irqsave (&zone->lock, flags); migratetype =check_migratetype_isolated (zone, page, pfn, migratetype);__free_one_page (page, pfn, zone, order, migratetype, fpi_flags); -spin_unlock (&zone->lock); +spin_unlock_irqrestore (&zone->lock, flags); } static void __meminit __init_single_page(struct page *page, unsigned long pfn,
@@ -3238,31 +3240,13 @@ static bool free_unref_page_prepare(struct page *page, unsigned long pfn)return true; } -static void free_unref_page_commit (struct page *page,unsigned long pfn) +static void free_unref_page_commit (struct page *page,unsigned long pfn, +int migratetype) {struct zone *zone =page_zone (page);struct per_cpu_pages *pcp; -int migratetype; - migratetype =get_pcppage_migratetype (page);__count_vm_event (PGFREE ); - -/* -* We only track unmovable, reclaimable and movable on pcp lists. -* Free ISOLATE pages back to the allocator because they are being -* offlined but treat HIGHATOMIC as movable pages so we can get those -* areas back if necessary. Otherwise, we may have to free -* excessively into the page allocator -*/ -if (migratetype >=MIGRATE_PCPTYPES ) { -if (unlikely (is_migrate_isolate (migratetype))) { -free_one_page (zone, page, pfn,0 , migratetype, -FPI_NONE ); -return ; - } - migratetype =MIGRATE_MOVABLE ; - } - pcp =this_cpu_ptr (zone->per_cpu_pageset);list_add (&page->lru, &pcp->lists[migratetype]); pcp->count++;
@@ -3277,12 +3261,29 @@ void free_unref_page(struct page *page) {unsigned long flags;unsigned long pfn =page_to_pfn (page); +int migratetype;if (!free_unref_page_prepare (page, pfn))return ; +/* +* We only track unmovable, reclaimable and movable on pcp lists. +* Place ISOLATE pages on the isolated list because they are being +* offlined but treat HIGHATOMIC as movable pages so we can get those +* areas back if necessary. Otherwise, we may have to free +* excessively into the page allocator +*/ + migratetype =get_pcppage_migratetype (page); +if (unlikely (migratetype >=MIGRATE_PCPTYPES )) { +if (unlikely (is_migrate_isolate (migratetype))) { +free_one_page (page_zone (page), page, pfn,0 , migratetype,FPI_NONE ); +return ; + } + migratetype =MIGRATE_MOVABLE ; + } +local_lock_irqsave (&pagesets.lock, flags); -free_unref_page_commit (page, pfn); +free_unref_page_commit (page, pfn, migratetype);local_unlock_irqrestore (&pagesets.lock, flags); }
@@ -3294,6 +3295,7 @@ void free_unref_page_list(struct list_head *list)struct page *page, *next;unsigned long flags, pfn;int batch_count =0 ; +int migratetype;/* Prepare pages for freeing */ list_for_each_entry_safe (page, next, list, lru) {
@@ -3301,15 +3303,28 @@ void free_unref_page_list(struct list_head *list)if (!free_unref_page_prepare (page, pfn))list_del (&page->lru);set_page_private (page, pfn); + +/* +* Free isolated pages directly to the allocator, see +* comment in free_unref_page. +*/ + migratetype =get_pcppage_migratetype (page); +if (unlikely (migratetype >=MIGRATE_PCPTYPES )) { +if (unlikely (is_migrate_isolate (migratetype))) { +free_one_page (page_zone (page), page, pfn,0 , + migratetype,FPI_NONE ); +list_del (&page->lru); + } + } }local_lock_irqsave (&pagesets.lock, flags);list_for_each_entry_safe (page, next, list, lru) { -unsigned long pfn =page_private (page); - + pfn =page_private (page);set_page_private (page,0 ); + migratetype =get_pcppage_migratetype (page);trace_mm_page_free_batched (page); -free_unref_page_commit (page, pfn); +free_unref_page_commit (page, pfn, migratetype);/* * Guard against excessive IRQ disabled times when we get
Signature · 1 line
2.26.2
PATCH v2 11/11 mm/page_alloc: Update PGFREE outside the zone lock in __free_pages_ok
In reply to Mel GormanVM events do not need explicit protection by disabling IRQs so update the counter with IRQs enabled in __free_pages_ok.
--- mm/page_alloc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
mm/page_alloc.c +2 -1
@@ -1569,10 +1569,11 @@ static void __free_pages_ok(struct page *page, unsigned int order, migratetype =get_pfnblock_migratetype (page, pfn);spin_lock_irqsave (&zone->lock, flags); -__count_vm_events (PGFREE ,1 << order); migratetype =check_migratetype_isolated (zone, page, pfn, migratetype);__free_one_page (page, pfn, zone, order, migratetype, fpi_flags);spin_unlock_irqrestore (&zone->lock, flags); + +__count_vm_events (PGFREE ,1 << order); }void __free_pages_core (struct page *page,unsigned int order)
Signature · 1 line
2.26.2
Peter Zijlstra
maintainerQuoted 5 lines · Mel Gorman
Why
local_lock?PREEMPT_RTconsiders the following sequence to be unsafe as documented inDocumentation/locking/locktypes.rstlocal_irq_disable(); raw_spin_lock(&lock);
Almost, the above is actually OK on RT. The problematic one is:
local_irq_disable(); spin_lock(&lock);
That doesn't work on RT since spin_lock() turns into a PI-mutex which then obviously explodes if it tries to block with IRQs disabled.
And it so happens, that's exactly the one at hand.
Quoted 9 lines · Peter Zijlstra
Quoted 5 lines · Mel Gorman
Why
local_lock?PREEMPT_RTconsiders the following sequence to be unsafe as documented inDocumentation/locking/locktypes.rstlocal_irq_disable(); raw_spin_lock(&lock);Almost, the above is actually OK on RT. The problematic one is:
Quoted 7 lines
local_irq_disable(); spin_lock(&lock);That doesn't work on RT since
spin_lock()turns into a PI-mutex which then obviously explodes if it tries to block with IRQs disabled.And it so happens, that's exactly the one at hand.
Ok, I completely messed up the leader because it was local_irq_disable()
+ spin_lock() that I was worried about. Once the series is complete, it is replated with
local_lock_irq(&lock_lock) spin_lock(&lock);
According to Documentation/locking/locktypes.rst, that should be safe. I'll rephrase the justification.
Signature · 2 lines
Mel Gorman SUSE Labs