ARM linux解析之压缩内核zImage的启动过程 二

时间:2021-02-05 04:45:35

3.   .text段开始,先是内核解压地址的确定

      再往下看,代码如下:

           .text

#ifdef CONFIG_AUTO_ZRELADDR

           @ determine final kernel image address

           mov     r4, pc

           and r4, r4, #0xf8000000

           add r4, r4, #TEXT_OFFSET

#else

           ldr  r4, =zreladdr

#endif

~~~~ 不要小这一段代码,东西好多啊。如哪入手呢?好吧,先从linux基本参数入手吧,见表.1,里面我写的很详细,因为表格我要放一页,解释我就写在上面了。TEXT_OFFSET是代码相对于物理内存的偏移,通常选为32k=0x8000。这个是有原因的,具体的原因后面会说。先看CONFIG_AUTO_ZRELADDR这个宏所含的内容,它的意思是如果你不知道ZRELADDR地址要定在内存什么地方,那么这段代码就可以帮你。看到0xf8000000了吧,那么后面有多少个0呢?答案是27个,那么227次方就是128M,这就明白了,只要你把解压程序放在你最后解压完成后的内核空间的128M之内的偏移的话,就可以自动设定好解压后内核要运行的地址ZRELADDR

如果你没有定义的话,那么,就会去取zreladdr作为最后解压的内核运行地。那么这个zreladdr是从哪里来的呢?答案是在:arch/arm/boot/compressed/Makefile中定义的

# Supply ZRELADDR to the decompressor via a linker symbol.

ifneq ($(CONFIG_AUTO_ZRELADDR),y)

LDFLAGS_vmlinux += --defsym zreladdr=$(ZRELADDR)

endif

ZRELADDR这又是哪里定义的呢?答案是在:arch/arm/boot/Makefile中定义的

ifneq ($(MACHINE),)

include $(srctree)/$(MACHINE)/Makefile.boot

endif

# Note: the following conditions must always be true:

#   ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)

#   PARAMS_PHYS must be within 4MB of ZRELADDR

#   INITRD_PHYS must be in RAM

ZRELADDR    := $(zreladdr-y)

PARAMS_PHYS:= $(params_phys-y)

INITRD_PHYS:= $(initrd_phys-y)

而里面的几个参数是在每个arch/arm/Mach-xxx/ Makefile.boot里面定义的,内容如下:

   zreladdr-y    := 0x20008000

params_phys-y := 0x20000100

initrd_phys-y    := 0x21000000

这下知道了,绕了一大圈,终于知道r4存的是什么了,就是最后内核解压的起址,也是最后解压后的内核的运行地址,记住,这个地址很重要。

 

解压内核参数

解压时symbol  

解释

ZTEXTADDR

千成不要看成ZTE啊,呵,这里是zImage的运行的起始地址,当内核从nor flash中运行的时候很重要,如果在ram中运行,这个设为0

ZBSSADDR

这个地址也是一样的,这个是BSS的地址,如果在nor中运行解压的话,这个地址很重要。这个要放在RAM

ZRELADDR

这个地址很重要,这个是解压后内核存放的地址,也是最后解压后内核的运行起址。

一般设为内存起址的32K之后,如ARM: 0x20008000

ZRELADDR = PHYS_OFFSET + TEXT_OFFSET

INITRD_PHYS

RAM disk的物理地址

INITRD_VIRT

RAM disk的虚拟地址

__virt_to_phys(INITRD_VIRT) = INITRD_PHYS

PARAMS_PHYS

内核参数的物理地址

内核参数

PHYS_OFFSET

实际RAM的物理地址

对于当前ARM来说,就是0x20000000

PAGE_OFFSET

内核空间的起始虚拟地址,通常: 0xC0000000,高端1G

__virt_to_phys(PAGE_OFFSET) = PHYS_OFFSET

TASK_SIZE

用户进程的内存的最太值(以字节为单位)

TEXTADDR

内核启运行的虚拟地址的起址,通常设为0xC0008000

TEXTADDR = PAGE_OFFSET + TEXT_OFFSET

__virt_to_phys(TEXTADDR) = ZRELADDR

TEXT_OFFSET

相对于内存起址的内核代码存放的偏移,通常设为 32k (0x8000)

DATAADDR

这个是内核数据段的虚拟地址的起址,当用zImage的时候不要定义。

.1 内核参数解释

4.   打开ARM系统的cache,为加快内核解压做好准备

      可以看到,打开cache的就一个函数,如下:

bl   cache_on

看起来很少,其实展开后内容还是很多的。我们来看看这个cache_on在哪里,可以找到代码如下:

           .align   5

cache_onmov    r3, #8             @ cache_on function

           b    call_cache_fn

这里设计的很精妙的,只可意会,注意mov  r3, #8,不多解释,跟进去call_cache_fn

call_cache_fn:  adr r12, proc_types

#ifdef CONFIG_CPU_CP15

           mrc      p15, 0, r9, c0, c0   @ get processor ID

#else

           ldr  r9, =CONFIG_PROCESSOR_ID

#endif

