Linux内存初始化
说明
Kernel版本:4.0.0
ARM处理器,Contex-A9,QEMU平台
上文完成了zone的初始化工作,接下来需要初始化伙伴系统(Buddy系统)。
mm_init
mm_init函数,主要对内核内存分配器进行初始化。
start_kernel->mm_init
/*
* Set up kernel memory allocators
*/
static void __init mm_init(void)
{
/*
* page_ext requires contiguous pages,
* bigger than MAX_ORDER unless SPARSEMEM.
*/
page_ext_init_flatmem();
mem_init(); //初始化buddy系统
kmem_cache_init(); //slab(slub)初始化,kmalloc初始化。
percpu_init_late();
pgtable_init();
vmalloc_init(); //vmalloc初始化
}
各个函数功能已经大概说明,下面来看mem_init。这部分代码也是和体系结构(arch)相关的。
mem_init
start_kernel->mm_init->mem_init
void __init mem_init(void)
{
......
/* this will put all unused low memory onto the freelists */
free_unused_memmap();
free_all_bootmem(); (1)
......
free_highpages();
mem_init_print_info(NULL); (2)
#define MLK(b, t) b, t, ((t) - (b)) >> 10
#define MLM(b, t) b, t, ((t) - (b)) >> 20
#define MLK_ROUNDUP(b, t) b, t, DIV_ROUND_UP(((t) - (b)), SZ_1K)
pr_notice("Virtual kernel memory layout:\n" (3)
" vector : 0x%08lx - 0x%08lx (%4ld kB)\n"
#ifdef CONFIG_HAVE_TCM
" DTCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
" ITCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
#endif
" fixmap : 0x%08lx - 0x%08lx (%4ld kB)\n"
" vmalloc : 0x%08lx - 0x%08lx (%4ld MB)\n"
" lowmem : 0x%08lx - 0x%08lx (%4ld MB)\n"
#ifdef CONFIG_HIGHMEM
" pkmap : 0x%08lx - 0x%08lx (%4ld MB)\n"
#endif
#ifdef CONFIG_MODULES
" modules : 0x%08lx - 0x%08lx (%4ld MB)\n"
#endif
" .text : 0x%p" " - 0x%p" " (%4td kB)\n"
" .init : 0x%p" " - 0x%p" " (%4td kB)\n"
" .data : 0x%p" " - 0x%p" " (%4td kB)\n"
" .bss : 0x%p" " - 0x%p" " (%4td kB)\n",
MLK(UL(CONFIG_VECTORS_BASE), UL(CONFIG_VECTORS_BASE) +
(PAGE_SIZE)),
#ifdef CONFIG_HAVE_TCM
MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
MLK(ITCM_OFFSET, (unsigned long) itcm_end),
#endif
MLK(FIXADDR_START, FIXADDR_END),
MLM(VMALLOC_START, VMALLOC_END),
MLM(PAGE_OFFSET, (unsigned long)high_memory),
#ifdef CONFIG_HIGHMEM
MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
(PAGE_SIZE)),
#endif
#ifdef CONFIG_MODULES
MLM(MODULES_VADDR, MODULES_END),
#endif
MLK_ROUNDUP(_text, _etext),
MLK_ROUNDUP(__init_begin, __init_end),
MLK_ROUNDUP(_sdata, _edata),
MLK_ROUNDUP(__bss_start, __bss_stop));
......
}
先讲一下(2)(3)的含义,然后再详细讲(1)吧
(2)打印一些内存相关统计信息,即启动后的Memory: 763140K/778240K available (4779K kernel code, 156K rwdata, 1364K rodata, 1444K init, 148K bss, 15100K reserved, 0K cma-reserved)
打印。
(3)打印虚拟内存各个段,即内核启动后如下信息。
Virtual kernel memory layout:
vector : 0xffff0000 - 0xffff1000 ( 4 kB)
fixmap : 0xffc00000 - 0xfff00000 (3072 kB)
vmalloc : 0xf0000000 - 0xff000000 ( 240 MB)
lowmem : 0xc0000000 - 0xef800000 ( 760 MB)
modules : 0xbf000000 - 0xc0000000 ( 16 MB)
.text : 0xc0008000 - 0xc0608138 (6145 kB)
.init : 0xc0609000 - 0xc0772000 (1444 kB)
.data : 0xc0772000 - 0xc0799000 ( 156 kB)
.bss : 0xc0799000 - 0xc07be138 ( 149 kB)
(1)free_all_bootmem
把bootmem释放给buddy系统。注意:其实没有用到bootmem,所以走的都是nobootmem.c中的函数。
free_all_bootmem
start_kernel->mm_init->mem_init->free_all_bootmem
unsigned long __init free_all_bootmem(void)
{
unsigned long pages;
reset_all_zones_managed_pages();
/*
* We need to use NUMA_NO_NODE instead of NODE_DATA(0)->node_id
* because in some case like Node0 doesn't have RAM installed
* low ram will be on Node1
*/
pages = free_low_memory_core_early();
totalram_pages += pages;
return pages;
}
start_kernel->mm_init->mem_init->free_all_bootmem->free_low_memory_core_early
释放low mem给buddy系统
static unsigned long __init free_low_memory_core_early(void)
{
unsigned long count = 0;
phys_addr_t start, end;
u64 i;
memblock_clear_hotplug(0, -1);
for_each_free_mem_range(i, NUMA_NO_NODE, &start, &end, NULL)
count += __free_memory_core(start, end);
return count;
}
把内存块传递到__free_pages_memory函数中,该函数定义如下:
start_kernel->mm_init->mem_init->free_all_bootmem->free_low_memory_core_early->__free_memory_core->__free_pages_memory
static inline unsigned long __ffs(unsigned long x)
{
return ffs(x) - 1;
}
static void __init __free_pages_memory(unsigned long start, unsigned long end)
{
int order;
while (start < end) {
order = min(MAX_ORDER - 1UL, __ffs(start));
while (start + (1UL << order) > end)
order--;
printk("start = %#x, ffs = %#x\n", start, order);
__free_pages_bootmem(pfn_to_page(start), order);
start += (1UL << order);
}
}
start和end指页帧号,while循环一直从开始页帧号start遍历到end,循环的步长和order有关。首先计算order的大小,取MAX_ORDER-1和__ffs(start)
的最小值。__ffs(start)
函数计算,start中第一个bit为1的位置。注意__ffs()=ffs()-1
。因为伙伴系统的链表都是2的n次幂,最大的链表是2的10次方。也就是1024,即0x400.所以通过ffs可以很方便计算出对齐边界。如start等于0x63000,那么__ffs(0x63300)
等于8,这里可以order选用8。
得到order后,我们可以把这块内存通过__free_pages_bootmem()
函数添加到伙伴系统。
需要注意的是,low mem中间存在空洞,即bootmem预留的内存。示例如下。中间空了十几个page,所以ffs需要重新计算,并逐阶上升:
[16:35:08:972]start = 0x67000, ffs = 0xa
[16:35:08:973]start = 0x67400, ffs = 0xa
[16:35:08:973]start = 0x67800, ffs = 0xa
[16:35:08:973]start = 0x67c00, ffs = 0xa
[16:35:08:973]start = 0x6800c, ffs = 0x2
[16:35:08:973]start = 0x68010, ffs = 0x4
[16:35:08:973]start = 0x68020, ffs = 0x5
[16:35:08:974]start = 0x68040, ffs = 0x6
[16:35:08:974]start = 0x68080, ffs = 0x7
[16:35:08:974]start = 0x68100, ffs = 0x8
[16:35:08:974]start = 0x68200, ffs = 0x9
[16:35:08:974]start = 0x68400, ffs = 0xa
[16:35:08:974]start = 0x68800, ffs = 0xa
[16:35:08:975]start = 0x68c00, ffs = 0xa
[16:35:08:975]start = 0x69000, ffs = 0xa
[16:35:08:975]start = 0x69400, ffs = 0xa
start_kernel->mm_init->mem_init->free_all_bootmem->free_low_memory_core_early->__free_memory_core->__free_pages_memory->__free_pages_bootmem->__free_pages_bootmem
void __init __free_pages_bootmem(struct page *page, unsigned int order)
{
unsigned int nr_pages = 1 << order;
struct page *p = page;
unsigned int loop;
prefetchw(p);
for (loop = 0; loop < (nr_pages - 1); loop++, p++) {
prefetchw(p + 1);
__ClearPageReserved(p);
set_page_count(p, 0); (1)
}
__ClearPageReserved(p);
set_page_count(p, 0);
page_zone(page)->managed_pages += nr_pages; (2)
set_page_refcounted(page); (3)
__free_pages(page, order); (4)
}
(1)逐页对page数据结构,page count清零。
(2)page所在zone,管理的page数量增加nr_pages。
(3)设置page count为1,即页面正在使用。
(4)buddy系统释放页面接口,与正常使用释放接口一致。上述(3)中先假定页面被使用了,然后使用接口释放。
__free_pages是buddy释放接口,这里就不详细展开了,可以直接度娘,或者有机会展开分析一下。
mem_init
函数中,除了free_all_bootmem
外,还有free_highpages
。区别在于前者释放的是低端内存,后者释放的是高端内存。high mem虽然未进行页表映射,但是物理页面是真实存在的,所以每个page也需要struct page
进行管理。也需要buddy系统进行分配。buddy分配内存时,优先从高端内存开始分配。当然在一些嵌入式产品中,内存限制,会使能highmem,但是物理内存往往到不了那么大。此种情况下当执行free_highpages
没有对应物理页面。分配内存时同理,没有高端内存可以分配,那么首选项就是低端内存了。
相关文章
Linux内存初始化(1)——memblock初始化
Linux内存初始化(2)——paging_init初始化
Linux内存初始化(3)——pglist_data/zone初始化
Linux内存初始化(4)——伙伴系统(buddy)