struct meminfo分析

Chipset:MSM8x25Q

Codebase:Android 4.1

Linux Kernel: 3.4.0

         在linux Kernel中,一开始内存相关的信息是由struct meminfo来保存的,每个物理连续的内存区域被保存为meminfo中的一个元素,也就是说在Linux使用中,整块物理内存可能是不连续的,可能其中某一中间区域是被其他cpu给使用掉了。

         那么内存相关信息又是从哪里收集到的呢,系统在boot阶段,如u-boot会将当前物理内存linux可以使用的部分通过TAG的形式传递给linux内核。Qualcomm使用的是叫lk的boot,不管用的是哪种boot类型,使用TAG来传递参数的原理是一样的。

         下面我们看下Linux内核是如何收集内存信息的。

Meminfo信息收集

系统启动有如下流程:

start_kernel -> setup_arch -> setup_machine_tags-> parse_tags -> parse_tag.

[html] view plain copy print ?
  1. static int __init parse_tag(const struct tag *tag) 
  2.     extern struct tagtable __tagtable_begin, __tagtable_end; 
  3.     struct tagtable *t; 
  4.  
  5.     for (t = &__tagtable_begin; t < &__tagtable_end; t++) 
  6.         if (tag->hdr.tag == t->tag) { 
  7.             t->parse(tag); 
  8.             break; 
  9.         } 
  10.  
  11.     return t < &__tagtable_end; 

__tagtable_begin被定义在kernel/arch/arm/kernel/vmlinux.lds.S中:

[html] view plain copy print ?
  1. .init.tagtable : { 
  2.         __tagtable_begin = .; 
  3.         *(.taglist.init) 
  4.         __tagtable_end = .; 
  5.     } 

另外,在arch/arm/kernel/setup.c中有如下函数定义:

[html] view plain copy print ?
  1. static int __init parse_tag_mem32(const struct tag *tag) 
  2.     return arm_add_memory(tag->u.mem.start, tag->u.mem.size); 
  3. __tagtable(ATAG_MEM, parse_tag_mem32); 

__tagtable是个宏定义:

[html] view plain copy print ?
  1. #define __tagtable(tag, fn) \ 
  2. static const struct tagtable__tagtable_##fn __tag = { tag, fn } 
里面的__tag的宏定义又如下:

[html] view plain copy print ?
  1. #define __tag __used__attribute__((__section__(".taglist.init"))) 

         __attribute__是一个特殊的GNU关键字,在这里的用法是:告诉编译器需要将其作用的函数或者数据放入”.taglist.init”这一段区域。

也就是说由__tagtable定义的函数将会被放在section“.taglist.init” 这个区域,而且__tagtable_begin指向的就是这个区域的首地址。所以在parse_tag()做for循环调用的时候,

必然会调用到parse_tag_mem32()。

         其中一点要注意的是,parse_tag_mem32()的TAG为ATAG_MEM, 所以在boot传过来的TAG参数如果是要定义为memory参数的话TAG一定要定义为ATAG_MEM,否则parse_tag_mem32()是无法解析到的!

         parse_tag_mem32()调用arm_add_memory().

/*start和size参数是从boot传过来的。*/
[html] view plain copy print ?
  1. int __init arm_add_memory(phys_addr_t start, unsigned long size) 
  2.     /*第一次进来meminfo.nr_banks值为0.*/ 
  3.     struct membank *bank = &meminfo.bank[meminfo.nr_banks]; 
  4.     /*最多能保存NR_BANKS个bank,本平台为8.*/ 
  5.     if (meminfo.nr_banks >= NR_BANKS) { 
  6.         printk(KERN_CRIT "NR_BANKS too low, " 
  7.             "ignoring memory at 0x%08llx\n", (long long)start); 
  8.         return -EINVAL; 
  9.     } 
  10.     /*页对齐后保存物理起始地址。*/ 
  11.     size -= start & ~PAGE_MASK; 
  12.     bank->start = PAGE_ALIGN(start); 
  13.     /*保存本bank size.*/ 
  14.     bank->size = size & PAGE_MASK; 
  15.  
  16.     /* 
  17.      * Check whether this memory region has non-zero size or 
  18.      * invalid node number. 
  19.      */ 
  20.     if (bank->size == 0) 
  21.         return -EINVAL; 
  22.     /*记录当前拥有bank数量。*/ 
  23.     meminfo.nr_banks++; 
  24.     return 0; 
  25. }<span style="font-family: Arial, Helvetica, sans-serif;"> </span> 