1:         ldr  r1, [r12, #0]          @ get value

           ldr  r2, [r12, #4]          @ get mask

           eor r1, r1, r9         @ (real ^ match)

           tst  r1, r2              @       & mask

 ARM(        addeq   pc, r12, r3       ) @ call cache function

 THUMB(         addeq   r12, r3            )

 THUMB(         moveq  pc, r12            ) @ call cache function

            add r12, r12, #PROC_ENTRY_SIZE

           b    1b

首先看一下proc_types是什么,定义如下:

proc_types:

        ......

 .word   0x000f0000           @ new CPU Id

           .word   0x000f0000

           W(b)    __armv7_mmu_cache_on

           W(b)    __armv7_mmu_cache_off

           W(b)    __armv7_mmu_cache_flush

       .......

            .word   0               @ unrecognised type

           .word   0

           mov     pc, lr

 THUMB(         nop                       )

           mov     pc, lr

 THUMB(         nop                       )

           mov     pc, lr

 THUMB(         nop                       )

可以看到这是一个以proc_types为起始地址的表,上面我列出了第一个表项,和最后一个表项,如果查表不成功,则走最后一个表项返回。它实现的功能就是存两个数据,三条跳转指令,我们可以第一条是它的值,第二条是它的mask值,三条跳转分别是:cache_on,cache_off,cache_flush

 

我想从ARMv4指令向下都是有CP15协处理器的吧,故:CONFIG_CPU_CP15是定义的,那下面我们来分析指令吧。

mrc      p15, 0, r9, c0, c0   @ get processor ID

这个意思是取得ARM处理器的ID,这个又要看《ARM Architecture Reference Manual》了,这里我找了arm1176jzfs的架构手册,也是我用的ARM所用的架构。里面的解释如下:

ARM linux解析之压缩内核zImage的启动过程 二

ARM linux解析之压缩内核zImage的启动过程 二

这里我们主要关心 Architecture这项,我们的ARM这个值是: 0x410FB767,说明用的是r0p7release

好了读取了这个值存入r9寄存器,然后使用算法(real ^ match) & mask,程序中:

( r9 ^r1)&r2这里r1 存是是表中的第一个CPUID值,r2mask值,对于我们的ARM,结果如下:

0x410FB767 ^ 0x000f0000 = 0x4100B767

0x4100B767 & 0x000f0000 = 0

match上了,这个时候就会如下:

ARM(         addeq   pc, r12, r3       ) @ call cache function

我们知道r3的值是0x8,那么r12表项的基址加上0x8就正好是表中的第一条跳转指令:

W(b)    __armv7_mmu_cache_on

明白了,为何r3要等于0x8了吧,如果要调用cache_off,那么只要把r3设为0xC就可以了。精妙吧。行接着往下看__armv7_mmu_cache_on,如下:

__armv7_mmu_cache_on:

           mov     r12, lr

#ifdef CONFIG_MMU

           mrc      p15, 0, r11, c0, c1, 4   @ read ID_MMFR0

           tst  r11, #0xf         @ VMSA  见注:

           blne    __setup_mmu

注:VMSA (Virtual Memory System Architecture),其实就是虚拟内存,通俗地地说就是否支持MMU

首先是保存lr寄存器到r12中,因为我们马上就要调用__setup_mmu了,最后返回也只要用r12就可以了。然后再查看cp15c7,c10,4看是否支持VMSA,具体的见注解。我们在这里我们的ARM肯定是支持的,所以就要建立页表,准备打开MMU,从而可以使能cache

好了下面,就是跳到__setup_mmu进行建产页表的过程,代码如下:

__setup_mmu:    sub r3, r4, #16384       @ Page directory size

           bic  r3, r3, #0xff          @ Align the pointer

           bic  r3, r3, #0x3f00

 

           mov     r0, r3

           mov     r9, r0, lsr #18

           mov     r9, r9, lsl #18        @ start of RAM

           add r10, r9, #0x10000000  @ a reasonable RAM size

           mov     r1, #0x12

           orr r1, r1, #3 << 10

           add r2, r3, #16384

1:        cmp     r1, r9              @ if virt > start of RAM

#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH

           orrhs    r1, r1, #0x08         @ set cacheable

#else

           orrhs    r1, r1, #0x0c         @ set cacheable, bufferable

#endif

           cmp     r1, r10             @ if virt > end of RAM

           bichs    r1, r1, #0x0c         @ clear cacheable, bufferable

           str  r1, [r0], #4      @ 1:1 mapping

           add r1, r1, #1048576

           teq r0, r2

           bne 1b

关于MMU的知识又有好多啊,同样可以参看《ARM Architecture Reference Manual》,还可以看《ARM体系架构与编程》关于MMU的部分,我这里只简单介绍一下我们这里用到MMU。这里只使用到了MMU的段页,故我只介绍与此相关的部分。

对于段页的大小ARM中为1M大小,对于32位的ARM,可寻址空间为4G=4096M,故每一个页表项表示1M空间的话,需要4096个页表项,也就是4K大小,而每一个页表项的大小是4字节,这就是说我们进行段映射的话,需要16K的大小存储段页表。

下面来看一下段页表的格式,如下:

ARM linux解析之压缩内核zImage的启动过程 二

.1 段页表项的具体内容

可以知道对于进行mmu段映射这种方式,一共有4K个这样的页表项,点大小16K字节。在这里我们的16k页表放哪呢?看程序第一句:

__setup_mmu:    sub r3, r4, #16384       @ Page directory size

我们知道r4存内核解压后的基址,那么这句就是把页表放在解压后的内核地址的前面16K空间如下图所示:

ARM linux解析之压缩内核zImage的启动过程 二

.2 linux内核地址空间

(里面地址是用的是以我用的ARM为例的)