当前位置: 首页 > news >正文

linux内核启动分析(一)

文章目录

  • 1.HEAD
    • 1.preserve_boot_args
      • 1.1 __inval_dcache_area
    • 2.el2_setup
    • 3. set_cpu_boot_mode_flag
    • 4. __create_page_tables
      • 4.1map_memory
    • 5. __cpu_setup
    • 6. __primary_switch
      • 6.1 __enable_mmu
      • 6.2 __primary_switched

最近工作中经常使用飞腾E2000的开发版,也遇到一些启动问题,所以追踪了一下linux内核启动流程。麒麟的代码我们看不了,但是我们可以直接看飞腾的开源内核代码,点击这里可以跳到gitee,我们使用的是5.10分支。

看这个文章需要的前置汇编知识点:

  1. .quad表示定义一个4字节的变量。.long表示定义一个8字节的变量。
  2. SYM_CODE_START表示定义一个函数。定义了之后可以通过bl或者b跳转到这个函数。

有一些指令不知道什么回事,可以看下一篇文章:看linux内核启动流程需要的汇编指令解释。

飞腾E2000的开发版可以使用uboot和UEFI,无论是uboot还是UEFI加载linux内核,并且启动linux内核都是从arch/arm64/kernel/head.S文件的_head这里开始运行的。下面我们来开始分析吧:

1.HEAD

	__HEAD
_head:/** DO NOT MODIFY. Image header expected by Linux boot-loaders.*/
#ifdef CONFIG_EFI/** This add instruction has no meaningful effect except that* its opcode forms the magic "MZ" signature required by UEFI.*/add	x13, x18, #0x16b	primary_entry
#elseb	primary_entry			// branch to kernel start, magic.long	0				// reserved
#endif.quad	0				// Image load offset from start of RAM, little-endianle64sym	_kernel_size_le			// Effective size of kernel image, little-endianle64sym	_kernel_flags_le		// Informative flags, little-endian.quad	0				// reserved.quad	0				// reserved.quad	0				// reserved.ascii	ARM64_IMAGE_MAGIC		// Magic number
#ifdef CONFIG_EFI.long	pe_header - _head		// Offset to the PE header.pe_header:__EFI_PE_HEADER
#else.long	0				// reserved
#endif

在_head里面只跑一个函数,就是primary_entry:

SYM_CODE_START(primary_entry)bl	preserve_boot_args		//保留引导加载程序中传递的参数到boot_args中bl	el2_setup				// 判断目前是EL1还是EL2,//如果是EL1就简单了,配置sctlr_el1寄存器就好了。//如果是EL2就复杂了,需要配置sctlr_el2寄存器,配置内存,hcr,gicadrp	x23, __PHYS_OFFSETand	x23, x23, MIN_KIMG_ALIGN - 1	// KASLR offset, defaults to 0bl	set_cpu_boot_mode_flag		//把其他cpu都配置成跟cpu0同样的特权等级bl	__create_page_tables		//创建页表/** The following calls CPU setup code, see arch/arm64/mm/proc.S for* details.* On return, the CPU will be ready for the MMU to be turned on and* the TCR will have been set.*/bl	__cpu_setup			// 初始化处理器以打开MMU。b	__primary_switch	//设置TTBR0和TTBR1,使能MMU,将kernel image重定位,跳转到__primary_switchedSYM_CODE_END(primary_entry)

primary_entry主要执行了以下几个步骤:

  1. 调用函数preserve_boot_args保留引导加载程序中传递的参数到boot_args中
  2. 调用函数el2_setup判断目前是EL1还是EL2,如果是EL1就简单了,配置sctlr_el1寄存器就好了;如果是EL2就复杂了,需要配置sctlr_el2寄存器,配置内存,hcr,gic。
  3. 调用函数set_cpu_boot_mode_flag把其他cpu都配置成跟cpu0同样的特权等级
  4. 调用函数__create_page_tables创建页表
  5. 调用函数__cpu_setup初始化处理器以打开MMU
  6. 调用函数__primary_switch设置TTBR0和TTBR1,使能MMU,将kernel image重定位,跳转到__primary_switched。

1.preserve_boot_args

SYM_CODE_START_LOCAL(preserve_boot_args)mov	x21, x0				// x21=FDT,将FDT的地址暂存在x21寄存器中,释放出x0以便后续做临时变量使用adr_l	x0, boot_args			// x0保存了boot_args变量的地址stp	x21, x1, [x0]			// 保存x0和x1的值到boot_args[0]和boot_args[1]stp	x2, x3, [x0, #16]		// 保存x2和x3的值到boot_args[2]和boot_args[3]dmb	sy				// needed before dc ivac with// MMU offmov	x1, #0x20			// 4 x 8 bytesb	__inval_dcache_area		// 让[boot_args,boot_args+#0x20]的内存数据缓存失效
SYM_CODE_END(preserve_boot_args)

1.1 __inval_dcache_area

