概述
链接脚本分析
- kernel的连接脚本并不是直接提供的,而是提供了一个汇编文件vmlinux.lds.S,然后在编译的时候再去编译这个汇编文件得到真正的链接脚本vmlinux.lds。
- vmlinux.lds.S在arch/arm/kernel/目录下。
- 为什么linux kernel不直接提供vmlinux.lds而要提供一个vmlinux.lds.S然后在编译时才去动态生成vmlinux.lds呢?猜测:.lds文件中只能写死,不能用条件编译。但是我们在kernel中链接脚本确实有条件编译的需求(但是lds格式又不支持),于是乎kernel工作者找了个投机取巧的方法,就是把vmlinux.lds写成一个汇编格式,然后汇编器处理的时候顺便条件编译给处理了,得到一个不需要条件编译的vmlinux.lds。
- 从vmlinux.lds中ENTRY(stext)可以知道入口符号是stext,可知kernel的起始是在arch/arm/kernel/head.S中
- 链接脚本的SECTIONS里面的开头有一句. = 0xC0000000 + 0x00008000;可知其链接地址为0xC0008000,因为内核运行时需要开启MMU,所以这是一个虚拟地址。那么它的物理地址是多少呢,见下面head.S文件分析。
head.S文件分析
(1)内核运行的物理地址与虚拟地址
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET) #define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)
KERNEL_RAM_VADDR,这个宏定义了内核运行时的虚拟地址。值为0xC0008000
KERNEL_RAM_PADDR,这个宏定义内核运行时的物理地址。值为0x30008000
以下是解析过程
PAGE_OFFSET定义在arch/arm/include/asm/memory.h中
#ifdef CONFIG_MMU /* * PAGE_OFFSET - the virtual address of the start of the kernel image * TASK_SIZE - the maximum size of a user space task. * TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area */ #define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET) ...... #else /* CONFIG_MMU */ ...... #ifndef PAGE_OFFSET #define PAGE_OFFSET (PHYS_OFFSET) #endif #endif /* !CONFIG_MMU */
从make生成的.config文件可以看出我们有定义CONFIG_MMU,所以PAGE_OFFSET的值等于CONFIG_PAGE_OFFSET,在.config中可以看到CONFIG_PAGE_OFFSET的值为0xC0000000,所以PAGE_OFFSET的值为0xC000000
在linux下使用grep命令搜索TEXT_OFFSET可知它的值为0x00008000
PHYS_OFFSET定义在arch/arm/mach-s5pv210/include/mach/memory.h中
#define PHYS_OFFSET UL(0x30000000)
(2)CONFIG_XIP_KERNEL与就地执行有关,一般用于Norflash
(3)注释详解
/* * Kernel startup entry point. * --------------------------- * * This is normally called from the decompressor code. The requirements * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0, * r1 = machine nr, r2 = atags pointer. * * This code is mostly position independent, so if you link the kernel at * 0xc0008000, you call this at __pa(0xc0008000). * * See linux/arch/arm/tools/mach-types for the complete list of machine * numbers for r1. * * We're trying to keep crap to a minimum; DO NOT add any machine specific * crap here - that's what the boot loader (or in extreme, well justified * circumstances, zImage) is for. */
- 内核的起始部分代码是被解压代码调用的。回忆之前讲zImage的时候,uboot启动内核后实际调用运行的是zImage前面的那段未经压缩的解压代码,解压代码运行时先将zImage后段的内核解压开,然后再去调用运行真正的内核入口。
- uboot中最后theKernel (0, machid, bd->bi_boot_params);执行内核时,运行时实际把0放入r0中,machid放入到了r1中,bd->bi_boot_params放入到了r2中。ARM的这种处理技巧刚好满足了kernel启动的条件和要求。
- kernel启动时MMU是关闭的,因此硬件上需要的是物理地址。但是内核是一个整体(zImage)只能被连接到一个地址(不能分散加载),这个连接地址肯定是虚拟地址。因此内核运行时前段head.S中尚未开启MMU之前的这段代码就很难受。所以这段代码必须是位置无关码,而且其中涉及到操作硬件寄存器等时必须使用物理地址
- 在arch/arm/tools/mach-types中保存着linux所支持的所有开发板的机器码
- 这段代码应该尽量的小,不要把机器有关的代码放在这里,而应该在bootloader中执行
(4)__HEAD定义了后面的代码属于段名为.head.text的段
内核启动的汇编阶段
- 关闭IRQ和FIQ中断并设置为SVC模式
- 从cp15协处理器的c0中读取cpu的id号到寄存器r9
- 执行__lookup_processor_type函数,进行CPU合法性检验
(1)我们从cp15协处理器的c0寄存器中读取出硬件的CPU ID号,然后调用这个函数来进行合法性检验。如果合法则继续启动,如果不合法则停止启动,转向__error_p启动失败。
(2)该函数检验cpu id的合法性方法是:内核会维护一个本内核支持的CPU ID号码的数组,然后该函数所做的就是将从硬件中读取的cpu id号码和数组中存储的各个id号码依次对比,如果没有一个相等则不合法,如果有一个相等的则合法。
- 执行__lookup_machine_type函数进行机器码检验
- 执行__vet_atags函数校验uboot给内核的传参ATAGS格式是否正确
- 执行__create_page_tables函数建立段式页表
(1)kernel建立页表其实分为2步。第一步,kernel先建立了一个段式页表(和uboot中之前建立的页表一样,页表以1MB为单位来区分的),这里的函数就是建立段式页表的。段式页表本身比较好建立(段式页表1MB一个映射,4GB空间需要4096个页表项,每个页表项4字节,因此一共需要16KB内存来做页表),坏处是比较粗不能精细管理内存;第二步,再去建立一个细页表(4kB为单位的细页表),然后启用新的细页表废除第一步建立的段式映射页表。
(2)内核启动的早期建立段式页表,并在内核启动前期使用;内核启动后期就会再次建立细页表并启用。等内核工作起来之后就只有细页表了。
- 保存__switch_data的地址到寄存器r13中
__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
(1)__switch_data类似于一个long类型的数组,每个元素占4个字节。他里面包含了函数的首地址、一些段的地址以及一些数据
- 保存__enable_mmu的地址到寄存器lr中
最后
以上就是欣慰导师为你收集整理的x210:uboot和系统移植扩展--内核启动之汇编初始化阶段的全部内容,希望文章能够帮你解决x210:uboot和系统移植扩展--内核启动之汇编初始化阶段所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复