下面对arm64 linux内核内存初始化专栏相关内容进行一次汇总,该总结中列出的部分函数前面还未详细讲解,后续会持续更新(arm64架构,sparse内存模型,linux 4.9):
stext//汇编代码运行阶段
|--->__create_page_tables:该函数主要完成linux内存中两块内存区域的页表创建和出事化:
| | (1):把idmap_text区域的物理地址映射到相等的虚拟地址上,并创建初始化相关页表.
| | 这种映射完成后,其虚拟地址等于物理地址。idmap_text区域都是一些打开MMU
| | 相关的代码(一致性映射).
| | (2)将kernel运行需要的内存区域(kernel txt、rodata、data、bss等等)进行映
| | 射,并创建初始化相关页表(swapper进程页表).
|--->__primary_switch
| |--->__enable_mmu:使能mmu(在idmap_text区域),linux进入内存虚拟地址访问的世界(以后cpu只能通过cpu访
| | 问内存空间)
start_kernel()//进入c代码运行阶段
|--->page_address_init()
| |
|--->setup_arch()
| |--->early_fixmap_init():给静态定义的fixmap虚拟空间建立中间level的页表entry.但是没有映射对应的物理
| | 地址,也就是说映射只做一部分,没有为其pte页表项赋值.由于当前linux内
| | 核内存处于初始化阶段MMU已经打开,cpu只能通过虚拟地址访问物理空间内容,但目
| | 前linux内核内存页表映射机制还未完全建立,内存管理的伙伴系统还没有ready,整
| | 个内存空间由memblock模块管理,该内存管理系统不能动态为物理地址关联虚拟地
| | 址.所以在执行完early_fixmap_init函数后,linux内核可以可以从fixmap虚拟地
| | 址空间分配一段虚拟地,并与memblock分配的物理地址空间建立映射.这样cpu就可
| | 以通过fixmap机制临时分配的虚拟地址访问membloc分配的物理空间.
| |--->early_ioremap_init():该函数完成early_ioremap机制初始化工作.一般传统驱动模块都是使用ioremap函
| | 数来完成地址映射的,但是该函数必须依赖伙伴系统来创建某个level的
| | Translation table(若该转换表不存在)。于是在内核启动初期需要通过
| | early_ioremap机制让设备寄存器对内存进行访问。一些硬件需要在内存管理系统
| | 运行起来之前就需要工作,内核采early_ioremap机制来映射内存给这些硬件驱动
| | 使用。并且这些硬件驱动在使用完early_ioremap的地址后需要尽快的释放掉这些
| | 内存,这样才能保证其他硬件模块继续使用。因此early_ioremap采用的是Fixed
| | map的temporary fixmap段虚拟地址(所以在early_fixmap_init函数完成后执行
| | 该函数)。
| |--->setup_machine_fdt():该函数完成以下几个工作:1.fixmap机制为dtb物理内存提供了固定的虚拟地址FDT,
| | 函数先将dtb物理地址对应的pfn填入到FDT虚拟地址对应的pte entry中去(页表内 | | 核静态分配,中间层页表entry在early_fixmap_init函数中被初始化),这样cpu | | 能通过FDT虚拟地址访问到dtb文件内容.2.扫描dtb文件,获取linux os物理内存的 | | 所有布局信息,并将dtb描述的内存区域添加到memblock模块中,后续该模块具有物 | | 理内存分配能力(返回的都是内存空间的物理地址需要结合fixmap机制提供的虚拟
| | 地址才能被cpu访问)
| |--->parse_early_param():解析内核启动时传入的参数,有些参数用于描述内核内存布局信息:主要关注类似
| | mem=XXX[KkmM],highmem=XXX[kKmM] 或 memmap=XXX[KkmM]" "@XXX[KkmM] 之类
| | 的参数。
| |--->arm64_memblock_init():当物理内存都添加进系统之后,linux通过该函数对物理内存进行一些整理,主要
| | 是将一些特殊的区域添加到memblock的reversed内存管理模块中去,比如:a.将
| | 内核启动参数中传入的预留内存划分到memblock管理系统的reversed区域(设置为
| | reversed类型), b.将内核代码段设置位reserved类型 c.将dtb中的reserved-
| | memory区域设置位reserved类型, d.移除在dts文件中具有no-map属性的
| | reserved-memory内存区域,f.申请公共区域CMA(通dma_contiguous_reserve)
| | 等
| |--->paging_init():在paging_init函数执行前,cpu只能访问Kernel Image和DTB的两段物理内存区域(该区域
| | 建立了相应的页表机制).其他内存区域虽然已经通过扫描dtb文件被memblock_add函数添加
| | 到内核系统,但是物理内存到虚拟地址的页表映射还未建立,所以对于大部分内存区域,内
| | 核只能通过memblock模块分配物理内存,但不能访问(虽然能用fixmap机制为少数的物理内
| | 存临时分配虚拟地址,但这只能覆盖很少一部分内存区域,并不能对整个物理内存进行虚拟
| | 地址映射,涉及到大块内存分配时,fixmap机制显得捉襟见肘).执行pageing_init函数就
| | 是为整个内存区域建立页表,实现物理地址到虚拟地址的映射:
| | (1)利用early_pgtable_alloc函数通过memblock和fixmap机制分配一页物理内存并关联
| | 虚拟地址,用于存放临时内核内存pgd页表.
| | (2)map_kernel函数先将内核image的各个段进行虚实映射(代码段,只读数据段,init段
| | 和数据段),建立并填充页表.该段映射虚拟地址位于Vmalloc区域。接着map_kernel
| | 函数还会将以前静态定义的fixmap段的pud页表项直接复制到新创建的pud页表中,在新
| | 内核页表中保存fixmap段物理地址的虚实映射关系(fixmap段与kernel image段共用
| | 一个pgd页表项,但在pud页表中属于不同的pud页表项,因此将以前静态定义的fixmap
| | pud页表项拷贝到新pud页表对应位置复用即可).
| | (3)利用map_mem函数将memblock添加的所有物理内存进行虚实映射,建立并填充页表。该
| | 段虚拟地址位于linux内核虚拟地址的线性映射区域,虚拟地址以PAGE_OFFSET为开端
| | (注意:a.映射过程中会剔除memblock系统中标有nomap标志的物理内存区域,b.线性映
| | 射区会再次对kernel image对应的段物理内存建立虚实映射关系,所以在内核态
| | kernel image段对应的物理地址会有两个虚拟地址一个在线性映射区一个在vmalloc
| | 区).
| | (4)替换页表,用新创建的pgd页表内容去替换掉swappper_pg_dir页表中的内容,后续
| | swappper_pg_dir物理地址会存放在ttbr1寄存器中.接着会释放掉新创建的pgd页表和
| | swappper_pg_dir以前对应的pud,pmd,pte页表(就在swappper_pg_dir页表下方).
| | 到此linux内核整个内存的页表机制初始化完成,以后cpu能通过对于虚拟地址访问到所有
| | 在线的内存区域(cpu获得虚拟地址,从ttbr1寄存器获取内核页表地址,通过mmu获取虚拟
| | 地址对应的物理地址并访问对应的内存空间)
| |--->bootmem_init():paging_init函数完成了内核内存页表的建立,实现了内核内存分页机制的初始化.接着调用
| | bootmem_init函数对与linux内核内存相关的一些数据结构进行初始化,包括:内存节
| | 点(struct pglist_data),内存section(struct mem_section),内存域zone(struct
| | zone)和页描述符(strcut page),主要针对SPARSE内存模
| | 型).
| | (1).调用arm64_memory_present函数遍历meiblock中的每个region,将每个region
| | 划分为1G大小的section,并为初始化每个section对应数据结构struct
| | mem_section的section_mem_map成员(主要设置section_mem_map成员的
| | present位和nid位).
| | (2).调用sparse_init变量内存区域完成如下两个任务:
| | a).给每个section分配一段物理内存用于存储section中每个pageblock的
| | 位图数据.位图数据存储区对应的虚拟地址保存在struct mem_section
| | 的pageblock_bitmap成员中
| | b).为每个section内存区域中的每个物理页分配物理内存空间,用于存储其
| | 所有页的结构体描述符structpage,并将linux内核vmmemmap虚拟地址区
| | 域中对应虚拟地址与刚分配的物理内存空间的物理地址建立页表映射关
| | 系,section首页对应的struct page的虚拟地址存储在
| | struct section的section_mem_map成员中(由此可以看出struct
| | mem_section的section_mem_map存储着该section区域的多个数据:该
| | section是否在线,section的编号,section所在的节点和section首
| | 页结构体描述符的虚拟地址等)。
| | (3).调用zone_sizes_init函数遍历内存区域:
| | a.对内存区域中所有节点对应的结构体描述符struct pglist_data的相关成员
| | 进行初始化
| | b.对每个内存节点中所有zone区域对应的结构体描述符struct zone中相关成员
| | 进行初始化.
| | c.对每个zone区域中所有物理页对应的结构体描述符struct page中相关成员进
| | 行初始化
|--->setup_per_cpu_areas():setup_per_cpu_areas函数完成每cpu高速缓存初始化
| |
|--->build_all_zonelists():build_all_zonlists函数主要是为node创建一个内存分配时的优先级的次序。将系统中各
| | 个节点的各个zone,按照备选节点的优先级顺序依次填写到对应节点结构体描述符的
| | strcut zonelist node_zonelists[]数组中.某node的zonlist可以按下面的优先级进行
| | 赋值:
| | (1).对于不同节点,本地node内存放在zonelist的最前面,其它node的内存根据其与
| | 本节点的distance的值从小到大依次排列。
| | (2).对于node内部不同的zone也存在优先级关系,normal zone排在dma zone的前面。
|--->page_alloc_init()
| |
|--->pidhash_init()
| |
|--->vfs_caches_init_early()
| |
|--->mm_init()
| |--->mem_init():伙伴系统初始化
| |--->kmem_cache_init():slab系统初始化
| |--->vmalloc_init():vmalloc初始化