从零到负一

【LM05】paging_init()以及内存的布局

2023/02/12

在前面笔记【LM04】Memblock及源码分析中,我提到到目前为止,完成VA->PA映射的内存空间有内核和FDT/DTS,其它添加和没有添加入memblock的内存空间并没有完成VA->PA的映射。接下来的映射工作将全部交给paging_init()来完成,这篇笔记也主要围绕这个函数展开。同时,我将简单分析下该函数完成后的内存布局。

memblock管理的内存空间

我们再次回到setup_arch()函数,看看在完成FIXMAP的映射后,还会进行什么其它的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ./arch/arm64/kernel/setup.c

void __init setup_arch(char **cmdline_p)
{
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;

*cmdline_p = boot_command_line;

early_fixmap_init();
early_ioremap_init();

setup_machine_fdt(__fdt_pointer);

...
...

xen_early_init();
efi_init();
arm64_memblock_init();

paging_init();

arm64_memblock_init()

这个函数从名字上就可以知道是干什么的,初始化arm64中的memblock。在setup_machine_fdt()函数中,我们已经将DTS中读取的信息存入memblock中。在这个函数中,更多的内存空间会被添加入memblock中的memoryreserved区域。

在完成这一系列工作后,我们就要开始调用paging_init()来完成相关的映射工作了。

paging_init()

这个函数比较简单,主要工作就是完成内核和memblock中内存的VA->PA映射。同时,将init_mm.pgd替换成swapper_pg_dir

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ./arch/arm64/mm/mmu.c

/*
* paging_init() sets up the page tables, initialises the zone memory
* maps and sets up the zero page.
*/
void __init paging_init(void)
{
// ----------------------------------------------------------- (1)
pgd_t *pgdp = pgd_set_fixmap(__pa_symbol(swapper_pg_dir));

// ----------------------------------------------------------- (2)
map_kernel(pgdp);

// ----------------------------------------------------------- (3)
// static void __init __map_memblock(pgd_t *pgdp, phys_addr_t start,
// phys_addr_t end, pgprot_t prot, int flags)
// {
// __create_pgd_mapping(pgdp, start, __phys_to_virt(start), end - start,
// prot, early_pgtable_alloc, flags);
// }
map_mem(pgdp);

// ----------------------------------------------------------- (4)
pgd_clear_fixmap();
cpu_replace_ttbr1(lm_alias(swapper_pg_dir));
init_mm.pgd = swapper_pg_dir;
memblock_free(__pa_symbol(init_pg_dir), __pa_symbol(init_pg_end) - __pa_symbol(init_pg_dir));
memblock_allow_resize();
}

(1) 将FIX_PGDswapper_pg_dir进行映射;
(2) 重新将内核进行映射,这次映射到swapper_pg_dir(之前映射到init_pg_dir) - 在Linux 4.20之前是没有init_pg_dir的,Linux 4.20之后才用它作为启动时的内核页表;
(3) 将memblockmemory区域的内存空间都进行映射,这里会使用memblock来进行页表物理内存的分配(early_pgtable_alloc()函数);
(4)
a) 清除FIX_PGD的临时映射;
b) 重新设置TTBR1
c) 更新init_mm.pgd
d) 释放init_pgmemblock占用的内存空间;
e) 允许memblock扩容。

内存的布局

至此,内核空间的内存都映射完成。这里我借用下大神LoyenWang的图和我自己实验平台的图,

这是我的实验平台的内存布局图,

从图中,我们可以看到,在内核空间,PAGE_OFFSET以上都是线性区域。在这个区域,物理内存和虚拟内存按照PAGE_OFFSET的偏移进行映射。PAGE_OFFSET以下区域,分别映射了vmemmap, PCI/IO, FIXMAP, vmalloc以及内核和modulesPAGE_OFFSETarch/arm64/include/asm/memory.h中进行了定义,查看文件获取更多信息。
在这篇笔记【LM03】FIXMAP和相关页表的创建中我已经介绍了这个布局了,这里就不做过多解释了。

参考资料

  1. 【原创】(三)Linux paging_init解析
  2. Linux内存管理(三):“看见”物理内存
  3. How to intepret the virtual kernel memory layout on ARM64?
CATALOG
  1. 1. memblock管理的内存空间
    1. 1.1. arm64_memblock_init()
  2. 2. paging_init()
  3. 3. 内存的布局
  4. 4. 参考资料