linux启动源码,简单介绍ARM linux的启动部分源代码

MACHINE_START(MINI2440, "MINI2440")

/* Maintainer: Michel Pollet */

.phys_io = S3C2410_PA_UART,

.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

.boot_params = S3C2410_SDRAM_PA + 0x100,

.map_io     = mini2440_map_io,

.init_machine   = mini2440_init,

.init_irq = s3c24xx_init_irq,

.timer   = &s3c24xx_timer,

MACHINE_END

OK, __lookup_machine_type这个例程的我们也搞明白了。回忆一下,启动代码现在已经完成的工作,R10寄存器中为指向proc_info_list结构体的指针(物理地址空间),这个结构体包含有关于我们的处理器的一些重要信息。R8寄存器中为指向一个与我们的平台相匹配的machine_desc结构体的指针,这个结构体中保存有一些关于我们的平台的重要信息。

回来接着看arch/arm/kernel/head.S文件中的stext:

bl __vet_atags

这个例程同样同样也是在arch/arm/kernel/head-common.S文件中定义:

__vet_atags:

tst   r2, #0x3        @ aligned?

bne   1f

ldr   r5, [r2, #0]       @ is first tag ATAG_CORE?

cmp   r5, #ATAG_CORE_SIZE

cmpne r5, #ATAG_CORE_SIZE_EMPTY

bne   1f

ldr   r5, [r2, #4]

ldr   r6, =ATAG_CORE

cmp   r5, r6

bne   1f

mov   pc, lr          @ atag pointer is ok

1: mov   r2, #0

mov   pc, lr

ENDPROC(__vet_atags)

这个例程接收机器信息(R8寄存器)为参数,并检测r2中传入的ATAGS 指针的合法性。内核使用tag来作为bootloader传递内核参数的方式。系统要求r2中传进来的ATAGS指针式4字节对齐的,同时要求ATAGS列表的第一个tag是一个ATAG_CORE类型的。

此时R10寄存器中保存有指向CPU信息结构体的指针,R8寄存器中保存有指向机器结构体的指针,R2寄存器中保存有指向tag表的指针,R9中还保存有CPU ID信息。

回到arch/arm/kernel/head.S文件中的stext,之后就要进入初始化过程中比较关键的一步了,开始设置mmu,但首先要填充一个临时的内核页表,映射4m的内存,这在初始化过程中是足够了:

bl  __create_page_tables

这个例程设置初始页表,这里只设置最起码的数量,只要能使内核运行即可,r8  = machinfo,r9  = cpuid,r10 = procinfo,在r4寄存器中返回物理页表地址。

__create_page_tables例程在文件arch/arm/kernel/head.S中定义:

__create_page_tables:

pgtbl r4          @ page table address

// pgtbl是一个宏,本文件的前面部分有定义:

// .macro pgtbl, rd

// ldr   rd, =(KERNEL_RAM_PADDR - 0x4000)

// .endm

// KERNEL_RAM_PADDR在本文件的前面有定义,为(PHYS_OFFSET + TEXT_OFFSET)

// PHYS_OFFSET在arch/arm/mach-s3c2410/include/mach/memory.h定义,

// 为UL(0x30000000)

// 而TEXT_OFFSET在arch/arm/Makefile中定义,为内核镜像在内存中到内存

// 开始位置的偏移(字节),为$(textofs-y)

// textofs-y也在文件arch/arm/Makefile中定义,

// 为textofs-y   := 0x00008000

// r4 = 30004000为临时页表的起始地址

// 首先即是初始化16K的页表,高12位虚拟地址为页表索引,所以为

// 4K*4 = 16K,大页表,每一个页表项,映射1MB虚拟地址。

// 这个地方还来了个循环展开,以优化性能。

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

// PROCINFO_MM_MMUFLAGS在arch/arm/kernel/asm-offsets.c文件中定义,

// 为DEFINE(PROCINFO_MM_MMUFLAGS,

// offsetof(struct proc_info_list, __cpu_mm_mmu_flags));

// R10寄存器保存的指针指向是我们前面找到的proc_info_list结构嘛。

// 为内核的第一个MB创建一致的映射,以为打开MMU做准备,这个映射将会被

// paging_init()移除,这里使用程序计数器来获得相应的段的基地址。

// 这个地方是直接映射。

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

// 接下来为内核的直接映射区设置页表。KERNEL_START在文件的前面定义,

// 为KERNEL_RAM_VADDR,即内核的虚拟地址。

// 而KERNEL_RAM_VADDR在文件的前面定义,则为(PAGE_OFFSET + TEXT_OFFSET)

// 映射完整的内核代码段,初始化数据段。

// PAGE_OFFSET为内核镜像开始的虚拟地址,在

// arch/arm/include/asm/memory.h中定义。在配置内核时选定具体值,默认

// 为0xC0000000。

// 因为最高12位的值是页表中的偏移地址,而第三高的四位必然为0,

// 每个页表项为4字节,右移20位之后,还得再左移两位回来,所以,这里只// 是左移18位。

// R3寄存器在经过了上面的操作之后,实际上是变成了指向内核镜像代码段

// 的指针(物理地址),在这个地方,再一次为内核镜像的第一个MB做了映射。

// R6随后指向了内核镜像的尾部。R0为页表项指针。

// 这里以1MB为单位来映射内核镜像。

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

// 为了使用启动参数,将物理内存的第一MB映射到内核虚拟地址空间的

// 第一个MB,r4存放的是页表的地址。这里的PAGE_OFFSET的虚拟地址

// 比上面的KERNEL_START要小0x8000

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]

// 上面的这个步骤显得似乎有些多余。

// 总结一下,这个建立临时页表的过程:

// 1、为内核镜像的第一个MB建立直接映射

// 2、为内核镜像完整的建立从虚拟地址到物理地址的映射

// 3、为物理内存的第一个MB建立到内核的虚拟地址空间的第一个MB的映射。

// OK,内核的临时页表建立完毕。整个初始化临时页表的过程都没有修改R8,

// R9和R10。

mov   pc, lr

ENDPROC(__create_page_tables)

回到stext:

ldr   r13, __switch_data    @ address to jump to after

@ mmu has been enabled

这个地方实际上是在r13中保存了另一个例程的地址。后面的分析中,遇到执行到这个例程的情况时会有详细说明。

接着看stext:

adr   lr, BSYM(__enable_mmu)      @ return (PIC) address

BSYM()是一个宏,在文件arch/arm/include/asm/unified.h中定义,为:

#define BSYM(sym) sym

也就是说这个语句也仅仅是把__enable_mmu例程的地址加载进lr寄存器中。为了方便之后调用的函数返回时,直接执行__enable_mmu例程。

接着看stext下一句:

ARM( add   pc, r10, #PROCINFO_INITFUNC )

ARM()也是一个宏,同样在文件arch/arm/include/asm/unified.h中定义,当配置内核为生成ARM镜像,则为:#define ARM(x...)  x

所以这一条语句也就是在调用一个例程。R10中保存的是procinfo结构的地址。PROCINFO_INITFUNC符号在arch/arm/kernel/asm-offsets.c文件中定义,为:

DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));

