栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

linux内核的启动过程之汇编—1.3

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

linux内核的启动过程之汇编—1.3

1.汇编的第二阶段分析
其中的汇编阶段也主要可以分为两个过程,在此分析第二个阶段,创建页表,启动mmu,跳转至c语言阶段。
1).校验处理器的id,以及传过来的r2参数,完成部分通用寄存器的传参;
r1 = machine no, r2 = atags or dtb,
r8 = phys_offset, r9 = cpuid, r10 = procinfo

2).创建mmu的页表,使能mmu

在经历了汇编第一阶段后,已经完成了处理器等参数的校验;实现了通用寄存器的传参;接下来进行汇编的第二个阶段,创建页表__create_page_tables,并且启动mmu;
2.MMU的简介
MMU,内存管理单元memory management unit,首先mmu是一个硬件,内核所做的内存管理就是针对mmu就行辅助操作映射页表,将页表放置在mmu指定的寄存器中。

最终的目地为创建页表,实现虚拟地址至物理地址的映射,先明确几个概念:虚拟地址,物理地址,编译地址,运行地址。
虚拟地址起始就是编译器生成的符号表对应的地址;物理地址即RAM对应的运行地址;
虚拟地址=编译地址;
物理地址=运行地址;
3.页表的创建__create_page_tables
这里需要说明,页表是存储在物理内存中,而不是虚拟内存;
此完成了三处的页表映射: 内核镜像代码,页表与内核启动参数映射,内核代码段映射。

__create_page_tables:
	pgtbl	r4, r8				@ page table address


	
	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

	

	adr	r0, __enable_mmu_loc
	ldmia	r0, {r3, r5, r6}
	sub	r0, r0, r3			@ virt->phys offset
	add	r5, r5, r0			@ phys __enable_mmu
	add	r6, r6, r0			@ phys __enable_mmu_end
	mov	r5, r5, lsr #20
	mov	r6, r6, lsr #20

