aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHeiko Carstens <heiko.carstens@de.ibm.com>2015-12-29 17:54:32 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2015-12-29 20:45:49 -0500
commit6cdb18ad98a49f7e9b95d538a0614cde827404b8 (patch)
treefb698abbb79de2d8b90d1042517bd08ba375c988
parentcc28d6d80f6ab494b10f0e2ec949eacd610f66e3 (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.h6
-rw-r--r--mm/vmstat.c10
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
179void __mod_zone_page_state(struct zone *, enum zone_stat_item item, int); 179void __mod_zone_page_state(struct zone *, enum zone_stat_item item, long);
180void __inc_zone_page_state(struct page *, enum zone_stat_item); 180void __inc_zone_page_state(struct page *, enum zone_stat_item);
181void __dec_zone_page_state(struct page *, enum zone_stat_item); 181void __dec_zone_page_state(struct page *, enum zone_stat_item);
182 182
183void mod_zone_page_state(struct zone *, enum zone_stat_item, int); 183void mod_zone_page_state(struct zone *, enum zone_stat_item, long);
184void inc_zone_page_state(struct page *, enum zone_stat_item); 184void inc_zone_page_state(struct page *, enum zone_stat_item);
185void dec_zone_page_state(struct page *, enum zone_stat_item); 185void 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 */
207static inline void __mod_zone_page_state(struct zone *zone, 207static 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 */
221void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item, 221void __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*/
321static inline void mod_state(struct zone *zone, 321static 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
359void mod_zone_page_state(struct zone *zone, enum zone_stat_item item, 359void 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 */
386void mod_zone_page_state(struct zone *zone, enum zone_stat_item item, 386void 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