Arm Linux 内存管理(一)————开启MMU

首先我们根据vmlinux.lds可以找到内核入口函数为 stext,我们就直接从stext开始,主要干了几件事情

1.safe_svcmode_maskall r9			    //设置CPU运行模式为SVC,并关中断  
2.bl	__vet_atags	                    //验证atags或者dtb是否有效 
3.bl	__create_page_tables            //创建临时映射        
4.b	__enable_mmu			            //使能mmu             
5.__mmap_switched                       //初始化堆栈,并进入start_kernel

一个个来看:

.macro safe_svcmode_maskall reg:req
	setmode	PSR_F_BIT | PSR_I_BIT | SVC_MODE, \reg
.endm

.macro	setmode, mode, reg
	msr	cpsr_c, #\mode
.endm

这里把很多条件编译给去除掉了,其实就是将CPSR寄存器FIQ和IRQ位置0,以及设置成SVC模式。

__vet_atags:
	tst	r2, #0x3			@ aligned?
	bne	1f

	ldr	r5, [r2, #0]
#ifdef CONFIG_OF_FLATTREE
	ldr	r6, =OF_DT_MAGIC		@ is it a DTB?
	cmp	r5, r6					//是不是DTB
	beq	2f
#endif
	cmp	r5, #ATAG_CORE_SIZE		@ is first tag ATAG_CORE?
	cmpne	r5, #ATAG_CORE_SIZE_EMPTY
	bne	1f
	ldr	r5, [r2, #4]
	ldr	r6, =ATAG_CORE
	cmp	r5, r6
	bne	1f

2:	ret	lr				@ atag/dtb pointer is ok

1:	mov	r2, #0
	ret	lr
ENDPROC(__vet_atags)

首先判断是不是DTB(根据头部MAGIC判断),如果是则返回,否则,判断是否为ATAG,如果是则返回,否则将r2寄存器清0,表示既不是DTB也不是ATAG。

__create_page_tables:        //页表映射初始化
    pgtbl	r4, r8				@ page table address

	/*
	 * Clear the swapper page table
	 */
	mov	r0, r4                       
	mov	r3, #0
	add	r6, r0, #PG_DIR_SIZE            
1:	str	r3, [r0], #4					//页表映射清0
	str	r3, [r0], #4
	str	r3, [r0], #4
	str	r3, [r0], #4
	teq	r0, r6
	bne	1b
__create_page_tables:        //恒等映射
    ldr	r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

	/*
	 * Create identity mapping to cater for __enable_mmu.
	 * This identity mapping will be removed by paging_init().
	 */
	adr	r0, __turn_mmu_on_loc								//这一段做恒等映射
	ldmia	r0, {r3, r5, r6}
	sub	r0, r0, r3			@ virt->phys offset
	add	r5, r5, r0			@ phys __turn_mmu_on
	add	r6, r6, r0			@ phys __turn_mmu_on_end
	mov	r5, r5, lsr #SECTION_SHIFT
	mov	r6, r6, lsr #SECTION_SHIFT

1:	orr	r3, r7, r5, lsl #SECTION_SHIFT	@ flags + kernel base
	str	r3, [r4, r5, lsl #PMD_ORDER]	@ identity mapping
	cmp	r5, r6
	addlo	r5, r5, #1			@ next section
	blo	1b
__create_page_tables:       //代码段,数据段等映射(PAGE_OFFSET ------_end-1)
    add	r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)		
	ldr	r6, =(_end - 1)											
	orr	r3, r8, r7
	add	r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)		
1:	str	r3, [r0], #1 << PMD_ORDER				
	add	r3, r3, #1 << SECTION_SHIFT		
	cmp	r0, r6
	bls	1b
__create_page_tables:               //DTB映射
    mov	r0, r2, lsr #SECTION_SHIFT										//DTB进行映射
	movs	r0, r0, lsl #SECTION_SHIFT								//物理地址 20位对齐
	subne	r3, r0, r8												//RAM偏移
	addne	r3, r3, #PAGE_OFFSET									//虚拟地址
	addne	r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)			//对应页表地址
	orrne	r6, r7, r0												//实际要写入页表的东西
	strne	r6, [r3], #1 << PMD_ORDER								//写入
	addne	r6, r6, #1 << SECTION_SHIFT								//下一个1m空间
	strne	r6, [r3]												//写入

1.页表映射初始化 :

       r8寄存器代表的是phys_offset,也就是实际的(物理地址-虚拟地址)的偏移,通过pgtbl r4 r8可以得到swapper_pg_dir 的物理地址。然后按4字节对其进行清0,直至大小超过#PG_DIR_SIZE为止。

