bootmem allocator分析

Codebase: android 4.1

Kernel: 3.4.0

Chipset: msm8x25q

在系统启动时,内存的伙伴系统/slab算法还没有初始化之前,系统也需要来作内存管理,分配一些核心数据结构,bootmem分配器就实现了该功能,它用于在启动阶段早期分配内存。

Bootmem分配器使用位图来管理页,位图数量和系统的物理内存也数量是相同的。当页被使用时,就标记为1,否则为0表示空闲页。

由于该分配器管理机制比较简单,并没有考虑性能和通用性,所以在伙伴系统完成初始化之后,bootmem分配器就要交出管理权,然后销毁掉。

在UMA系统上,只有一个bootmemallocator,名字叫bootmem_node_data, 位于kernel/mm/bootmem.c中,它通过成为struct pglist_data的一个元素与变量contig_page_data联系起来。

[html] view plain copy print ?
  1. struct pglist_data __refdata contig_page_data = { 
  2.     .bdata = &bootmem_node_data[0] 
  3. }; 
  4. bootmem_data_t bootmem_node_data[MAX_NUMNODES] __initdata; 

Bootmem 初始化:

系统启动的时候有如下流程:

start_kernel -> setup_arch -> paging_init-> bootmem_init

[html] view plain copy print ?
  1. void __init bootmem_init(void) 
  2.     unsigned long min, max_low, max_high; 
  3.  
  4.     max_low = max_high = 0; 
  5.     /*min: 物理内存起始地址pfn号 
  6. max_low:  低端内存结束地址pfn号 
  7. max_high: 高端内存结束地址pfn号 
  8. */ 
  9.     find_limits(&min, &max_low, &max_high); 
  10.     /*根据参数看是初始化Lowmem区域?*/ 
  11.     arm_bootmem_init(min, max_low); 
  12.  
  13.     /* 
  14.      * Sparsemem tries to allocate bootmem in memory_present(), 
  15.      * so must be done after the fixed reservations 
  16.      */ 
  17.     arm_memory_present(); 
  18.  
  19.     /* 
  20.      * sparse_init() needs the bootmem allocator up and running. 
  21.      */ 
  22.     sparse_init(); 
  23.     /* 
  24.      * Now free the memory - free_area_init_node needs 
  25.      * the sparse mem_map arrays initialized by sparse_init() 
  26.      * for memmap_init_zone(), otherwise all PFNs are invalid. 
  27.      */ 
  28.     arm_bootmem_free(min, max_low, max_high); 
  29.  
  30. /*保存lowmem和highmem对应的pfn numbers, 
  31. 这并表示实际能操作的pfn number,因为start pfn不一定从0开始。*/ 
  32.     max_low_pfn = max_low - PHYS_PFN_OFFSET; 
  33.     max_pfn = max_high - PHYS_PFN_OFFSET; 

find_limits():

里面有些函数需要展开来分析下,先看find_limits:

[html] view plain copy print ?
  1. static void __init find_limits(unsigned long *min, unsigned long *max_low, 
  2.                    unsigned long *max_high) 
  3.     struct meminfo *mi = &meminfo; 
  4.     int i; 
  5.     /*循环直到是highmem的bank才停止。*/ 
  6.     /* This assumes the meminfo array is properly sorted */ 
  7.     *min = bank_pfn_start(&mi->bank[0]); 
  8.     for_each_bank (i, mi) 
  9.         if (mi->bank[i].highmem) 
  10.                 break; 
  11.     /*获得lowmem和highmem结束地址的pfn.*/ 
  12.     *max_low = bank_pfn_end(&mi->bank[i - 1]); 
  13.     *max_high = bank_pfn_end(&mi->bank[mi->nr_banks - 1]); 

函数比较简单,不过要注意的是,这里获得的内存是内核当前拥有的memory,当然也包含了已经被reserved的区域。arm_bootmem_init()会重新划分。

arm_bootmem_init():

arm_bootmem_init()是核心的函数。