//__inval_dcache_area(kaddr, size)
SYM_FUNC_START_PI(__inval_dcache_area)/* FALLTHROUGH *//**	__dma_inv_area(start, size)*	- start   - virtual start address of region*	- size    - size in question*/add	x1, x1, x0			//X1存放kaddr+sizedcache_line_size x2, x3sub	x3, x2, #1tst	x1, x3				// end cache line aligned?bic	x1, x1, x3b.eq	1fdc	civac, x1			// clean & invalidate D / U line
1:	tst	x0, x3				// start cache line aligned?bic	x0, x0, x3b.eq	2fdc	civac, x0			// clean & invalidate D / U lineb	3f
2:	dc	ivac, x0			// invalidate D / U line
3:	add	x0, x0, x2cmp	x0, x1b.lo	2bdsb	syret
SYM_FUNC_END_PI(__inval_dcache_area)

2.el2_setup

/** If we're fortunate enough to boot at EL2, ensure that the world is* sane before dropping to EL1.** Returns either BOOT_CPU_MODE_EL1 or BOOT_CPU_MODE_EL2 in w0 if* booted in EL1 or EL2 respectively.*/
SYM_FUNC_START(el2_setup)msr	SPsel, #1			//往SPsel写1,说明使用SP_EL0mrs	x0, CurrentEL		//获取当前特权等级cmp	x0, #CurrentEL_EL2	//看看是不是特权等级是否为EL2b.eq	1f				//如果是,就跳转到1fmov_q	x0, (SCTLR_EL1_RES1 | ENDIAN_SET_EL1)//msr	sctlr_el1, x0		//配置EL1的系统控制寄存器mov	w0, #BOOT_CPU_MODE_EL1		// 返回值存在w0寄存器中isb								//内存屏障ret								//返回//这里说明当前等级是EL2
1:	mov_q	x0, (SCTLR_EL2_RES1 | ENDIAN_SET_EL2)msr	sctlr_el2, x0		//配置EL2的系统控制寄存器#ifdef CONFIG_ARM64_VHE/** Check for VHE being present. For the rest of the EL2 setup,* x2 being non-zero indicates that we do have VHE, and that the* kernel is intended to run at EL2.*/mrs	x2, id_aa64mmfr1_el1	//配置内存模式寄存器ubfx	x2, x2, #ID_AA64MMFR1_VHE_SHIFT, #4	//把虚拟机扩展支持位提取出来
#elsemov	x2, xzr
#endif/* Hyp configuration. *///Hypervisor配置寄存器mov_q	x0, HCR_HOST_NVHE_FLAGS	//访问到EL2的指令转发到未定义指令cbz	x2, set_hcr		//x2为0(不支持虚拟机扩展,也就是传统分裂模式)则跳转到set_hcrmov_q	x0, HCR_HOST_VHE_FLAGS	//设置中断路由到EL2、启动EL2设施、
set_hcr://虚拟机扩展模式msr	hcr_el2, x0		//写入hcr_el2isb					//内存屏障/** Allow Non-secure EL1 and EL0 to access physical timer and counter.* This is not necessary for VHE, since the host kernel runs in EL2,* and EL0 accesses are configured in the later stage of boot process.* Note that when HCR_EL2.E2H == 1, CNTHCTL_EL2 has the same bit layout* as CNTKCTL_EL1, and CNTKCTL_EL1 accessing instructions are redefined* to access CNTHCTL_EL2. This allows the kernel designed to run at EL1* to transparently mess with the EL0 bits via CNTKCTL_EL1 access in* EL2.*/cbnz	x2, 1f			//x2为0(不支持虚拟机扩展)则跳转到1fmrs	x0, cnthctl_el2		//读取Hypervisor控制的计数寄存器orr	x0, x0, #3			// Enable EL1 physical timersmsr	cnthctl_el2, x0
1:msr	cntvoff_el2, xzr		// 物理计数器和虚拟计数器一致,不偏移#ifdef CONFIG_ARM_GIC_V3/* GICv3 system register access */mrs	x0, id_aa64pfr0_el1		//读取处理器特性寄存器ubfx	x0, x0, #ID_AA64PFR0_GIC_SHIFT, #4cbz	x0, 3f		//如果不是gic3或者4.0。跳转到3f//说明gic版本为3.0或者4.0mrs_s	x0, SYS_ICC_SRE_EL2		//读取中断控制器启用寄存器orr	x0, x0, #ICC_SRE_EL2_SRE	// Set ICC_SRE_EL2.SRE==1orr	x0, x0, #ICC_SRE_EL2_ENABLE	// Set ICC_SRE_EL2.Enable==1msr_s	SYS_ICC_SRE_EL2, x0isb					// Make sure SRE is now setmrs_s	x0, SYS_ICC_SRE_EL2		// Read SRE back,tbz	x0, #0, 3f			// and check that it sticksmsr_s	SYS_ICH_HCR_EL2, xzr		// Reset ICC_HCR_EL2 to defaults3:
#endif/* Populate ID registers. *///填充虚拟机ID寄存器mrs	x0, midr_el1mrs	x1, mpidr_el1msr	vpidr_el2, x0	//虚拟化处理器ID寄存器msr	vmpidr_el2, x1	//虚拟化多处理器ID寄存器#ifdef CONFIG_COMPATmsr	hstr_el2, xzr			// Disable CP15 traps to EL2
#endif/* EL2 debug */	mrs	x1, id_aa64dfr0_el1		//读取AArch64调试特性寄存器sbfx	x0, x1, #ID_AA64DFR0_PMUVER_SHIFT, #4cmp	x0, #1b.lt	4f				// Skip if no PMU presentmrs	x0, pmcr_el0			//读取性能监视器控制寄存器ubfx	x0, x0, #11, #5			//允许EL2访问性能监视器控制寄存器
4:csel	x3, xzr, x0, lt			// all PMU counters from EL1/* Statistical profiling */ubfx	x0, x1, #ID_AA64DFR0_PMSVER_SHIFT, #4cbz	x0, 7f				// Skip if SPE not presentcbnz	x2, 6f				// VHE?mrs_s	x4, SYS_PMBIDR_EL1		// If SPE available at EL2,找不到该寄存器and	x4, x4, #(1 << SYS_PMBIDR_EL1_P_SHIFT)cbnz	x4, 5f				// then permit sampling of physicalmov	x4, #(1 << SYS_PMSCR_EL2_PCT_SHIFT | \1 << SYS_PMSCR_EL2_PA_SHIFT)msr_s	SYS_PMSCR_EL2, x4		// addresses and physical counter
5:mov	x1, #(MDCR_EL2_E2PB_MASK << MDCR_EL2_E2PB_SHIFT)orr	x3, x3, x1			// If we don't have VHE, thenb	7f				// use EL1&0 translation.
6:						// For VHE, use EL2 translationorr	x3, x3, #MDCR_EL2_TPMS		// and disable access from EL1
7:msr	mdcr_el2, x3			// Configure debug traps/* LORegions */mrs	x1, id_aa64mmfr1_el1	//AArch64内存模型特征寄存器ubfx	x0, x1, #ID_AA64MMFR1_LOR_SHIFT, 4cbz	x0, 1fmsr_s	SYS_LORC_EL1, xzr
1:/* Stage-2 translation */msr	vttbr_el2, xzr		//虚拟化转换表基寄存器cbz	x2, install_el2_stubmov	w0, #BOOT_CPU_MODE_EL2		// This CPU booted in EL2isbretSYM_INNER_LABEL(install_el2_stub, SYM_L_LOCAL)