Meminfo检查

         在meminfo信息收集完成之后,系统会先对它作一个检查:

Start_kernel -> setup_arch -> sanity_check_meminfo.

[html] view plain copy print ?
  1. void __init sanity_check_meminfo(void) 
  2.     int i, j, highmem = 0
  3. ~~snip 
  4.     /*对每个bank都做检查。*/ 
  5.     for (i = 0, j = 0; i < meminfo.nr_banks; i++) { 
  6.         struct membank *bank = &meminfo.bank[j]; 
  7.         *bank = meminfo.bank[i]; 
  8.         /*这里表示是PAE扩展的情况???*/ 
  9.         if (bank->start > ULONG_MAX) 
  10.             highmem = 1
  11.  
  12. #ifdef CONFIG_HIGHMEM 
  13.         /*如果物理地址比在vmalloc_min之上或者小于内核逻辑 
  14. 映射地址空间(俗称lowmem或者地段内存),那么就被认为是高端内存。 
  15. vmalloc_min被定义为vmalloc的最低地址。关于vmalloc可以了解下linux 
  16. 的虚拟内存空间布局划分。其实它和lowmem最高地址中间还留有8M的 
  17. 空间防止越界。*/ 
  18.         if (__va(bank->start) >= vmalloc_min || 
  19.             __va(bank->start) < (void *)PAGE_OFFSET) 
  20.             highmem = 1
  21.  
  22.         bank->highmem = highmem
  23.  
  24.         /* 
  25.          * Split those memory banks which are partially overlapping 
  26.          * the vmalloc area greatly simplifying things later. 
  27.          */ 
  28.         /*表示meminfo其中的一个bank的物理地址其中一部分处于 
  29. Lowmem,一部分却又处于Highmem,这种情况需要将bank再重新划分 
  30. 成两个bank。*/ 
  31.         if (!highmem && __va(bank->start) < vmalloc_min && 
  32.             bank->size > vmalloc_min - __va(bank->start)) { 
  33.             if (meminfo.nr_banks >= NR_BANKS) { 
  34.                 printk(KERN_CRIT "NR_BANKS too low, " 
  35.                          "ignoring high memory\n"); 
  36.             } else { 
  37.                 /*将当前跟着的bank元素都往后挪一个位置,以保存新划分出来的 
  38. Bank。*/ 
  39.                 memmove(bank + 1, bank, 
  40.                     (meminfo.nr_banks - i) * sizeof(*bank)); 
  41.                 meminfo.nr_banks++; 
  42.                 i++; 
  43.                 /*保存size和start,既然代码跑这里来了,肯定为highmem了。*/ 
  44.                 bank[1].size -= vmalloc_min - __va(bank->start); 
  45.                 bank[1].start = __pa(vmalloc_min - 1) + 1; 
  46.                 bank[1].highmem = highmem = 1; 
  47.                 j++; 
  48.             } 
  49.             /*lowmem的size, start保持不变。*/ 
  50.             bank->size = vmalloc_min - __va(bank->start); 
  51.         } 
  52. #else 
  53.         bank->highmem = highmem
  54.         /*系统没有enable high memory时直接忽略highmem.*/ 
  55.         /* 
  56.          * Highmem banks not allowed with !CONFIG_HIGHMEM. 
  57.          */ 
  58.         if (highmem) { 
  59.             printk(KERN_NOTICE "Ignoring RAM at %.8llx-%.8llx " 
  60.                    "(!CONFIG_HIGHMEM).\n", 
  61.                    (unsigned long long)bank->start, 
  62.                    (unsigned long long)bank->start + bank->size - 1); 
  63.             continue; 
  64.         } 
  65.         /*判断物理起始地址是不是落在vmalloc区域,或者小于lowmem区域。*/ 
  66.         /* 
  67.          * Check whether this memory bank would entirely overlap 
  68.          * the vmalloc area. 
  69.          */ 
  70.         if (__va(bank->start) >= vmalloc_min || 
  71.             __va(bank->start) < (void *)PAGE_OFFSET) { 
  72.             printk(KERN_NOTICE "Ignoring RAM at %.8llx-%.8llx " 
  73.                    "(vmalloc region overlap).\n", 
  74.                    (unsigned long long)bank->start, 
  75.                    (unsigned long long)bank->start + bank->size - 1); 
  76.             continue; 
  77.         } 
  78. /*判断物理结束地址是不是落在vmalloc区域*/ 
  79.         /* 
  80.          * Check whether this memory bank would partially overlap 
  81.          * the vmalloc area. 
  82.          */ 
  83.         if (__va(bank->start + bank->size) > vmalloc_min || 
  84.             __va(bank->start + bank->size) < __va(bank->start)) { 
  85.             unsigned long newsize = vmalloc_min - __va(bank->start); 
  86.             printk(KERN_NOTICE "Truncating RAM at %.8llx-%.8llx " 
  87.                    "to -%.8llx (vmalloc region overlap).\n", 
  88.                    (unsigned long long)bank->start, 
  89.                    (unsigned long long)bank->start + bank->size - 1, 
  90.                    (unsigned long long)bank->start + newsize - 1); 
  91.             bank->size = newsize
  92.         } 
  93. #endif 
  94.         /*当bank的结束地址比当前的arm_lowmem_limit 还要大的话重新更新。*/ 
  95.         if (!bank->highmem && bank->start + bank->size > arm_lowmem_limit) 
  96.             arm_lowmem_limit = bank->start + bank->size; 
  97.  
  98.         j++; 
  99.     } 
  100. #ifdef CONFIG_HIGHMEM 
  101.     if (highmem) { 
  102.         const char *reason = NULL
  103.     /*vipt属于arm cache的一种模式,如果alias了vipt,那么Highmem就 
  104. 不会被使用了。*/ 
  105.         if (cache_is_vipt_aliasing()) { 
  106.             /* 
  107.              * Interactions between kmap and other mappings 
  108.              * make highmem support with aliasing VIPT caches 
  109.              * rather difficult. 
  110.              */ 
  111.             reason = "with VIPT aliasing cache"
  112.         } 
  113.         if (reason) { 
  114.             printk(KERN_CRIT "HIGHMEM is not supported %s, ignoring high memory\n", 
  115.                 reason); 
  116.             while (j > 0 && meminfo.bank[j - 1].highmem) 
  117.                 j--; 
  118.         } 
  119.     } 
  120. #endif 
  121.     meminfo.nr_banks = j
  122.     /* arm_lowmem_limit 以上都被认为是高端内存了。*/ 
  123.     high_memory = __va(arm_lowmem_limit - 1) + 1; 
  124.     memblock_set_current_limit(arm_lowmem_limit); 