[html] view plain copy print ?
  1. static void __init arm_bootmem_init(unsigned long start_pfn, 
  2.     unsigned long end_pfn) 
  3.     struct memblock_region *reg; 
  4.     unsigned int boot_pages; 
  5.     phys_addr_t bitmap; 
  6.     pg_data_t *pgdat; 
  7.  
  8.     /* 
  9.      * Allocate the bootmem bitmap page.  This must be in a region 
  10.      * of memory which has already been mapped. 
  11.      */ 
  12.     /*end_pfn – start_pfn为lowmem的pfn numbers。*/ 
  13.     boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn); 
  14.     /*根据pfn numbers来分配bitmap位图, 以L1 cache能操作的字节数作对齐, 
  15. 是为了让L1 cache能操作? 最大能分配地址为end_pfn。*/ 
  16.     bitmap = memblock_alloc_base(boot_pages << PAGE_SHIFT, L1_CACHE_BYTES, 
  17.                 __pfn_to_phys(end_pfn)); 
  18.  
  19.     /* 
  20.      * Initialise the bootmem allocator, handing the 
  21.      * memory banks over to bootmem. 
  22.      */ 
  23.     node_set_online(0); 
  24.     pgdat = NODE_DATA(0); 
  25.     /*初始化pgda也就是全局变量contig_page_data 中的bdata元素,也就是 
  26. bootmem_node_data 变量。*/ 
  27.     init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn); 
  28.     /*在前面meminfo介绍中有说到,struct memblock中的memroy 元素表示 
  29. 空闲内存区域,而reseved表示要保留的区域。所以这里会将reserved对应的 
  30. Bitmap标为1,而free memory标志为0.*/ 
  31.     /* Free the lowmem regions from memblock into bootmem. */ 
  32.     for_each_memblock(memory, reg) { 
  33.         unsigned long start = memblock_region_memory_base_pfn(reg); 
  34.         unsigned long end = memblock_region_memory_end_pfn(reg); 
  35.  
  36.         if (end >= end_pfn) 
  37.             end = end_pfn
  38.         if (start >= end) 
  39.             break; 
  40.  
  41.         free_bootmem(__pfn_to_phys(start), (end - start) << PAGE_SHIFT); 
  42.     } 
  43.  
  44.     /* Reserve the lowmem memblock reserved regions in bootmem. */ 
  45.     for_each_memblock(reserved, reg) { 
  46.         unsigned long start = memblock_region_reserved_base_pfn(reg); 
  47.         unsigned long end = memblock_region_reserved_end_pfn(reg); 
  48.  
  49.         if (end >= end_pfn) 
  50.             end = end_pfn
  51.         if (start >= end) 
  52.             break; 
  53.  
  54.         reserve_bootmem(__pfn_to_phys(start), 
  55.                     (end - start) << PAGE_SHIFT, BOOTMEM_DEFAULT); 
  56.     } 

bootmem_bootmap_pages():

继续分解函数,先看bootmem_bootmap_pages().

[html] view plain copy print ?
  1. unsigned long __init bootmem_bootmap_pages(unsigned long pages) 
  2.     unsigned long bytes = bootmap_bytes(pages); 
  3.     /*以4k作为一个单位分配*/ 
  4.     return PAGE_ALIGN(bytes) >> PAGE_SHIFT; 
  5. static unsigned long __init bootmap_bytes(unsigned long pages) 
  6.     /*每个page作为一个bit保留在unsigned long变量中。 */ 
  7.     unsigned long bytes = DIV_ROUND_UP(pages, 8); 
  8.  
  9. return ALIGN(bytes, sizeof(long)); 

memblock_alloc_base():

