1. 开机后各可用内存和实际物理内存之间的关系
要计算开机之后剩余的总内存,首先必须弄清楚kernel在开机之后出现的几个可用内存变量之间的关系,今天以一个项目的log来分析开机之后memory的计算方式和各可用内存和总的实际内存之间的关系:
我们在开机之后kernel会有一句log如下:
- [ 0.000000] <0>-(0)[0:swapper]Memory: 3919424K/4027408K available (12244K kernel code, 1856K rwdata, 5636K rodata, 1056K init, 13278K bss, 107984K reserved)
内存初始化信息的解读如下:
absent:表示不可用的物理内存大小。譬如有一些物理内存被BIOS保留、对kernel是不可用的,这部分物理内存被计入”absent”之中,这里没有出现。
107984k reserved:包括【initrd】和【内核代码及数据】等,其中内核代码和部分数据包含在下列统计值中:
12244k kernel code :表示kernel的代码,属于reserved memory;
1856k rwdata和5636K rodata :表示kernel的可读可写的数据和只读数据大小,属于reserved memory;
1056k init :表示init code和init data,属于reserved memory,但引导完成之后会释放给free memory;
13278k bss:表示kernel bss段的大小;
它们之间的关系如下:
reserved 包括 kernel code, data 和 init,由于它还包括initrd和其它更多的内容,所以reserved远远大于 kernel code + data + init 之和;
此时会有以下几个疑惑:
1. 这里的4027408K是从哪里计算出来的,他与实际总内存4G之间差值包含了哪些内容?
2. 3919424K又是如何计算出来的?
经过查看kernel code可以回答上面的两个问题:
- void __init mem_init_print_info(const char *str)
- {
- unsigned long physpages, codesize, datasize, rosize, bss_size;
- unsigned long init_code_size, init_data_size;
-
- physpages = get_num_physpages();
- codesize = _etext - _stext;
- datasize = _edata - _sdata;
- rosize = __end_rodata - __start_rodata;
- bss_size = __bss_stop - __bss_start;
- init_data_size = __init_end - __init_begin;
- init_code_size = _einittext - _sinittext;
- ……
- printk("Memory: %luK/%luK available "
- "(%luK kernel code, %luK rwdata, %luK rodata, "
- "%luK init, %luK bss, %luK reserved"
- #ifdef CONFIG_HIGHMEM
- ", %luK highmem"
- #endif
- "%s%s)\n",
- nr_free_pages() << (PAGE_SHIFT-10), physpages << (PAGE_SHIFT-10),
- codesize >> 10, datasize >> 10, rosize >> 10,
- (init_data_size + init_code_size) >> 10, bss_size >> 10,
- (physpages - totalram_pages) << (PAGE_SHIFT-10),
- #ifdef CONFIG_HIGHMEM
- totalhigh_pages << (PAGE_SHIFT-10),
- #endif
- str ? ", " : "", str ? str : "");
- kernel_reserve_meminfo.available = nr_free_pages() << (PAGE_SHIFT - 10);
- kernel_reserve_meminfo.total = physpages << (PAGE_SHIFT - 10);
- kernel_reserve_meminfo.kernel_code = codesize >> 10;
- kernel_reserve_meminfo.rwdata = datasize >> 10;
- kernel_reserve_meminfo.rodata = rosize >> 10;
- kernel_reserve_meminfo.init = (init_data_size + init_code_size) >> 10;
- kernel_reserve_meminfo.bss = bss_size >> 10;
- kernel_reserve_meminfo.reserved =
- (physpages - totalram_pages) << (PAGE_SHIFT-10);
-
- #ifdef CONFIG_HIGHMEM
- kernel_reserve_meminfo.highmem = totalhigh_pages << (PAGE_SHIFT - 10);
- #endif
- }
有上面的代码可以看到:
3919424K = nr_free_pages() * 4;
4027408K = get_num_physpages() * 4;
这里的get_num_physpages()是遍历memory node上面所有的page得出的总的内存大小值,具体可以参见代码:
- static inline unsigned long get_num_physpages(void)
- {
- int nid;
- unsigned long phys_pages = 0;
-
- for_each_online_node(nid)
- phys_pages += node_present_pages(nid);
-
- return phys_pages;
- }
那么这里就有一个疑问,既然是遍历所有memory node上的page得出的总的内存大小,那为什么不等于总的实际物理内存4G的大小呢?刚开始在这里也是百思不得其解,最后透过整理整个memory layout发现结论,这里讲一下如何整理整个项目的memory layout。
首先在kernel log中搜索PHY layout,得出如下结果:
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]atf-reserved-memory@44600000 : 0x44600000 - 0x4460ffff (0x10000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]atf-ramdump-memory@44610000 : 0x44610000 - 0x4463ffff (0x30000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]cache-dump-memory@44640000 : 0x44640000 - 0x4466ffff (0x30000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]consys-reserve-memory : 0xbde00000 - 0xbdffffff (0x200000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]spm-reserve-memory : 0xbe180000 - 0xbe195fff (0x16000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]reserve-memory-scp_share : 0x8f000000 - 0x8fffffff (0x1000000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]spi-reserve-memory : 0xbe160000 - 0xbe175fff (0x16000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]kernel : 0x40000000 - 0x445fffff (0x4600000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]kernel : 0x44670000 - 0x87ffffff (0x43990000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]kernel : 0x88200000 - 0x8effffff (0x6e00000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]kernel : 0x90000000 - 0xb3ffffff (0x24000000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]kernel : 0xbb000000 - 0xbddfffff (0x2e00000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]kernel : 0xbe000000 - 0xbe15ffff (0x160000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]kernel : 0xbe176000 - 0xbe17ffff (0xa000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]kernel : 0xbe196000 - 0xbe19ffff (0xa000)
- [ 0.000000] <0>-(0)[0:swapper][PHY layout]kernel : 0xc0000000 - 0x13fffffff (0x80000000)
- [ 0.468130] <4>.(4)[1:swapper/0][PHY layout]tee_reserved_mem : 0xbff80000 - 0xbffbffff (0x40000)
- [ 0.469290] <4>[PHY layout]FB (dt) : 0xbe3a0000 - 0xbff7ffff (0x1be0000)
- [ 1.613119] <4>.(4)[1:swapper/0][PHY layout]ccci_md0 at LK : 0xb4000000 - 0xbaffffff (0x7000000)
- [ 1.615123] <4>.(4)[1:swapper/0][PHY layout]ccci_share_mem at LK : 0x88000000 - 0x881fffff (0x200000)
将所有的地址按照顺序连接起来,发现少了两块内存,在kernel log中搜索确实内存块的起始地址的地址可以发现是被mblock_reserve所保留(注:在搜索PHY layout中所缺少的memory块都可以通过在kernel log中搜索相应的缺失块的起始地址而找到相应的reserve的块以及用途)
,最后得出memory layout如下:
end |
0x13fffffff |
kernel |
size: 0x80000000 |
start |
0xc0000000 |
end |
0xbfffffff |
mblock reserve |
size: 0x40000 |
start |
0xbffc0000 |
end |
0xbffbffff |
tee_reserved_mem(security) |
size: 0x40000 |
start |
0xbff80000 |
end |
0xbff7ffff |
Frame Buffer |
size: 0x1be0000 |
start |
0xbe3a0000 |
end |
0xbe39ffff |
mblock reserve |
size: 0x200000 |
start |
0xbe1a0000 |
end |
0xbe19ffff |
kernel |
size: 0xa000 |
start |
0xbe196000 |
end |
0xbe195fff |
spm-reserve-memory |
size: 0x16000 |
start |
0xbe180000 |
end |
0xbe17ffff |
kernel |
size: 0xa000 |
start |
0xbe176000 |
end |
0xbe175fff |
spi-reserve-memory |
size: 0x16000 |
start |
0xbe160000 |
end |
0xbe15ffff |
kernel |
size: 0x160000 |
start |
0xbe000000 |
end |
0xbdffffff |
consys-reserve-memory(BT&WIFI) |
size: 0x200000 |
start |
0xbde00000 |
end |
0xbddfffff |
kernel |
size: 0x2e00000 |
start |
0xbb000000 |
end |
0xbaffffff |
ccci_md0(Modem) |
size: 0x7000000 |
start |
0xb4000000 |
end |
0xb3ffffff |
kernel |
size: 0x24000000 |
start |
0x90000000 |
end |
0x8fffffff |
reserve-memory-scp_share |
size: 0x1000000 |
start |
0x8f000000 |
end |
0x8effffff |
kernel |
size: 0x6e00000 |
start |
0x88200000 |
end |
0x881fffff |
ccci_share_mem(Modem) |
size: 0x200000 |
start |
0x88000000 |
end |
0x87ffffff |
kernel |
size: 0x43990000 |
start |
0x44670000 |
end |
0x4466ffff |
cache-dump-memory |
size: 0x30000 |
start |
0x44640000 |
end |
0x4463ffff |
atf-ramdump-memory(security) |
size: 0x30000 |
start |
0x44610000 |
end |
0x4460ffff |
atf-reserved-memory(Security) |
size: 0x10000 |
start |
0x44600000 |
end |
0x445fffff |
kernel |
size: 0x4600000 |
start |
0x40000000 |
我们用总的内存4G减去所有reserve的内存:
4*1024*1024*1024-(0x10000 + 0x30000 + 0x30000 + 0x200000 + 0x1000000 + 0x7000000 + 0x200000 + 0x16000 + 0x16000 + 0x200000 + 0x1be0000 + 0x40000 + 0x40000) = 4027408K,这回答了文章开头的第一个问题,所以开机get_num_physpages()所获得的总的内存是实际的物理内存减掉kernel所reserve的所有的内存块之后剩余的内存。
第二个问题3919424K,他等于nr_free_pages() * 4,这个值其实就是4027408K减掉kernel此时用于保留reserved(initrd、初始化代码init、内核代码及数据)的数据之后所剩余的内存。即4027408K - (107984K + absent(如果有)) = 3919424K,这个值就是实际开机运行之后所能使用的最大内存,讲到这里就会有一个疑问,在执行cat /proc/meminfo所得到的结果中:
- cat proc/meminfo
- MemTotal: 3922896 kB
- MemFree: 1629172 kB
- MemAvailable: 2602560 kB
- Buffers: 13848 kB
- Cached: 994316 kB
- SwapCached: 0 kB
此时的MemTotal的值大于3919424K,此时3922896K - 3919424K = 3472K.
那么这里的3472K去了是从哪里回收回来的呢?通过查阅相关资料,发现:
initrd和初始化代码init在引导完成之后会被释放掉,所以最终的可用内存会比dmesg显示的available更多一点,相应的源代码可参见函数:
free_initrd_mem() 和 free_initmem();
查看代码之后再kernel log中搜索Freeing得出如下结论:
- [ 2.321286] <4>.(4)[1:swapper/0]Freeing initrd memory: 2340K (ffffffc005000000 - ffffffc005249000)
- [ 5.318178] <4>.(4)[1:swapper/0]Freeing unused kernel memory: 1056K (ffffffc0011f9000 - ffffffc001301000)
- [ 5.319419] <4>.(4)[1:swapper/0]Freeing alternatives memory: 76K (ffffffc001301000 - ffffffc001314000)
这里的76K + 1056K + 2340K = 3472K,而3472K + 3919424K = 3922896K,刚好就是meminfo里面的MemTotal的大小。
(注:大多数log开始打出来的可用内存和后面free掉的内存之和相加等于meminfo的MemTotal值,有部分时候这两者之和可能略小于meminfo的MemTotal的值,这是由于log打印问题导致log未完全打印出所有的freeing的memory块(大多数情况如果按照以上方式计算出来的结果如果不等于meminfo的MemTotal可以大胆的怀疑是log未打全),但是meminfo里面统计到了这部分内存)
到这里这几个总的可用内存和实际的物理内存之间的关系算是理清了。
2. 计算Android开机之后kernel消耗的物理内存
通过第一节的介绍我们可以得出在开机之后kernel所reserve的内存计算公式就是:
实际的物理内存 - meminfo的Memtotal值,争对此例就是:
4*1024*1024K - 3922896K = 271408K
而Android开机之后kernel实际消耗的物理内存还得加上kernel stack,slab,shmem,pagetables,vmalloc_used的大小,当然以上的变量大小是变化的。
所以这里仅仅只是值开机之后的kernel消耗的内存大小,而以上各变量之和可以用dumpsys meminfo中的一个值来概括:
- Total RAM: 3,922,896K (status normal)
- Free RAM: 2,290,037K ( 129,229K cached pss + 913,696K cached kernel + 999,820K free + 51,584K ion cached + 195,708K gpu cached)
- Used RAM: 1,635,654K (1,184,850K used pss + 366,316K kernel + 40,960K trace buffer + 43,528K ion disp + 0K cma usage)
- Lost RAM: -2,803K
- ZRAM: 8K physical used for 0K in swap (1,961,444K total swap)
- Tuning: 192 (large 384), oom 322,560K, restore limit 107,520K (high-end-gfx)
就是Used RAM中的kernel选项的值,在framework中的定义如下:
- public long getKernelUsedSizeKb() {
- return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB]
- + mInfos[Debug.MEMINFO_VM_ALLOC_USED] + mInfos[Debug.MEMINFO_PAGE_TABLES]
- + mInfos[Debug.MEMINFO_KERNEL_STACK];
- }
这个值在使用当中是不断变化的,只有kernel reserve部分的值是恒定不变的,无论Android使用多长时间,
所以这里特指在Android开机之后的时刻的值:
即4*1024*1024K - 3922896K + 366316K = 637724K
讲到这里基本就搞清楚了开机kernel所消耗内存大小的计算方法。