3. set_cpu_boot_mode_flag

SYM_FUNC_START_LOCAL(set_cpu_boot_mode_flag)adr_l	x1, __boot_cpu_mode	//把__boot_cpu_mode地址赋值给x1cmp	w0, #BOOT_CPU_MODE_EL2	//如果当前cpu处于EL2b.ne	1f					//跳转到1add	x1, x1, #4				//当前cpu在EL1,使用__boot_cpu_mode[1]//当前cpu在EL2,使用__boot_cpu_mode[0]
1:	str	w0, [x1]			//将w0写入__boot_cpu_modedmb	sydc	ivac, x1			// Invalidate potentially stale cache lineret
SYM_FUNC_END(set_cpu_boot_mode_flag)

set_cpu_boot_mode_flag主要是根据cpu当前的特权等级,把w0寄存器,也就是当前模式记录在__boot_cpu_mode中。

4. __create_page_tables

SYM_FUNC_START_LOCAL(__create_page_tables)mov	x28, lr	//lr是连接寄存器/** Invalidate the init page tables to avoid potential dirty cache lines* being evicted. Other page tables are allocated in rodata as part of* the kernel image, and thus are clean to the PoC per the boot* protocol.*/adrp	x0, init_pg_dir	//获取内核init页表的基地址adrp	x1, init_pg_end	//获取内核init页表的基地址sub	x1, x1, x0bl	__inval_dcache_area	//清除Dcache/** Clear the init page tables.*///把init_pg_dir到init_pg_end这段内存清零//也就是把内核页表清零adrp	x0, init_pg_diradrp	x1, init_pg_endsub	x1, x1, x0
1:	stp	xzr, xzr, [x0], #16	//把0写入以x0为地址的内存中,然后x0自增16stp	xzr, xzr, [x0], #16stp	xzr, xzr, [x0], #16stp	xzr, xzr, [x0], #16subs	x1, x1, #64b.ne	1bmov	x7, SWAPPER_MM_MMUFLAGS/** Create the identity mapping.*///创建恒等映射,也就是虚拟地址和物理地址相同adrp	x0, idmap_pg_dir	//恒等映射的页全局目录的起始地址adrp	x3, __idmap_text_start		// 恒等映射代码节的起始地址#ifdef CONFIG_ARM64_VA_BITS_52	//不支持,不用看mrs_s	x6, SYS_ID_AA64MMFR2_EL1and	x6, x6, #(0xf << ID_AA64MMFR2_LVA_SHIFT)mov	x5, #52cbnz	x6, 1f
#endifmov	x5, #VA_BITS_MIN	//虚拟地址位数
1:adr_l	x6, vabits_actual	//获取PC到vabits_actual的相对偏移地址str	x5, [x6]		//定位PC的虚拟地址dmb	sydc	ivac, x6		// 使x6所在的dcache失效/** VA_BITS may be too small to allow for an ID mapping to be created* that covers system RAM if that is located sufficiently high in the* physical address space. So for the ID map, use an extended virtual* range in that case, and configure an additional translation level* if needed.** Calculate the maximum allowed value for TCR_EL1.T0SZ so that the* entire ID map region can be mapped. As T0SZ == (64 - #bits used),* this number conveniently equals the number of leading zeroes in* the physical address of __idmap_text_end.*///T0SZ决定了输出的物理地址位数,这里查看其是否足够覆盖物理地址adrp	x5, __idmap_text_end	//获取__idmap_text_end的页基地址clz	x5, x5	//计算x5高位0的个数cmp	x5, TCR_T0SZ(VA_BITS_MIN) // default T0SZ small enough?b.ge	1f			// .. then skip VA range extensionadr_l	x6, idmap_t0sz	//计算idmap_t0sz的页内偏移str	x5, [x6]		//把x5的数据写入以x6为地址的内存中dmb	sydc	ivac, x6		// Invalidate potentially stale cache line#if (VA_BITS < 48)
#define EXTRA_SHIFT	(PGDIR_SHIFT + PAGE_SHIFT - 3)
#define EXTRA_PTRS	(1 << (PHYS_MASK_SHIFT - EXTRA_SHIFT))/** If VA_BITS < 48, we have to configure an additional table level.* First, we have to verify our assumption that the current value of* VA_BITS was chosen such that all translation levels are fully* utilised, and that lowering T0SZ will always result in an additional* translation level to be configured.*/
#if VA_BITS != EXTRA_SHIFT
#error "Mismatch between VA_BITS and page size/number of translation levels"
#endifmov	x4, EXTRA_PTRScreate_table_entry x0, x3, EXTRA_SHIFT, x4, x5, x6	//配置额外的页表
#else/** If VA_BITS == 48, we don't have to configure an additional* translation level, but the top-level table has more entries.*/mov	x4, #1 << (PHYS_MASK_SHIFT - PGDIR_SHIFT)str_l	x4, idmap_ptrs_per_pgd, x5
#endif
1:ldr_l	x4, idmap_ptrs_per_pgd	//取idmap_ptrs_per_pgd的页内偏移到x4中mov	x5, x3				// __pa(__idmap_text_start)adr_l	x6, __idmap_text_end		// __pa(__idmap_text_end)//为指定的虚拟地址范围映射内存map_memory x0, x1, x3, x6, x7, x3, x4, x10, x11, x12, x13, x14	//映射,写入页表/** Map the kernel image (starting with PHYS_OFFSET).*///内核镜像映射adrp	x0, init_pg_dir			//页表基地址mov_q	x5, KIMAGE_VADDR		// 代码段的虚拟地址add	x5, x5, x23			// add KASLR displacementmov	x4, PTRS_PER_PGD	//PGD表项的数量adrp	x6, _end			// 代码段的物理地址末端adrp	x3, _text			// 代码段的物理地址起始位置sub	x6, x6, x3			// 代码段长度add	x6, x6, x5			// 代码段虚拟地址末端map_memory x0, x1, x5, x6, x7, x3, x4, x10, x11, x12, x13, x14	//创建内核镜像的映射关系/** Since the page tables have been populated with non-cacheable* accesses (MMU disabled), invalidate those tables again to* remove any speculatively loaded cache lines.*/dmb	syadrp	x0, idmap_pg_diradrp	x1, idmap_pg_endsub	x1, x1, x0bl	__inval_dcache_area	//使dcache失效adrp	x0, init_pg_diradrp	x1, init_pg_endsub	x1, x1, x0bl	__inval_dcache_area	//使dcache失效ret	x28	//返回
SYM_FUNC_END(__create_page_tables)