[html] view plain copy print ?
  1. phys_addr_t __init memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr) 
  2.     phys_addr_t alloc; 
  3.     alloc = __memblock_alloc_base(size, align, max_addr); 
  4.     if (alloc == 0) 
  5.         panic("ERROR: Failed to allocate 0x%llx bytes below 0x%llx.\n", 
  6.               (unsigned long long) size, (unsigned long long) max_addr); 
  7.  
  8.     return alloc; 
  9. phys_addr_t __init __memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr) 
  10.     return memblock_alloc_base_nid(size, align, max_addr, MAX_NUMNODES); 
  11. static phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size, 
  12.                     phys_addr_t align, phys_addr_t max_addr, 
  13.                     int nid) 
  14.     phys_addr_t found; 
  15.     size = round_up(size, align); 
  16.     found = memblock_find_in_range_node(0, max_addr, size, align, nid); 
  17.     if (found && !memblock_reserve(found, size)) 
  18.         return found; 
  19.     return 0; 
  20. phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t start, 
  21.                     phys_addr_t end, phys_addr_t size, 
  22.                     phys_addr_t align, int nid) 
  23.     phys_addr_t this_start, this_end, cand; 
  24.     u64 i; 
  25.     /* avoid allocating the first page */ 
  26.     /*保留第一页,用来干嘛?*/ 
  27.     start = max_t(phys_addr_t, start, PAGE_SIZE); 
  28.     end = max(start, end); 
  29.     /*从struct memblock的一块空闲区域的最高地址往下分配一块区域。*/ 
  30. for_each_free_mem_range_reverse(i, nid, &this_start, &this_end, NULL) { 
  31.         /*this_start 和this_end 在start 和end中间的话直接返回,否则 
  32. 返回end。*/ 
  33.         this_start = clamp(this_start, start, end); 
  34.         this_end = clamp(this_end, start, end); 
  35.         if (this_end < size
  36.             continue; 
  37.         /*得到分配内存地址,大小为size。*/ 
  38.         cand = round_down(this_end - size, align); 
  39.         if (cand >= this_start) 
  40.             return cand; 
  41.     } 
  42.     return 0; 

init_bootmem_node():

[html] view plain copy print ?
  1. unsigned long __init init_bootmem_node(pg_data_t *pgdat, unsigned long freepfn, 
  2.                 unsigned long startpfn, unsigned long endpfn) 
  3.     return init_bootmem_core(pgdat->bdata, freepfn, startpfn, endpfn); 
  4. static unsigned long __init init_bootmem_core(bootmem_data_t *bdata, 
  5.     unsigned long mapstart, unsigned long start, unsigned long end) 
  6.     unsigned long mapsize; 
  7.  
  8.     mminit_validate_memmodel_limits(&start, &end); 
  9.     /*得到bitmap表的首地址以及最小和最大pfn*/ 
  10.     bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart)); 
  11.     bdata->node_min_pfn = start
  12.     bdata->node_low_pfn = end
  13.     /*加入到全局的bdata_list链表变量中,方便管理。*/ 
  14.     link_bootmem(bdata); 
  15.  
  16.     /* 
  17.      * Initially all pages are reserved - setup_arch() has to 
  18.      * register free RAM areas explicitly. 
  19.      */ 
  20.     /*将bitmap表中每个bit都设置成已经使用了。下一步 
  21. for_each_memblock()会重新设置。*/ 
  22.     mapsize = bootmap_bytes(end - start); 
  23.     memset(bdata->node_bootmem_map, 0xff, mapsize); 
  24.  
  25.     bdebug("nid=%td start=%lx map=%lx end=%lx mapsize=%lx\n", 
  26.         bdata - bootmem_node_data, start, mapstart, end, mapsize); 
  27.  
  28.     return mapsize; 

free_bootmem()/reserve_bootmem():

这两个函数比较简单了,表示将bootmem的页分别标记成空闲和使用中。

到此,bootmem allocator已经初始化完成。

Bootmem内存分配:

Bootmem的分配有多种接口,不过最终调用的都是__alloc_bootmem(),而__alloc_bootmem()调用了___alloc_bootmem_nopanic()。

路径: kernel/kernel/include/linux/bootmem.h

