内核空间vmalloc的空间初始化

       我们最近在调试32位产品的时候遇到了这样的问题:使用ddr虚拟disk做硬盘,来启动系统(因为32位产品可以访问的最大地址空间仅为4G,因此内核空间的地址也显的比较紧张)

      我们虚拟的disk的实现是,使用一大块连续的物理内存做disk,并将这块物理内存通过vm_map_ram将物理线性地址映射到vmalloc的虚拟地址空间,那么此时就会出现一个问题,当我们映射的区域很大的时候超过VMALLOC_END-VMALLOC_START的时候就会发现无法映射这么大的地址空间,映射过程中会报出溢出的log


从上面的代码很容易看出是因为  start+size>end,那么到底vmaloc可以映射多大的地址空间呢,这个我们得从kernel的地址空间说起了,那么对于32位的处理器,内核空间应该是什么样的呢?如下图


我们可以看到对于内核空间仅仅有1G,而线性映射区占据了896M,从3G+896M开始为高端内存,高端内存里包括了Vmalloc区域和一些临时映射区和永久映射区


那么对于kernel来说到底能够给Vmalloc多少空间呢,这个我们得结合代码看一下了:在arch/arm/mm/mmu.c中有如下代码:


上面的代码中我们可以分析得出:当cmdline中没有vmalloc=size的命令是,early_vmalloc是不会执行的,因此vmalloc的大小由1081行的vmalloc_min决定,很明显当我们没有设置vmalloc size的时候vmalloc区域只有240M的大小,因此会出现我们开始的问题了。那么既然太小我们当然要看一下vmalloc到底可以设置成多大(大能大到多少,小能小到多少??),从early_vmalloc中我们不难得出如下结论:vmalloc的最大size只能为984,最小为16M,因此我们设置vmalloc的大小为大于所需的size即可。

到这里问题是解决了,但是我们肯定有个疑问,整个内核空间就1G,如果900多M都给了Vmalloc,那么哪里来的这么多空间呢??这里我们有得看代码分析一下了,这个得看一下sanity_check_meminfo(不喜欢贴这么多代码,但是不得不贴出来了,为了说明问题):

注意一下:高端内存的概念这里不介绍了,自己补习一下就行了(32位机存在这个概念,64的就没有了)

void __init sanity_check_meminfo(void)
{
phys_addr_t memblock_limit = 0;
int highmem = 0;
phys_addr_t vmalloc_limit = __pa(vmalloc_min - 1) + 1;//将上面得到的vmalloc_min-PAGE_OFFSET得到物理地址
struct memblock_region *reg;
bool should_use_highmem = false;

/*这里主要是获取传参过来的物理地址块*/
for_each_memblock(memory, reg) {
phys_addr_t block_start = reg->base;
phys_addr_t block_end = reg->base + reg->size;
phys_addr_t size_limit = reg->size;


if (reg->base >= vmalloc_limit)//判断是否有相应的块超过了这个vmalloc_min,如果是则这块物理地址需要通过高端内存映射了,不能走线性映射区了
highmem = 1;
else
size_limit = vmalloc_limit - reg->base;//如果没有超过我们看这块物理块得起始地址到vmalloc_min之间的大小限制是多少!




if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) {


if (highmem) {
pr_notice("Ignoring RAM at %pa-%pa (!CONFIG_HIGHMEM)\n",
 &block_start, &block_end);
memblock_remove(reg->base, reg->size);//如果走高端内存那么我们需要将这块内存从memblock中移除
should_use_highmem = true;
continue;
}


if (reg->size > size_limit) {//如果是内存块的部分区域超过vmalloc_min,那么超过的区域需要通过高端内存映射,因此需要使用memblock_remove移除
phys_addr_t overlap_size = reg->size - size_limit;


pr_notice("Truncating RAM at %pa-%pa to -%pa",
 &block_start, &block_end, &vmalloc_limit);
memblock_remove(vmalloc_limit, overlap_size);
block_end = vmalloc_limit;
should_use_highmem = true;
}
}


if (!highmem) {//这里就是vmalloc来源的关键所在了
if (block_end > arm_lowmem_limit) {
if (reg->size > size_limit)
arm_lowmem_limit = vmalloc_limit;//通过vmalloc_limit去更新arm_lowmem_limit
else
arm_lowmem_limit = block_end;
}


/*
* Find the first non-pmd-aligned page, and point
* memblock_limit at it. This relies on rounding the
* limit down to be pmd-aligned, which happens at the
* end of this function.
*
* With this algorithm, the start or end of almost any
* bank can be non-pmd-aligned. The only exception is
* that the start of the bank 0 must be section-
* aligned, since otherwise memory would need to be
* allocated when mapping the start of bank 0, which
* occurs before any free memory is mapped.
*/
if (!memblock_limit) {
if (!IS_ALIGNED(block_start, PMD_SIZE))
memblock_limit = block_start;
else if (!IS_ALIGNED(block_end, PMD_SIZE))
memblock_limit = arm_lowmem_limit;
}


}
}


if (should_use_highmem)
pr_notice("Consider using a HIGHMEM enabled kernel.\n");


high_memory = __va(arm_lowmem_limit - 1) + 1;//high_memory的地址会被arm_lowmem_init进行更新,我们知道VMALLOC_START=high_memory+8M
       /*因此到这里我们可以知道,当vmalloc较大时,线性映射区(原来的896M)会被压缩到很小*/


/*
* Round the memblock limit down to a pmd size.  This
* helps to ensure that we will allocate memory from the
* last full pmd, which should be mapped.
*/
if (memblock_limit)
memblock_limit = round_down(memblock_limit, PMD_SIZE);
if (!memblock_limit)
memblock_limit = arm_lowmem_limit;


memblock_set_current_limit(memblock_limit);
}


好了,问题解决了,也知道vmalloc从哪里来的,到哪里去的了!!


不正之处,请多多指正!!!


猜你喜欢

转载自blog.csdn.net/u014645605/article/details/76173732
今日推荐