Vmalloc_min一开始编译的时候就被初始化的:

[html] view plain copy print ?
  1. static void * __initdata vmalloc_min
  2.          (void*)(VMALLOC_END - (240 << 20) - VMALLOC_OFFSET); 
VMALLOC_END:表示vmalloc区域结束地址。

240<<20:vmalloc区域有240M大小。

VMALLOC_OFFSET:为8M。vmalloc区域和lowmem区域有8M的空闲区间,防止访问越界。

当然,vamlloc_min也可以通过cmdline的方式传到kernel中作修改。

[html] view plain copy print ?
  1. static int __init early_vmalloc(char *arg) 
  2.     /*将vmalloc size解析成unsigned long类型。*/ 
  3.     unsigned long vmalloc_reserve = memparse(arg, NULL); 
  4.  
  5.     if (vmalloc_reserve < SZ_16M) { 
  6.         vmalloc_reserve = SZ_16M
  7.         printk(KERN_WARNING 
  8.             "vmalloc area too small, limiting to %luMB\n", 
  9.             vmalloc_reserve >> 20); 
  10.     } 
  11.  
  12.     if (vmalloc_reserve > VMALLOC_END - (PAGE_OFFSET + SZ_32M)) { 
  13.         vmalloc_reserve = VMALLOC_END - (PAGE_OFFSET + SZ_32M); 
  14.         printk(KERN_WARNING 
  15.             "vmalloc area is too big, limiting to %luMB\n", 
  16.             vmalloc_reserve >> 20); 
  17.     } 
  18.     /*改变vmalloc_min变量,这样就得到了自己想要的vmalloc size了。*/ 
  19.     vmalloc_min = (void *)(VMALLOC_END - vmalloc_reserve); 
  20.     return 0; 
  21. early_param("vmalloc", early_vmalloc); 

