内核启动流程——汇编阶段的head.S文件

本文详细解析了Linux内核启动过程中的关键步骤,包括校验启动合法性、建立段式映射页表及构建C语言运行环境等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除。

一、内容总结

汇编阶段或者说内核引导阶段,主要是/arch/arm/kernel/head.S文件,主要完成以下内容:

(1)校验启动合法性(CPU ID、机器码,uboot给内核的传参格式等)。

(2)建立段式映射的页表,并开启MMU以方便使用内存。

(3)构建C运行环境,跳入C阶段。

整个汇编阶段的工作,都体现在下面这段代码中:

二、分析head.S文件

内核的链接脚本可知,内核的入口地址在/arch/arm/kernel/head.S文件的ENTRY(stext)处。 

所以我们开始分析 /arch/arm/kernel/head.S 文件。

2.1 内核的链接地址与物理地址

 

(1)KERNEL_RAM_VADDR,表示内核的链接地址,它是一个虚拟地址,值为 0xC0008000 。

(2)KERNEL_RAM_PADDR,表示内核的链接地址所对应的物理地址,其值为 0x30008000。

2.2 内核的入口地址

(1)__HEAD定义了一个段名为“.head.text ”的段。

// 在/include/linux/init.h文件中
/* For assembly routines */
#define __HEAD		.section	".head.text","ax" //定义了段名为.head.text的段
#define __INIT		.section	".init.text","ax"
#define __FINIT		.previous

(2)ENTRY(stext),这表明内核的入口地址是标号stext。

在/include/linux/linkage.h文件中有如下代码:

//在/include/linux/linkage.h文件中

#ifndef ENTRY
#define ENTRY(name) \
  .globl name; \
   ALIGN; \
   name:
#endif

则 ENTRY(stext) 就相当于下面的代码:

.globl stext
       ALIGN
stext:

(3)uboot启动内核后,实际调用zImage前面的那段未经压缩的解压代码,解压代码运行时先将zImage后面的部分解压开,然后再到内核入口(即这里的stext)去启动内核。话说这过程具体是怎样的?

(4)内核启动需要一定先决条件,这个条件由启动内核的bootloader(比如uboot)来构建保证。

(5)ARM体系中,函数调用时实际是通过寄存器传参的。函数调用时传参有两种设计:一种是寄存器传参,另一种是栈内存传参。uboot中最后theKernel (0, machid, bd->bi_boot_params);执行内核时,实际把0放在r0中,machid放在r1中,bd->bi_boot_params放在r2中。ARM的这种处理技巧刚好满足了kernel启动的条件和要求。

(6)因为内核还没有启动起来,此时MMU是关闭的,因此硬件上需要的是物理地址。但是内核的链接地址是一个虚拟地址。因此head.S文件中尚未开启MMU之前的代码必须是位置无关码,而且其中涉及到操作硬件寄存器等时必须使用物理地址。

2.3 校验CPU_ID的合法性

通过__lookup_processor_type函数检验CPU_ID的合法性:

__lookup_processor_type函数位于/arch/arm/kernel/head-common.S文件中,内容如下:

首先从cp15协处理器的c0寄存器中读取出硬件的CPU ID号,然后调用__lookup_processor_type函数来进行合法性检验(内核会维护一个本内核支持的CPU ID号码的数组,然后该函数把从硬件中读取到的CPU ID和数组中存储的各个ID号进行对比,如果没有一个相等则不合法,如果有一个相等的则合法),如果合法则继续启动,如果不合法则停止启动,转向__error_p启动失败。

2.4 校验机器码的合法性

通过__lookup_machine_type函数校验机器码的合法性:

 

 __lookup_machine_type函数位于/arch/arm/kernel/head-common.S文件中,内容如下:

__lookup_machine_type函数的设计方法,与上面校验cpu id的函数一样的。

内核启动时设计上面的两个校验,也是为了内核启动的安全性着想。

2.5 校验uboot给内核传参的格式

利用__vet_atags函数,对uboot通过struct tag给内核传参的格式进行校验。

__vet_atags函数位于/arch/arm/kernel/head-common.S文件中,内容如下:

该函数的设计方法与上面__lookup_machine_type函数一样,用来对uboot通过struct tag给内核传参的格式进行校验。传递的参数包括:板子的内存分布、bootargs等内容。

如果uboot给内核传参的格式不对(比如uboot的bootargs设置不正确),内核将启动不起来。

2.6 建立段式页表

利用__create_page_tables函数建立段式页表。

__create_page_tables函数位于/arch/arm/kernel/head-common.S文件中,内容如下:

/*
 * Setup the initial page tables.  We only setup the barest
 * amount which are required to get the kernel running, which
 * generally means mapping in the kernel code.
 *
 * r8  = machinfo
 * r9  = cpuid
 * r10 = procinfo
 *
 * Returns:
 *  r0, r3, r6, r7 corrupted
 *  r4 = physical page table address
 */
__create_page_tables:
	pgtbl	r4				@ page table address

	/*
	 * Clear the 16K level 1 swapper page table
	 */
	mov	r0, r4
	mov	r3, #0
	add	r6, r0, #0x4000