[html] view plain copy print ?
  1. /*按指定size从ZONE_NORMAL区域分配*/ 
  2. #define alloc_bootmem(x) \   
  3.     __alloc_bootmem(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT) 
  4. /*按指定size从ZONE_NORMAL区域分配, 以align对齐*/ 
  5. #define alloc_bootmem_align(x, align) \ 
  6.     __alloc_bootmem(x, align, BOOTMEM_LOW_LIMIT) 
  7. /*按指定size从ZONE_NORMAL区域分配, 以一页对齐*/ 
  8. #define alloc_bootmem_pages(x) \ 
  9.     __alloc_bootmem(x, PAGE_SIZE, BOOTMEM_LOW_LIMIT) 
  10. /* SMP_CACHE_BYTES 是为了让数据能更好地在L1 cache中使用,虽然是SMP开头。*/ 
  11. #define alloc_bootmem_nopanic(x) \ 
  12.     __alloc_bootmem_nopanic(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT) 
  13. #define alloc_bootmem_node(pgdat, x) \ 
  14.     __alloc_bootmem_node(pgdat, x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT) 
  15.  
  16. /*以_node后缀结尾表示只在NUMA系统上使用。*/ 
  17. #define alloc_bootmem_node(pgdat, x) \ 
  18.     __alloc_bootmem_node(pgdat, x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT) 
  19. #define alloc_bootmem_node_nopanic(pgdat, x) \ 
  20.     __alloc_bootmem_node_nopanic(pgdat, x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT) 
  21. #define alloc_bootmem_pages_node(pgdat, x) \ 
  22.     __alloc_bootmem_node(pgdat, x, PAGE_SIZE, BOOTMEM_LOW_LIMIT) 
  23. #define alloc_bootmem_pages_node_nopanic(pgdat, x) \ 
  24.     __alloc_bootmem_node_nopanic(pgdat, x, PAGE_SIZE, BOOTMEM_LOW_LIMIT) 
  25.  
  26. /*这几个和上面的区别是从ZONE_DMA区域分配。*/ 
  27. #define alloc_bootmem_low(x) \ 
  28.     __alloc_bootmem_low(x, SMP_CACHE_BYTES, 0) 
  29. #define alloc_bootmem_low_pages(x) \ 
  30.     __alloc_bootmem_low(x, PAGE_SIZE, 0) 
  31. #define alloc_bootmem_low_pages_node(pgdat, x) \ 
  32.     __alloc_bootmem_low_node(pgdat, x, PAGE_SIZE, 0) 

关于内存的分配,请允许我偷懒下,不对代码做详细分析了,有点费时间,流程如下:

__alloc_bootmem -> ___alloc_bootmem -> ___alloc_bootmem_nopanic -> alloc_bootmem_core -> find_next_zero_bit

大概的步骤就是:

1.      扫描bitmap位图,寻找空闲的位

2.      如果查找的页紧挨着上一次分配的页,就先检查这次要分配的内存是否能在上一页直接分配,因为bootmem allocator支持小于一页的分配。

3.      在新分配的页的bitmap对应的bit设置为1后,将当前的偏移保存,如果页没有完全分配,那么页里面的偏移量也保存。

Bootmem内存释放:

内核提供的释放bootmem接口是free_bootmem(unsignedlong addr, unsigned long size), 还有一个是用于NUMA的。

         这个接口没有分析,据资料记载说分配页可能会有风险。

Bootmem停用:

一旦伙伴系统初始化完成之后,bootmemallocator就要停止使用了,系统是通过函数free_all_bootmem()来停止的,有如下调用流程:

start_kernel -> mm_init -> mem_init -> free_all_bootmem -> free_all_bootmem_core -> __free_pages_bootmem -> __free_pages

         可以看到最终调用的是__free_pages(),这个函数会将这些Pages释放到伙伴系统中管理。

注意这里只是将空闲的页释放掉了。占据的页还存在。

         由于bootmem分配的页里面的数据基本上都是用于内存基本结构,在系统运行期间会一直被用到,所以不会被释放。不过像__init这种类型的数据段只在系统开机的时候被使用,所以系统初始化完成之后就可以释放掉了。

         系统使用的函数是free_initmem(), 调用流程如下:

start_kernel -> rest_init -> kernel_init -> init_post -> free_initmem

[html] view plain copy print ?
  1. void free_initmem(void) 
  2.     unsigned long reclaimed_initmem; 
  3.  
  4.     poison_init_mem(__init_begin, __init_end - __init_begin); 
  5.     if (!machine_is_integrator() && !machine_is_cintegrator()) { 
  6.         reclaimed_initmem = free_area(__phys_to_pfn(__pa(__init_begin)), 
  7.                         __phys_to_pfn(__pa(__init_end)), 
  8.                         "init"); 
  9.         totalram_pages += reclaimed_initmem; 
  10.     } 

可以看到,释放的数据就是保存在__init_begin和__init_end那一段之间!当然,最后也是调用__free_pages()释放到伙伴系统中管理的。


使用bootmem分配内存的时候需要注意下一定要处于bootmem 分配器初始化和销毁之间!


2013/03/21

你可能感兴趣的:(bootmem allocator分析)