__create_page_tables主要执行了一下几个步骤:

  1. mov x28, lr保存返回的地址
  2. 清除init页表的dcache
  3. 循环使用stp把init_pg_dir到init_pg_end这段内存写0
  4. 创建恒等映射,使得虚拟地址和物理地址相同
  5. 创建内核镜像的映射
  6. 使这两个页表的dcache失效

注意:
恒等映射将idmap_pg_dir页表对应的物理空间为__idmap_text_start 到__idmap_text_end,也就是代码段的范围。粗粒度内核页表将 init_pg_dir 地址保存到ttbr1_el1 ;init_pg_dir页表对应的物理空间为_text 到_end,也就是内核镜像代码段。这两个页表后面会在paging_init之后丢弃。

4.1map_memory

我们看看map_memory是怎么创建填写也页表的:

	.macro map_memory, tbl, rtbl, vstart, vend, flags, phys, pgds, istart, iend, tmp, count, svsub \vend, \vend, #1	//虚拟地址减一add \rtbl, \tbl, #PAGE_SIZE	//第一级页表项的地址,是页全局基地址的下一页mov \sv, \rtblmov \count, #0//compute_indices是用来计算vstart和vend对应的 pgtable level的index的,两者之差保存在count中compute_indices \vstart, \vend, #PGDIR_SHIFT, \pgds, \istart, \iend, \count//populate_entries最终建立指向下一级的映射或者last level映射populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmpmov \tbl, \svmov \sv, \rtbl#if SWAPPER_PGTABLE_LEVELS > 3compute_indices \vstart, \vend, #PUD_SHIFT, #PTRS_PER_PUD, \istart, \iend, \countpopulate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmpmov \tbl, \svmov \sv, \rtbl
