diff options
author | Heiko Carstens <heiko.carstens@de.ibm.com> | 2015-12-29 17:54:32 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2015-12-29 20:45:49 -0500 |
commit | 6cdb18ad98a49f7e9b95d538a0614cde827404b8 (patch) | |
tree | fb698abbb79de2d8b90d1042517bd08ba375c988 | |
parent | cc28d6d80f6ab494b10f0e2ec949eacd610f66e3 (diff) |
mm/vmstat: fix overflow in mod_zone_page_state()
mod_zone_page_state() takes a "delta" integer argument. delta contains
the number of pages that should be added or subtracted from a struct
zone's vm_stat field.
If a zone is larger than 8TB this will cause overflows. E.g. for a
zone with a size slightly larger than 8TB the line
mod_zone_page_state(zone, NR_ALLOC_BATCH, zone->managed_pages);
in mm/page_alloc.c:free_area_init_core() will result in a negative
result for the NR_ALLOC_BATCH entry within the zone's vm_stat, since 8TB
contain 0x8xxxxxxx pages which will be sign extended to a negative
value.
Fix this by changing the delta argument to long type.
This could fix an early boot problem seen on s390, where we have a 9TB
system with only one node. ZONE_DMA contains 2GB and ZONE_NORMAL the
rest. The system is trying to allocate a GFP_DMA page but ZONE_DMA is
completely empty, so it tries to reclaim pages in an endless loop.
This was seen on a heavily patched 3.10 kernel. One possible
explaination seem to be the overflows caused by mod_zone_page_state().
Unfortunately I did not have the chance to verify that this patch
actually fixes the problem, since I don't have access to the system
right now. However the overflow problem does exist anyway.
Given the description that a system with slightly less than 8TB does
work, this seems to be a candidate for the observed problem.
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Cc: Christoph Lameter <cl@linux.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | include/linux/vmstat.h | 6 | ||||
-rw-r--r-- | mm/vmstat.c | 10 |
2 files changed, 8 insertions, 8 deletions
diff --git a/include/linux/vmstat.h b/include/linux/vmstat.h index 5dbc8b0ee567..3e5d9075960f 100644 --- a/include/linux/vmstat.h +++ b/include/linux/vmstat.h | |||
@@ -176,11 +176,11 @@ extern void zone_statistics(struct zone *, struct zone *, gfp_t gfp); | |||
176 | #define sub_zone_page_state(__z, __i, __d) mod_zone_page_state(__z, __i, -(__d)) | 176 | #define sub_zone_page_state(__z, __i, __d) mod_zone_page_state(__z, __i, -(__d)) |
177 | 177 | ||
178 | #ifdef CONFIG_SMP | 178 | #ifdef CONFIG_SMP |
179 | void __mod_zone_page_state(struct zone *, enum zone_stat_item item, int); | 179 | void __mod_zone_page_state(struct zone *, enum zone_stat_item item, long); |
180 | void __inc_zone_page_state(struct page *, enum zone_stat_item); | 180 | void __inc_zone_page_state(struct page *, enum zone_stat_item); |
181 | void __dec_zone_page_state(struct page *, enum zone_stat_item); | 181 | void __dec_zone_page_state(struct page *, enum zone_stat_item); |
182 | 182 | ||
183 | void mod_zone_page_state(struct zone *, enum zone_stat_item, int); | 183 | void mod_zone_page_state(struct zone *, enum zone_stat_item, long); |
184 | void inc_zone_page_state(struct page *, enum zone_stat_item); | 184 | void inc_zone_page_state(struct page *, enum zone_stat_item); |
185 | void dec_zone_page_state(struct page *, enum zone_stat_item); | 185 | void dec_zone_page_state(struct page *, enum zone_stat_item); |
186 | 186 | ||
@@ -205,7 +205,7 @@ void set_pgdat_percpu_threshold(pg_data_t *pgdat, | |||
205 | * The functions directly modify the zone and global counters. | 205 | * The functions directly modify the zone and global counters. |
206 | */ | 206 | */ |
207 | static inline void __mod_zone_page_state(struct zone *zone, | 207 | static inline void __mod_zone_page_state(struct zone *zone, |
208 | enum zone_stat_item item, int delta) | 208 | enum zone_stat_item item, long delta) |
209 | { | 209 | { |
210 | zone_page_state_add(delta, zone, item); | 210 | zone_page_state_add(delta, zone, item); |
211 | } | 211 | } |
diff --git a/mm/vmstat.c b/mm/vmstat.c index 0d5712b0206c..4ebc17d948cb 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c | |||
@@ -219,7 +219,7 @@ void set_pgdat_percpu_threshold(pg_data_t *pgdat, | |||
219 | * particular counter cannot be updated from interrupt context. | 219 | * particular counter cannot be updated from interrupt context. |
220 | */ | 220 | */ |
221 | void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item, | 221 | void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item, |
222 | int delta) | 222 | long delta) |
223 | { | 223 | { |
224 | struct per_cpu_pageset __percpu *pcp = zone->pageset; | 224 | struct per_cpu_pageset __percpu *pcp = zone->pageset; |
225 | s8 __percpu *p = pcp->vm_stat_diff + item; | 225 | s8 __percpu *p = pcp->vm_stat_diff + item; |
@@ -318,8 +318,8 @@ EXPORT_SYMBOL(__dec_zone_page_state); | |||
318 | * 1 Overstepping half of threshold | 318 | * 1 Overstepping half of threshold |
319 | * -1 Overstepping minus half of threshold | 319 | * -1 Overstepping minus half of threshold |
320 | */ | 320 | */ |
321 | static inline void mod_state(struct zone *zone, | 321 | static inline void mod_state(struct zone *zone, enum zone_stat_item item, |
322 | enum zone_stat_item item, int delta, int overstep_mode) | 322 | long delta, int overstep_mode) |
323 | { | 323 | { |
324 | struct per_cpu_pageset __percpu *pcp = zone->pageset; | 324 | struct per_cpu_pageset __percpu *pcp = zone->pageset; |
325 | s8 __percpu *p = pcp->vm_stat_diff + item; | 325 | s8 __percpu *p = pcp->vm_stat_diff + item; |
@@ -357,7 +357,7 @@ static inline void mod_state(struct zone *zone, | |||
357 | } | 357 | } |
358 | 358 | ||
359 | void mod_zone_page_state(struct zone *zone, enum zone_stat_item item, | 359 | void mod_zone_page_state(struct zone *zone, enum zone_stat_item item, |
360 | int delta) | 360 | long delta) |
361 | { | 361 | { |
362 | mod_state(zone, item, delta, 0); | 362 | mod_state(zone, item, delta, 0); |
363 | } | 363 | } |
@@ -384,7 +384,7 @@ EXPORT_SYMBOL(dec_zone_page_state); | |||
384 | * Use interrupt disable to serialize counter updates | 384 | * Use interrupt disable to serialize counter updates |
385 | */ | 385 | */ |
386 | void mod_zone_page_state(struct zone *zone, enum zone_stat_item item, | 386 | void mod_zone_page_state(struct zone *zone, enum zone_stat_item item, |
387 | int delta) | 387 | long delta) |
388 | { | 388 | { |
389 | unsigned long flags; | 389 | unsigned long flags; |
390 | 390 | ||