bootmem allocator分析

来源:互联网 发布:手机qq防盗软件 编辑:程序博客网 时间:2024/04/30 21:20

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

find_limits():

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

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

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

arm_bootmem_init():

arm_bootmem_init()是核心的函数。

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

bootmem_bootmap_pages():

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

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

memblock_alloc_base():

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

init_bootmem_node():

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

free_bootmem()/reserve_bootmem():

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

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

Bootmem内存分配:

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

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

[html] view plaincopy
  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 plaincopy
  1. void free_initmem(void)  
  2. {  
  3.     unsigned long reclaimed_initmem;  
  4.   
  5.     poison_init_mem(__init_begin, __init_end - __init_begin);  
  6.     if (!machine_is_integrator() && !machine_is_cintegrator()) {  
  7.         reclaimed_initmem = free_area(__phys_to_pfn(__pa(__init_begin)),  
  8.                         __phys_to_pfn(__pa(__init_end)),  
  9.                         "init");  
  10.         totalram_pages += reclaimed_initmem;  
  11.     }  
  12. }  

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


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


0 0
原创粉丝点击