#endif#if SWAPPER_PGTABLE_LEVELS > 2compute_indices \vstart, \vend, #SWAPPER_TABLE_SHIFT, #PTRS_PER_PMD, \istart, \iend, \countpopulate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmpmov \tbl, \sv
#endifcompute_indices \vstart, \vend, #SWAPPER_BLOCK_SHIFT, #PTRS_PER_PTE, \istart, \iend, \countbic \count, \phys, #SWAPPER_BLOCK_SIZE - 1populate_entries \tbl, \count, \istart, \iend, \flags, #SWAPPER_BLOCK_SIZE, \tmp.endm

其中主要函数有两个:

  1. compute_indices:它是用来计算 vstart 和 vend 对应的 pgtable level 的 index 的,两者之差保存在 count 中;
  2. populate_entries:最终建立指向下一级的映射或者 last level 映射。

5. __cpu_setup

SYM_FUNC_START(__cpu_setup)tlbi	vmalle1				// 使本地TLB失效dsb	nshmov	x1, #3 << 20		//x1=0x300000msr	cpacr_el1, x1			// 使能EL1和EL0执行 FP/ASIMD指令mov	x1, #1 << 12			// Reset mdscr_el1 and disablemsr	mdscr_el1, x1			//对AArch64 DCC寄存器的L0访问被捕获isb					// Unmask debug exceptions now,enable_dbg				// since this is per-cpureset_pmuserenr_el0 x1			// Disable PMU access from EL0reset_amuserenr_el0 x1			// Disable AMU access from EL0/** Memory region attributes*/mov_q	x5, MAIR_EL1_SET	//设置nGnRnE等内存属性
#ifdef CONFIG_ARM64_MTE		//如果使能内存标签扩展支持/** Update MAIR_EL1, GCR_EL1 and TFSR*_EL1 if MTE is supported* (ID_AA64PFR1_EL1[11:8] > 1).*/mrs	x10, ID_AA64PFR1_EL1ubfx	x10, x10, #ID_AA64PFR1_MTE_SHIFT, #4cmp	x10, #ID_AA64PFR1_MTEb.lt	1f/* Normal Tagged memory type at the corresponding MAIR index */mov	x10, #MAIR_ATTR_NORMAL_TAGGEDbfi	x5, x10, #(8 *  MT_NORMAL_TAGGED), #8/* initialize GCR_EL1: all non-zero tags excluded by default */mov	x10, #(SYS_GCR_EL1_RRND | SYS_GCR_EL1_EXCL_MASK)msr_s	SYS_GCR_EL1, x10/** If GCR_EL1.RRND=1 is implemented the same way as RRND=0, then* RGSR_EL1.SEED must be non-zero for IRG to produce* pseudorandom numbers. As RGSR_EL1 is UNKNOWN out of reset, we* must initialize it.*/mrs	x10, CNTVCT_EL0ands	x10, x10, #SYS_RGSR_EL1_SEED_MASKcsinc	x10, x10, xzr, nelsl	x10, x10, #SYS_RGSR_EL1_SEED_SHIFTmsr_s	SYS_RGSR_EL1, x10/* clear any pending tag check faults in TFSR*_EL1 */msr_s	SYS_TFSR_EL1, xzrmsr_s	SYS_TFSRE0_EL1, xzr
1:
#endifmsr	mair_el1, x5	//对内存的8个区域写入属性/** Set/prepare TCR and TTBR. We use 512GB (39-bit) address range for* both user and kernel.*///准备TCRmov_q	x10, TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \TCR_TG_FLAGS | TCR_KASLR_FLAGS | TCR_ASID16 | \TCR_TBI0 | TCR_A1 | TCR_KASAN_FLAGStcr_clear_errata_bits x10, x9, x5	//清除该CPU上触发勘误表的TCR位。#ifdef CONFIG_ARM64_VA_BITS_52ldr_l		x9, vabits_actualsub		x9, xzr, x9add		x9, x9, #64tcr_set_t1sz	x10, x9
#elseldr_l		x9, idmap_t0sz	//读取idmap_t0sz
#endiftcr_set_t0sz	x10, x9	//跟新t0sz,这样我们就可以加载ID映射/** Set the IPS bits in TCR_EL1.*/tcr_compute_pa_size x10, #TCR_IPS_SHIFT, x5, x6		//设置TCR.IPS到最高支持
#ifdef CONFIG_ARM64_HW_AFDBM	//如果支持Access和Dirty页面标志的硬件更新/** Enable hardware update of the Access Flags bit.* Hardware dirty bit management is enabled later,* via capabilities.*/mrs	x9, ID_AA64MMFR1_EL1and	x9, x9, #0xfcbz	x9, 1f		//如果cpu允许硬件访问标志更新功能orr	x10, x10, #TCR_HA		// 设置硬件访问标志更新功能
1:
#endif	/* CONFIG_ARM64_HW_AFDBM */msr	tcr_el1, x10		//写入tcr_el1/** Prepare SCTLR*/mov_q	x0, SCTLR_EL1_SETret					// return to head.S
SYM_FUNC_END(__cpu_setup)