1:	str	r3, [r0], #4
	str	r3, [r0], #4
	str	r3, [r0], #4
	str	r3, [r0], #4
	teq	r0, r6
	bne	1b

	ldr	r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

	/*
	 * Create identity mapping for first MB of kernel to
	 * cater for the MMU enable.  This identity mapping
	 * will be removed by paging_init().  We use our current program
	 * counter to determine corresponding section base address.
	 */
	mov	r6, pc
	mov	r6, r6, lsr #20			@ start of kernel section
	orr	r3, r7, r6, lsl #20		@ flags + kernel base
	str	r3, [r4, r6, lsl #2]		@ identity mapping

	/*
	 * Now setup the pagetables for our kernel direct
	 * mapped region.
	 */
	add	r0, r4,  #(KERNEL_START & 0xff000000) >> 18
	str	r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
	ldr	r6, =(KERNEL_END - 1)
	add	r0, r0, #4
	add	r6, r4, r6, lsr #18
1:	cmp	r0, r6
	add	r3, r3, #1 << 20
	strls	r3, [r0], #4
	bls	1b

#ifdef CONFIG_XIP_KERNEL
	/*
	 * Map some ram to cover our .data and .bss areas.
	 */
	orr	r3, r7, #(KERNEL_RAM_PADDR & 0xff000000)
	.if	(KERNEL_RAM_PADDR & 0x00f00000)
	orr	r3, r3, #(KERNEL_RAM_PADDR & 0x00f00000)
	.endif
	add	r0, r4,  #(KERNEL_RAM_VADDR & 0xff000000) >> 18
	str	r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]!
	ldr	r6, =(_end - 1)
	add	r0, r0, #4
	add	r6, r4, r6, lsr #18
1:	cmp	r0, r6
	add	r3, r3, #1 << 20
	strls	r3, [r0], #4
	bls	1b
#endif

	/*
	 * Then map first 1MB of ram in case it contains our boot params.
	 */
	add	r0, r4, #PAGE_OFFSET >> 18
	orr	r6, r7, #(PHYS_OFFSET & 0xff000000)
	.if	(PHYS_OFFSET & 0x00f00000)
	orr	r6, r6, #(PHYS_OFFSET & 0x00f00000)
	.endif
	str	r6, [r0]

#ifdef CONFIG_DEBUG_LL
	ldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
	/*
	 * Map in IO space for serial debugging.
	 * This allows debug messages to be output
	 * via a serial console before paging_init.
	 */
	ldr	r3, [r8, #MACHINFO_PGOFFIO]
	add	r0, r4, r3
	rsb	r3, r3, #0x4000			@ PTRS_PER_PGD*sizeof(long)
	cmp	r3, #0x0800			@ limit to 512MB
	movhi	r3, #0x0800
	add	r6, r0, r3
	ldr	r3, [r8, #MACHINFO_PHYSIO]
	orr	r3, r3, r7
1:	str	r3, [r0], #4
	add	r3, r3, #1 << 20
	teq	r0, r6
	bne	1b
#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
	/*
	 * If we're using the NetWinder or CATS, we also need to map
	 * in the 16550-type serial port for the debug messages
	 */
	add	r0, r4, #0xff000000 >> 18
	orr	r3, r7, #0x7c000000
	str	r3, [r0]
#endif
#ifdef CONFIG_ARCH_RPC
	/*
	 * Map in screen at 0x02000000 & SCREEN2_BASE
	 * Similar reasons here - for debug.  This is
	 * only for Acorn RiscPC architectures.
	 */
	add	r0, r4, #0x02000000 >> 18
	orr	r3, r7, #0x02000000
	str	r3, [r0]
	add	r0, r4, #0xd8000000 >> 18
	str	r3, [r0]
#endif
#endif
	mov	pc, lr

(1)Linux内核本身被链接在虚拟地址处,因此kernel希望尽快建立页表并且启动MMU。

(2)kernel建立页表分为以下两步:

第一步,先建立一个段式页表(1MB为单位的段页表)。段式页表建立过程简单(段式页表1MB一个映射,4GB空间需要4096个页表项,每个页表项4字节,因此一共需要16KB内存来做页表),但不能精细管理内存。上面的函数就是用来建立段式页表的。

第二步,然后建立一个细页表(4kb为单位的细页表),然后启用新的细页表,并废除第一步建立的段式映射页表。

(3)内核启动的早期建立段式页表,并在内核启动早期使用;内核启动的后期再次建立细页表并启用。等内核工作起来后,就只有细页表了。

2.7 构建C语言运行环境

 建立段式页表后进入__switch_data部分,它是一个函数指针数组,定义在/arch/arm/kernel/head-common.S文件中。

 

分析可知下一步要执行__mmap_switched函数。

该函数主要完成以下工作:

(1)复制数据段、清除bss段(目的是构建C语言运行环境)。

(2)将CPU ID号、机器码、TAG传参的首地址保存起来。

(3)通过“b start_kernel”跳转到C语言运行阶段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值