内存初始化之物理内存初始化
- 内存管理
- 2023-07-01
- 429热度
- 0评论
恒等映射与内核镜像映射__create_page_tables
- preserve_boot_args:保持启动参数到boot_args[]数组
- set_cpu_boot_maode_flag:设置关于cpu boot相关的全局变量
- __create_page_tables:创建恒等映射页表,内核映像映射页表
- __cpu_setup:为打开mmu做一些cpu相关的初始化
- __primary_switch:启动mmu,并跳转start_kernel()函数
恒等映射
(text:__idmap_text_start~__idmap_text_end/data:idmap_pg_dir~idmap_pg_end)
一旦启动MMU就需要使用虚拟地址,现代处理器大多数是多级流水线,处理器会提前预取多条指令到流水线中,打开MMU时,这些指令都是物理地址预取的;在MMU开启后,将以虚拟地址访问,这样就会出错,所以引入了“恒等映射”,即在过渡阶段的代码,虚拟地址和物理地址相等。恒等映射完成后,就启动MMU,进入虚拟地址访问阶段。恒等映射的代码在 __idmap_text_start~__idmap_text_end,可以从System.map文件中查询到。
kernel/build/System.map
ffffffc00899b000 T __idmap_text_start
ffffffc00899b000 T init_kernel_el
ffffffc00899b00c t init_el1
ffffffc00899b034 t init_el2
ffffffc00899b1e8 t __cpu_stick_to_vhe
ffffffc00899b1f8 t set_cpu_boot_mode_flag
ffffffc00899b21c T secondary_holding_pen
ffffffc00899b240 t pen
ffffffc00899b254 T secondary_entry
ffffffc00899b260 t secondary_startup
ffffffc00899b27c t __secondary_switched
ffffffc00899b310 t __secondary_too_slow
ffffffc00899b31c T __enable_mmu
ffffffc00899b37c T __cpu_secondary_check52bitva
ffffffc00899b380 t __no_granule_support
ffffffc00899b3a4 t __relocate_kernel
ffffffc00899b3ec t __primary_switch
ffffffc00899b428 t enter_vhe
ffffffc00899b460 T cpu_resume
ffffffc00899b488 T cpu_do_resume
ffffffc00899b52c T idmap_cpu_replace_ttbr1
ffffffc00899b560 t __idmap_kpti_flag
ffffffc00899b564 T idmap_kpti_install_ng_mappings
ffffffc00899b5a0 t do_pgd
ffffffc00899b5b8 t next_pgd
ffffffc00899b5c8 t skip_pgd
ffffffc00899b608 t walk_puds
ffffffc00899b610 t next_pud
ffffffc00899b614 t walk_pmds
ffffffc00899b61c t do_pmd
ffffffc00899b634 t next_pmd
ffffffc00899b644 t skip_pmd
ffffffc00899b654 t walk_ptes
ffffffc00899b65c t do_pte
ffffffc00899b680 t skip_pte
ffffffc00899b690 t __idmap_kpti_secondary
ffffffc00899b6d8 T __cpu_setup
ffffffc00899b7dc T __idmap_text_end
恒等映射目的就是为__idmap_text_start~__idmap_text_end这段代码创建一个映射页表,使其虚拟地址和物理地址是相等的。在vmlinux.lds.S中,事先已经分配了IDMAP_DIR_SIZE的空间用于存储页表,通常器页表为3个连续的4KB页面,分别对于PGD,PUD,PMD页表,这里没有使用PTE,所以粒度是2MB的大小。
arch/arm64/kernel/vmlinux.lds.S
idmap_pg_dir = .;
. += IDMAP_DIR_SIZE;
idmap_pg_end = .;
粗粒度的内核映像映射
(text: kernel_text / data:init_pg_dir~init_pg_end)
之所以要创建第二个页表,是因为cpu刚启动时,物理内存一般都在低地址(不过超过256TB),恒等映射的地址实际也在用户空间,即MMU启用后idmap_pg_dir会填入TTBR0,而内核空间链接地址(虚拟地址)都是在高地址,需要填入TTBR1,因此需要再创建一张表,映射整个内核镜像,且虚拟地址空间是再高地址0xffff xxxx xxxx xxxx
arch/arm64/kernel/head.S
/*
* Map the kernel image (starting with PHYS_OFFSET).
*/
///调用map_memory宏建立整个内核镜像代码段 的映射页表;
/**************************************************************************
* 为什么要建第二张表?
* CPU刚启动时,物理内存一般都在低地址(不会超过256T大小),恒等映射的地址实际在用户空间了,
* 即MMU启用后idmap_pg_dir会填入TTBR0;
* 而内核空间的链接地址都是在高地址(内核空间在高地址),需要填入TTBR1;
* 因此,这里再建一张表,映射整个内核镜像,且虚拟地址空间是在高地址区0xffffxxxx xxxx xxxx
* 注:init_pg_dir和idmap_pg_dir两个页表映射区别:
* (1)init_pg_dir映射的虚拟地址在高位0xffff xxxx xxxx xxxx;
* idmap_pg_dir映射的虚拟地址在低位0x0000 xxxx xxxx xxxx;
* MMU启用后,init_pg_dir填入TTBR1,idmap_pg_dir填入TTBR0;
* (2)init_pg_dir映射大小是整个内核镜像,idmap_pg_dir映射2M, 只是内存访问过渡,成功开启MMU即可;
***************************************************************************/
adrp x0, init_pg_dir
mov_q x5, KIMAGE_VADDR // compile time __va(_text)
add x5, x5, x23 // add KASLR displacement
mov x4, PTRS_PER_PGD
adrp x6, _end // runtime __pa(_end)
adrp x3, _text // runtime __pa(_text)
sub x6, x6, x3 // _end - _text
add x6, x6, x5 // runtime __va(_end)
map_memory x0, x1, x5, x6, x7, x3, x4, x10, x11, x12, x13, x14
fixmap映射
先创建好页表,建立好虚拟地址到物理地址的映射关系。
Linux内核要访问物理内存,一旦开启MMU后,就只能通过虚拟地址查询页表找到物理地址进行访问,上一章节中建立恒等映射和粗粒度内核映像映射的页表,因此只能保证内核镜像正常访问。如果要解析DTB,访问设备IO等依然是无法访问的,因为查询不到对应的页表。因此内核引入了fixmap机制,就是事先分配一段虚拟地址空间,然后给定其虚拟地址创建好页表,页表中的表项最后一级指向的物理页帧号先不填充,等到实际要访问那段物理内存后再将其填充,内后通过fixmap这段虚拟地址范围就可以通过查询页表访问到物理内存。
Fixmap最关键要实现的目的就是将一段空间的虚拟地址与物理地址对应上,linux内核通过虚拟地址访问到物理空间,那既然是通过虚拟地址访问到物理地址,那必须构建填充这段虚拟地址到物理地址的页表,这样Linux内核经过MMU利用查找页表找到对应的物理地址进行访问。
fixmap空间分类
Fixmap是一段固定范围的虚拟地址,在其在编译的时候就确定好了。下面是添加一段打印可以查看FIXMAP区域的各小段的地址范围。
void __init early_fixmap_init(void)
{
pgd_t *pgdp;
p4d_t *p4dp, p4d;
pud_t *pudp;
pmd_t *pmdp;
unsigned long addr = FIXADDR_START;
pgdp = pgd_offset_k(addr);
p4dp = p4d_offset(pgdp, addr);
printk(\"FIX_HOLE :0x
printk(\"FIX_FDT_END :0x
printk(\"FIX_FDT :0x
printk(\"FIX_EARLYCON_MEM_BASE:0x
printk(\"FIX_BTMAP_END :0x
printk(\"FIX_BTMAP_BEGIN :0x
printk(\"FIX_PTE :0x
printk(\"FIX_PMD :0x
printk(\"FIX_PUD :0x
printk(\"FIX_PGD :0x
printk(\"FIXADDR_START~TOP :0x
FIXADDR_START,FIXADDR_TOP,(FIXADDR_TOP-FIXADDR_START) >> 10);
.......
}
[ 0.000000] FIX_HOLE :0xfffffffdfe000000 //0x000007FFFFFFEFF0
[ 0.000000] FIX_FDT_END :0xfffffffdfdfff000
[ 0.000000] FIX_FDT :0xfffffffdfdc00000 //0x000007FFFFFFEFEE
[ 0.000000] FIX_EARLYCON_MEM_BASE:0xfffffffdfdbff000
[ 0.000000] FIX_BTMAP_END :0xfffffffdfdbf9000
[ 0.000000] FIX_BTMAP_BEGIN :0xfffffffdfda3a000
[ 0.000000] FIX_PTE :0xfffffffdfda39000
[ 0.000000] FIX_PMD :0xfffffffdfda38000
[ 0.000000] FIX_PUD :0xfffffffdfda37000
[ 0.000000] FIX_PGD :0xfffffffdfda36000 //0x000007FFFFFFEFED
[ 0.000000] FIXADDR_START~TOP :0xfffffffdfdbf9000 - 0xfffffffdfe000000 ( 4124 KB)
上面0xfffffffdfdbf9000 - 0xfffffffdfe000000这段虚拟地址范围就是fixed map区域,这段区域可以通过FIXADDR_START和FIXADDR_TOP来确定。Fixmap虚拟地址平均分成两个部分,两个部分permanent fixed addresses和temporary fixed addresses。permanent fixed addresses是永久映射,temporary fixed addresses是临时映射。永久映射是指在建立的映射关系在kernel阶段不会改变,仅供特定模块一直使用。临时映射就是模块使用前创建映射,使用后解除映射。fixmap区域又被继续细分,分配给不同模块使用。kernel中定义枚举类型作为index,根据index可以计算在fixmap区域的虚拟地址。
arch/arm64/include/asm/fixmap.h
enum fixed_addresses {
FIX_HOLE,
/*
* Reserve a virtual window for the FDT that is 2 MB larger than the
* maximum supported size, and put it at the top of the fixmap region.
* The additional space ensures that any FDT that does not exceed
* MAX_FDT_SIZE can be mapped regardless of whether it crosses any
* 2 MB alignment boundaries.
*
* Keep this at the top so it remains 2 MB aligned.
*/
#define FIX_FDT_SIZE (MAX_FDT_SIZE + SZ_2M)
FIX_FDT_END,
FIX_FDT = FIX_FDT_END + FIX_FDT_SIZE / PAGE_SIZE - 1,
FIX_EARLYCON_MEM_BASE,
FIX_TEXT_POKE0,
#ifdef CONFIG_ACPI_APEI_GHES
/* Used for GHES mapping from assorted contexts */
FIX_APEI_GHES_IRQ,
FIX_APEI_GHES_SEA,
#ifdef CONFIG_ARM_SDE_INTERFACE
FIX_APEI_GHES_SDEI_NORMAL,
FIX_APEI_GHES_SDEI_CRITICAL,
#endif
#endif /* CONFIG_ACPI_APEI_GHES */
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
FIX_ENTRY_TRAMP_TEXT3,
FIX_ENTRY_TRAMP_TEXT2,
FIX_ENTRY_TRAMP_TEXT1,
FIX_ENTRY_TRAMP_DATA,
#define TRAMP_VALIAS (__fix_to_virt(FIX_ENTRY_TRAMP_TEXT1))
#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
__end_of_permanent_fixed_addresses,
/*
* Temporary boot-time mappings, used by early_ioremap(),
* before ioremap() is functional.
*/
#define NR_FIX_BTMAPS (SZ_256K / PAGE_SIZE)
#define FIX_BTMAPS_SLOTS 7
#define TOTAL_FIX_BTMAPS (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)
FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,
/*
* Used for kernel page table creation, so unmapped memory may be used
* for tables.
*/
FIX_PTE,
FIX_PMD,
FIX_PUD,
FIX_PGD,
__end_of_fixed_addresses
};
#define FIXADDR_SIZE (__end_of_permanent_fixed_addresses << PAGE_SHIFT)
#define FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)
fixmap初始化
前面描述了,fixmap就是让一段固定的虚拟地址空间与一段物理空间建立映射,以便linux内核通过虚拟地址才能访问到对应物理地址的空间数据,虚拟地址到物理地址的转换是通过mmu查询页表得来的,因此需要构建填充虚拟地址到物理地址转换的页表。在linux内核中,页表存储通过定义了3个全局数组bm_pud,bm_pmd,bt_pte来存储。因此early_fixmap_init的目的来填充这几个数组(页表)。
static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;
static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;
static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;
没有建立PGD,PGD在swapper_pg_dir中,在内核镜像的数据段
PTRS_PER_PTE/PMD/PUD为页表entry的数目
#define PTRS_PER_PTE (1 << (PAGE_SHIFT - 3))
arch/arm64/mm/mmu.c
void __init early_fixmap_init(void)
{
pgd_t *pgd;
p4d_t *p4dp, p4d;
pud_t *pud;
pmd_t *pmd;
unsigned long addr = FIXADDR_START; (1)FIXADDR_START定义了fixedmap区域的起始地址。
pgdp = pgd_offset_k(addr);
p4dp = p4d_offset(pgdp, addr);//3级页表中p4dp=pgd
p4d = READ_ONCE(*p4dp);//读表项中的内容
(2)获取addr对应的pgd全局页表表项地址,页表是swapper_pg_dir的空间
if (CONFIG_PGTABLE_LEVELS > 3 &&
!(pgd_none(*pgd) || pgd_page_paddr(*pgd) == __pa_symbol(bm_pud))) {
pud = pud_offset_kimg(pgd, addr);
} else {
(3)因为是3级页表p4d_node=0,因此不会进入这里,也就是不会使用bm_pud
if (p4d_none(p4d))
__p4d_populate(p4dp, __pa_symbol(bm_pud), P4D_TYPE_TABLE);
pud = fixmap_pud(addr);
(4)获取addr在PUD页表项中的偏移地址,这里是3级页表,所以pud=pgdp
}
if (pud_none(*pud))
__pud_populate(pud, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);
(5)将bm_pmd的物理地址写到pgd页表对应表项中
pmd = fixmap_pmd(addr);
(6)获取addr在对应页表中表项的地址(虚拟地址)。
__pmd_populate(pmd, __pa_symbol(bm_pte), PMD_TYPE_TABLE);
(7)将bm_pte的物理地址写到pmd页表中。
}
TIPS:当使用3级页表时,内核如何判断是否需要创建PUD页表?
arch/arm64/include/asm/pgtable-types.h
#if CONFIG_PGTABLE_LEVELS == 2
#include <asm-generic/pgtable-nopmd.h>
#elif CONFIG_PGTABLE_LEVELS == 3
#include <asm-generic/pgtable-nopud.h>
#elif CONFIG_PGTABLE_LEVELS == 4
#include <asm-generic/pgtable-nop4d.h>
#endif
从上可知,页表是3级页表时,包含的pud相关的头文件时#include <asm-generic/pgtable-nopud.h>
include/asm-generic/pgtable-nopud.h
static inline int p4d_none(p4d_t p4d) { return 0; } //直接返回0
static inline int p4d_bad(p4d_t p4d) { return 0; }
static inline int p4d_present(p4d_t p4d) { return 1; }
static inline void p4d_clear(p4d_t *p4d) { }
#define p4d_populate(mm, p4d, pud) do { } while (0)
#define p4d_populate_safe(mm, p4d, pud) do { } while (0)
#define set_p4d(p4dptr, p4dval) set_pud((pud_t *)(p4dptr), (pud_t) { p4dval })
static inline pud_t *pud_offset(p4d_t *p4d, unsigned long address)
{
return (pud_t *)p4d;
}
#define pud_offset pud_offset
#define pud_val(x) (p4d_val((x).p4d))
#define __pud(x) ((pud_t) { __p4d(x) })
#define p4d_page(p4d) (pud_page((pud_t){ p4d }))
#define p4d_pgtable(p4d) ((pud_t *)(pud_pgtable((pud_t){ p4d })))
#define pud_alloc_one(mm, address) NULL
#define pud_free(mm, x) do { } while (0)
#define pud_free_tlb(tlb, x, a) do { } while (0)
#undef pud_addr_end
#define pud_addr_end(addr, end) (end)
实际上,early_fixmap_init只是建立了一个映射的框架,实际的物理地址和虚拟地址的映射关系是没有填充的,这个需要实际使用的时候再去填充对应的pte entry。
bm_pud/bm_pmd/bm_pte是全局数组(全局数据段),该阶段访问这几个全局数组的虚拟地址能够可以通过mmu转化为物理地址,因为这几个变量是属于内核映像中,在上一章节中内核镜像中的所有包括数据段、代码段等都可以进行访问了,因此这几个全局数组的虚拟地址是不需要映射的。
fixmap相关函数
#define pte_offset_phys(dir,addr) (pmd_page_paddr(READ_ONCE(*(dir))) + pte_index(addr) * sizeof(pte_t))
//查找虚拟地址对应PTE的物理地址(基地址),也就是对应PMD条目中的值。
#define pte_set_fixmap(addr) ((pte_t *)set_fixmap_offset(FIX_PTE, addr))
//获取addr(物理地址)对应的虚拟地址,其虚拟地址在FIX_PTE这个范围(建立映射)。
#define pte_set_fixmap_offset(pmd, addr) pte_set_fixmap(pte_offset_phys(pmd, addr))
//获取addr在PTE页表项的虚拟地址,其虚拟地址范围在FIX_PTE这个范围(建立映射)。
#define pte_clear_fixmap() clear_fixmap(FIX_PTE)
//清除FIX_PTE虚拟地址的映射
#define pmd_set_fixmap(addr) ((pmd_t *)set_fixmap_offset(FIX_PMD, addr))
#define pmd_set_fixmap_offset(pud, addr) pmd_set_fixmap(pmd_offset_phys(pud, addr))
#define pmd_clear_fixmap() clear_fixmap(FIX_PMD)
#define pud_set_fixmap(addr) ((pud_t *)set_fixmap_offset(FIX_PUD, addr))
#define pud_set_fixmap_offset(p4d, addr) pud_set_fixmap(pud_offset_phys(p4d, addr))
#define pud_clear_fixmap() clear_fixmap(FIX_PUD)
#define pgd_set_fixmap(addr) ((pgd_t *)set_fixmap_offset(FIX_PGD, addr))
#define pgd_clear_fixmap() clear_fixmap(FIX_PGD)
fixmap io映射
static void __iomem *prev_map[FIX_BTMAPS_SLOTS] __initdata;
static unsigned long prev_size[FIX_BTMAPS_SLOTS] __initdata;
static unsigned long slot_virt[FIX_BTMAPS_SLOTS] __initdata;
void __init early_ioremap_setup(void)
{
int i;
for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
if (WARN_ON(prev_map[i]))
break;
for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);
}
Ioremap的空间存放再slot_vir数组中,其虚拟地址空间每一个跨度为NR_FIX_BITMAPS。
实际进行IO映射的时候,会调用到__early_ioremap函数,在该函数中回去填充pte entry,这样虚拟地址和io设备的物理地址就匹配上了。
fixmap DTB映射
arch/arm64/kernel/setup.c
setup_machine_fdt->
void *__init fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot)
{
const u64 dt_virt_base = __fix_to_virt(FIX_FDT); //从FIXMAP中获取设备树的虚拟地址
int offset;
void *dt_virt;
/*
* Check whether the physical FDT address is set and meets the minimum
* alignment requirement. Since we are relying on MIN_FDT_ALIGN to be
* at least 8 bytes so that we can always access the magic and size
* fields of the FDT header after mapping the first chunk, double check
* here if that is indeed the case.
*/
BUILD_BUG_ON(MIN_FDT_ALIGN < 8);
if (!dt_phys || dt_phys
return NULL;
/*
* Make sure that the FDT region can be mapped without the need to
* allocate additional translation table pages, so that it is safe
* to call create_mapping_noalloc() this early.
*
* On 64k pages, the FDT will be mapped using PTEs, so we need to
* be in the same PMD as the rest of the fixmap.
* On 4k pages, we\'ll use section mappings for the FDT so we only
* have to be in the same PUD.
*/
BUILD_BUG_ON(dt_virt_base
BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> SWAPPER_TABLE_SHIFT !=
__fix_to_virt(FIX_BTMAP_BEGIN) >> SWAPPER_TABLE_SHIFT);
offset = dt_phys
dt_virt = (void *)dt_virt_base + offset;
/* map the first chunk so we can read the size from the header */
create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE),
dt_virt_base, SWAPPER_BLOCK_SIZE, prot);
//根据提供的物理地址和虚拟地址设置页表entry,建立dbt物理地址到fixmap中虚拟地址的映射
if (fdt_magic(dt_virt) != FDT_MAGIC)
return NULL;
//获取dtb文件大小
*size = fdt_totalsize(dt_virt);
//DTB的大小不能超过2M
if (*size > MAX_FDT_SIZE)
return NULL;
//如果DTB文件结尾的地址空间超过了上面建立的2M地址范围,需要紧接这再映射2M地址空间。
if (offset + *size > SWAPPER_BLOCK_SIZE)
create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), dt_virt_base,
round_up(offset + *size, SWAPPER_BLOCK_SIZE), prot);
return dt_virt;
}
TIPS:如何打开linux内核pr_debug相关的打印
打开pr_debug的打印
(1)将Default console loglevel 设置到8
Kernel hacking
> printk and dmesg options
(8)Default console loglevel (1-15)
(2)在对应的模块上编译添加-DDEBUG宏
diff --git a/drivers/of/Makefile b/drivers/of/Makefile
index e0360a44306e..25bc584536b3 100644
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
+ccflags-y :=-DDEBUG
Memblock
Linux内核使用伙伴系统管理内存,在伙伴系统之前,内核通过memblock来管理。在系统启动阶段,使用memblock记录理内存的使用情况,可以分成好几块。
- 永久分配给系统内核:内核镜像占用的部分,如代码、数据段等;设备树DTB等
- 预留给外设的连续内存:如GPU/Camera/多核共享等需要预留大量连续内存。
- 其他部分:以上的剩余部分内存,需要进行内存管理。
Memblock将以上内存按功能划分为若干内存区,使用不同的类型存放在memory和reserved两个集合中,memory即动态内存,reserved即静态分配的内存。
获取物理内存大小
在设备树中,使用节点名称为memory来描述内存信息,如果系统中有多个内存范围,那么device tree中可能会创建多个内存节点,或者一个单独的内存节点通过reg属性指定内存的访问。
假设一个64位系统具有以下的物理内存块:
- RAM:起始地址0x0,长度0x80000000(2GB)
- RAM:起始地址0x100000000,长度0x100000000(4GB)
方法一
memory@0 {
device_type = \"memory\";
reg = < 0x000000000 0x00000000 0x00000000 0x80000000
0x000000001 0x00000000 0x00000001 0x00000000>;
};
第一个整数(0x00000000):表示物理地址的高32位。
第二个整数(0x00000000):表示物理地址的低32位。在这个例子中,物理地址为0x00000000。
第三个整数(0x00000000):表示大小的高32位。
第四个整数(0x80000000):表示大小的低32位。在这个例子中,大小为0x80000000,即2GB。
第五个整数(0x00000001):表示物理地址的高32位。
第六个整数(0x00000000):表示物理地址的低32位。在这个例子中,物理地址为0x100000000。
第七个整数(0x00000001):表示大小的高32位。
第八个整数(0x00000000):表示大小的低32位。在这个例子中,大小为0x100000000,即4GB。
方法二
memory@0 {
device_type = \"memory\";
reg = < 0x000000000 0x00000000 0x00000000 0x80000000>;
};
memory@100000000 {
device_type = \"memory\";
reg = < 0x000000001 0x00000000 0x00000001 0x00000000>;
};
有些平台中在设备树中有时并没有去描述该节点,那是因为在uboot启动的时候会创建或改写该节点,实际的物理内存大小可能在boot0阶段就探测到了。
int fdt_fixup_memory_banks(void *blob, u64 start[], u64 size[], int banks)
{
int err, nodeoffset;
int len, i;
u8 tmp[MEMORY_BANKS_MAX * 16]; /* Up to 64-bit address + 64-bit size */
if (banks > MEMORY_BANKS_MAX) {
printf(\"
\" Recompile with higher MEMORY_BANKS_MAX?\\n\",
__FUNCTION__, banks, MEMORY_BANKS_MAX);
return -1;
}
err = fdt_check_header(blob);
if (err < 0) {
printf(\"
return err;
}
/* find or create \"/memory\" node. */
nodeoffset = fdt_find_or_add_subnode(blob, 0, \"memory\");
if (nodeoffset < 0)
return nodeoffset;
err = fdt_setprop(blob, nodeoffset, \"device_type\", \"memory\",
sizeof(\"memory\"));
if (err < 0) {
printf(\"WARNING: could not set
fdt_strerror(err));
return err;
}
for (i = 0; i < banks; i++) {
if (start[i] == 0 && size[i] == 0)
break;
}
banks = i;
if (!banks)
return 0;
for (i = 0; i < banks; i++)
if (start[i] == 0 && size[i] == 0)
break;
banks = i;
len = fdt_pack_reg(blob, tmp, start, size, banks);
err = fdt_setprop(blob, nodeoffset, \"reg\", tmp, len);
if (err < 0) {
printf(\"WARNING: could not set
\"reg\", fdt_strerror(err));
return err;
}
return 0;
所以,在设备树中找不到描述,可以在系统启动阶段在uboot阶段查看内存节点。
=> fdt list /memory
memory {
reg = <0x00000000 0x40000000 0x00000000 0x80000000>;
device_type = \"memory\";
};
物理地址起始:0x40000000
物理内存大小:0x80000000(2GB)
内核调用early_init_dt_scan_nodes扫描DTB,然后将物理内存同故宫memblock_add添加到memblock中进行管理。
drivers/os/fdt.c
void __init early_init_dt_scan_nodes(void)
{
int rc = 0;
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* Retrieve various information from the /chosen node */
rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
if (!rc)
pr_warn(\"No chosen node found, continuing without\\n\");
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
early_init_dt_add_memory_arch(base, size);
memblock_add(base, size);
//从设备树中读取到物理内存的地址和大小,添加到memblock中
/* Handle linux,usable-memory-range property */
early_init_dt_check_for_usable_mem_range();
}
管理结构体
- 第一层:struct memblock,定义一个全局变量,用来维护所有的物理内存;
- 第二层:struct memblock_type,系统中内存类型,包括可分配使用的内存和保留的内存;
- 第三层:struct memblock_region,描述具体内存区域,包含在struct memblock_type中的regions数组中,最多存放128个。
mm/memblock.c
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdata_memblock;
struct memblock memblock __initdata_memblock = {
.memory.regions = memblock_memory_init_regions,
.memory.cnt = 1, /* empty dummy entry */
.memory.max = INIT_MEMBLOCK_REGIONS,
.memory.name = \"memory\",
.reserved.regions = memblock_reserved_init_regions,
.reserved.cnt = 1, /* empty dummy entry */
.reserved.max = INIT_MEMBLOCK_RESERVED_REGIONS,
.reserved.name = \"reserved\",
.bottom_up = false,
.current_limit = MEMBLOCK_ALLOC_ANYWHERE,
};
定义了memblock全局变量,因此是不需要初始化的,在定义的时候就进行了初始化。regions指向的也是静态全局的数组,数组的大小为INIT_MEMBLOCK_REGIONS(128),在实际代码中,可以看到,当超过这个数组时,这个数组将会进行动态扩大。
memblock主要接口函数
Memblock系统提供一些列接口供内核模块使用,包括内存区块的添加、预留、内存申请等功能。
- memblock_add:将内存块添加到可用内存集合,添加新的内存块区域到memblock.memory中。
- memblock_reserve:将内存块添加到预留内存集合
- memblock_phys_alloc:用于申请memblock中的物理内存
- memblock_remove:删除内存块区域
- memblock_alloc:分配内存
- memblock_free:释放内存
memblock_add
memblock_add函数将物理内存区块添加到可用内存集合中,结构管理图如下
memblock_reserve
与memblock_add类似
memblock_alloc
void *memblock_alloc(phys_addr_t size, phys_addr_t align)
memblock_alloc_try_nid
memblock_alloc_internal
memblock_alloc_range_nid
memblock_find_in_range_node
phys_to_virt(alloc)
最终调用memblock_find_in_range_node实现物理内存的分配。memblock_phys_alloc函数与该函数类似,区别是memblock_alloc在分配后会会调用phys_to_virt将物理地址转化为虚拟地址,而memblock_phys_alloc不会。
Arm64 memblock init
物理内存都添加到系统之后,会调用arm64_memblock_init对整个物理内存进行整理,主要的工作就是remove掉一些no-map区域(不归内核管理),同时保留一些关键区域,如内核镜像区,dtb中reserved的内存节点。
上图中,浅绿色的就是reserved部分,不能被分配使用,而剩下的部分就可以通过调用上小章节中的函数去使用内存了。
小结:
(1)系统通过memblock以数组memory type的方式记录物理内存空间,数组中每一个内存区域描述了一段内存信息,包括base,size,node id等。
(2)在memblock信息中,已经被使用或者被内核定义需要保留的区域,会存储在reserved 数组中。
(3)memory type数组中并不是代表整个内核系统的内存空间,因为股份驱动会保留一段内存区域供自己单独使用,其在dts中具有no-map熟悉的reserved-memory节点,不会由内核创建地址映射。
(4)可以通过内核调试节点/sys/kernel/debug/memblockk进行查询相关信息
paging_init
上一章节中,物理内存通过该memblock模块添加进了系统,但是此时仍然只有DTB和image所在的两端物理内存可以访问,其他物理内存还访问不了,因为其还没有建立其页表。即使可以通过memblock_alloc分配物理内存,但是也不能访问,因为其虚拟地址对应的页表没有生成,只有是创建了页表才能通过虚拟地址转化访问物理地址。
void __init paging_init(void)
{
pgd_t *pgdp = pgd_set_fixmap(__pa_symbol(swapper_pg_dir));
//(1)获取一页内存用于构建PGD映射表,返回的是虚拟地址。
map_kernel(pgdp);
//(2)完成内核的映射,包括text,data,bss段等。
map_mem(pgdp);
//(3)将memblock子系统添加到物理内存进行映射
pgd_clear_fixmap();
cpu_replace_ttbr1(lm_alias(swapper_pg_dir));
//(4)切换页表,新建立页表内容替换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));
//(5)新的映射更新完成,释放掉临时空间
memblock_allow_resize();
}
构建PGD映射表
页目录直接使用的是swapper_pg_dir,一个条目映射的空间本身就很大,一个entry对应范围有512GB。
arch/arm64/include/asm/fixmap.h
enum fixed_addresses {
......
/*
* Used for kernel page table creation, so unmapped memory may be used
* for tables.
*/
FIX_PTE,
FIX_PMD,
FIX_PUD,
FIX_PGD,
......
};
pgd_t *pgdp = pgd_set_fixmap(__pa_symbol(swapper_pg_dir));
#define pgd_set_fixmap(addr) ((pgd_t *)set_fixmap_offset(FIX_PGD, addr))
#define set_fixmap_offset(idx, phys) \\
__set_fixmap_offset(idx, phys, FIXMAP_PAGE_NORMAL)
#define __set_fixmap_offset(idx, phys, flags) \\
({ \\
unsigned long ________addr; \\
__set_fixmap(idx, phys, flags); \\
________addr = fix_to_virt(idx) + ((phys) & (PAGE_SIZE - 1)); \\
________addr; \\
})
arch/arm64/kernel/vmlinux.lds.S
swapper_pg_dir = .;
. += PAGE_SIZE;
swapper_pg_dir是实现分配的一段空间,处于内核镜像的data段。
通过__pa_symbol先将swapper_pg_dir转化为物理地址,然后与FIX_PGD地址范围进行映射,后续就可以通过虚拟地址FIX_PGD这段访问访问到swapper_pg_dir这块物理空间。
early_pgtable_alloc
对内核各个段、以及memblock管理的物理内存建立映射,在上一章节中已经获取到了PGD全局目录页表,但是接下来的PUD,PMD,PTE对应的页表是需要进行动态分配的,空间的分配可以使用memblock提供的函数进行分配,但是如何进行访问填充页表了?memblock分配空间内核是没法直接访问的,因为没有创建页表,没法通过查表的方式进行查找到物理地址。这个时候前面fixmap就发挥作用了,在fixmap章节中,已经创建了虚拟地址到物理地址的页表,有一段实际的虚拟地址对应的物理地址是待填充的,那就是FIX_PTE~FIX_PGD,所以就可以利用这段空间将memblock分配到的物理地址与FIX_PTE~FIX_PGD对应上,这样内核就可以通过虚拟地址进行访问了,就可以填充页表内容。
内核访问物理内存使用的都是虚拟地址,而硬件模块比如MMU等访问内存使用的是物理地址,不需要从虚拟地址到物理地址转换(否则就陷入循环了)。虚拟地址转为物理地址需要查找页表找到对应的物理地址,而这个页表需要进行填充(建立映射关系),因此内核在填充页表的时候,也是使用的虚拟地址访问。只要把各级页表填充好之后就可以了,最终MMU在翻译的时候就访问的是物理地址。
static phys_addr_t __init early_pgtable_alloc(int shift)
{
phys_addr_t phys;
void *ptr;
phys = memblock_phys_alloc_range(PAGE_SIZE, PAGE_SIZE, 0,
MEMBLOCK_ALLOC_NOLEAKTRACE);
//(1)先分配一块物理内存
ptr = pte_set_fixmap(phys);
//(2)将当前的物理内存与fixmap的虚拟地址进行映射,映射完成后,内核即可访问这段内存,用的是PTE这段,PGD,PUD,PMD用在哪里?
memset(ptr, 0, PAGE_SIZE);
pte_clear_fixmap();
return phys;
}
从上可以看出分配一个页表需要PAGE_SIZE的大小,也就等于一个物理页帧大小4KB。页表有512个条目,每个条目占用8字节。
内核镜像细粒度映射-map_kernel
Map_kernel主要完成内核中各个段的映射,包括text、rodata、init、data、bss等各个段。
static void __init map_kernel(pgd_t *pgdp)
map_kernel_segment(pgdp, _stext, _etext, text_prot, &vmlinux_text, 0,
VM_NO_GUARD);
map_kernel_segment(pgdp, __start_rodata, __inittext_begin, PAGE_KERNEL,
&vmlinux_rodata, NO_CONT_MAPPINGS, VM_NO_GUARD);
map_kernel_segment(pgdp, __inittext_begin, __inittext_end, text_prot,
&vmlinux_inittext, 0, VM_NO_GUARD); //.init
map_kernel_segment(pgdp, __initdata_begin, __initdata_end, PAGE_KERNEL,
&vmlinux_initdata, 0, VM_NO_GUARD);//.data
map_kernel_segment(pgdp, _data, _end, PAGE_KERNEL, &vmlinux_data, 0, 0); //.bss
启动日志
[ 0.000000] Virtual kernel memory layout:
[ 0.000000] modules : 0xffffffc000000000 - 0xffffffc008000000 ( 128 MB)
[ 0.000000] vmalloc : 0xffffffc008000000 - 0xfffffffdf0000000 ( 247 GB)
[ 0.000000] .text : 0xffffffc008080000 - 0xffffffc008a30000 ( 9920 KB)
[ 0.000000] .rodata : 0xffffffc008a30000 - 0xffffffc008d70000 ( 3328 KB)
[ 0.000000] .init : 0xffffffc008d70000 - 0xffffffc008ef0000 ( 1536 KB)
[ 0.000000] .data : 0xffffffc008ef0000 - 0xffffffc00900f008 ( 1149 KB)
[ 0.000000] .bss : 0xffffffc00900f008 - 0xffffffc009069920 ( 363 KB)
[ 0.000000] fixed : 0xfffffffdfdbf9000 - 0xfffffffdfe000000 ( 4124 KB)
[ 0.000000] PCI I/O : 0xfffffffdfe800000 - 0xfffffffdff800000 ( 16 MB)
[ 0.000000] vmemmap : 0xfffffffe00000000 - 0xffffffff00000000 ( 4 GB maximum)
[ 0.000000] 0xfffffffe00000000 - 0xfffffffe02000000 ( 32 MB actual)
[ 0.000000] memory : 0xffffff8000000000 - 0xffffff8080000000 ( 2048 MB)
[ 0.000000] PAGE_OFFSET : 0xffffff8000000000
[ 0.000000] PHYS_OFFSET : 0x 40000000
[ 0.000000] KIMAGE_VADDR : 0xffffffc008000000
static void __init map_kernel_segment(pgd_t *pgdp, void *va_start, void *va_end,
pgprot_t prot, struct vm_struct *vma,
int flags, unsigned long vm_flags)
{
phys_addr_t pa_start = __pa_symbol(va_start); //将虚拟地址转为物理地址
unsigned long size = va_end - va_start;
BUG_ON(!PAGE_ALIGNED(pa_start));
BUG_ON(!PAGE_ALIGNED(size));
__create_pgd_mapping(pgdp, pa_start, (unsigned long)va_start, size, prot,
early_pgtable_alloc, flags);
if (!(vm_flags & VM_NO_GUARD))
size += PAGE_SIZE;
vma->addr = va_start;
vma->phys_addr = pa_start;
vma->size = size;
vma->flags = VM_MAP | vm_flags;
vma->caller = __builtin_return_address(0);
vm_area_add_early(vma);
}
线性映射-map_mem
完成对物理内存的映射,这部分的物理内存是同故宫memblock_add添加系统中的,函数中将会遍历memblock中的各个块,然后调用__map_memblock来完成实际的映射操作。
static void __init map_mem(pgd_t *pgdp)
{
......
memblock_mark_nomap(kernel_start, kernel_end - kernel_start);
//(1)不对设置了MEMBLOCK_NOMAP的标志映射
/* map all the memory banks */
for_each_mem_range(i, &start, &end) {
if (start >= end)
break;
/*
* The linear map must allow allocation tags reading/writing
* if MTE is present. Otherwise, it has the same attributes as
* PAGE_KERNEL.
*/
__map_memblock(pgdp, start, end, pgprot_tagged(PAGE_KERNEL),
flags);
}
//(2)遍历memblock中的各个块并完成内存的映射
}
遍历memblock.memory进行逐一映射。
static void __init map_mem(pgd_t *pgdp)
{
......
memblock_mark_nomap(kernel_start, kernel_end - kernel_start);
//(1)不对设置了MEMBLOCK_NOMAP的标志映射
/* map all the memory banks */
for_each_mem_range(i, &start, &end) {
if (start >= end)
break;
/*
* The linear map must allow allocation tags reading/writing
* if MTE is present. Otherwise, it has the same attributes as
* PAGE_KERNEL.
*/
__map_memblock(pgdp, start, end, pgprot_tagged(PAGE_KERNEL),
flags);
}
//(2)遍历memblock中的各个块并完成内存的映射
}
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);
}
Start是要映射的物理地址,__phys_to_virt(start)是要映射的虚拟地址,由此可见,这段空间是进行的线性映射。
__create_pgd_mapping
map_kernel与map_mem最终都会调用__create_pgd_mapping进行映射。
static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,
unsigned long virt, phys_addr_t size,
pgprot_t prot,
phys_addr_t (*pgtable_alloc)(int),
int flags)
{
unsigned long addr, end, next;
pgd_t *pgdp = pgd_offset_pgd(pgdir, virt);
//获取要映射地址virt在PGD页表目录的表项对应的地址(虚拟地址),接下来将会进行填充内容(下一级页表的物理地址)。
/*
* If the virtual and physical address don\'t have the same offset
* within a page, we cannot map the region as the caller expects.
*/
if (WARN_ON((phys ^ virt) & ~PAGE_MASK))
return;
//让物理内存由原理的按字节计算位置改为按页计算位置
phys &= PAGE_MASK;
addr = virt & PAGE_MASK;
end = PAGE_ALIGN(virt + size);//按PAGE对齐的方式算,结束地址多少。
do {
next = pgd_addr_end(addr, end);
//找到当前PGD的结束地址,一般来说PGD entry只有一个,所以这里的循环只会有依次。原因是一个PGD有512个条目,每个条目表示512GB(2^39)的虚拟地址空间。
alloc_init_pud(pgdp, addr, next, phys, prot, pgtable_alloc,
flags); //初始化该PGD条目对应的PUD
phys += next - addr;
} while (pgdp++, addr = next, addr != end);
}
alloc_init_pud
static void alloc_init_pud(pgd_t *pgdp, unsigned long addr, unsigned long end,
phys_addr_t phys, pgprot_t prot,
phys_addr_t (*pgtable_alloc)(int),
int flags)
{
unsigned long next;
pud_t *pudp;
p4d_t *p4dp = p4d_offset(pgdp, addr);
//获取第四级页表中页表项的地址,MR527是三级页表,所以p4dp=pgdp。
p4d_t p4d = READ_ONCE(*p4dp); //读取表项中的内容,实际读的就是PGD目录(3级)
//判断表项内容是否为空,如果为空需要进行PUD,这里表项不为空,因为是3级页表,所以不需要创建PUD
if (p4d_none(p4d)) {
p4dval_t p4dval = P4D_TYPE_TABLE | P4D_TABLE_UXN;
phys_addr_t pud_phys;
if (flags & NO_EXEC_MAPPINGS)
p4dval |= P4D_TABLE_PXN;
BUG_ON(!pgtable_alloc);
pud_phys = pgtable_alloc(PUD_SHIFT);
__p4d_populate(p4dp, pud_phys, p4dval);
p4d = READ_ONCE(*p4dp);
}
BUG_ON(p4d_bad(p4d));
/*
* No need for locking during early boot. And it doesn\'t work as
* expected with KASLR enabled.
*/
if (system_state != SYSTEM_BOOTING)
mutex_lock(&fixmap_lock);
pudp = pud_set_fixmap_offset(p4dp, addr);
//计算所在PUD(PGD)偏移表项的地址(虚拟地址),其地址(虚拟)空间在fixmap范围内FIX_PUD(FIX_PGD)范围内,因为要访问其物理空间,需要查询页表,所以使用之前创建好的页表,填充映射好后,可以直接访问。
do {
pud_t old_pud = READ_ONCE(*pudp);
next = pud_addr_end(addr, end);
//PUD起始和结束位置,大小是1GB。空间比较大,只循环一次。
/*
* For 4K granule only, attempt to put down a 1GB block
*/
if (use_1G_block(addr, next, phys) &&
(flags & NO_BLOCK_MAPPINGS) == 0) {
pud_set_huge(pudp, phys, prot);
/*
* After the PUD entry has been populated once, we
* only allow updates to the permission attributes.
*/
BUG_ON(!pgattr_change_is_safe(pud_val(old_pud),
READ_ONCE(pud_val(*pudp))));
} else {
alloc_init_cont_pmd(pudp, addr, next, phys, prot,
pgtable_alloc, flags);//循环在各个PUD映射表现建立对应PMD页表
BUG_ON(pud_val(old_pud) != 0 &&
pud_val(old_pud) != READ_ONCE(pud_val(*pudp)));
}
phys += next - addr;
} while (pudp++, addr = next, addr != end);
pud_clear_fixmap();
if (system_state != SYSTEM_BOOTING)
mutex_unlock(&fixmap_lock);
}
alloc_init_cont_pmd
static void alloc_init_cont_pmd(pud_t *pudp, unsigned long addr,
unsigned long end, phys_addr_t phys,
pgprot_t prot,
phys_addr_t (*pgtable_alloc)(int), int flags)
{
unsigned long next;
pud_t pud = READ_ONCE(*pudp);//获取PUD页表中addr对应的表项内容,也就是PMD页表地址
/*
* Check for initial section mappings in the pgd/pud.
*/
BUG_ON(pud_sect(pud));
//如果PUD页表为空,则分配一个页表,页表中的表项为创建512个。页表大小一个为4K,每个表项占8字节。
if (pud_none(pud)) {
pudval_t pudval = PUD_TYPE_TABLE | PUD_TABLE_UXN;
phys_addr_t pmd_phys;
if (flags & NO_EXEC_MAPPINGS)
pudval |= PUD_TABLE_PXN;
BUG_ON(!pgtable_alloc);
pmd_phys = pgtable_alloc(PMD_SHIFT);
__pud_populate(pudp, pmd_phys, pudval);//将PMD页表的物理地址填充到映射地址对应的PUD(实际上是PGD,3级页表)表项中
pud = READ_ONCE(*pudp);
}
BUG_ON(pud_bad(pud));
do {
pgprot_t __prot = prot;
next = pmd_cont_addr_end(addr, end);
//一个PMD entry映射范围是2M,所以计算需要多少个entry。但是如果是连续的物理内存,init_pmd不是只初始化一个entry,而是一下初始化多个entry,多少个entry由CONT_PMDS。所以这里的地址范围next的距离将是CONT_PMDS*PMD_SIZE。
/* use a contiguous mapping if the range is suitably aligned */
if ((((addr | next | phys) & ~CONT_PMD_MASK) == 0) &&
(flags & NO_CONT_MAPPINGS) == 0)
__prot = __pgprot(pgprot_val(prot) | PTE_CONT);
init_pmd(pudp, addr, next, phys, __prot, pgtable_alloc, flags);
//初始化PMD页表,创建下一级页表,同时将其物理地址填充到表项中。
phys += next - addr;
} while (addr = next, addr != end);
}
init_pmd
static void init_pmd(pud_t *pudp, unsigned long addr, unsigned long end,
phys_addr_t phys, pgprot_t prot,
phys_addr_t (*pgtable_alloc)(int), int flags)
{
unsigned long next;
pmd_t *pmdp;
pmdp = pmd_set_fixmap_offset(pudp, addr);
//获取映射地址addr对应PMD页表项的地址(虚拟地址),其地址范围在FIX_PMD中,因为访问物理内存也需要查询页表,那就将其物理地址映射到FIXMAP范围,就可以进行直接访问虚拟地址了。
do {
pmd_t old_pmd = READ_ONCE(*pmdp);//遍历PMD表项,
next = pmd_addr_end(addr, end);
//每个PMD的映射范围是2M,遍历需要多少个PTE。
/* try section mapping first */
if (((addr | next | phys) & ~PMD_MASK) == 0 &&
(flags & NO_BLOCK_MAPPINGS) == 0) {
pmd_set_huge(pmdp, phys, prot);
/*
* After the PMD entry has been populated once, we
* only allow updates to the permission attributes.
*/
BUG_ON(!pgattr_change_is_safe(pmd_val(old_pmd),
READ_ONCE(pmd_val(*pmdp))));
} else {
alloc_init_cont_pte(pmdp, addr, next, phys, prot,
pgtable_alloc, flags);
BUG_ON(pmd_val(old_pmd) != 0 &&
pmd_val(old_pmd) != READ_ONCE(pmd_val(*pmdp)));
}
phys += next - addr;
} while (pmdp++, addr = next, addr != end);
pmd_clear_fixmap();
}
static void alloc_init_cont_pte(pmd_t *pmdp, unsigned long addr,
unsigned long end, phys_addr_t phys,
pgprot_t prot,
phys_addr_t (*pgtable_alloc)(int),
int flags)
{
unsigned long next;
pmd_t pmd = READ_ONCE(*pmdp);//获得PTE映射表的头地址
BUG_ON(pmd_sect(pmd));
if (pmd_none(pmd)) {//如果没有该表则创建一个
pmdval_t pmdval = PMD_TYPE_TABLE | PMD_TABLE_UXN;
phys_addr_t pte_phys;
if (flags & NO_EXEC_MAPPINGS)
pmdval |= PMD_TABLE_PXN;
pte_phys = pgtable_alloc(PAGE_SHIFT);
__pmd_populate(pmdp, pte_phys, pmdval);
pmd = READ_ONCE(*pmdp);
}
do {
pgprot_t __prot = prot;
next = pte_cont_addr_end(addr, end);
//一个PTE entry映射范围是4K,所以计算需要多少个entry。但是如果是连续的物理内存,init_pmd不是只初始化一个entry,而是一下初始化多个entry,多少个entry由CONT_PTES。所以这里的地址范围next的距离将是CONT_PTES*PTE_SIZE。
/* use a contiguous mapping if the range is suitably aligned */
if ((((addr | next | phys) & ~CONT_PTE_MASK) == 0) &&
(flags & NO_CONT_MAPPINGS) == 0)
__prot = __pgprot(pgprot_val(prot) | PTE_CONT);
init_pte(pmdp, addr, next, phys, __prot);//初始化每一个PTE的表项记录,对应物理页帧
phys += next - addr;
} while (addr = next, addr != end);
}
init_pte
static void init_pte(pmd_t *pmdp, unsigned long addr, unsigned long end,
phys_addr_t phys, pgprot_t prot)
{
pte_t *ptep;
ptep = pte_set_fixmap_offset(pmdp, addr);//根据addr找到对应的PTE Entry位置
do {
pte_t old_pte = READ_ONCE(*ptep);
//读这个entry的值,一般来说新建的entry是没有valid的值的
set_pte(ptep, pfn_pte(__phys_to_pfn(phys), prot));
//将物理地址转换为页帧,然后写入PTE
/*
* After the PTE entry has been populated once, we
* only allow updates to the permission attributes.
*/
BUG_ON(!pgattr_change_is_safe(pte_val(old_pte),
READ_ONCE(pte_val(*ptep))));
phys += PAGE_SIZE;
} while (ptep++, addr += PAGE_SIZE, addr != end);
pte_clear_fixmap();
}
内核debug日志
内核debug日志
map_kernel_segment:pgdp:fffffffdfda36000,[va_start:ffffffc008a30000,va_end:ffffffc008d70000] paging_init+0x14c/0x524
__create_pgd_mapping:pgdp:0xfffffffdfda36800, size pgd_t:8, map_kernel_segment+0xf4/0x160
alloc_init_pud,333: pgdp:0xfffffffdfda36800, map_kernel_segment+0xf4/0x160
alloc_init_cont_pmd,274: pudp:0xfffffffdfda36800, size pud_t:8,map_kernel_segment+0xf4/0x160
init_pmd,235: pmdp:0xfffffffdfda38228, size pmd_t:8, addr:0xffffffc008a30000,end:0xffffffc008d70000 map_kernel_segment+0xf4/0x160
alloc_init_cont_pte,193: pmdp:0xfffffffdfda38228,addr:0xffffffc008a30000,end:0xffffffc008c00000 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39200,size pte_t:8, end:0xffffffc008a40000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39280,size pte_t:8, end:0xffffffc008a50000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39300,size pte_t:8, end:0xffffffc008a60000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39380,size pte_t:8, end:0xffffffc008a70000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39400,size pte_t:8, end:0xffffffc008a80000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39480,size pte_t:8, end:0xffffffc008a90000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39500,size pte_t:8, end:0xffffffc008aa0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39580,size pte_t:8, end:0xffffffc008ab0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39600,size pte_t:8, end:0xffffffc008ac0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39680,size pte_t:8, end:0xffffffc008ad0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39700,size pte_t:8, end:0xffffffc008ae0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39780,size pte_t:8, end:0xffffffc008af0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39800,size pte_t:8, end:0xffffffc008b00000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39880,size pte_t:8, end:0xffffffc008b10000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39900,size pte_t:8, end:0xffffffc008b20000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39980,size pte_t:8, end:0xffffffc008b30000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39a00,size pte_t:8, end:0xffffffc008b40000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39a80,size pte_t:8, end:0xffffffc008b50000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39b00,size pte_t:8, end:0xffffffc008b60000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39b80,size pte_t:8, end:0xffffffc008b70000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39c00,size pte_t:8, end:0xffffffc008b80000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39c80,size pte_t:8, end:0xffffffc008b90000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39d00,size pte_t:8, end:0xffffffc008ba0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39d80,size pte_t:8, end:0xffffffc008bb0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39e00,size pte_t:8, end:0xffffffc008bc0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39e80,size pte_t:8, end:0xffffffc008bd0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39f00,size pte_t:8, end:0xffffffc008be0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39f80,size pte_t:8, end:0xffffffc008bf0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda3a000,size pte_t:8, end:0xffffffc008c00000,number:16 map_kernel_segment+0xf4/0x160
alloc_init_cont_pte,193: pmdp:0xfffffffdfda38230,addr:0xffffffc008c00000,end:0xffffffc008d70000 map_kernel_segment+0xf4/0x160
memblock_reserve: [0x00000000bfffc000-0x00000000bfffcfff] memblock_alloc_range_nid+0xec/0x154
memblock_add_range: [0x00000000bfffc000] memblock_reserve+0xac/0x160
memblock_insert_region:name:reserved [0x00000000bfffc000] size:1000,memblock_add_range.constprop.0.isra.0+0x19c/0x214
alloc_init_cont_pte,204: pte_phys:0xbfffc000, map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39080,size pte_t:8, end:0xffffffc008c10000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39100,size pte_t:8, end:0xffffffc008c20000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39180,size pte_t:8, end:0xffffffc008c30000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39200,size pte_t:8, end:0xffffffc008c40000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39280,size pte_t:8, end:0xffffffc008c50000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39300,size pte_t:8, end:0xffffffc008c60000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39380,size pte_t:8, end:0xffffffc008c70000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39400,size pte_t:8, end:0xffffffc008c80000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39480,size pte_t:8, end:0xffffffc008c90000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39500,size pte_t:8, end:0xffffffc008ca0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39580,size pte_t:8, end:0xffffffc008cb0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39600,size pte_t:8, end:0xffffffc008cc0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39680,size pte_t:8, end:0xffffffc008cd0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39700,size pte_t:8, end:0xffffffc008ce0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39780,size pte_t:8, end:0xffffffc008cf0000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39800,size pte_t:8, end:0xffffffc008d00000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39880,size pte_t:8, end:0xffffffc008d10000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39900,size pte_t:8, end:0xffffffc008d20000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39980,size pte_t:8, end:0xffffffc008d30000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39a00,size pte_t:8, end:0xffffffc008d40000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39a80,size pte_t:8, end:0xffffffc008d50000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39b00,size pte_t:8, end:0xffffffc008d60000,number:16 map_kernel_segment+0xf4/0x160
init_pte,179: ptep:0xfffffffdfda39b80,size pte_t:8, end:0xffffffc008d70000,number:16 map_kernel_segment+0xf4/0x160
number表示在函数中调用一次init_pte调用set_pte的此次,为16次。
bootmem_init
完成了linux物理内存框架的初始化,包括Node, Zone, Page Frame以及对应的数据结构等。
void __init bootmem_init(void)
{
unsigned long min, max;
min = PFN_UP(memblock_start_of_DRAM());
max = PFN_DOWN(memblock_end_of_DRAM());
early_memtest(min << PAGE_SHIFT, max << PAGE_SHIFT);
max_pfn = max_low_pfn = max;
min_low_pfn = min;
arch_numa_init();
/*
* must be done after arch_numa_init() which calls numa_init() to
* initialize node_online_map that gets used in hugetlb_cma_reserve()
* while allocating required CMA size across online nodes.
*/
#if defined(CONFIG_HUGETLB_PAGE) && defined(CONFIG_CMA)
arm64_hugetlb_cma_reserve();
#endif
dma_pernuma_cma_reserve();
kvm_hyp_reserve();
/*
* sparse_init() tries to allocate memory from memblock, so must be
* done after the fixed reservations
*/
sparse_init();
zone_sizes_init(min, max);
/*
* Reserve the CMA area after arm64_dma_phys_limit was initialised.
*/
dma_contiguous_reserve(arm64_dma_phys_limit);
/*
* request_standard_resources() depends on crashkernel\'s memory being
* reserved, so do it here.
*/
if (IS_ENABLED(CONFIG_ZONE_DMA) || IS_ENABLED(CONFIG_ZONE_DMA32))
reserve_crashkernel();
memblock_dump_all();
sparse_init
Linux内核使用通常有三种内存模型,前面两种基本不再使用,目前常用的就是Sparse memory model,sparse init就是对该模型的初始化,主要的目的就是将memblock.memory添加到struct mem_section进行管理。
memory_present
static void __init memblocks_present(void)
{
unsigned long start, end;
int i, nid;
for_each_mem_pfn_range(i, MAX_NUMNODES, &start, &end, &nid)
memory_present(nid, start, end);
//从memblock.memory进行遍历可用内存,每块memory返回的是PFN的范围start~end,每个PFN大小4KB。
}
/* Record a memory area against a node. */
static void __init memory_present(int nid, unsigned long start, unsigned long end)
{
unsigned long pfn;
#ifdef CONFIG_SPARSEMEM_EXTREME
if (unlikely(!mem_section)) {
unsigned long size, align;
size = sizeof(struct mem_section *) * NR_SECTION_ROOTS;
align = 1 << (INTERNODE_CACHE_SHIFT);
mem_section = memblock_alloc(size, align);
//分配NR_SECTION_ROOTS个数组指针,用于指向struct mem_section的实例。
if (!mem_section)
panic(\"
__func__, size, align);
}
#endif
start &= PAGE_SECTION_MASK;
mminit_validate_memmodel_limits(&start, &end);
for (pfn = start; pfn < end; pfn += PAGES_PER_SECTION) {
unsigned long section = pfn_to_section_nr(pfn);
struct mem_section *ms;
sparse_index_init(section, nid);
->section = sparse_index_alloc(nid);
//为存在的mem_section分配一个实例,并添加到mem_sction中。
set_section_nid(section, nid);
ms = __nr_to_section(section);
//获取该section的指针
if (!ms->section_mem_map) {
ms->section_mem_map = sparse_encode_early_nid(nid) |
SECTION_IS_ONLINE;
__section_mark_present(ms, section);
}
//设置该section的online标志和node id值
}
}
物理内存空间按照seciton来组织的,每个section内部其memory是连续的。一个section包含多个page,在内核中由PAGES_PER_SECTION来决定,linux 5.15内核3级页表中为32768,因此内存的范围是32768*4KB=128MB,也就是说一个section最大的内存范围是128MB。
PFN找到对应的page,可以通过PFN->section->page。
page找到PFN,page->section index->memory_section->section_mem_map->PFN
sparse_init_nid
内存添加到mem_section后,就进行遍历present的section,然后为其分配对应的section结构以及对应的struct page结构体。
for_each_present_section_nr(pnum_begin + 1, pnum_end) {
int nid = sparse_early_nid(__nr_to_section(pnum_end));
if (nid == nid_begin) {
map_count++;
continue;
}
/* Init node with sections in range [pnum_begin, pnum_end) */
sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
nid_begin = nid;
pnum_begin = pnum_end;
map_count = 1;
}
static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
unsigned long pnum_end,
unsigned long map_count)
{
struct mem_section_usage *usage;
unsigned long pnum;
struct page *map;
//(1)为mem_section中的mem_section_usage分配内存,用于存储内存段的使用情况
usage = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nid),
mem_section_usage_size() * map_count);
if (!usage) {
pr_err(\"
goto failed;
}
//(2)为struct page结构体分配内存,一个section最大指向128MB的空间,将分配128*1024/4个数量。
sparse_buffer_init(map_count * section_map_size(), nid);
for_each_present_section_nr(pnum_begin, pnum) {
unsigned long pfn = section_nr_to_pfn(pnum);
if (pnum >= pnum_end)
break;
map = __populate_section_memmap(pfn, PAGES_PER_SECTION,
nid, NULL);
//获取该section对应的page 结构体地址,如果使能了vmemmap模型,则地址范围在vmememap区域中,需要建立vmmemap到page frame的页表。
if (!map) {
pr_err(\"
__func__, nid);
pnum_begin = pnum;
sparse_buffer_fini();
goto failed;
}
check_usemap_section_nr(nid, usage);
sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
SECTION_IS_EARLY);
//设置section对应的page结构体指针及其标志
usage = (void *) usage + mem_section_usage_size();
}
sparse_buffer_fini();
return;
failed:
/* We failed to allocate, mark all the following pnums as not present */
for_each_present_section_nr(pnum_begin, pnum) {
struct mem_section *ms;
if (pnum >= pnum_end)
break;
ms = __nr_to_section(pnum);
ms->section_mem_map = 0;
}
}
虚拟地址空间vmmepmap区域,是内核中page数据的虚拟地址,针对sparse内存模型,内核申请的page返回的地址在该区域。
zone_sizes_init
static void __init zone_sizes_init(unsigned long min, unsigned long max)
{
unsigned long max_zone_pfns[MAX_NR_ZONES] = {0};
unsigned int __maybe_unused acpi_zone_dma_bits;
unsigned int __maybe_unused dt_zone_dma_bits;
phys_addr_t __maybe_unused dma32_phys_limit = max_zone_phys(32);
#ifdef CONFIG_ZONE_DMA
acpi_zone_dma_bits = fls64(acpi_iort_dma_get_max_cpu_address());
dt_zone_dma_bits = fls64(of_dma_get_max_cpu_address(NULL));
zone_dma_bits = min3(32U, dt_zone_dma_bits, acpi_zone_dma_bits);
arm64_dma_phys_limit = max_zone_phys(zone_dma_bits);
max_zone_pfns[ZONE_DMA] = PFN_DOWN(arm64_dma_phys_limit);
//跟进实际物理内存计算ZONE_DMA区的最大PFN数量
#endif
#ifdef CONFIG_ZONE_DMA32
max_zone_pfns[ZONE_DMA32] = disable_dma32 ? 0 : PFN_DOWN(dma32_phys_limit);
//计算ZONE_DMA_32区的最大PFN数量
if (!arm64_dma_phys_limit)
arm64_dma_phys_limit = dma32_phys_limit;
#endif
max_zone_pfns[ZONE_NORMAL] = max;
//计算ZONE_NORMAL区的最大PFN数量
printk(\"
max_zone_pfns[ZONE_DMA], max_zone_pfns[ZONE_DMA32], max_zone_pfns[ZONE_NORMAL],(void *)_RET_IP_);
free_area_init(max_zone_pfns);
//初始化node和zone信息,以及page结构体。
}
free_area_init
void __init free_area_init(unsigned long *max_zone_pfn)
{
unsigned long start_pfn, end_pfn;
int i, nid, zone;
bool descending;
/* Record where the zone boundaries are */
memset(arch_zone_lowest_possible_pfn, 0,
sizeof(arch_zone_lowest_possible_pfn));
memset(arch_zone_highest_possible_pfn, 0,
sizeof(arch_zone_highest_possible_pfn));
start_pfn = find_min_pfn_with_active_regions();
descending = arch_has_descending_max_zone_pfns();
for (i = 0; i < MAX_NR_ZONES; i++) {
if (descending)
zone = MAX_NR_ZONES - i - 1;
else
zone = i;
if (zone == ZONE_MOVABLE)
continue;
//ZONE_MOVABLE是一个虚拟ZONE,实际内存空间不是独立的,因此不需要初始化
end_pfn = max(max_zone_pfn[zone], start_pfn);
arch_zone_lowest_possible_pfn[zone] = start_pfn;
arch_zone_highest_possible_pfn[zone] = end_pfn;
//填充每个zone的地址范围
start_pfn = end_pfn;
}
/* Find the PFNs that ZONE_MOVABLE begins at in each node */
memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
find_zone_movable_pfns_for_nodes();
//获取每个节点中ZONE_MOVABLE PFNs
/* Print out the zone ranges */
pr_info(\"Zone ranges:\\n\");
for (i = 0; i < MAX_NR_ZONES; i++) {
if (i == ZONE_MOVABLE)
continue;
pr_info(\"
if (arch_zone_lowest_possible_pfn[i] ==
arch_zone_highest_possible_pfn[i])
pr_cont(\"empty\\n\");
else
pr_cont(\"[mem
(u64)arch_zone_lowest_possible_pfn[i]
<< PAGE_SHIFT,
((u64)arch_zone_highest_possible_pfn[i]
<< PAGE_SHIFT) - 1);
}
//打印每个Zone区域的地址范围。
/* Print out the PFNs ZONE_MOVABLE begins at in each node */
pr_info(\"Movable zone start for each node\\n\");
for (i = 0; i < MAX_NUMNODES; i++) {
if (zone_movable_pfn[i])
pr_info(\" Node
(u64)zone_movable_pfn[i] << PAGE_SHIFT);
}
//打印每个节点中ZONE_MOVABLE地址范围
/*
* Print out the early node map, and initialize the
* subsection-map relative to active online memory ranges to
* enable future \"sub-section\" extensions of the memory map.
*/
pr_info(\"Early memory node ranges\\n\");
for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid) {
pr_info(\" node
(u64)start_pfn << PAGE_SHIFT,
((u64)end_pfn << PAGE_SHIFT) - 1);
subsection_map_init(start_pfn, end_pfn - start_pfn);
}
//初始化每个node,实际上ARM64上通常只有一个
/* Initialise every node */
mminit_verify_pageflags_layout();
setup_nr_node_ids();
for_each_online_node(nid) {
pg_data_t *pgdat = NODE_DATA(nid);
free_area_init_node(nid);
//初始化node相关结构体中pgdat内容,包括各个ZONE区域的spanned_pages,present_pages,memap_pages,nr_kernel_pages,nr_all_pages等等
/* Any memory on that node */
if (pgdat->node_present_pages)
node_set_state(nid, N_MEMORY);
//将node状态从N_ONLINE切换到N_MEMORY状态
check_for_memory(pgdat, nid);
}
memmap_init();
//遍历memblock的region,跟进PFN找到对应的struct page,对该结构体进行初始化,设置MIFRATE_MOVABLE标志等等。
}
free_area_init_node
static void __init free_area_init_node(int nid)
{
pg_data_t *pgdat = NODE_DATA(nid);
unsigned long start_pfn = 0;
unsigned long end_pfn = 0;
/* pg_data_t should be reset to zero when it\'s allocated */
WARN_ON(pgdat->nr_zones || pgdat->kswapd_highest_zoneidx);
//获取该节点中PFN的起始号和结束号
get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);
pgdat->node_id = nid;
pgdat->node_start_pfn = start_pfn; //设置该节点的起始PFN
pgdat->per_cpu_nodestats = NULL;
pr_info(\"Initmem setup node
(u64)start_pfn << PAGE_SHIFT,
end_pfn ? ((u64)end_pfn << PAGE_SHIFT) - 1 : 0);
calculate_node_totalpages(pgdat, start_pfn, end_pfn);
//计算pgdat中struct zone成员中的spanned_pages,present_pages等变量内容
alloc_node_mem_map(pgdat);
pgdat_set_deferred_range(pgdat);
free_area_init_core(pgdat);
// 设置 zone data结构体,包括设置其所有的pages reserved,所有memory queues是空,清空memory bitmaps等等
}
free_area_init_core
static void __init free_area_init_core(struct pglist_data *pgdat)
{
enum zone_type j;
int nid = pgdat->node_id;
pgdat_init_internals(pgdat);
pgdat->per_cpu_nodestats = &boot_nodestats;
for (j = 0; j < MAX_NR_ZONES; j++) {
struct zone *zone = pgdat->node_zones + j;
unsigned long size, freesize, memmap_pages;
size = zone->spanned_pages;
freesize = zone->present_pages;
/*
* Adjust freesize so that it accounts for how much memory
* is used by this zone for memmap. This affects the watermark
* and per-cpu initialisations
*/
memmap_pages = calc_memmap_size(size, freesize);
if (!is_highmem_idx(j)) {
if (freesize >= memmap_pages) {
freesize -= memmap_pages;
if (memmap_pages)
pr_debug(\"
zone_names[j], memmap_pages);
} else
pr_warn(\"
zone_names[j], memmap_pages, freesize);
}
/* Account for reserved pages */
if (j == 0 && freesize > dma_reserve) {
freesize -= dma_reserve;
pr_debug(\"
}
if (!is_highmem_idx(j))
nr_kernel_pages += freesize;
/* Charge for highmem memmap if there are enough kernel pages */
else if (nr_kernel_pages > memmap_pages * 2)
nr_kernel_pages -= memmap_pages;
nr_all_pages += freesize;
/*
* Set an approximate value for lowmem here, it will be adjusted
* when the bootmem allocator frees pages into the buddy system.
* And all highmem pages will be managed by the buddy system.
*/
zone_init_internals(zone, j, nid, freesize);
if (!size)
continue;
set_pageblock_order();
setup_usemap(zone);
init_currently_empty_zone(zone, zone->zone_start_pfn, size);
//初始化伙伴系统中使用的free_area[]
}
}
zone_init_free_lists
static void __meminit zone_init_free_lists(struct zone *zone)
{
unsigned int order, t;
for_each_migratetype_order(order, t) {
INIT_LIST_HEAD(&zone->free_area[order].free_list[t]);
zone->free_area[order].nr_free = 0;
}
//初始化free_area[]对应的链表。
//for_each_migratetype_order可用于迭代指定迁移类型的所有分配阶,先遍历free_area[],再遍历free_list[]
}
启动打印信息
启动打印信息。
[ 0.000000] Zone ranges:
[ 0.000000] DMA [mem 0x0000000040000000-0x00000000bfffffff]
[ 0.000000] DMA32 empty
[ 0.000000] Normal empty
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000040000000-0x0000000041ffffff]
[ 0.000000] node 0: [mem 0x0000000042000000-0x000000004210ffff]
[ 0.000000] node 0: [mem 0x0000000042110000-0x00000000421fffff]
[ 0.000000] node 0: [mem 0x0000000042200000-0x0000000042243fff]
[ 0.000000] node 0: [mem 0x0000000042244000-0x00000000423fffff]
[ 0.000000] node 0: [mem 0x0000000042400000-0x0000000042443fff]
[ 0.000000] node 0: [mem 0x0000000042444000-0x00000000bfffffff]
内核如何直到给定的分配内存属于何种迁移类型?
内核提供两个标志,分别用于分配内存是可移动的(__GFP_MOVABLE)或可回收的(__GFP_RECATMABLE),如果这些标志都没有设置,则分配的内存假定为不可移动。
如何初始化可移动性的分组?
build_all_zonelists
主要是为node创建一个内存分配时优先级的顺序。将系统中各个节点的各个zone,按照备选节点的优先级顺序依次填写到对应结构体描述符的struct zonelist node_zonelist[]数组中。某node的zonelist可以按下面的优先级进行赋值:
(1)对于不同节点,本地node内存放在zonelist的最前面,其他node的内存根据其与本节点的distance值从小到大依次排列。
(2)对于node内部不同的zone也存在优先级关系,normal zone排在dma zone的前面。