__cpu_setup执行步骤如下:

  1. tlbi vmalle1 使本地TLB失效
  2. 使能EL1和EL0执行 FP/ASIMD指令
  3. 允许AArch64 DCC寄存器的L0访问被捕获
  4. 禁止从EL0访问PMU和AMU
  5. 给内存的8个region设置上DEVICE_nGnRnE,DEVICE_nGnRE,DEVICE_GRE,NORMAL_NC,NORMAL,NORMAL_WT,NORMAL这8个属性。
  6. 清除该CPU上触发勘误表的TCR位
  7. 跟新t0sz,这样我们就可以加载ID映射
  8. 设置硬件访问标志更新功能

6. __primary_switch

SYM_FUNC_START_LOCAL(__primary_switch)
#ifdef CONFIG_RANDOMIZE_BASEmov	x19, x0				// 保留新的SCTLR_EL1值mrs	x20, sctlr_el1			// 保留旧的SCTLR EL1值
#endifadrp	x1, init_pg_dir		//获取init_pg_dir的页表基地址bl	__enable_mmu			//开启mmu
#ifdef CONFIG_RELOCATABLE
#ifdef CONFIG_RELRmov	x24, #0				// no RELR displacement yet
#endifbl	__relocate_kernel
#ifdef CONFIG_RANDOMIZE_BASE	//我们没开,不看ldr	x8, =__primary_switched	//把__primary_switched的内容放入x8adrp	x0, __PHYS_OFFSET	//获取内核代码段的页表基地址blr	x8	//跳转到__primary_switched运行,返回的时候返回下一个指令/** If we return here, we have a KASLR displacement in x23 which we need* to take into account by discarding the current kernel mapping and* creating a new one.*/pre_disable_mmu_workaroundmsr	sctlr_el1, x20			// disable the MMUisbbl	__create_page_tables		// recreate kernel mappingtlbi	vmalle1				// Remove any stale TLB entriesdsb	nshisbmsr	sctlr_el1, x19			// re-enable the MMUisbic	iallu				// flush instructions fetcheddsb	nsh				// via old mappingisbbl	__relocate_kernel
#endif
#endifldr	x8, =__primary_switched	//把__primary_switched的内容放入x8adrp	x0, __PHYS_OFFSET	//获取内核代码段的页表基地址br	x8						//跳转到__primary_switched,并且不返回
SYM_FUNC_END(__primary_switch)

__primary_switch执行步骤如下:

  1. 获取init_pg_dir的页表基地址
  2. 调用函数__enable_mmu开启mmu
  3. 调用函数__primary_switched,并且不再返回

6.1 __enable_mmu

SYM_FUNC_START(__enable_mmu)mrs	x2, ID_AA64MMFR0_EL1	//读取内存模型特征寄存器ubfx	x2, x2, #ID_AA64MMFR0_TGRAN_SHIFT, 4	//提取28到31这4位cmp     x2, #ID_AA64MMFR0_TGRAN_SUPPORTED_MIN	//如果支持4k页b.lt    __no_granule_support		//卡死cmp     x2, #ID_AA64MMFR0_TGRAN_SUPPORTED_MAX	//如果不支持4k页b.gt    __no_granule_support		//卡死//只有4KB粒度支持52位输入输出地址update_early_cpu_boot_status 0, x2, x3	//启动中的CPU更新失败状态adrp	x2, idmap_pg_dir	//读取内核页全局目录页表到x2phys_to_ttbr x1, x1phys_to_ttbr x2, x2msr	ttbr0_el1, x2			//内核页全局目录页表写入ttbr0_el1offset_ttbr1 x1, x3msr	ttbr1_el1, x1			//内核镜像的init目录页表写入ttbr1_el1isbmsr	sctlr_el1, x0	//写入sctlr_el1寄存器isb/** Invalidate the local I-cache so that any instructions fetched* speculatively from the PoC are discarded, since they may have* been dynamically patched at the PoU.*/ic	iallu	//icache失效dsb	nsh		//内存屏障isbret
SYM_FUNC_END(__enable_mmu)

__enable_mmu执行步骤如下:

  1. 读取内存模型特征寄存器,判断是否支持我们内核设置的页大小,现在我们内核设置的页大小是4k,根据读取内存模型特征寄存器的值判断这个cpu是否支持4k页
  2. 启动中的CPU更新失败状态
  3. 设置ttbr0_el1和ttbr1_el1寄存器
  4. icache失效和内存屏障

6.2 __primary_switched

