当前位置: 首页 > 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;又见面了…...

华为OD机试模拟题 用 C++ 实现 - 连续子串(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明连续子串题目输入输出示例一输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD …...

【软考——系统架构师】UML 建模与架构文档化

&#x1f50e;这里是【软考——系统架构师】&#xff0c;关注我考试轻松过线 &#x1f44d;如果对你有帮助&#xff0c;给博主一个免费的点赞以示鼓励 欢迎各位&#x1f50e;点赞&#x1f44d;评论收藏⭐️ 文章目录UML 基础UML 软件开发过程系统架构文档化送书福利UML 基础 U…...

Spring中常用注解

声明 bean 的注解 Component&#xff1a;泛指各种组件 Controller、Service、Repository 都可以称为Component Controller&#xff1a;控制层 Service&#xff1a;业务层 Repository&#xff1a;数据访问层Bean 的生命周期属性 Scope 设置类型包括&#xff1a;设置 Spring 容器…...

基于SpringCloud的可靠消息最终一致性06:轮询事务消息

上一节把可靠消息最终一致性的正常逻辑代码顺序执行了一次,并且对于同一个事务消息,在正常情况下它要被发送至少两次。 这是因为在发送消息之前,TransactionMessageService就已经把消息保存到了数据库中。而在首次消费完消息后,TransactionMessageListener并没有从数据库中…...

Python Flask + Echarts 轻松制作动态酷炫大屏( 附代码)

目录一、确定需求方案二、整体架构设计三、编码实现 &#xff08;关键代码&#xff09;四、完整代码五、运行效果1.动态实时更新数据效果图 说明: 其中 今日抓拍&#xff0c;抓拍总数&#xff0c;预警信息统计&#xff0c;监控点位统计图表 做了动态实时更新处理。 ​ 2.静态…...

Wepack(1):SourceMap讲解以及使用

今天我们来讲讲定位源码的工具 Sourcemap &#xff0c; 我们先讲最简单的配置&#xff0c;之后才补充 sourcemap 的其他属性 Sourcemap 作用 可以在打包的代码直接对应相应源码 例如 vue2 , vue3可以把对应的错误上传到相关服务器 使用 webpack.config.js const config …...

华为OD机试题,用 Java 解【最多等和不相交连续子序列】问题

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

Kubernetes06:Controller

Kubernetes06:Controller 1、什么是controller 管理和运行容器的对象&#xff0c;是一个物理概念 在集群上管理和运行容器的对象 2、Pod和Controller之间的关系 Pod是通过controller来实现应用的运维 比如伸缩、滚动升级等等操作Pod和Controller之间通过 label 标签建立关系…...

采购文件中 RFI、RFQ、RFP、IFB的区别

【PMBOK的描述】   采购文件用于征求潜在卖方的建议书。如果主要依据价格来选择卖方&#xff08;如购买商业或标准产品时&#xff09;&#xff0c;通常就使用标书、投标或报价等术语。如果主要依据其他考虑&#xff08;如技术能力或技术方法&#xff09;来选择卖方&#xff0…...

linux升级gcc版本详细教程

0.前言一般linux操作系统默认的gcc版本都比较低&#xff0c;例如centos7系统默认的gcc版本为4.8.5。gcc是从4.7版本开始支持C11的&#xff0c;4.8版本对C11新特性的编译支持还不够完善&#xff0c;因此如果需要更好的体验C11以及以上版本的新特性&#xff0c;需要升级gcc到一个…...

三里屯做网站的公司/售卖链接

Window>>Preferences>>General>>Editors>>Text Editors>>Annotations 选中右边的Warnings 把旁边的勾都去掉.. 转载于:https://www.cnblogs.com/zhaogaojian/p/6601578.html...

网站建设上qq图标去除/建站快车

快速导读&#xff1a;Q1&#xff1a;vbs怎么调用子程序?你可以这样操作比如你要调用C:\m.exe Set ws CreateObject("Wscript.Shell") Set fs CreateObject("Scripting.FileSystemObject") ws.Run "c:\m.exe" 希望能帮助你&#xff01;&#x…...

教育网站 php/免费发链接的网站

启动失败IDLE使用套接字在IDLE GUI进程和用户代码执行过程之间进行通信。无论何时Shellstart或重新启动&#xff0c;都必须建立连接。(后者用分隔线表示’RESTART’)。如果用户进程无法连接到GUI进程&#xff0c;则会显示一个失败的常见原因是用户编写的文件与标准库模块同名&a…...

重庆网站建设子沃科技熊掌号/搭建网站的步骤

Datawhale干货 作者&#xff1a;黄雨龙&#xff0c;中国科学技术大学对于回归问题&#xff0c;Datawhale已经梳理过完整的实践方案&#xff08;可点击&#xff09;&#xff0c;本文对多分类的数据挖掘问题做了完整的方案总结。一、赛题数据赛题背景本赛题是一个多分类的数据挖掘…...

adobe dreamweaver建设网站/发布平台

问题重述 margin是外边距属性&#xff0c;它的定义是边框和边框之间的距离&#xff0c;但是有些时候这些边距是合并的&#xff0c;你知道这是怎么回事吗&#xff1f; 代码演示 1.垂直边距合并 css .box1{width: 100px;height: 100px;margin-bottom: 25px;background-color…...

机票网站开发知乎/免费数据统计网站

项目中需要对地址进行解析&#xff0c;然后补齐省市区&#xff0c;下面这篇博客有很好的参考意义&#xff0c;减少了很多工作量&#xff0c;那个正则用的很巧妙。 地址解析步骤如下&#xff1a; 1、检查是否存在省份 2、如果存在省份&#xff0c;将会检查省份是否明确标注省&am…...