这里的内核源代码是根据九鼎科技为s5pvx210开发板移植好的内核来分析的

1:内核运行的物理地址与虚拟地址

    (1)KERNEL_RAM_VADDR(VADDR就是virtual address),这个宏定义了内核运行时的虚拟地址。值为0xC0008000

    (2)KERNEL_RAM_PADDR(PADDR就是physical address),这个宏定义内核运行时的物   理地址。值为0x30008000

    (3)总结:内核运行的物理地址是0x30008000,对应的虚拟地址是0xC0008000。而物理地址为什么是30008000是因为在移植uboot的时候为了得到连续的512M的内存,将内存地址的开头设置为30000000


2:内核的真正入口

    (1)内核的真正入口就是ENTRY(stext)处

    (2)前面的__HEAD定义了后面的代码属于段名为.head.text的段


3:内核运行的硬件条件

    (1) 内核的起始部分代码是被解压代码调用的。回忆之前讲zImage的时候,uboot启动内核后实际调用运行的是zImage前面的那段未经压缩的解压代码,解压代码运行时先将zImage后段的内核解压开,然后再去调用运行真正的内核入口。

    (2) 内核启动不是无条件的,而是有一定的先决条件,这个条件由启动内核的bootloader(我们这里就是uboot)来构建保证。

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

    (4) kernel启动时MMU是关闭的,因此硬件上需要的是物理地址。但是内核是一个整体(zImage)只能被连接到一个地址(不能分散加载),这个连接地址肯定是虚拟地址。因此内核运行时前段head.S中尚未开启MMU之前的这段代码就很难受。所以这段代码必须是位置无关码,而且其中涉及到操作硬件寄存器等时必须使用物理地址。


4:内核启动的汇编阶段

__lookup_processor_type

    (1) 我们从cp15协处理器的c0寄存器中读取出硬件的CPU ID号,然后调用这个函数来进行合法性检验。如果合法则继续启动,如果不合法则停止启动,转向__error_p启动失败。

    (2) 该函数检验cpu id的合法性方法是:内核会维护一个本内核支持的CPU ID号码的数组,然后该函数所做的就是将从硬件中读取的cpu id号码和数组中存储的各个id号码依次对比,如果没有一个相等则不合法,如果有一个相等的则合法。

    (3) 内核启动时设计这个校验,也是为了内核启动的安全性着想。                                    

 __lookup_machine_type

    该函数的设计理念和思路和上面校验cpu id的函数一样的。不同之处是本函数校验的是机器码。

__vet_atags

    (1) 该函数的设计理念和思路和上面2个一样,不同之处是用来校验uboot给内核的传参ATAGS格式是否正确。这里说的传参指的是uboot通过tag给内核传的参数(主要是板子的内存分布memtag、uboot的bootargs)

    (2) 内核认为如果uboot给我的传参格式不正确,那么我就不启动。

    (3) uboot给内核传参的部分如果不对,是会导致内核不启动的。譬如uboot的bootargs设置不正确内核可能就会不启动。

__create_page_tables

    (1) 顾名思义,这个函数用来建立页表。

    (2) linux内核本身被连接在虚拟地址处,因此kernel希望尽快建立页表并且启动MMU进入虚拟地址工作状态。但是kernel本身工作起来后页表体系是非常复杂的,建立起来也不是那么容易的。kernel想了一个好办法

    (3) kernel建立页表其实分为2步。第一步,kernel先建立了一个段式页表(和uboot中之前建立的页表一样,页表以1MB为单位来区分的),这里的函数就是建立段式页表的。段式页表本身比较好建立(段式页表1MB一个映射,4GB空间需要4096个页表项,每个页表项4字节,因此一共需要16KB内存来做页表),坏处是比较粗不能精细管理内存;第二步,再去建立一个细页表(4kb为单位的细页表),然后启用新的细页表废除第一步建立的段式映射页表。

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

__switch_data

    (1) 建立了段式页表后进入了__switch_data部分,这东西是个函数指针数组。

    (2) 分析得知下一步要执行__mmap_switched函数

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

    (4) 保存起来cpu id号、机器码、tag传参的首地址。

    (5) b start_kernel跳转到C语言运行阶段。


总结:汇编阶段其实也没干啥,主要原因是uboot干了大部分活。汇编阶段主要就是校验启动合法性、建立段式映射的页表并开启MMU以方便使用内存、跳入C阶段。


来自:朱老师物联网大讲堂笔记