SYM_FUNC_START_LOCAL(__primary_switched)adrp	x4, init_thread_union	//init_thread_union地址保存在x4中,它存放了init进程结构体add	sp, x4, #THREAD_SIZE	//设置sp指针为init_thread_union偏移THREAD_SIZEadr_l	x5, init_task		//init_task地址保存在x5msr	sp_el0, x5			//保存当前进程描述符到sp_el0,使用用户态的堆栈,说明是用户态程序#ifdef CONFIG_ARM64_PTR_AUTH__ptrauth_keys_init_cpu	x5, x6, x7, x8
#endifadr_l	x8, vectors			// 读取vectors的地址msr	vbar_el1, x8			// 设置异常向量表isbstp	xzr, x30, [sp, #-16]!	//把将xzr和保存在x30中的链接地址入栈mov	x29, sp		//将栈指针保存到x29#ifdef CONFIG_SHADOW_CALL_STACKadr_l	scs_sp, init_shadow_call_stack	// Set shadow call stack
#endifstr_l	x21, __fdt_pointer, x5		//将FDT地址保存到__fdt_pointer变量ldr_l	x4, kimage_vaddr		// Save the offset betweensub	x4, x4, x0			// the kernel virtual andstr_l	x4, kimage_voffset, x5		//将kimage的虚拟地址和物理地址的偏移保存到kimage_voffset中// Clear BSSadr_l	x0, __bss_startmov	x1, xzradr_l	x2, __bss_stopsub	x2, x2, x0bl	__pi_memset		//清理bss段数据dsb	ishst				// Make zero page visible to PTW#ifdef CONFIG_KASANbl	kasan_early_init
#endif
#ifdef CONFIG_RANDOMIZE_BASEtst	x23, ~(MIN_KIMG_ALIGN - 1)	// already running randomized?b.ne	0fmov	x0, x21				// pass FDT address in x0bl	kaslr_early_init		// parse FDT for KASLR optionscbz	x0, 0f				// KASLR disabled? just proceedorr	x23, x23, x0			// record KASLR offsetldp	x29, x30, [sp], #16		// we must enable KASLR, returnret					// to __primary_switch()
0:
#endifadd	sp, sp, #16		//sp加一mov	x29, #0			mov	x30, #0b	start_kernel	//跳转到start_kernel
SYM_FUNC_END(__primary_switched)

__primary_switched主要执行了一下步骤:

  1. 初始化init_task的结构体和堆栈
  2. 设置异常向量表
  3. 将FDT地址保存到__fdt_pointer变量
  4. 将kimage的虚拟地址和物理地址的偏移保存到kimage_voffset中
  5. 清理bss段数据
  6. 跳转到start_kernel

到这里head.S的启动就看完了。

相关文章:

linux内核启动分析(一)

文章目录1.HEAD1.preserve_boot_args1.1 __inval_dcache_area2.el2_setup3. set_cpu_boot_mode_flag4. __create_page_tables4.1map_memory5. __cpu_setup6. __primary_switch6.1 __enable_mmu6.2 __primary_switched最近工作中经常使用飞腾E2000的开发版&#xff0c;也遇到一些…...

wireshark常见使用操作讲解以及几个故障解决案例分享

&#xff08;1&#xff09;网卡选择 对于电脑本身有多个网卡的时候&#xff0c;选择网卡就成为了一个困惑的地方&#xff0c;其实这里很简单&#xff0c;只要把鼠标放在对应的网卡上面就可以看到地址等信息&#xff0c;就容易判断出来了。 &#xff08;2&#xff09;过滤器 直…...

利用逻辑分析仪解析串口通讯数据

利用逻辑分析仪解析串口通讯数据&#x1f527;采用的是市面上最为广泛使用的USB逻辑分析仪: &#x1f4da;资料下载&#xff1a; 链接: https://pan.baidu.com/s/1c9lwWDbtJxaJED-kzSbiJg 提取码: 5vnr&#x1f528;测试工具为&#xff1a;Logic 2.4.6&#xff0c;也可以使用Pu…...

新整理的前端面试题

pinia和vuex的区别&#xff08;1&#xff09;pinia它没有mutation,他只有state&#xff0c;getters&#xff0c;action【同步、异步】使用他来修改state数据&#xff08;2&#xff09;pinia他默认也是存入内存中&#xff0c;如果需要使用本地存储&#xff0c;在配置上比vuex麻烦…...

数据仓库-数仓分层

层级 全拼 职责划分 ODS(源数据层) Operational DataStore ODS层存储最原始的数据&#xff0c; 对数据不做任何加工处理&#xff1b; 源数据主要来自业务数据库和日志&#xff0c;这些数据是用户操作业务系统产生&#xff0c;所以叫操作型数据(Operational Data) 。 DWD(…...

【Linux】Linux根文件系统扩容

场景&#xff1a;根文件系统需要至少100GB的剩余空间&#xff0c;但是目前就剩余91GB。因此&#xff0c;我们需要对根文件系统进行扩容。# df -h 文件系统 容量 已用 可用 已用% 挂载点 devtmpfs 3.9G 0 3.9G 0% /dev tmpfs …...

RPC编程:Hessian RPC一个老的RPC框架(一)