1:	orr	r3, r7, r5, lsl #20		@ flags + kernel base
	str	r3, [r4, r5, lsl #2]		@ identity mapping
	teq	r5, r6
	addne	r5, r5, #1			@ next section
	bne	1b

	

	mov	r3, pc      									  3.4.1               
	mov	r3, r3, lsr #20								      3.4.2
	orr	r3, r7, r3, lsl #20								  3.4.3
	add	r0, r4,  #(KERNEL_START & 0xff000000) >> 18		  3.4.4
	str	r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!	  3.4.5
	ldr	r6, =(KERNEL_END - 1)							  3.4.6
	add	r0, r0, #4										  3.4.7
	add	r6, r4, r6, lsr #18								  3.4.8
1:	cmp	r0, r6											  3.4.9
	add	r3, r3, #1 << 20								  3.4.10
	strls	r3, [r0], #4								  3.4.11
	bls	1b												  3.4.12					

#ifdef CONFIG_XIP_KERNEL
	
	add	r3, r8, #TEXT_OFFSET
	orr	r3, r3, r7
	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

	

	mov	r0, r2, lsr #20
	movs	r0, r0, lsl #20
	moveq	r0, r8
	sub	r3, r0, r8
	add	r3, r3, #PAGE_OFFSET
	add	r3, r4, r3, lsr #18
	orr	r6, r7, r0
	str	r6, [r3]

#ifdef CONFIG_DEBUG_LL
#ifndef CONFIG_DEBUG_ICEDCC
	
	addruart r7, r3

	mov	r3, r3, lsr #20
	mov	r3, r3, lsl #2

	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
	mov	r3, r7, lsr #20
	ldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
	orr	r3, r7, r3, lsl #20
1:	str	r3, [r0], #4
	add	r3, r3, #1 << 20
	teq	r0, r6
		bne	1b

#else 
	
	ldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
#endif 

#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
	
	add	r0, r4, #0xff000000 >> 18
	orr	r3, r7, #0x7c000000
	str	r3, [r0]
#endif
#ifdef CONFIG_ARCH_RPC
	
	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
ENDPROC(__create_page_tables)

3.1.pgtbl宏分析
其中r8中存放的是,PHYS_OFFSET为物理内存RAM的起始偏移地址;
pgtbl是一个汇编宏,具体实现为从PHYS_OFFSET找到内核的页表起始的物理地址;

	.macro	pgtbl, rd, phys
	add	rd, phys, #TEXT_OFFSET - 0x4000
	.endm

最终将r4设置成页表的物理地址,后续的页表映射依赖于此参数来配置页表项。
3.2.清除内核页表
因为后续需要使用到内核页表,所以开始先把此物理地址块进行初始化,另外,内核页表是存储在物理地址中的,在此处是使用了arm的L1的1M的section映射方式,需要映射4G的虚拟内存,因此只需要4096个即可满足要求(4G/1M=4096个)。
r4寄存器里面目前存储的是页表的起始物理地址,r6中存储的是起始物理地址+0x4000即刚好可以满足4096个L1页表项的使用,然后通过r3寄存器循环清除此段内存的值,便实现了页表清空工作。
最后一句实现了将proinfo结构体的__cpu_mm_mmu_flags的地址赋值给r7寄存器;

struct proc_info_list {
	unsigned int		cpu_val;
	unsigned int		cpu_mask;
	unsigned long		__cpu_mm_mmu_flags;	
	unsigned long		__cpu_io_mmu_flags;	
	unsigned long		__cpu_flush;		
	const char		*arch_name;
	const char		*elf_name;
	unsigned int		elf_hwcap;
	const char		*cpu_name;
	struct processor	*proc;
	struct cpu_tlb_fns	*tlb;
	struct cpu_user_fns	*user;
	struct cpu_cache_fns	*cache;
};

3.3 内核代码段区域映射
实现内核代码段的映射,此和3.4 3.5的操作是类似的,实际就是填充物理页表里面的页表项;
3.4内核镜像段区域映射
首先3.4.1拿到当前的PC指针;
3.4.2:r3=r3>>20
3.4.3:r3=r7+r3<<20;
3.4.4:R4已保存了页表物理起始地址,最终实现将实际的页表项保存在r0中;
3.4.5:将r3的结果写入内核起始虚拟地址所在的L1表项
3.4.6:r6为内核代码的截止处的虚拟地址;
3.4.7:r0保存下一个即将要填写的L1页表项的物理地址;
3.4.8:R6值是内核代码结尾的所在L1页表的表项的物理地址;
3.4.9-3.4.12:循环填充页表直到r0==r6;

3.5启动参数与页表映射
我们知道,在执行此处映射之前 r2 = atags or dtb, r8 = phys_offset,r7存放的是section的描述符的内容,也就是r2存放的是启动参数,r8存放的是DDR的物理起始地址;uboot将内核启动参数放在物理内存起始的1M内存中,所以需要对其映射,以便访问,此处实际是向物理页表中写入对应的页表项;
4.使能mmu
如何操作mmu,主要是通过cp15协处理器进行实现,主要的配置就是将页表的物理地址写入ttbl0中,然后使能相关寄存器进行实现, 此处是先将使能mmu存在r13里面,后续再执行r13里面的内容,然后设置内核堆栈段的信息,保存cpuid、dtb等参数存入至全局变量中;

__enable_mmu:
#if defined(CONFIG_ALIGNMENT_TRAP) && __LINUX_ARM_ARCH__ < 6
	orr	r0, r0, #CR_A
#else
	bic	r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
	bic	r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
	bic	r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
	bic	r0, r0, #CR_I
#endif
	mov	r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | 
		      domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | 
		      domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | 
		      domain_val(DOMAIN_IO, DOMAIN_CLIENT))
	mcr	p15, 0, r5, c3, c0, 0		@ load domain access register
	mcr	p15, 0, r4, c2, c0, 0		@ load page table pointer
	b	__turn_mmu_on
ENDPROC(__enable_mmu)

5.跳转至c阶段
做了这么多,完成了汇编阶段的使命,就可以通过b跳转指令跳转至c语言中,第一句c语言为start_kernel处。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/945930.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号