2.恒等映射 :

       后面需要打开MMU,打开MMU后要使用的就是虚拟地址了,而之前的PC都是用的物理地址,可能会出现错误,所以在这一段代码段(__turn_mmu_on----__turn_mmu_on_end )需要做一个恒等映射(即物理地址与虚拟地址相同),这样打开MMU时也不会出现问题。首先需要了解在这一阶段里,内存采用的是段式管理,基地址(12bit)+偏移地址(20bit),也就是说4GB的空间分段管理,每段1MB,共4k段,所以页表大小就是4k*4 = 16k。

       通过system.map我们可以得到 __turn_mmu_on的虚拟地址0xc0100000  __turn_mmu_on_end 的虚拟地址 0xc0100020,假设物理偏移是0xc0000000   r5 = 0x00100000 , r6 = 0x00100020  偏移地址是20位所以右移20位可以得到r5 = 0x001,r6 = 0x001,r5,r6其实在一个段中,所以我们知道我们需要写到页表(0x00000400)的第1项,每项都是4字节,所以第一项地址就是 0x00000404,将对应物理地址以及mmu的flag写入到该页表项,如果大小超过1M,那么重复该过程,直至r5与r6相等。

3:代码段,数据段等映射(PAGE_OFFSET ------_end-1)

        这一步和第2步类似,根据虚拟地址找到对应的表项,再在表项中写入对应的地址即可。

4.DTB映射:

        DTB不超过1M,所以这里就固定进行2段映射区。

这样所有的临时映射就已经都完成了,接下来就可以打开mmu了。

__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
#ifdef CONFIG_ARM_LPAE
	mcrr	p15, 0, r4, r5, c2		@ load TTBR0
#else
	mov	r5, #DACR_INIT
	mcr	p15, 0, r5, c3, c0, 0		@ load domain access register
	mcr	p15, 0, r4, c2, c0, 0		@ load page table pointer			//页表物理地址的基地址
#endif
	b	__turn_mmu_on
ENDPROC(__enable_mmu)

ENTRY(__turn_mmu_on)
	mov	r0, r0
	instr_sync
	mcr	p15, 0, r0, c1, c0, 0		@ write control reg				//打开mmu
	mrc	p15, 0, r3, c0, c0, 0		@ read id reg
	instr_sync
	mov	r3, r3
	mov	r3, r13										//__mmap_switched
	ret	r3
__turn_mmu_on_end:
ENDPROC(__turn_mmu_on)

1.设置页表基地址 :

        CP15中的寄存器C2保存的是页表的基地址,即一级映射描述符表的基地址。代码中就是把r4 写到cp15的c2中,r4前面也分析过了,就是swapper_pg_dir 的物理地址。

2.打开MMU :

       CP15的寄存器C1中的M位,控制是否打开MMU,前面已经设置好了r0寄存器,所以只需要将r0写入cp15的C1中即可,这里省略了很多打开前的操作,如果有兴趣可以自行去分析源码。

__mmap_switched:
	adr	r3, __mmap_switched_data

	ldmia	r3!, {r4, r5, r6, r7}
	cmp	r4, r5				@ Copy data segment if needed		//从存储地址拷贝数据到数据段起始地址
1:	cmpne	r5, r6
	ldrne	fp, [r4], #4
	strne	fp, [r5], #4
	bne	1b

	mov	fp, #0				@ Clear BSS (and zero fp)			//bss段数据为0
1:	cmp	r6, r7
	strcc	fp, [r6],#4
	bcc	1b

 ARM(	ldmia	r3, {r4, r5, r6, r7, sp})
 THUMB(	ldmia	r3, {r4, r5, r6, r7}	)
 THUMB(	ldr	sp, [r3, #16]		)
	str	r9, [r4]			@ Save processor ID					//保存 一些信息 后续要用  cpu_id
	str	r1, [r5]			@ Save machine type					//machine id
	str	r2, [r6]			@ Save atags pointer				//dtb地址
	cmp	r7, #0
	strne	r0, [r7]			@ Save control register values
	b	start_kernel											//进入c语言启动kernel
ENDPROC(__mmap_switched)

1.拷贝data段数据:

          判断__data_loc和_sdata的地址是否相同,如果不相同则需要将__data_loc的数据拷贝到_sdata中来。如果相同,则表明已经拷贝过了,那就跳过。

2.bss段清0:

         将bss段数据清0。

3.保存一些信息到寄存器:

         将cpu_id machine_id 以及dtb地址写入对应的寄存器,在后续进入到start_kernel还会用到,所以需要保存。

4.进入start_kernel

         终于到了我们熟悉的函数了,这之后就是C语言的启动的kernel了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值