RPC编程&#xff1a;Hessian RPC一个老的RPC框架一&#xff1a;Hessian RPC1&#xff1a;Hession RPC一个老的RPC框架2&#xff1a;老&#xff0c;为什么还要研究&#xff1f;3&#xff1a;Hession RPC概念二&#xff1a;Hessian RPC设计思想1&#xff1a;Hession依赖于服务器2…...

逆向 x蜂窝 zzzghostsigh

逆向 x蜂窝 zzzghostsigh 版本 9.3.7 新版本是64位的so charles 抓包 目标字段 zzzghostsigh frida java function hook_xPreAuthencode() {Java.perform(function() {var helper Java.use("com.mfw.tnative.AuthorizeHelper");helper.xPreAuthencode.implemen…...

QML 鼠标事件

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 QML 中有一些元素本身是不具备交互能力的(例如:Rectangle、Text、Image 等),那么如何通过鼠标来控制它们的行为呢?这里就需要用到 MouseArea 元素了,它继承于 Item 且不可见,通常需要与可见元素结合使…...

极智项目 | 实战pytorch arcface人脸识别

欢迎关注我的公众号 [极智视界]&#xff0c;获取我的更多经验分享 大家好&#xff0c;我是极智视界&#xff0c;本文介绍 实战pytorch arcface人脸识别&#xff0c;并提供完整项目源码。 本文介绍的实战arcface人脸识别项目&#xff0c;提供完整的可以一键训练、测试的项目工程…...

【IP技术】ipv4和ipv6是什么?

IPv4和IPv6是两种互联网协议&#xff0c;用于在互联网上标识和寻址设备。IPv4&#xff08;Internet Protocol version 4&#xff09;是互联网协议的第四个版本&#xff0c;是当前广泛使用的互联网协议。IPv4地址由32位二进制数构成&#xff0c;通常表示为4个十进制数&#xff0…...

linux基本功系列之uniq命令实战

文章目录前言一. uniq的命令介绍二. 语法格式及常用选项三. 参考案例3.1 统计行数3.2 对文本进行去重3.3 显示不重复的行3.4 仅显示重复的行&#xff0c;且显示重复的行的所有行3.5 忽略字母大小写总结前言 大家好&#xff0c;又见面了&#xff0c;我是沐风晓月&#xff0c;本…...

六、SpringBoot项目搭建

日志 Java 主流日志工具库 统一接口 什么是 REST&#xff1f; Representational State Transfer——“表现层状态转化”。可以总结为一句话&#xff1a;REST 是所有 Web 应用都应该遵守的架构设计指导原则。面向资源是 REST 最明显的特征&#xff0c;对于同一个资源的一组不…...

【LeetCode】2363. 合并相似的物品

2363. 合并相似的物品 题目描述 给你两个二维整数数组 items1 和 items2 &#xff0c;表示两个物品集合。每个数组 items 有以下特质&#xff1a; items[i] [valuei, weighti] 其中 valuei 表示第 i 件物品的 价值 &#xff0c;weighti 表示第 i 件物品的 重量 。items 中每…...

华为OD机试题,用 Java 解【出租车计费】问题

最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…...

【人脸识别】DDL:数据分布知识蒸馏思想,提升困难样本(遮挡、低分辨率等)识别效果

论文题目&#xff1a;《Improving Face Recognition from Hard Samples via Distribution Distillation Loss》 论文地址&#xff1a;https://arxiv.org/pdf/2002.03662v3.pdf 代码地址&#xff1a;https://github.com/HuangYG123/DDL 1.前言及相关工作 Large facial variatio…...

如何管理好仓库/库房?

仓库管理是企业管理中不可缺少的一部分&#xff0c;事关企业能否正常运行的关键之一&#xff0c;古人有云&#xff1a;“三军未动粮草先行”&#xff0c;一个企业仓库管理做不好&#xff0c;他的生产管理肯定也是做不好的&#xff0c;不是说生产管理人员的管理能力不具备&#…...

Unity Lighting -- Unity的光源简介

在主菜单栏中&#xff0c;点击Window -> Rendering -> Light Explorer打开光源管理器&#xff0c;这个标签页可以看到场景中所有的光源&#xff0c;包括每个光源的类型&#xff0c;形状&#xff0c;模式&#xff0c;颜色&#xff0c;强度&#xff0c;阴影等信息。 在主菜…...

Android仿网易云音乐歌单详情页

效果图实现思路&#xff1a;1、Activity设置自定义Shared Element切换动画2、透明状态栏&#xff08;透明Toolbar,使背景图上移&#xff09;3、Toolbar底部增加和背景一样的高斯模糊图&#xff0c;并上移图片&#xff08;为了使背景图的底部作为Toolbar的背景&#xff09;4、上…...

linux基本功系列之free命令实战

文章目录前言一. free命令介绍二. 语法格式及常用选项三. 参考案例3.1 查看free相关的信息3.2 以MB的形式显示内存的使用情况3.3 以总和的形式显示内存的使用情况3.4 周期性的查询内存的使用情况3.5 以更人性化的形式来查看内存的结果输出总结前言 大家好&#xff0c;又见面了…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...