vmalloc_min的变化也会导致lowmem也就是低端的内存大小的变化。所以实际应用中,high memory的定义并非一定像书上所说的为896M之上。

Meminfo使用:

做完了检查之后就是使用了,在使用部分,meminfo的信息其实都传给了一个叫structmemblock的结构,后续由它来完成内存区域信息保存的责任。它会将一些必要的区域给保留出来供系统使用,例如kernel的text, code段。其他未使用部分系统才能使用。来看看实现函数arm_memblock_init().

[html] view plain copy print ?
  1. void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc) 
  2.     int i; 
  3.     /*将struct meminfo的信息都放入到了struct memblock中去,它会将保留的区域和空闲的区域用memory 和reserved变量分别保存。*/ 
  4.     for (i = 0; i < mi->nr_banks; i++) 
  5.         memblock_add(mi->bank[i].start, mi->bank[i].size); 
  6.  
  7.     /* kernel的text段需要作为保留部分。其实看system.map会发现 
  8. _stext为symbol里的其实地址,而_end为结束地址。所以这块memblock 
  9. Region包括了virtual memory layout中的.init, .bss, .data, .text这几个区域。*/ 
  10.     memblock_reserve(__pa(_stext), _end - _stext); 
  11. /* 本平台的phys_initrd_start 这里为0.*/ 
  12. #ifdef CONFIG_BLK_DEV_INITRD 
  13.     if (phys_initrd_size && 
  14.         !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) { 
  15.         pr_err("INITRD: 0x%08lx+0x%08lx is not a memory region - disabling initrd\n", 
  16.                phys_initrd_start, phys_initrd_size); 
  17.         phys_initrd_start = phys_initrd_size = 0; 
  18.     } 
  19.     if (phys_initrd_size && 
  20.         memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) { 
  21.         pr_err("INITRD: 0x%08lx+0x%08lx overlaps in-use memory region - disabling initrd\n", 
  22.                phys_initrd_start, phys_initrd_size); 
  23.         phys_initrd_start = phys_initrd_size = 0; 
  24.     } 
  25.     if (phys_initrd_size) { 
  26.         memblock_reserve(phys_initrd_start, phys_initrd_size); 
  27.  
  28.         /* Now convert initrd to virtual addresses */ 
  29.         initrd_start = __phys_to_virt(phys_initrd_start); 
  30.         initrd_end = initrd_start + phys_initrd_size; 
  31.     } 
  32. #endif 
  33.     /*这部分空间是给页表留着的。*/ 
  34.     arm_mm_memblock_reserve(); 
  35.     /*空函数。*/ 
  36.     arm_dt_memblock_reserve(); 
  37.     /*如果平台有定义这几的reserve函数,那么调用它。 
  38. 在前面的mempool文章中,我们已经分析过了,平台会 
  39. 预留一百多M的memory供系统ION分配。*/ 
  40.     /* reserve any platform specific memblock areas */ 
  41.     if (mdesc->reserve) 
  42.         mdesc->reserve(); 
  43.     /*关于cma,是系统为了reserved memory而出现的。 
  44. 它的优点是:当某些模块如audio/camera需要连续物理大块内存 
  45. 时,能申请到,而不用的时候,又可以被其他模块申请。避免了内存 
  46. 浪费。其原理利用的是内存数据迁移。不过本平台没用使用到。*/ 
  47.     /* 
  48.      * reserve memory for DMA contigouos allocations, 
  49.      * must come from DMA area inside low memory 
  50.      */ 
  51.     dma_contiguous_reserve(min(arm_dma_limit, arm_lowmem_limit)); 
  52.  
  53.     arm_memblock_steal_permitted = false
  54.     memblock_allow_resize(); 
  55.     memblock_dump_all(); 

到这里,接下来的任务基本上就交给struct memblock完成了!


20130318

你可能感兴趣的:(struct meminfo分析)