內核空間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從哪裏來的,到哪裏去的了!!


不正之處,請多多指正!!!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章