也就是调用结构体proc_info_list的__cpu_flush成员函数。回去查看arch/arm/mm/proc-arm920.S文件中struct proc_info_list结构体的变量的定义,可以看到这个成员为:

b  __arm920_setup

也就是说,在设置好内核临时页表之后调用了例程__arm920_setup,这个例程同样在arch/arm/mm/proc-arm920.S中:

__arm920_setup:

mov   r0, #0

mcr   p15, 0, r0, c7, c7    @ invalidate I,D caches on v4

mcr   p15, 0, r0, c7, c10, 4      @ drain write buffer on v4

#ifdef CONFIG_MMU

mcr   p15, 0, r0, c8, c7    @ invalidate I,D TLBs on v4

#endif

adr   r5, arm920_crval

ldmia r5, {r5, r6}

mrc   p15, 0, r0, c1, c0    @ get control register v4

bic   r0, r0, r5

orr   r0, r0, r6

mov   pc, lr

这一段首先使i,d caches内容无效,然后清除write buffer,接着使TLB内容无效。接下来加载变量arm920_crval的地址,我们看到arm920_crval变量的内容为:

rm920_crval:

crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130

crval为一个宏,在arch/arm/mm/proc-macros.S中定义:

.macro crval, clear, mmuset, ucset

#ifdef CONFIG_MMU

