From: Wen Congyang <redacted>
The patch-set was divided from following thread's patch-set.
https://lkml.org/lkml/2012/9/5/201
The last version of this patchset:
https://lkml.org/lkml/2012/10/5/469
If you want to know the reason, please read following thread.
https://lkml.org/lkml/2012/10/2/83
The patch-set has only the function of kernel core side for physical
memory hot remove. So if you use the patch, please apply following
patches.
- bug fix for memory hot remove
https://lkml.org/lkml/2012/10/19/56
- acpi framework
https://lkml.org/lkml/2012/10/19/156
The patches can free/remove the following things:
- /sys/firmware/memmap/X/{end, start, type} : [PATCH 2/10]
- mem_section and related sysfs files : [PATCH 3-4/10]
- memmap of sparse-vmemmap : [PATCH 5-7/10]
- page table of removed memory : [RFC PATCH 8/10]
- node and related sysfs files : [RFC PATCH 9-10/10]
* [PATCH 2/10] checks whether the memory can be removed or not.
If you find lack of function for physical memory hot-remove, please let me
know.
How to test this patchset?
1. apply this patchset and build the kernel. MEMORY_HOTPLUG, MEMORY_HOTREMOVE,
ACPI_HOTPLUG_MEMORY must be selected.
2. load the module acpi_memhotplug
3. hotplug the memory device(it depends on your hardware)
You will see the memory device under the directory /sys/bus/acpi/devices/.
Its name is PNP0C80:XX.
4. online/offline pages provided by this memory device
You can write online/offline to /sys/devices/system/memory/memoryX/state to
online/offline pages provided by this memory device
5. hotremove the memory device
You can hotremove the memory device by the hardware, or writing 1 to
/sys/bus/acpi/devices/PNP0C80:XX/eject.
Note: if the memory provided by the memory device is used by the kernel, it
can't be offlined. It is not a bug.
Known problems:
1. hotremoving memory device may cause kernel panicked
This bug will be fixed by Liu Jiang's patch:
https://lkml.org/lkml/2012/7/3/1
Changelogs from v1 to v2:
Patch1: new patch, offline memory twice. 1st iterate: offline every non primary
memory block. 2nd iterate: offline primary (i.e. first added) memory
block.
Patch3: new patch, no logical change, just remove reduntant codes.
Patch9: merge the patch from wujianguo into this patch. flush tlb on all cpu
after the pagetable is changed.
Patch12: new patch, free node_data when a node is offlined
Wen Congyang (6):
memory-hotplug: try to offline the memory twice to avoid dependence
memory-hotplug: remove redundant codes
memory-hotplug: introduce new function arch_remove_memory() for
removing page table depends on architecture
memory-hotplug: remove page table of x86_64 architecture
memory-hotplug: remove sysfs file of node
memory-hotplug: free node_data when a node is offlined
Yasuaki Ishimatsu (6):
memory-hotplug: check whether all memory blocks are offlined or not
when removing memory
memory-hotplug: remove /sys/firmware/memmap/X sysfs
memory-hotplug: unregister memory section on SPARSEMEM_VMEMMAP
memory-hotplug: implement register_page_bootmem_info_section of
sparse-vmemmap
memory-hotplug: remove memmap of sparse-vmemmap
memory-hotplug: memory_hotplug: clear zone when removing the memory
arch/ia64/mm/discontig.c | 14 ++
arch/ia64/mm/init.c | 18 ++
arch/powerpc/mm/init_64.c | 14 ++
arch/powerpc/mm/mem.c | 12 +
arch/s390/mm/init.c | 12 +
arch/s390/mm/vmem.c | 14 ++
arch/sh/mm/init.c | 17 ++
arch/sparc/mm/init_64.c | 14 ++
arch/tile/mm/init.c | 8 +
arch/x86/include/asm/pgtable_types.h | 1 +
arch/x86/mm/init_32.c | 12 +
arch/x86/mm/init_64.c | 409 ++++++++++++++++++++++++++++++++++
arch/x86/mm/pageattr.c | 47 ++--
drivers/acpi/acpi_memhotplug.c | 8 +-
drivers/base/memory.c | 6 +
drivers/firmware/memmap.c | 98 ++++++++-
include/linux/firmware-map.h | 6 +
include/linux/memory_hotplug.h | 15 +-
include/linux/mm.h | 5 +-
mm/memory_hotplug.c | 409 ++++++++++++++++++++++++++++++++--
mm/sparse.c | 5 +-
21 files changed, 1087 insertions(+), 57 deletions(-)
From: Yasuaki Ishimatsu <redacted>
We remove the memory like this:
1. lock memory hotplug
2. offline a memory block
3. unlock memory hotplug
4. repeat 1-3 to offline all memory blocks
5. lock memory hotplug
6. remove memory(TODO)
7. unlock memory hotplug
All memory blocks must be offlined before removing memory. But we don't hold
the lock in the whole operation. So we should check whether all memory blocks
are offlined before step6. Otherwise, kernel maybe panicked.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
Signed-off-by: Wen Congyang <redacted>
Signed-off-by: Yasuaki Ishimatsu <redacted>
---
drivers/base/memory.c | 6 +++++
include/linux/memory_hotplug.h | 1 +
mm/memory_hotplug.c | 47 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 54 insertions(+), 0 deletions(-)
@@ -675,6 +675,12 @@ int offline_memory_block(struct memory_block *mem)returnret;}+/* return true if the memory block is offlined, otherwise, return false */+boolis_memblock_offlined(structmemory_block*mem)+{+returnmem->state==MEM_OFFLINE;+}+/**Initializethesysfssupportformemorydevices...*/
@@ -1061,6 +1061,53 @@ repeat:gotorepeat;}+lock_memory_hotplug();++/*+*wehaveofflinedallmemoryblockslikethis:+*1.lockmemoryhotplug+*2.offlineamemoryblock+*3.unlockmemoryhotplug+*+*repeatstep1-3toofflinethememoryblock.Allmemoryblocks+*mustbeofflinedbeforeremovingmemory.Butwedon'tholdthe+*lockinthewholeoperation.Soweshouldcheckwhetherall+*memoryblocksareofflined.+*/++for(pfn=start_pfn;pfn<end_pfn;pfn+=PAGES_PER_SECTION){+section_nr=pfn_to_section_nr(pfn);+if(!present_section_nr(section_nr))+continue;++section=__nr_to_section(section_nr);+/* same memblock? */+if(mem)+if((section_nr>=mem->start_section_nr)&&+(section_nr<=mem->end_section_nr))+continue;++mem=find_memory_block_hinted(section,mem);+if(!mem)+continue;++ret=is_memblock_offlined(mem);+if(!ret){+pr_warn("removing memory fails, because memory "+"[%#010llx-%#010llx] is onlined\n",+PFN_PHYS(section_nr_to_pfn(mem->start_section_nr)),+PFN_PHYS(section_nr_to_pfn(mem->end_section_nr+1))-1);++kobject_put(&mem->dev.kobj);+unlock_memory_hotplug();+returnret;+}+}++if(mem)+kobject_put(&mem->dev.kobj);+unlock_memory_hotplug();+return0;}#else
From: Yasuaki Ishimatsu <redacted>
When (hot)adding memory into system, /sys/firmware/memmap/X/{end, start, type}
sysfs files are created. But there is no code to remove these files. The patch
implements the function to remove them.
Note: The code does not free firmware_map_entry which is allocated by bootmem.
So the patch makes memory leak. But I think the memory leak size is
very samll. And it does not affect the system.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
Signed-off-by: Wen Congyang <redacted>
Signed-off-by: Yasuaki Ishimatsu <redacted>
---
drivers/firmware/memmap.c | 98 +++++++++++++++++++++++++++++++++++++++++-
include/linux/firmware-map.h | 6 +++
mm/memory_hotplug.c | 5 ++-
3 files changed, 106 insertions(+), 3 deletions(-)
@@ -41,6 +42,7 @@ struct firmware_map_entry {constchar*type;/* type of the memory range */structlist_headlist;/* entry for the linked list */structkobjectkobj;/* kobject for each entry */+unsignedintbootmem:1;/* allocated from bootmem */};/*
@@ -79,7 +81,26 @@ static const struct sysfs_ops memmap_attr_ops = {.show=memmap_attr_show,};++staticinlinestructfirmware_map_entry*+to_memmap_entry(structkobject*kobj)+{+returncontainer_of(kobj,structfirmware_map_entry,kobj);+}++staticvoidrelease_firmware_map_entry(structkobject*kobj)+{+structfirmware_map_entry*entry=to_memmap_entry(kobj);++if(entry->bootmem)+/* There is no way to free memory allocated from bootmem */+return;++kfree(entry);+}+staticstructkobj_typememmap_ktype={+.release=release_firmware_map_entry,.sysfs_ops=&memmap_attr_ops,.default_attrs=def_attrs,};
From: Yasuaki Ishimatsu <redacted>
When a memory is added, we update zone's and pgdat's start_pfn and
spanned_pages in the function __add_zone(). So we should revert them
when the memory is removed.
The patch adds a new function __remove_zone() to do this.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
Signed-off-by: Yasuaki Ishimatsu <redacted>
Signed-off-by: Wen Congyang <redacted>
---
mm/memory_hotplug.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 207 insertions(+), 0 deletions(-)
@@ -312,10 +312,213 @@ static int __meminit __add_section(int nid, struct zone *zone,returnregister_new_memory(nid,__pfn_to_section(phys_start_pfn));}+/* find the smallest valid pfn in the range [start_pfn, end_pfn) */+staticintfind_smallest_section_pfn(intnid,structzone*zone,+unsignedlongstart_pfn,+unsignedlongend_pfn)+{+structmem_section*ms;++for(;start_pfn<end_pfn;start_pfn+=PAGES_PER_SECTION){+ms=__pfn_to_section(start_pfn);++if(unlikely(!valid_section(ms)))+continue;++if(unlikely(pfn_to_nid(start_pfn))!=nid)+continue;++if(zone&&zone!=page_zone(pfn_to_page(start_pfn)))+continue;++returnstart_pfn;+}++return0;+}++/* find the biggest valid pfn in the range [start_pfn, end_pfn). */+staticintfind_biggest_section_pfn(intnid,structzone*zone,+unsignedlongstart_pfn,+unsignedlongend_pfn)+{+structmem_section*ms;+unsignedlongpfn;++/* pfn is the end pfn of a memory section. */+pfn=end_pfn-1;+for(;pfn>=start_pfn;pfn-=PAGES_PER_SECTION){+ms=__pfn_to_section(pfn);++if(unlikely(!valid_section(ms)))+continue;++if(unlikely(pfn_to_nid(pfn))!=nid)+continue;++if(zone&&zone!=page_zone(pfn_to_page(pfn)))+continue;++returnpfn;+}++return0;+}++staticvoidshrink_zone_span(structzone*zone,unsignedlongstart_pfn,+unsignedlongend_pfn)+{+unsignedlongzone_start_pfn=zone->zone_start_pfn;+unsignedlongzone_end_pfn=zone->zone_start_pfn+zone->spanned_pages;+unsignedlongpfn;+structmem_section*ms;+intnid=zone_to_nid(zone);++zone_span_writelock(zone);+if(zone_start_pfn==start_pfn){+/*+*Ifthesectionissmallestsectioninthezone,itneed+*shrinkzone->zone_start_pfnandzone->zone_spanned_pages.+*Inthiscase,wefindsecondsmallestvalidmem_section+*forshrinkingzone.+*/+pfn=find_smallest_section_pfn(nid,zone,end_pfn,+zone_end_pfn);+if(pfn){+zone->zone_start_pfn=pfn;+zone->spanned_pages=zone_end_pfn-pfn;+}+}elseif(zone_end_pfn==end_pfn){+/*+*Ifthesectionisbiggestsectioninthezone,itneed+*shrinkzone->spanned_pages.+*Inthiscase,wefindsecondbiggestvalidmem_sectionfor+*shrinkingzone.+*/+pfn=find_biggest_section_pfn(nid,zone,zone_start_pfn,+start_pfn);+if(pfn)+zone->spanned_pages=pfn-zone_start_pfn+1;+}++/*+*Thesectionisnotbiggestorsmallestmem_sectioninthezone,it+*onlycreatesaholeinthezone.Sointhiscase,weneednot+*changethezone.Butperhaps,thezonehasonlyholedata.Thus+*itcheckthezonehasonlyholeornot.+*/+pfn=zone_start_pfn;+for(;pfn<zone_end_pfn;pfn+=PAGES_PER_SECTION){+ms=__pfn_to_section(pfn);++if(unlikely(!valid_section(ms)))+continue;++if(page_zone(pfn_to_page(pfn))!=zone)+continue;++/* If the section is current section, it continues the loop */+if(start_pfn==pfn)+continue;++/* If we find valid section, we have nothing to do */+zone_span_writeunlock(zone);+return;+}++/* The zone has no valid section */+zone->zone_start_pfn=0;+zone->spanned_pages=0;+zone_span_writeunlock(zone);+}++staticvoidshrink_pgdat_span(structpglist_data*pgdat,+unsignedlongstart_pfn,unsignedlongend_pfn)+{+unsignedlongpgdat_start_pfn=pgdat->node_start_pfn;+unsignedlongpgdat_end_pfn=+pgdat->node_start_pfn+pgdat->node_spanned_pages;+unsignedlongpfn;+structmem_section*ms;+intnid=pgdat->node_id;++if(pgdat_start_pfn==start_pfn){+/*+*Ifthesectionissmallestsectioninthepgdat,itneed+*shrinkpgdat->node_start_pfnandpgdat->node_spanned_pages.+*Inthiscase,wefindsecondsmallestvalidmem_section+*forshrinkingzone.+*/+pfn=find_smallest_section_pfn(nid,NULL,end_pfn,+pgdat_end_pfn);+if(pfn){+pgdat->node_start_pfn=pfn;+pgdat->node_spanned_pages=pgdat_end_pfn-pfn;+}+}elseif(pgdat_end_pfn==end_pfn){+/*+*Ifthesectionisbiggestsectioninthepgdat,itneed+*shrinkpgdat->node_spanned_pages.+*Inthiscase,wefindsecondbiggestvalidmem_sectionfor+*shrinkingzone.+*/+pfn=find_biggest_section_pfn(nid,NULL,pgdat_start_pfn,+start_pfn);+if(pfn)+pgdat->node_spanned_pages=pfn-pgdat_start_pfn+1;+}++/*+*Ifthesectionisnotbiggestorsmallestmem_sectioninthepgdat,+*itonlycreatesaholeinthepgdat.Sointhiscase,weneednot+*changethepgdat.+*Butperhaps,thepgdathasonlyholedata.Thusitcheckthepgdat+*hasonlyholeornot.+*/+pfn=pgdat_start_pfn;+for(;pfn<pgdat_end_pfn;pfn+=PAGES_PER_SECTION){+ms=__pfn_to_section(pfn);++if(unlikely(!valid_section(ms)))+continue;++if(pfn_to_nid(pfn)!=nid)+continue;++/* If the section is current section, it continues the loop */+if(start_pfn==pfn)+continue;++/* If we find valid section, we have nothing to do */+return;+}++/* The pgdat has no valid section */+pgdat->node_start_pfn=0;+pgdat->node_spanned_pages=0;+}++staticvoid__remove_zone(structzone*zone,unsignedlongstart_pfn)+{+structpglist_data*pgdat=zone->zone_pgdat;+intnr_pages=PAGES_PER_SECTION;+intzone_type;+unsignedlongflags;++zone_type=zone-pgdat->node_zones;++pgdat_resize_lock(zone->zone_pgdat,&flags);+shrink_zone_span(zone,start_pfn,start_pfn+nr_pages);+shrink_pgdat_span(pgdat,start_pfn,start_pfn+nr_pages);+pgdat_resize_unlock(zone->zone_pgdat,&flags);+}+staticint__remove_section(structzone*zone,structmem_section*ms){unsignedlongflags;structpglist_data*pgdat=zone->zone_pgdat;+unsignedlongstart_pfn;+intscn_nr;intret=-EINVAL;if(!valid_section(ms))
@@ -325,6 +528,10 @@ static int __remove_section(struct zone *zone, struct mem_section *ms)if(ret)returnret;+scn_nr=__section_nr(ms);+start_pfn=section_nr_to_pfn(scn_nr);+__remove_zone(zone,start_pfn);+pgdat_resize_lock(pgdat,&flags);sparse_remove_one_section(zone,ms);pgdat_resize_unlock(pgdat,&flags);
From: Wen Congyang <redacted>
We call hotadd_new_pgdat() to allocate memory to store node_data. So we
should free it when removing a node.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
CC: Yasuaki Ishimatsu <redacted>
Signed-off-by: Wen Congyang <redacted>
---
mm/memory_hotplug.c | 20 +++++++++++++++++++-
1 files changed, 19 insertions(+), 1 deletions(-)
@@ -1320,9 +1320,12 @@ static int check_cpu_on_node(void *data)/* offline the node if all memory sections of this node are removed */staticvoidtry_offline_node(intnid){+pg_data_t*pgdat=NODE_DATA(nid);unsignedlongstart_pfn=NODE_DATA(nid)->node_start_pfn;-unsignedlongend_pfn=start_pfn+NODE_DATA(nid)->node_spanned_pages;+unsignedlongend_pfn=start_pfn+pgdat->node_spanned_pages;unsignedlongpfn;+structpage*pgdat_page=virt_to_page(pgdat);+inti;for(pfn=start_pfn;pfn<end_pfn;pfn+=PAGES_PER_SECTION){unsignedlongsection_nr=pfn_to_section_nr(pfn);
@@ -1349,6 +1352,21 @@ static void try_offline_node(int nid)*/node_set_offline(nid);unregister_one_node(nid);++if(!PageSlab(pgdat_page)&&!PageCompound(pgdat_page))+/* node data is allocated from boot memory */+return;++/* free waittable in each zone */+for(i=0;i<MAX_NR_ZONES;i++){+structzone*zone=pgdat->node_zones+i;++if(zone->wait_table)+vfree(zone->wait_table);+}++arch_refresh_nodedata(nid,NULL);+arch_free_nodedata(pgdat);}int__refremove_memory(intnid,u64start,u64size)
From: Wen Congyang <redacted>
This patch introduces a new function try_offline_node() to
remove sysfs file of node when all memory sections of this
node are removed. If some memory sections of this node are
not removed, this function does nothing.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
CC: Yasuaki Ishimatsu <redacted>
Signed-off-by: Wen Congyang <redacted>
---
drivers/acpi/acpi_memhotplug.c | 8 +++++-
include/linux/memory_hotplug.h | 2 +-
mm/memory_hotplug.c | 58 ++++++++++++++++++++++++++++++++++++++-
3 files changed, 64 insertions(+), 4 deletions(-)
@@ -1299,7 +1300,58 @@ static int is_memblock_offlined_cb(struct memory_block *mem, void *arg)returnret;}-int__refremove_memory(u64start,u64size)+staticintcheck_cpu_on_node(void*data)+{+structpglist_data*pgdat=data;+intcpu;++for_each_present_cpu(cpu){+if(cpu_to_node(cpu)==pgdat->node_id)+/*+*thecpuonthisnodeisn'tremoved,andwecan't+*offlinethisnode.+*/+return-EBUSY;+}++return0;+}++/* offline the node if all memory sections of this node are removed */+staticvoidtry_offline_node(intnid)+{+unsignedlongstart_pfn=NODE_DATA(nid)->node_start_pfn;+unsignedlongend_pfn=start_pfn+NODE_DATA(nid)->node_spanned_pages;+unsignedlongpfn;++for(pfn=start_pfn;pfn<end_pfn;pfn+=PAGES_PER_SECTION){+unsignedlongsection_nr=pfn_to_section_nr(pfn);++if(!present_section_nr(section_nr))+continue;++if(pfn_to_nid(pfn)!=nid)+continue;++/*+*somememorysectionsofthisnodearenotremoved,andwe+*can'tofflinenodenow.+*/+return;+}++if(stop_machine(check_cpu_on_node,NODE_DATA(nid),NULL))+return;++/*+*allmemory/cpuofthisnodeareremoved,wecanofflinethis+*nodenow.+*/+node_set_offline(nid);+unregister_one_node(nid);+}++int__refremove_memory(intnid,u64start,u64size){unsignedlongstart_pfn,end_pfn;intret=0;
@@ -1355,7 +1409,7 @@ int offline_pages(unsigned long start_pfn, unsigned long nr_pages){return-EINVAL;}-intremove_memory(u64start,u64size)+intremove_memory(intnid,u64start,u64size){return-EINVAL;}
From: Wen Congyang <redacted>
For hot removing memory, we sholud remove page table about the memory.
So the patch searches a page table about the removed memory, and clear
page table.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
CC: Yasuaki Ishimatsu <redacted>
Signed-off-by: Wen Congyang <redacted>
Signed-off-by: Jianguo Wu <redacted>
Signed-off-by: Jiang Liu <redacted>
---
arch/x86/include/asm/pgtable_types.h | 1 +
arch/x86/mm/init_64.c | 223 ++++++++++++++++++++++++++++++++++
arch/x86/mm/pageattr.c | 47 ++++----
3 files changed, 249 insertions(+), 22 deletions(-)
@@ -675,6 +675,227 @@ int arch_add_memory(int nid, u64 start, u64 size)}EXPORT_SYMBOL_GPL(arch_add_memory);+staticinlinevoidfree_pagetable(structpage*page)+{+structzone*zone;+boolbootmem=false;++/* bootmem page has reserved flag */+if(PageReserved(page)){+__ClearPageReserved(page);+bootmem=true;+}++__free_page(page);++if(bootmem){+zone=page_zone(page);+zone_span_writelock(zone);+zone->present_pages++;+zone_span_writeunlock(zone);+totalram_pages++;+}+}++staticvoidfree_pte_table(pte_t*pte_start,pmd_t*pmd)+{+pte_t*pte;+inti;++for(i=0;i<PTRS_PER_PTE;i++){+pte=pte_start+i;+if(pte_val(*pte))+return;+}++/* free a pte talbe */+free_pagetable(pmd_page(*pmd));+pmd_clear(pmd);+}++staticvoidfree_pmd_table(pmd_t*pmd_start,pud_t*pud)+{+pmd_t*pmd;+inti;++for(i=0;i<PTRS_PER_PMD;i++){+pmd=pmd_start+i;+if(pmd_val(*pmd))+return;+}++/* free a pmd talbe */+free_pagetable(pud_page(*pud));+pud_clear(pud);+}++staticvoidfree_pud_table(pud_t*pud_start,pgd_t*pgd)+{+pud_t*pud;+inti;++for(i=0;i<PTRS_PER_PUD;i++){+pud=pud_start+i;+if(pud_val(*pud))+return;+}++/* free a pud table */+free_pagetable(pgd_page(*pgd));+pgd_clear(pgd);+}++staticvoid__meminit+phys_pte_remove(pte_t*pte_page,unsignedlongaddr,unsignedlongend)+{+unsignedpages=0;+inti=pte_index(addr);++pte_t*pte=pte_page+pte_index(addr);++for(;i<PTRS_PER_PTE;i++,addr+=PAGE_SIZE,pte++){++if(addr>=end)+break;++if(!pte_present(*pte))+continue;++pages++;+set_pte(pte,__pte(0));+}++update_page_count(PG_LEVEL_4K,-pages);+}++staticvoid__meminit+phys_pmd_remove(pmd_t*pmd_page,unsignedlongaddr,unsignedlongend)+{+unsignedlongpages=0,next;+inti=pmd_index(addr);++for(;i<PTRS_PER_PMD&&addr<end;i++,addr=next){+unsignedlongpte_phys;+pmd_t*pmd=pmd_page+pmd_index(addr);+pte_t*pte;++next=pmd_addr_end(addr,end);++if(!pmd_present(*pmd))+continue;++if(pmd_large(*pmd)){+if(IS_ALIGNED(addr,PMD_SIZE)&&+IS_ALIGNED(next,PMD_SIZE)){+set_pmd(pmd,__pmd(0));+pages++;+continue;+}++/*+*Weuse2Mpage,butweneedtoremovepartofthem,+*sosplit2Mpageto4Kpage.+*/+pte=alloc_low_page(&pte_phys);+BUG_ON(!pte);+__split_large_page((pte_t*)pmd,+(unsignedlong)__va(addr),pte);++spin_lock(&init_mm.page_table_lock);+pmd_populate_kernel(&init_mm,pmd,__va(pte_phys));+spin_unlock(&init_mm.page_table_lock);++/* Do a global flush tlb after splitting a large page */+flush_tlb_all();+}++spin_lock(&init_mm.page_table_lock);+pte=map_low_page((pte_t*)pmd_page_vaddr(*pmd));+phys_pte_remove(pte,addr,next);+free_pte_table(pte,pmd);+unmap_low_page(pte);+spin_unlock(&init_mm.page_table_lock);+}+update_page_count(PG_LEVEL_2M,-pages);+}++staticvoid__meminit+phys_pud_remove(pud_t*pud_page,unsignedlongaddr,unsignedlongend)+{+unsignedlongpages=0,next;+inti=pud_index(addr);++for(;i<PTRS_PER_PUD&&addr<end;i++,addr=next){+unsignedlongpmd_phys;+pud_t*pud=pud_page+pud_index(addr);+pmd_t*pmd;++next=pud_addr_end(addr,end);++if(!pud_present(*pud))+continue;++if(pud_large(*pud)){+if(IS_ALIGNED(addr,PUD_SIZE)&&+IS_ALIGNED(next,PUD_SIZE)){+set_pud(pud,__pud(0));+pages++;+continue;+}++/*+*Weuse1Gpage,butweneedtoremovepartofthem,+*sosplit1Gpageto2Mpage.+*/+pmd=alloc_low_page(&pmd_phys);+BUG_ON(!pmd);+__split_large_page((pte_t*)pud,+(unsignedlong)__va(addr),+(pte_t*)pmd);++spin_lock(&init_mm.page_table_lock);+pud_populate(&init_mm,pud,__va(pmd_phys));+spin_unlock(&init_mm.page_table_lock);++/* Do a global flush tlb after splitting a large page */+flush_tlb_all();+}++pmd=map_low_page((pmd_t*)pud_page_vaddr(*pud));+phys_pmd_remove(pmd,addr,next);+free_pmd_table(pmd,pud);+unmap_low_page(pmd);+}++update_page_count(PG_LEVEL_1G,-pages);+}++void__meminit+kernel_physical_mapping_remove(unsignedlongstart,unsignedlongend)+{+unsignedlongnext;++start=(unsignedlong)__va(start);+end=(unsignedlong)__va(end);++for(;start<end;start=next){+pgd_t*pgd=pgd_offset_k(start);+pud_t*pud;++next=pgd_addr_end(start,end);++if(!pgd_present(*pgd))+continue;++pud=map_low_page((pud_t*)pgd_page_vaddr(*pgd));+phys_pud_remove(pud,__pa(start),__pa(next));+free_pud_table(pud,pgd);+unmap_low_page(pud);+}++flush_tlb_all();+}+#ifdef CONFIG_MEMORY_HOTREMOVEint__refarch_remove_memory(u64start,u64size){
@@ -687,6 +908,8 @@ int __ref arch_remove_memory(u64 start, u64 size)ret=__remove_pages(zone,start_pfn,nr_pages);WARN_ON_ONCE(ret);+kernel_physical_mapping_remove(start,start+size);+returnret;}#endif
From: Yasuaki Ishimatsu <redacted>
All pages of virtual mapping in removed memory cannot be freed, since some pages
used as PGD/PUD includes not only removed memory but also other memory. So the
patch checks whether page can be freed or not.
How to check whether page can be freed or not?
1. When removing memory, the page structs of the revmoved memory are filled
with 0FD.
2. All page structs are filled with 0xFD on PT/PMD, PT/PMD can be cleared.
In this case, the page used as PT/PMD can be freed.
Applying patch, __remove_section() of CONFIG_SPARSEMEM_VMEMMAP is integrated
into one. So __remove_section() of CONFIG_SPARSEMEM_VMEMMAP is deleted.
Note: vmemmap_kfree() and vmemmap_free_bootmem() are not implemented for ia64,
ppc, s390, and sparc.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
CC: Wen Congyang <redacted>
Signed-off-by: Yasuaki Ishimatsu <redacted>
---
arch/ia64/mm/discontig.c | 8 +++
arch/powerpc/mm/init_64.c | 8 +++
arch/s390/mm/vmem.c | 8 +++
arch/sparc/mm/init_64.c | 8 +++
arch/x86/mm/init_64.c | 119 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/mm.h | 2 +
mm/memory_hotplug.c | 17 +------
mm/sparse.c | 5 +-
8 files changed, 158 insertions(+), 17 deletions(-)
@@ -1642,6 +1642,8 @@ int vmemmap_populate(struct page *start_page, unsigned long pages, int node);voidvmemmap_populate_print_last(void);voidregister_page_bootmem_memmap(unsignedlongsection_nr,structpage*map,unsignedlongsize);+voidvmemmap_kfree(structpage*memmpa,unsignedlongnr_pages);+voidvmemmap_free_bootmem(structpage*memmpa,unsignedlongnr_pages);enummf_flags{MF_COUNT_INCREASED=1<<0,
@@ -613,12 +613,13 @@ static inline struct page *kmalloc_section_memmap(unsigned long pnum, int nid,/* This will make the necessary allocations eventually. */returnsparse_mem_map_populate(pnum,nid);}-staticvoid__kfree_section_memmap(structpage*memmap,unsignedlongnr_pages)+staticvoid__kfree_section_memmap(structpage*page,unsignedlongnr_pages){-return;/* XXX: Not implemented yet */+vmemmap_kfree(page,nr_pages);}staticvoidfree_map_bootmem(structpage*page,unsignedlongnr_pages){+vmemmap_free_bootmem(page,nr_pages);}#elsestaticstructpage*__kmalloc_section_memmap(unsignedlongnr_pages)
From: Yasuaki Ishimatsu <redacted>
For removing memmap region of sparse-vmemmap which is allocated bootmem,
memmap region of sparse-vmemmap needs to be registered by get_page_bootmem().
So the patch searches pages of virtual mapping and registers the pages by
get_page_bootmem().
Note: register_page_bootmem_memmap() is not implemented for ia64, ppc, s390,
and sparc.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
Signed-off-by: Wen Congyang <redacted>
Signed-off-by: Yasuaki Ishimatsu <redacted>
---
arch/ia64/mm/discontig.c | 6 ++++
arch/powerpc/mm/init_64.c | 6 ++++
arch/s390/mm/vmem.c | 6 ++++
arch/sparc/mm/init_64.c | 6 ++++
arch/x86/mm/init_64.c | 52 ++++++++++++++++++++++++++++++++++++++++
include/linux/memory_hotplug.h | 11 +-------
include/linux/mm.h | 3 +-
mm/memory_hotplug.c | 37 +++++++++++++++++++++++++---
8 files changed, 113 insertions(+), 14 deletions(-)
From: Wen Congyang <redacted>
For removing memory, we need to remove page table. But it depends
on architecture. So the patch introduce arch_remove_memory() for
removing page table. Now it only calls __remove_pages().
Note: __remove_pages() for some archtecuture is not implemented
(I don't know how to implement it for s390).
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
CC: Yasuaki Ishimatsu <redacted>
Signed-off-by: Wen Congyang <redacted>
---
arch/ia64/mm/init.c | 18 ++++++++++++++++++
arch/powerpc/mm/mem.c | 12 ++++++++++++
arch/s390/mm/init.c | 12 ++++++++++++
arch/sh/mm/init.c | 17 +++++++++++++++++
arch/tile/mm/init.c | 8 ++++++++
arch/x86/mm/init_32.c | 12 ++++++++++++
arch/x86/mm/init_64.c | 15 +++++++++++++++
include/linux/memory_hotplug.h | 1 +
mm/memory_hotplug.c | 2 ++
9 files changed, 97 insertions(+), 0 deletions(-)
@@ -85,6 +85,7 @@ extern void __online_page_free(struct page *page);#ifdef CONFIG_MEMORY_HOTREMOVEexternboolis_pageblock_removable_nolock(structpage*page);+externintarch_remove_memory(u64start,u64size);#endif /* CONFIG_MEMORY_HOTREMOVE *//* reasonably generic interface to expand the physical pages in a zone */
From: Yasuaki Ishimatsu <redacted>
Currently __remove_section for SPARSEMEM_VMEMMAP does nothing. But even if
we use SPARSEMEM_VMEMMAP, we can unregister the memory_section.
So the patch add unregister_memory_section() into __remove_section().
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
CC: Wen Congyang <redacted>
Signed-off-by: Yasuaki Ishimatsu <redacted>
---
mm/memory_hotplug.c | 13 ++++++++-----
1 files changed, 8 insertions(+), 5 deletions(-)
From: Wen Congyang <redacted>
offlining memory blocks and checking whether memory blocks are offlined
are very similar. This patch introduces a new function to remove
redundant codes.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
CC: Yasuaki Ishimatsu <redacted>
Signed-off-by: Wen Congyang <redacted>
---
mm/memory_hotplug.c | 101 ++++++++++++++++++++++++++++-----------------------
1 files changed, 55 insertions(+), 46 deletions(-)
@@ -1012,20 +1012,14 @@ int offline_pages(unsigned long start_pfn, unsigned long nr_pages)return__offline_pages(start_pfn,start_pfn+nr_pages,120*HZ);}-intremove_memory(u64start,u64size)+staticintwalk_memory_range(unsignedlongstart_pfn,unsignedlongend_pfn,+void*arg,int(*func)(structmemory_block*,void*)){structmemory_block*mem=NULL;structmem_section*section;-unsignedlongstart_pfn,end_pfn;unsignedlongpfn,section_nr;intret;-intreturn_on_error=0;-intretry=0;--start_pfn=PFN_DOWN(start);-end_pfn=start_pfn+PFN_DOWN(size);-repeat:for(pfn=start_pfn;pfn<end_pfn;pfn+=PAGES_PER_SECTION){section_nr=pfn_to_section_nr(pfn);if(!present_section_nr(section_nr))
@@ -1042,22 +1036,61 @@ repeat:if(!mem)continue;-ret=offline_memory_block(mem);+ret=func(mem,arg);if(ret){-if(return_on_error){-kobject_put(&mem->dev.kobj);-returnret;-}else{-retry=1;-}+kobject_put(&mem->dev.kobj);+returnret;}}if(mem)kobject_put(&mem->dev.kobj);-if(retry){-return_on_error=1;+return0;+}++staticintoffline_memory_block_cb(structmemory_block*mem,void*arg)+{+int*ret=arg;+interror=offline_memory_block(mem);++if(error!=0&&*ret==0)+*ret=error;++return0;+}++staticintis_memblock_offlined_cb(structmemory_block*mem,void*arg)+{+intret=!is_memblock_offlined(mem);++if(unlikely(ret))+pr_warn("removing memory fails, because memory "+"[%#010llx-%#010llx] is onlined\n",+PFN_PHYS(section_nr_to_pfn(mem->start_section_nr)),+PFN_PHYS(section_nr_to_pfn(mem->end_section_nr+1))-1);++returnret;+}++intremove_memory(u64start,u64size)+{+unsignedlongstart_pfn,end_pfn;+intret=0;+intretry=1;++start_pfn=PFN_DOWN(start);+end_pfn=start_pfn+PFN_DOWN(size);++repeat:+walk_memory_range(start_pfn,end_pfn,&ret,+offline_memory_block_cb);+if(ret){+if(!retry)+returnret;++retry=0;+ret=0;gotorepeat;}
@@ -1075,37 +1108,13 @@ repeat:*memoryblocksareofflined.*/-for(pfn=start_pfn;pfn<end_pfn;pfn+=PAGES_PER_SECTION){-section_nr=pfn_to_section_nr(pfn);-if(!present_section_nr(section_nr))-continue;--section=__nr_to_section(section_nr);-/* same memblock? */-if(mem)-if((section_nr>=mem->start_section_nr)&&-(section_nr<=mem->end_section_nr))-continue;--mem=find_memory_block_hinted(section,mem);-if(!mem)-continue;--ret=is_memblock_offlined(mem);-if(!ret){-pr_warn("removing memory fails, because memory "-"[%#010llx-%#010llx] is onlined\n",-PFN_PHYS(section_nr_to_pfn(mem->start_section_nr)),-PFN_PHYS(section_nr_to_pfn(mem->end_section_nr+1))-1);--kobject_put(&mem->dev.kobj);-unlock_memory_hotplug();-returnret;-}+ret=walk_memory_range(start_pfn,end_pfn,NULL,+is_memblock_offlined_cb);+if(ret){+unlock_memory_hotplug();+returnret;}-if(mem)-kobject_put(&mem->dev.kobj);unlock_memory_hotplug();return0;
From: Wen Congyang <redacted>
memory can't be offlined when CONFIG_MEMCG is selected.
For example: there is a memory device on node 1. The address range
is [1G, 1.5G). You will find 4 new directories memory8, memory9, memory10,
and memory11 under the directory /sys/devices/system/memory/.
If CONFIG_MEMCG is selected, we will allocate memory to store page cgroup
when we online pages. When we online memory8, the memory stored page cgroup
is not provided by this memory device. But when we online memory9, the memory
stored page cgroup may be provided by memory8. So we can't offline memory8
now. We should offline the memory in the reversed order.
When the memory device is hotremoved, we will auto offline memory provided
by this memory device. But we don't know which memory is onlined first, so
offlining memory may fail. In such case, iterate twice to offline the memory.
1st iterate: offline every non primary memory block.
2nd iterate: offline primary (i.e. first added) memory block.
This idea is suggested by KOSAKI Motohiro.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
CC: Yasuaki Ishimatsu <redacted>
Signed-off-by: Wen Congyang <redacted>
---
mm/memory_hotplug.c | 16 ++++++++++++++--
1 files changed, 14 insertions(+), 2 deletions(-)
From: Ni zhan Chen <hidden> Date: 2012-10-23 10:30:20
On 10/23/2012 06:30 PM, wency@cn.fujitsu.com wrote:
From: Wen Congyang <redacted>
The patchset doesn't support kernel memory hot-remove, correct? If the
answer is yes, you should point out in your patchset changelog.
The patch-set was divided from following thread's patch-set.
https://lkml.org/lkml/2012/9/5/201
The last version of this patchset:
https://lkml.org/lkml/2012/10/5/469
If you want to know the reason, please read following thread.
https://lkml.org/lkml/2012/10/2/83
The patch-set has only the function of kernel core side for physical
memory hot remove. So if you use the patch, please apply following
patches.
- bug fix for memory hot remove
https://lkml.org/lkml/2012/10/19/56
- acpi framework
https://lkml.org/lkml/2012/10/19/156
The patches can free/remove the following things:
- /sys/firmware/memmap/X/{end, start, type} : [PATCH 2/10]
- mem_section and related sysfs files : [PATCH 3-4/10]
- memmap of sparse-vmemmap : [PATCH 5-7/10]
- page table of removed memory : [RFC PATCH 8/10]
- node and related sysfs files : [RFC PATCH 9-10/10]
* [PATCH 2/10] checks whether the memory can be removed or not.
If you find lack of function for physical memory hot-remove, please let me
know.
How to test this patchset?
1. apply this patchset and build the kernel. MEMORY_HOTPLUG, MEMORY_HOTREMOVE,
ACPI_HOTPLUG_MEMORY must be selected.
2. load the module acpi_memhotplug
3. hotplug the memory device(it depends on your hardware)
You will see the memory device under the directory /sys/bus/acpi/devices/.
Its name is PNP0C80:XX.
4. online/offline pages provided by this memory device
You can write online/offline to /sys/devices/system/memory/memoryX/state to
online/offline pages provided by this memory device
5. hotremove the memory device
You can hotremove the memory device by the hardware, or writing 1 to
/sys/bus/acpi/devices/PNP0C80:XX/eject.
Note: if the memory provided by the memory device is used by the kernel, it
can't be offlined. It is not a bug.
Known problems:
1. hotremoving memory device may cause kernel panicked
This bug will be fixed by Liu Jiang's patch:
https://lkml.org/lkml/2012/7/3/1
Changelogs from v1 to v2:
Patch1: new patch, offline memory twice. 1st iterate: offline every non primary
memory block. 2nd iterate: offline primary (i.e. first added) memory
block.
Patch3: new patch, no logical change, just remove reduntant codes.
Patch9: merge the patch from wujianguo into this patch. flush tlb on all cpu
after the pagetable is changed.
Patch12: new patch, free node_data when a node is offlined
Wen Congyang (6):
memory-hotplug: try to offline the memory twice to avoid dependence
memory-hotplug: remove redundant codes
memory-hotplug: introduce new function arch_remove_memory() for
removing page table depends on architecture
memory-hotplug: remove page table of x86_64 architecture
memory-hotplug: remove sysfs file of node
memory-hotplug: free node_data when a node is offlined
Yasuaki Ishimatsu (6):
memory-hotplug: check whether all memory blocks are offlined or not
when removing memory
memory-hotplug: remove /sys/firmware/memmap/X sysfs
memory-hotplug: unregister memory section on SPARSEMEM_VMEMMAP
memory-hotplug: implement register_page_bootmem_info_section of
sparse-vmemmap
memory-hotplug: remove memmap of sparse-vmemmap
memory-hotplug: memory_hotplug: clear zone when removing the memory
arch/ia64/mm/discontig.c | 14 ++
arch/ia64/mm/init.c | 18 ++
arch/powerpc/mm/init_64.c | 14 ++
arch/powerpc/mm/mem.c | 12 +
arch/s390/mm/init.c | 12 +
arch/s390/mm/vmem.c | 14 ++
arch/sh/mm/init.c | 17 ++
arch/sparc/mm/init_64.c | 14 ++
arch/tile/mm/init.c | 8 +
arch/x86/include/asm/pgtable_types.h | 1 +
arch/x86/mm/init_32.c | 12 +
arch/x86/mm/init_64.c | 409 ++++++++++++++++++++++++++++++++++
arch/x86/mm/pageattr.c | 47 ++--
drivers/acpi/acpi_memhotplug.c | 8 +-
drivers/base/memory.c | 6 +
drivers/firmware/memmap.c | 98 ++++++++-
include/linux/firmware-map.h | 6 +
include/linux/memory_hotplug.h | 15 +-
include/linux/mm.h | 5 +-
mm/memory_hotplug.c | 409 ++++++++++++++++++++++++++++++++--
mm/sparse.c | 5 +-
21 files changed, 1087 insertions(+), 57 deletions(-)
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
On 10/23/2012 06:30 PM, wency@cn.fujitsu.com wrote:
quoted
From: Wen Congyang <redacted>
The patchset doesn't support kernel memory hot-remove, correct? If the
answer is yes, you should point out in your patchset changelog.
The answer is no. If you only apply this patchset, you only can hotremove
the memory by SCI. If you want to hotremove it by writing 1 to the file
/sys/bus/acpi/devices/PNP0C80:XX/eject, you should apply the following
patchset:
https://lkml.org/lkml/2012/10/19/156
Thanks
Wen Congyang
quoted
The patch-set was divided from following thread's patch-set.
https://lkml.org/lkml/2012/9/5/201
The last version of this patchset:
https://lkml.org/lkml/2012/10/5/469
If you want to know the reason, please read following thread.
https://lkml.org/lkml/2012/10/2/83
The patch-set has only the function of kernel core side for physical
memory hot remove. So if you use the patch, please apply following
patches.
- bug fix for memory hot remove
https://lkml.org/lkml/2012/10/19/56
- acpi framework
https://lkml.org/lkml/2012/10/19/156
The patches can free/remove the following things:
- /sys/firmware/memmap/X/{end, start, type} : [PATCH 2/10]
- mem_section and related sysfs files : [PATCH 3-4/10]
- memmap of sparse-vmemmap : [PATCH 5-7/10]
- page table of removed memory : [RFC PATCH 8/10]
- node and related sysfs files : [RFC PATCH 9-10/10]
* [PATCH 2/10] checks whether the memory can be removed or not.
If you find lack of function for physical memory hot-remove, please
let me
know.
How to test this patchset?
1. apply this patchset and build the kernel. MEMORY_HOTPLUG,
MEMORY_HOTREMOVE,
ACPI_HOTPLUG_MEMORY must be selected.
2. load the module acpi_memhotplug
3. hotplug the memory device(it depends on your hardware)
You will see the memory device under the directory
/sys/bus/acpi/devices/.
Its name is PNP0C80:XX.
4. online/offline pages provided by this memory device
You can write online/offline to
/sys/devices/system/memory/memoryX/state to
online/offline pages provided by this memory device
5. hotremove the memory device
You can hotremove the memory device by the hardware, or writing 1 to
/sys/bus/acpi/devices/PNP0C80:XX/eject.
Note: if the memory provided by the memory device is used by the
kernel, it
can't be offlined. It is not a bug.
Known problems:
1. hotremoving memory device may cause kernel panicked
This bug will be fixed by Liu Jiang's patch:
https://lkml.org/lkml/2012/7/3/1
Changelogs from v1 to v2:
Patch1: new patch, offline memory twice. 1st iterate: offline every
non primary
memory block. 2nd iterate: offline primary (i.e. first
added) memory
block.
Patch3: new patch, no logical change, just remove reduntant codes.
Patch9: merge the patch from wujianguo into this patch. flush tlb on
all cpu
after the pagetable is changed.
Patch12: new patch, free node_data when a node is offlined
Wen Congyang (6):
memory-hotplug: try to offline the memory twice to avoid dependence
memory-hotplug: remove redundant codes
memory-hotplug: introduce new function arch_remove_memory() for
removing page table depends on architecture
memory-hotplug: remove page table of x86_64 architecture
memory-hotplug: remove sysfs file of node
memory-hotplug: free node_data when a node is offlined
Yasuaki Ishimatsu (6):
memory-hotplug: check whether all memory blocks are offlined or not
when removing memory
memory-hotplug: remove /sys/firmware/memmap/X sysfs
memory-hotplug: unregister memory section on SPARSEMEM_VMEMMAP
memory-hotplug: implement register_page_bootmem_info_section of
sparse-vmemmap
memory-hotplug: remove memmap of sparse-vmemmap
memory-hotplug: memory_hotplug: clear zone when removing the memory
arch/ia64/mm/discontig.c | 14 ++
arch/ia64/mm/init.c | 18 ++
arch/powerpc/mm/init_64.c | 14 ++
arch/powerpc/mm/mem.c | 12 +
arch/s390/mm/init.c | 12 +
arch/s390/mm/vmem.c | 14 ++
arch/sh/mm/init.c | 17 ++
arch/sparc/mm/init_64.c | 14 ++
arch/tile/mm/init.c | 8 +
arch/x86/include/asm/pgtable_types.h | 1 +
arch/x86/mm/init_32.c | 12 +
arch/x86/mm/init_64.c | 409
++++++++++++++++++++++++++++++++++
arch/x86/mm/pageattr.c | 47 ++--
drivers/acpi/acpi_memhotplug.c | 8 +-
drivers/base/memory.c | 6 +
drivers/firmware/memmap.c | 98 ++++++++-
include/linux/firmware-map.h | 6 +
include/linux/memory_hotplug.h | 15 +-
include/linux/mm.h | 5 +-
mm/memory_hotplug.c | 409
++++++++++++++++++++++++++++++++--
mm/sparse.c | 5 +-
21 files changed, 1087 insertions(+), 57 deletions(-)
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Yasuaki Ishimatsu <redacted>
When a memory is added, we update zone's and pgdat's start_pfn and
spanned_pages in the function __add_zone(). So we should revert them
when the memory is removed.
The patch adds a new function __remove_zone() to do this.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
Signed-off-by: Yasuaki Ishimatsu <redacted>
Signed-off-by: Wen Congyang <redacted>
---
mm/memory_hotplug.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 207 insertions(+), 0 deletions(-)
@@ -312,10 +312,213 @@ static int __meminit __add_section(int nid, struct zone *zone,returnregister_new_memory(nid,__pfn_to_section(phys_start_pfn));}+/* find the smallest valid pfn in the range [start_pfn, end_pfn) */+staticintfind_smallest_section_pfn(intnid,structzone*zone,+unsignedlongstart_pfn,+unsignedlongend_pfn)+{+structmem_section*ms;++for(;start_pfn<end_pfn;start_pfn+=PAGES_PER_SECTION){+ms=__pfn_to_section(start_pfn);++if(unlikely(!valid_section(ms)))+continue;++if(unlikely(pfn_to_nid(start_pfn))!=nid)
if (unlikely(pfn_to_nid(start_pfn) != nid))
+ continue;
+
+ if (zone && zone != page_zone(pfn_to_page(start_pfn)))
+ continue;
+
+ return start_pfn;
+ }
+
+ return 0;
+}
+
+/* find the biggest valid pfn in the range [start_pfn, end_pfn). */
+static int find_biggest_section_pfn(int nid, struct zone *zone,
+ unsigned long start_pfn,
+ unsigned long end_pfn)
+{
+ struct mem_section *ms;
+ unsigned long pfn;
+
+ /* pfn is the end pfn of a memory section. */
+ pfn = end_pfn - 1;
+ for (; pfn >= start_pfn; pfn -= PAGES_PER_SECTION) {
+ ms = __pfn_to_section(pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ if (unlikely(pfn_to_nid(pfn)) != nid)
if (unlikely(pfn_to_nid(pfn) != nid))
quoted hunk
+ continue;
+
+ if (zone && zone != page_zone(pfn_to_page(pfn)))
+ continue;
+
+ return pfn;
+ }
+
+ return 0;
+}
+
+static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
+ unsigned long end_pfn)
+{
+ unsigned long zone_start_pfn = zone->zone_start_pfn;
+ unsigned long zone_end_pfn = zone->zone_start_pfn + zone->spanned_pages;
+ unsigned long pfn;
+ struct mem_section *ms;
+ int nid = zone_to_nid(zone);
+
+ zone_span_writelock(zone);
+ if (zone_start_pfn == start_pfn) {
+ /*
+ * If the section is smallest section in the zone, it need
+ * shrink zone->zone_start_pfn and zone->zone_spanned_pages.
+ * In this case, we find second smallest valid mem_section
+ * for shrinking zone.
+ */
+ pfn = find_smallest_section_pfn(nid, zone, end_pfn,
+ zone_end_pfn);
+ if (pfn) {
+ zone->zone_start_pfn = pfn;
+ zone->spanned_pages = zone_end_pfn - pfn;
+ }
+ } else if (zone_end_pfn == end_pfn) {
+ /*
+ * If the section is biggest section in the zone, it need
+ * shrink zone->spanned_pages.
+ * In this case, we find second biggest valid mem_section for
+ * shrinking zone.
+ */
+ pfn = find_biggest_section_pfn(nid, zone, zone_start_pfn,
+ start_pfn);
+ if (pfn)
+ zone->spanned_pages = pfn - zone_start_pfn + 1;
+ }
+
+ /*
+ * The section is not biggest or smallest mem_section in the zone, it
+ * only creates a hole in the zone. So in this case, we need not
+ * change the zone. But perhaps, the zone has only hole data. Thus
+ * it check the zone has only hole or not.
+ */
+ pfn = zone_start_pfn;
+ for (; pfn < zone_end_pfn; pfn += PAGES_PER_SECTION) {
+ ms = __pfn_to_section(pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ if (page_zone(pfn_to_page(pfn)) != zone)
+ continue;
+
+ /* If the section is current section, it continues the loop */
+ if (start_pfn == pfn)
+ continue;
+
+ /* If we find valid section, we have nothing to do */
+ zone_span_writeunlock(zone);
+ return;
+ }
+
+ /* The zone has no valid section */
+ zone->zone_start_pfn = 0;
+ zone->spanned_pages = 0;
+ zone_span_writeunlock(zone);
+}
+
+static void shrink_pgdat_span(struct pglist_data *pgdat,
+ unsigned long start_pfn, unsigned long end_pfn)
+{
+ unsigned long pgdat_start_pfn = pgdat->node_start_pfn;
+ unsigned long pgdat_end_pfn =
+ pgdat->node_start_pfn + pgdat->node_spanned_pages;
+ unsigned long pfn;
+ struct mem_section *ms;
+ int nid = pgdat->node_id;
+
+ if (pgdat_start_pfn == start_pfn) {
+ /*
+ * If the section is smallest section in the pgdat, it need
+ * shrink pgdat->node_start_pfn and pgdat->node_spanned_pages.
+ * In this case, we find second smallest valid mem_section
+ * for shrinking zone.
+ */
+ pfn = find_smallest_section_pfn(nid, NULL, end_pfn,
+ pgdat_end_pfn);
+ if (pfn) {
+ pgdat->node_start_pfn = pfn;
+ pgdat->node_spanned_pages = pgdat_end_pfn - pfn;
+ }
+ } else if (pgdat_end_pfn == end_pfn) {
+ /*
+ * If the section is biggest section in the pgdat, it need
+ * shrink pgdat->node_spanned_pages.
+ * In this case, we find second biggest valid mem_section for
+ * shrinking zone.
+ */
+ pfn = find_biggest_section_pfn(nid, NULL, pgdat_start_pfn,
+ start_pfn);
+ if (pfn)
+ pgdat->node_spanned_pages = pfn - pgdat_start_pfn + 1;
+ }
+
+ /*
+ * If the section is not biggest or smallest mem_section in the pgdat,
+ * it only creates a hole in the pgdat. So in this case, we need not
+ * change the pgdat.
+ * But perhaps, the pgdat has only hole data. Thus it check the pgdat
+ * has only hole or not.
+ */
+ pfn = pgdat_start_pfn;
+ for (; pfn < pgdat_end_pfn; pfn += PAGES_PER_SECTION) {
+ ms = __pfn_to_section(pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ if (pfn_to_nid(pfn) != nid)
+ continue;
+
+ /* If the section is current section, it continues the loop */
+ if (start_pfn == pfn)
+ continue;
+
+ /* If we find valid section, we have nothing to do */
+ return;
+ }
+
+ /* The pgdat has no valid section */
+ pgdat->node_start_pfn = 0;
+ pgdat->node_spanned_pages = 0;
+}
+
+static void __remove_zone(struct zone *zone, unsigned long start_pfn)
+{
+ struct pglist_data *pgdat = zone->zone_pgdat;
+ int nr_pages = PAGES_PER_SECTION;
+ int zone_type;
+ unsigned long flags;
+
+ zone_type = zone - pgdat->node_zones;
+
+ pgdat_resize_lock(zone->zone_pgdat, &flags);
+ shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
+ shrink_pgdat_span(pgdat, start_pfn, start_pfn + nr_pages);
+ pgdat_resize_unlock(zone->zone_pgdat, &flags);
+}
+
static int __remove_section(struct zone *zone, struct mem_section *ms)
{
unsigned long flags;
struct pglist_data *pgdat = zone->zone_pgdat;
+ unsigned long start_pfn;
+ int scn_nr;
int ret = -EINVAL;
if (!valid_section(ms))
@@ -325,6 +528,10 @@ static int __remove_section(struct zone *zone, struct mem_section *ms) if (ret) return ret;+ scn_nr = __section_nr(ms);+ start_pfn = section_nr_to_pfn(scn_nr);+ __remove_zone(zone, start_pfn);+ pgdat_resize_lock(pgdat, &flags); sparse_remove_one_section(zone, ms); pgdat_resize_unlock(pgdat, &flags);
From: Yasuaki Ishimatsu <redacted>
When a memory is added, we update zone's and pgdat's start_pfn and
spanned_pages in the function __add_zone(). So we should revert them
when the memory is removed.
The patch adds a new function __remove_zone() to do this.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <redacted>
CC: Len Brown <redacted>
CC: Christoph Lameter <redacted>
Cc: Minchan Kim <redacted>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <redacted>
Signed-off-by: Yasuaki Ishimatsu <redacted>
Signed-off-by: Wen Congyang <redacted>
---
mm/memory_hotplug.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 207 insertions(+), 0 deletions(-)
@@ -312,10 +312,213 @@ static int __meminit __add_section(int nid, struct zone *zone,returnregister_new_memory(nid,__pfn_to_section(phys_start_pfn));}+/* find the smallest valid pfn in the range [start_pfn, end_pfn) */+staticintfind_smallest_section_pfn(intnid,structzone*zone,+unsignedlongstart_pfn,+unsignedlongend_pfn)+{+structmem_section*ms;++for(;start_pfn<end_pfn;start_pfn+=PAGES_PER_SECTION){+ms=__pfn_to_section(start_pfn);++if(unlikely(!valid_section(ms)))+continue;++if(unlikely(pfn_to_nid(start_pfn))!=nid)
if (unlikely(pfn_to_nid(start_pfn) != nid))
Thanks for pointing it out. I will fix it soon.
Wen Congyang
quoted
+ continue;
+
+ if (zone && zone != page_zone(pfn_to_page(start_pfn)))
+ continue;
+
+ return start_pfn;
+ }
+
+ return 0;
+}
+
+/* find the biggest valid pfn in the range [start_pfn, end_pfn). */
+static int find_biggest_section_pfn(int nid, struct zone *zone,
+ unsigned long start_pfn,
+ unsigned long end_pfn)
+{
+ struct mem_section *ms;
+ unsigned long pfn;
+
+ /* pfn is the end pfn of a memory section. */
+ pfn = end_pfn - 1;
+ for (; pfn >= start_pfn; pfn -= PAGES_PER_SECTION) {
+ ms = __pfn_to_section(pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ if (unlikely(pfn_to_nid(pfn)) != nid)
if (unlikely(pfn_to_nid(pfn) != nid))
quoted
+ continue;
+
+ if (zone && zone != page_zone(pfn_to_page(pfn)))
+ continue;
+
+ return pfn;
+ }
+
+ return 0;
+}
+
+static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
+ unsigned long end_pfn)
+{
+ unsigned long zone_start_pfn = zone->zone_start_pfn;
+ unsigned long zone_end_pfn = zone->zone_start_pfn + zone->spanned_pages;
+ unsigned long pfn;
+ struct mem_section *ms;
+ int nid = zone_to_nid(zone);
+
+ zone_span_writelock(zone);
+ if (zone_start_pfn == start_pfn) {
+ /*
+ * If the section is smallest section in the zone, it need
+ * shrink zone->zone_start_pfn and zone->zone_spanned_pages.
+ * In this case, we find second smallest valid mem_section
+ * for shrinking zone.
+ */
+ pfn = find_smallest_section_pfn(nid, zone, end_pfn,
+ zone_end_pfn);
+ if (pfn) {
+ zone->zone_start_pfn = pfn;
+ zone->spanned_pages = zone_end_pfn - pfn;
+ }
+ } else if (zone_end_pfn == end_pfn) {
+ /*
+ * If the section is biggest section in the zone, it need
+ * shrink zone->spanned_pages.
+ * In this case, we find second biggest valid mem_section for
+ * shrinking zone.
+ */
+ pfn = find_biggest_section_pfn(nid, zone, zone_start_pfn,
+ start_pfn);
+ if (pfn)
+ zone->spanned_pages = pfn - zone_start_pfn + 1;
+ }
+
+ /*
+ * The section is not biggest or smallest mem_section in the zone, it
+ * only creates a hole in the zone. So in this case, we need not
+ * change the zone. But perhaps, the zone has only hole data. Thus
+ * it check the zone has only hole or not.
+ */
+ pfn = zone_start_pfn;
+ for (; pfn < zone_end_pfn; pfn += PAGES_PER_SECTION) {
+ ms = __pfn_to_section(pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ if (page_zone(pfn_to_page(pfn)) != zone)
+ continue;
+
+ /* If the section is current section, it continues the loop */
+ if (start_pfn == pfn)
+ continue;
+
+ /* If we find valid section, we have nothing to do */
+ zone_span_writeunlock(zone);
+ return;
+ }
+
+ /* The zone has no valid section */
+ zone->zone_start_pfn = 0;
+ zone->spanned_pages = 0;
+ zone_span_writeunlock(zone);
+}
+
+static void shrink_pgdat_span(struct pglist_data *pgdat,
+ unsigned long start_pfn, unsigned long end_pfn)
+{
+ unsigned long pgdat_start_pfn = pgdat->node_start_pfn;
+ unsigned long pgdat_end_pfn =
+ pgdat->node_start_pfn + pgdat->node_spanned_pages;
+ unsigned long pfn;
+ struct mem_section *ms;
+ int nid = pgdat->node_id;
+
+ if (pgdat_start_pfn == start_pfn) {
+ /*
+ * If the section is smallest section in the pgdat, it need
+ * shrink pgdat->node_start_pfn and pgdat->node_spanned_pages.
+ * In this case, we find second smallest valid mem_section
+ * for shrinking zone.
+ */
+ pfn = find_smallest_section_pfn(nid, NULL, end_pfn,
+ pgdat_end_pfn);
+ if (pfn) {
+ pgdat->node_start_pfn = pfn;
+ pgdat->node_spanned_pages = pgdat_end_pfn - pfn;
+ }
+ } else if (pgdat_end_pfn == end_pfn) {
+ /*
+ * If the section is biggest section in the pgdat, it need
+ * shrink pgdat->node_spanned_pages.
+ * In this case, we find second biggest valid mem_section for
+ * shrinking zone.
+ */
+ pfn = find_biggest_section_pfn(nid, NULL, pgdat_start_pfn,
+ start_pfn);
+ if (pfn)
+ pgdat->node_spanned_pages = pfn - pgdat_start_pfn + 1;
+ }
+
+ /*
+ * If the section is not biggest or smallest mem_section in the pgdat,
+ * it only creates a hole in the pgdat. So in this case, we need not
+ * change the pgdat.
+ * But perhaps, the pgdat has only hole data. Thus it check the pgdat
+ * has only hole or not.
+ */
+ pfn = pgdat_start_pfn;
+ for (; pfn < pgdat_end_pfn; pfn += PAGES_PER_SECTION) {
+ ms = __pfn_to_section(pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ if (pfn_to_nid(pfn) != nid)
+ continue;
+
+ /* If the section is current section, it continues the loop */
+ if (start_pfn == pfn)
+ continue;
+
+ /* If we find valid section, we have nothing to do */
+ return;
+ }
+
+ /* The pgdat has no valid section */
+ pgdat->node_start_pfn = 0;
+ pgdat->node_spanned_pages = 0;
+}
+
+static void __remove_zone(struct zone *zone, unsigned long start_pfn)
+{
+ struct pglist_data *pgdat = zone->zone_pgdat;
+ int nr_pages = PAGES_PER_SECTION;
+ int zone_type;
+ unsigned long flags;
+
+ zone_type = zone - pgdat->node_zones;
+
+ pgdat_resize_lock(zone->zone_pgdat, &flags);
+ shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
+ shrink_pgdat_span(pgdat, start_pfn, start_pfn + nr_pages);
+ pgdat_resize_unlock(zone->zone_pgdat, &flags);
+}
+
static int __remove_section(struct zone *zone, struct mem_section *ms)
{
unsigned long flags;
struct pglist_data *pgdat = zone->zone_pgdat;
+ unsigned long start_pfn;
+ int scn_nr;
int ret = -EINVAL;
if (!valid_section(ms))
@@ -325,6 +528,10 @@ static int __remove_section(struct zone *zone, struct mem_section *ms) if (ret) return ret;+ scn_nr = __section_nr(ms);+ start_pfn = section_nr_to_pfn(scn_nr);+ __remove_zone(zone, start_pfn);+ pgdat_resize_lock(pgdat, &flags); sparse_remove_one_section(zone, ms); pgdat_resize_unlock(pgdat, &flags);
On 10/23/2012 06:30 PM, wency@cn.fujitsu.com wrote:
From: Yasuaki Ishimatsu<redacted>
......
+ /* The zone has no valid section */
+ zone->zone_start_pfn = 0;
+ zone->spanned_pages = 0;
+ zone_span_writeunlock(zone);
+}
+
+static void shrink_pgdat_span(struct pglist_data *pgdat,
+ unsigned long start_pfn, unsigned long end_pfn)
+{
......
+ /* The pgdat has no valid section */
+ pgdat->node_start_pfn = 0;
+ pgdat->node_spanned_pages = 0;
+}
Hi,
If we hot-remove memory only and leave the cpus alive, the corresponding
node will not be removed. But the node_start_pfn and node_spanned_pages
in pg_data will be reset to 0. In this case, when we hot-add the memory
back next time, the node_start_pfn will always be 0 because no pfn is less
than 0. After that, if we hot-remove the memory again, it will cause kernel
panic in function find_biggest_section_pfn() when it tries to scan all
the pfns.
The zone will also have the same problem.
This patch sets start_pfn to the start_pfn of the section being added when
spanned_pages of the zone or pg_data is 0.
---How to reproduce---
1. hot-add a container with some memory and cpus;
2. hot-remove the container's memory, and leave cpus there;
3. hot-add these memory again;
4. hot-remove them again;
then, the kernel will panic.
---Call trace---
[10530.646285] BUG: unable to handle kernel paging request at
00000fff82a8cc38
[10530.729670] IP: [<ffffffff811c0d55>] find_biggest_section_pfn+0xe5/0x180
......
[10533.064975] Call Trace:
[10533.094162] [<ffffffff811c0fcf>] ? __remove_zone+0x2f/0x1b0
[10533.161757] [<ffffffff811c1124>] __remove_zone+0x184/0x1b0
[10533.228318] [<ffffffff811c11dc>] __remove_section+0x8c/0xb0
[10533.295916] [<ffffffff811c12e7>] __remove_pages+0xe7/0x120
[10533.362476] [<ffffffff81654f7c>] arch_remove_memory+0x2c/0x80
[10533.432151] [<ffffffff81655bb6>] remove_memory+0x56/0x90
[10533.496633] [<ffffffff813da0c8>]
acpi_memory_device_remove_memory+0x48/0x73
[10533.580846] [<ffffffff813da55a>] acpi_memory_device_notify+0x153/0x274
[10533.659865] [<ffffffff813a63cf>] ? acpi_bus_get_device+0x2f/0x77
[10533.732653] [<ffffffff813a6589>] ? acpi_bus_notify+0xb5/0xec
[10533.801291] [<ffffffff813b6786>] acpi_ev_notify_dispatch+0x41/0x5f
[10533.876156] [<ffffffff813a3867>] acpi_os_execute_deferred+0x27/0x34
[10533.952062] [<ffffffff81090589>] process_one_work+0x219/0x680
[10534.021736] [<ffffffff81090528>] ? process_one_work+0x1b8/0x680
[10534.093488] [<ffffffff813a3840>] ?
acpi_os_wait_events_complete+0x23/0x23
[10534.175622] [<ffffffff810923be>] worker_thread+0x12e/0x320
[10534.242181] [<ffffffff81092290>] ? manage_workers+0x110/0x110
[10534.311855] [<ffffffff81098396>] kthread+0xc6/0xd0
[10534.370111] [<ffffffff8167c7c4>] kernel_thread_helper+0x4/0x10
[10534.440824] [<ffffffff81672230>] ? retint_restore_args+0x13/0x13
[10534.513612] [<ffffffff810982d0>] ? __init_kthread_worker+0x70/0x70
[10534.588480] [<ffffffff8167c7c0>] ? gs_change+0x13/0x13
......
[10535.045543] ---[ end trace 96d845dbf33fee11 ]---
Signed-off-by: Tang Chen <redacted>
---
mm/memory_hotplug.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)