Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752117AbcJZEaS (ORCPT ); Wed, 26 Oct 2016 00:30:18 -0400 Received: from LGEAMRELO12.lge.com ([156.147.23.52]:39019 "EHLO lgeamrelo12.lge.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751036AbcJZEaR (ORCPT ); Wed, 26 Oct 2016 00:30:17 -0400 X-Original-SENDERIP: 156.147.1.151 X-Original-MAILFROM: iamjoonsoo.kim@lge.com X-Original-SENDERIP: 10.177.222.138 X-Original-MAILFROM: iamjoonsoo.kim@lge.com Date: Wed, 26 Oct 2016 13:31:24 +0900 From: Joonsoo Kim To: Vlastimil Babka Cc: Andrew Morton , Rik van Riel , Johannes Weiner , mgorman@techsingularity.net, Laura Abbott , Minchan Kim , Marek Szyprowski , Michal Nazarewicz , "Aneesh Kumar K.V" , linux-mm@kvack.org, linux-kernel@vger.kernel.org Subject: Re: [PATCH v6 3/6] mm/cma: populate ZONE_CMA Message-ID: <20161026043123.GA2901@js1304-P5Q-DELUXE> References: <1476414196-3514-1-git-send-email-iamjoonsoo.kim@lge.com> <1476414196-3514-4-git-send-email-iamjoonsoo.kim@lge.com> <33f0a8f3-38d1-e527-f71f-839afe0b2ed9@suse.cz> <20161018082730.GA20442@js1304-P5Q-DELUXE> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20161018082730.GA20442@js1304-P5Q-DELUXE> User-Agent: Mutt/1.5.21 (2010-09-15) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 10650 Lines: 316 On Tue, Oct 18, 2016 at 05:27:30PM +0900, Joonsoo Kim wrote: > On Tue, Oct 18, 2016 at 09:42:57AM +0200, Vlastimil Babka wrote: > > On 10/14/2016 05:03 AM, js1304@gmail.com wrote: > > >@@ -145,6 +145,35 @@ static int __init cma_activate_area(struct cma *cma) > > > static int __init cma_init_reserved_areas(void) > > > { > > > int i; > > >+ struct zone *zone; > > >+ pg_data_t *pgdat; > > >+ > > >+ if (!cma_area_count) > > >+ return 0; > > >+ > > >+ for_each_online_pgdat(pgdat) { > > >+ unsigned long start_pfn = UINT_MAX, end_pfn = 0; > > >+ > > >+ for (i = 0; i < cma_area_count; i++) { > > >+ if (pfn_to_nid(cma_areas[i].base_pfn) != > > >+ pgdat->node_id) > > >+ continue; > > >+ > > >+ start_pfn = min(start_pfn, cma_areas[i].base_pfn); > > >+ end_pfn = max(end_pfn, cma_areas[i].base_pfn + > > >+ cma_areas[i].count); > > >+ } > > >+ > > >+ if (!end_pfn) > > >+ continue; > > >+ > > >+ zone = &pgdat->node_zones[ZONE_CMA]; > > >+ > > >+ /* ZONE_CMA doesn't need to exceed CMA region */ > > >+ zone->zone_start_pfn = max(zone->zone_start_pfn, start_pfn); > > >+ zone->spanned_pages = min(zone_end_pfn(zone), end_pfn) - > > >+ zone->zone_start_pfn; > > > > Hmm, do the max/min here work as intended? IIUC the initial > > Yeap. > > > zone_start_pfn is UINT_MAX and zone->spanned_pages is 1? So at least > > the max/min should be swapped? > > No. CMA zone's start/end pfn are updated as node's start/end pfn. > > > Also the zone_end_pfn(zone) on the second line already sees the > > changes to zone->zone_start_pfn in the first line, so it's kind of a > > mess. You should probably cache zone_end_pfn() to a temporary > > variable before changing zone_start_pfn. > > You're right although it doesn't cause any problem. I look at the code > again and find that max/min isn't needed. Calculated start/end pfn > should be inbetween node's start/end pfn so max(zone->zone_start_pfn, > start_pfn) will return start_pfn and messed up min(zone_end_pfn(zone), > end_pfn) will return end_pfn in all the cases. > > Anyway, I will fix it as following. > > zone->zone_start_pfn = start_pfn > zone->spanned_pages = end_pfn - start_pfn Hello, Here comes fixed one. ----------->8------------ >From 93fb05a83d74f9e2c8caebc2fa6d1a8807c9ffb6 Mon Sep 17 00:00:00 2001 From: Joonsoo Kim Date: Thu, 24 Mar 2016 22:29:10 +0900 Subject: [PATCH] mm/cma: populate ZONE_CMA Until now, reserved pages for CMA are managed in the ordinary zones where page's pfn are belong to. This approach has numorous problems and fixing them isn't easy. (It is mentioned on previous patch.) To fix this situation, ZONE_CMA is introduced in previous patch, but, not yet populated. This patch implement population of ZONE_CMA by stealing reserved pages from the ordinary zones. Unlike previous implementation that kernel allocation request with __GFP_MOVABLE could be serviced from CMA region, allocation request only with GFP_HIGHUSER_MOVABLE can be serviced from CMA region in the new approach. This is an inevitable design decision to use the zone implementation because ZONE_CMA could contain highmem. Due to this decision, ZONE_CMA will work like as ZONE_HIGHMEM or ZONE_MOVABLE. I don't think it would be a problem because most of file cache pages and anonymous pages are requested with GFP_HIGHUSER_MOVABLE. It could be proved by the fact that there are many systems with ZONE_HIGHMEM and they work fine. Notable disadvantage is that we cannot use these pages for blockdev file cache page, because it usually has __GFP_MOVABLE but not __GFP_HIGHMEM and __GFP_USER. But, in this case, there is pros and cons. In my experience, blockdev file cache pages are one of the top reason that causes cma_alloc() to fail temporarily. So, we can get more guarantee of cma_alloc() success by discarding that case. Implementation itself is very easy to understand. Steal when cma area is initialized and recalculate various per zone stat/threshold. Reviewed-by: Aneesh Kumar K.V Signed-off-by: Joonsoo Kim --- include/linux/memory_hotplug.h | 3 -- include/linux/mm.h | 1 + mm/cma.c | 62 ++++++++++++++++++++++++++++++++++++++---- mm/internal.h | 3 ++ mm/page_alloc.c | 29 +++++++++++++++++--- 5 files changed, 86 insertions(+), 12 deletions(-) diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h index 01033fa..ea5af47 100644 --- a/include/linux/memory_hotplug.h +++ b/include/linux/memory_hotplug.h @@ -198,9 +198,6 @@ extern void get_page_bootmem(unsigned long ingo, struct page *page, void mem_hotplug_begin(void); void mem_hotplug_done(void); -extern void set_zone_contiguous(struct zone *zone); -extern void clear_zone_contiguous(struct zone *zone); - #else /* ! CONFIG_MEMORY_HOTPLUG */ /* * Stub functions for when hotplug is off diff --git a/include/linux/mm.h b/include/linux/mm.h index e9caec6..6348d35 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -1923,6 +1923,7 @@ extern __printf(2, 3) extern void zone_pcp_update(struct zone *zone); extern void zone_pcp_reset(struct zone *zone); +extern void setup_zone_pageset(struct zone *zone); /* page_alloc.c */ extern int min_free_kbytes; diff --git a/mm/cma.c b/mm/cma.c index 384c2cb..7e888f9 100644 --- a/mm/cma.c +++ b/mm/cma.c @@ -38,6 +38,7 @@ #include #include "cma.h" +#include "internal.h" struct cma cma_areas[MAX_CMA_AREAS]; unsigned cma_area_count; @@ -116,10 +117,9 @@ static int __init cma_activate_area(struct cma *cma) for (j = pageblock_nr_pages; j; --j, pfn++) { WARN_ON_ONCE(!pfn_valid(pfn)); /* - * alloc_contig_range requires the pfn range - * specified to be in the same zone. Make this - * simple by forcing the entire CMA resv range - * to be in the same zone. + * In init_cma_reserved_pageblock(), present_pages is + * adjusted with assumption that all pages come from + * a single zone. It could be fixed but not yet done. */ if (page_zone(pfn_to_page(pfn)) != zone) goto err; @@ -145,6 +145,34 @@ static int __init cma_activate_area(struct cma *cma) static int __init cma_init_reserved_areas(void) { int i; + struct zone *zone; + pg_data_t *pgdat; + + if (!cma_area_count) + return 0; + + for_each_online_pgdat(pgdat) { + unsigned long start_pfn = UINT_MAX, end_pfn = 0; + + for (i = 0; i < cma_area_count; i++) { + if (pfn_to_nid(cma_areas[i].base_pfn) != + pgdat->node_id) + continue; + + start_pfn = min(start_pfn, cma_areas[i].base_pfn); + end_pfn = max(end_pfn, cma_areas[i].base_pfn + + cma_areas[i].count); + } + + if (!end_pfn) + continue; + + zone = &pgdat->node_zones[ZONE_CMA]; + + /* ZONE_CMA doesn't need to exceed CMA region */ + zone->zone_start_pfn = start_pfn; + zone->spanned_pages = end_pfn - start_pfn; + } for (i = 0; i < cma_area_count; i++) { int ret = cma_activate_area(&cma_areas[i]); @@ -153,9 +181,33 @@ static int __init cma_init_reserved_areas(void) return ret; } + /* + * Reserved pages for ZONE_CMA are now activated and this would change + * ZONE_CMA's managed page counter and other zone's present counter. + * We need to re-calculate various zone information that depends on + * this initialization. + */ + build_all_zonelists(NULL, NULL); + for_each_populated_zone(zone) { + zone_pcp_update(zone); + set_zone_contiguous(zone); + } + + /* + * We need to re-init per zone wmark by calling + * init_per_zone_wmark_min() but doesn't call here because it is + * registered on core_initcall and it will be called later than us. + */ + for_each_populated_zone(zone) { + if (!is_zone_cma(zone)) + continue; + + setup_zone_pageset(zone); + } + return 0; } -core_initcall(cma_init_reserved_areas); +pure_initcall(cma_init_reserved_areas); /** * cma_init_reserved_mem() - create custom contiguous area from reserved memory diff --git a/mm/internal.h b/mm/internal.h index 537ac99..bb7317d 100644 --- a/mm/internal.h +++ b/mm/internal.h @@ -156,6 +156,9 @@ extern void post_alloc_hook(struct page *page, unsigned int order, gfp_t gfp_flags); extern int user_min_free_kbytes; +extern void set_zone_contiguous(struct zone *zone); +extern void clear_zone_contiguous(struct zone *zone); + #if defined CONFIG_COMPACTION || defined CONFIG_CMA /* diff --git a/mm/page_alloc.c b/mm/page_alloc.c index ac44b01..39d1311 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -1610,16 +1610,38 @@ void __init page_alloc_init_late(void) } #ifdef CONFIG_CMA +static void __init adjust_present_page_count(struct page *page, long count) +{ + struct zone *zone = page_zone(page); + + /* We don't need to hold a lock since it is boot-up process */ + zone->present_pages += count; +} + /* Free whole pageblock and set its migration type to MIGRATE_CMA. */ void __init init_cma_reserved_pageblock(struct page *page) { unsigned i = pageblock_nr_pages; + unsigned long pfn = page_to_pfn(page); struct page *p = page; + int nid = page_to_nid(page); + + /* + * ZONE_CMA will steal present pages from other zones by changing + * page links so page_zone() is changed. Before that, + * we need to adjust previous zone's page count first. + */ + adjust_present_page_count(page, -pageblock_nr_pages); do { __ClearPageReserved(p); set_page_count(p, 0); - } while (++p, --i); + + /* Steal pages from other zones */ + set_page_links(p, ZONE_CMA, nid, pfn); + } while (++p, ++pfn, --i); + + adjust_present_page_count(page, pageblock_nr_pages); set_pageblock_migratetype(page, MIGRATE_CMA); @@ -4886,7 +4908,6 @@ static void build_zonelists(pg_data_t *pgdat) */ static void setup_pageset(struct per_cpu_pageset *p, unsigned long batch); static DEFINE_PER_CPU(struct per_cpu_pageset, boot_pageset); -static void setup_zone_pageset(struct zone *zone); /* * Global mutex to protect against size modification of zonelists @@ -5316,7 +5337,7 @@ static void __meminit zone_pageset_init(struct zone *zone, int cpu) pageset_set_high_and_batch(zone, pcp); } -static void __meminit setup_zone_pageset(struct zone *zone) +void __meminit setup_zone_pageset(struct zone *zone) { int cpu; zone->pageset = alloc_percpu(struct per_cpu_pageset); @@ -7503,7 +7524,7 @@ void free_contig_range(unsigned long pfn, unsigned nr_pages) } #endif -#ifdef CONFIG_MEMORY_HOTPLUG +#if defined CONFIG_MEMORY_HOTPLUG || defined CONFIG_CMA /* * The zone indicated has a new number of managed_pages; batch sizes and percpu * page high values need to be recalulated. -- 1.9.1