Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1624933
  • 博文数量: 511
  • 博客积分: 967
  • 博客等级: 准尉
  • 技术积分: 2560
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-06 14:19
文章分类

全部博文(511)

文章存档

2016年(11)

2015年(61)

2014年(257)

2013年(63)

2012年(119)

分类: Android平台

2014-01-24 11:07:25

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 plaincopy
  1. static int __init parse_tag(const struct tag *tag)  
  2. {  
  3.     extern struct tagtable __tagtable_begin, __tagtable_end;  
  4.     struct tagtable *t;  
  5.   
  6.     for (t = &__tagtable_begin; t < &__tagtable_end; t++)  
  7.         if (tag->hdr.tag == t->tag) {  
  8.             t->parse(tag);  
  9.             break;  
  10.         }  
  11.   
  12.     return t < &__tagtable_end;  
  13. }  


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

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

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

[html] view plaincopy
  1. static int __init parse_tag_mem32(const struct tag *tag)  
  2. {  
  3.     return arm_add_memory(tag->u.mem.start, tag->u.mem.size);  
  4. }  
  5. __tagtable(ATAG_MEM, parse_tag_mem32);  

__tagtable是个宏定义:


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



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

Meminfo检查

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

Start_kernel -> setup_arch -> sanity_check_meminfo.

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

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


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

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


Meminfo使用:


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


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


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


 

20130318

阅读(1201) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~