.word clear

.word mmuset

#else

.word clear

.word ucset

#endif

.endm

其实也就是定义两个变量而已。之后,在r0中,得到了我们想要往协处理器相应寄存器中写入的内容。

之后的 __arm920_setup返回,mov  pc, lr,即是调用例程__enable_mmu,这个例程在文件arch/arm/kernel/head.S中:

__enable_mmu:

#ifdef CONFIG_ALIGNMENT_TRAP

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

在这儿设置了页目录地址(r4寄存器中保存),然后设置domain的保护,在前面建立页表的例程中,注意到,页表项的控制信息,是从struct proc_info_list结构体的某字段中取的,其页目录项的 domain都是0,domain寄存器中的domain 0对应的是0b11,表示访问模式为manager,不受限制。在这里同时也完成r0的某些位的进一步设置。

然后,__enable_mmu例程又调用了__turn_mmu_on,在同一个文件中定义:

__turn_mmu_on:

mov   r0, r0

mcr   p15, 0, r0, c1, c0, 0    @ write control reg

mrc   p15, 0, r3, c0, c0, 0    @ read id reg

mov   r3, r3

mov   r3, r13

mov   pc, r3

ENDPROC(__turn_mmu_on)

接下来写控制寄存器:

mcr p15, 0, r0, c1, c0 ,0

一切设置就此生效,到此算是完成了打开d,icache和mmu的工作。

注意:arm的d cache必须和mmu一起打开,而i cache可以单独打开。其实,cache和mmu的关系实在是紧密,每一个页表项都有标志标示是否是cacheable的,可以说本来就是设计一起使用的

前面有提到过,r13中存放的其实是另外一个例程的地址,其值是变量__switch_data的第一个字段,即一个函数指针的值,__switch_data变量是在arch/arm/kernel/head-common.S中定义的:

__switch_data:

.long __mmap_switched

.long __data_loc         @ r4

.long _data           @ r5

.long __bss_start        @ r6

.long _end            @ r7

.long processor_id       @ r4

.long __machine_arch_type      @ r5

.long __atags_pointer       @ r6

.long cr_alignment       @ r7

.long init_thread_union + THREAD_START_SP @ sp

前面的ldr r13 __switch_data,实际上也就是加载符号__mmap_switched的地址,实际上__mmap_switched是一个arch/arm/kernel/head-common.S中定义的例程。接着来看这个例程的定义,在arch/arm/kernel/head-common.S文件中:

__mmap_switched:

adr   r3, __switch_data + 4

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)

1: cmp   r6, r7

strcc fp, [r6],#4

bcc   1b

ldmia r3, {r4, r5, r6, r7, sp}

str   r9, [r4]        @ Save processor ID

str   r1, [r5]        @ Save machine type

str   r2, [r6]        @ Save atags pointer

bic   r4, r0, #CR_A         @ Clear 'A' bit

stmia r7, {r0, r4}       @ Save control register values

b  start_kernel

ENDPROC(__mmap_switched)

这个例程完成如下工作:

1、使r3指向__switch_data变量的第二个字段(从1开始计数)。

2、执行了一条加载指令,也就是在r4, r5, r6, r7寄存器中分别加载4个符号__data_loc,_data, __bss_start ,_end的地址,这四个符号都是在链接脚本arch/arm/kernel/vmlinux.lds.S中出现的,标识了镜像各个段的地址,我们应该不难猜出他们所代表的段。

3、如果需要的话则复制数据段(数据段和BSS段是紧邻的)。

4、初始化BSS段,全部清零,BSS是未初始化的全局变量区域。

5、又看到一条加载指令,同样在一组寄存器中加载借个符号的地址,r4中为processor_id,r5中为__machine_arch_type, r6中为__atags_pointer, r7中为cr_alignment ,sp中为init_thread_union + THREAD_START_SP。

c2c9ed493cd281aa86d8a6f5178c4c01.gif [1] [2] [3] [4] 610626052e95c7fbe3d254abc769d9ad.gif

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值