您的位置:首页 > 运维架构 > Linux

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

2014-07-12 15:36 330 查看
首先,我们要知道在zImage的生成过程中,是把arch/arm/boot/compressed/head.s 和解压代码misc.c,decompress.c加在压缩内核的最前面最终生成zImage的,那么它的启动过程就是从这个head.s开始的,并且如果代码从RAM运行的话,是与位置无关的,可以加载到内存的任何地方。

下面以arch/arm/boot/compressed/head.s为主线进行启动过程解析。

1. head.s的debug宏定义部分

最开始的一段都是head.s的debug宏定义部分,这部分可以方便我们调试时使用。

如下:

#ifdef DEBUG

#if defined(CONFIG_DEBUG_ICEDCC)

#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_V6K) || defined(CONFIG_CPU_V7)

.macro loadsp,
rb, tmp

.endm

.macro writeb,
ch, rb

mcr p14,
0, \ch, c0, c5, 0

.endm

#elif defined(CONFIG_CPU_XSCALE)

.macro loadsp,
rb, tmp

.endm

.macro writeb,
ch, rb

mcr p14,
0, \ch, c8, c0, 0

.endm

#else

.macro loadsp,
rb, tmp

.endm

.macro writeb,
ch, rb

mcr p14,
0, \ch, c1, c0, 0

.endm

#endif

#else

#include <mach/debug-macro.S>

.macro writeb,
ch, rb

senduart
\ch, \rb

.endm

#if defined(CONFIG_ARCH_SA1100)

.macro loadsp,
rb, tmp

mov \rb,
#0x80000000 @ physical base address

#ifdef CONFIG_DEBUG_LL_SER3

add \rb,
\rb, #0x00050000 @ Ser3

#else

add \rb,
\rb, #0x00010000 @ Ser1

#endif

.endm

#elif defined(CONFIG_ARCH_S3C2410)

.macro loadsp,
rb, tmp

mov \rb,
#0x50000000

add \rb,
\rb, #0x4000 * CONFIG_S3C_LOWLEVEL_UART_PORT

.endm

#else

.macro loadsp,
rb, tmp

addruart
\rb, \tmp

.endm

#endif

#endif

#endif

如果开启DEBUGging宏的话,这部分代码分两段CONFIG_DEBUG_ICEDCC是用ARMv6以上的加构支持的ICEDCC技术进行调试,DCC(Debug
Communications Channel)是ARM的一个调试通信通道,在串口无法使用的时候可以使用这个通道进行数据的通信,具体的技术参前ARM公司文档《ARM
Architecture Reference Manual》。

第二部分首先#include <mach/debug-macro.S>,这个文件定义位于arch/arm/mach-xxxx/include/mach/debug-macro.S里面,所以这个是和平台相关的,里面定义了每个平台的相关的串口操作,因这个时候系统还没有起来,所以它所用的串口配置参数是依赖于前一级bootloader所设置好的,如我们使用的u-boot设置好所有的参数。如我们的EVB板ARM的实现如下:

#include <mach/hardware.h>

#include <mach/platform.h>

.macro addruart,
rp, rv

ldr \rp, =ARM_EVB_UART0_BASE @
System peripherals (phys address)

ldr \rv, =(IO_BASE+ ARM_EVB _UART0_BASE) @
System peripherals (virt address)

.endm

.macro senduart,rd,rx

strb \rd,
[\rx, #(0x00)] @ Write to Transmitter Holding Register

.endm

.macro waituart,rd,rx

1001: ldr \rd, [\rx, #(0x18)] @
Read Status Register

tst \rd, #0x20 @when
TX FIFO Full, then wait

bne 1001b

.endm

.macro busyuart,rd,rx

1001: ldr \rd, [\rx, #(0x18)] @
Read Status Register

tst \rd, #0x08 @
when uart is busy then wait

bne 1001b

.endm

主要实现 addruart,senduart,waituart,busyuart这四个函数的具体实施。这个是调试函数打印的基础。

下面是调试打印用到的kputc和kphex

.macro kputc,val

mov r0,
\val

bl putc

.endm

.macro kphex,val,len

mov r0,
\val

mov r1,
#\len

bl phex

.endm

它所调用的putcphex是在head.s最后的一段定义的,如下

#ifdef DEBUG

.align 2

.type phexbuf,#object

phexbuf: .space 12

.size phexbuf,
. - phexbuf

上面是分配打印hex的buffer,下面是具体的实现:

@ phex corrupts {r0, r1, r2, r3}

phex: adr
r3, phexbuf

mov r2,
#0

strb r2,
[r3, r1]

1: subs r1,
r1, #1

movmi r0, r3

bmi puts

and r2, r0, #15

mov r0,
r0, lsr #4

cmp r2,
#10

addge r2,
r2, #7

add r2, r2, #'0'

strb r2,
[r3, r1]

b 1b

@ puts corrupts {r0, r1, r2, r3}

puts: loadsp r3,
r1

1: ldrb r2,
[r0], #1

teq r2, #0

moveq pc,
lr

2: writeb r2,
r3

mov r1,
#0x00020000

3: subs r1,
r1, #1

bne 3b

teq r2, #'\n'

moveq r2,
#'\r'

beq 2b

teq r0, #0

bne 1b

mov pc,
lr

@ putc corrupts {r0, r1, r2, r3}

putc:

mov r2,
r0

mov r0,
#0

loadsp r3,
r1

b 2b

@ memdump corrupts {r0, r1, r2, r3, r10, r11, r12, lr}

memdump: mov r12,
r0

mov r10,
lr

mov r11,
#0

2: mov r0,
r11, lsl #2

add r0, r0, r12

mov r1,
#8

bl phex

mov r0,
#':'

bl putc

1: mov r0,
#' '

bl putc

ldr r0,
[r12, r11, lsl #2]

mov r1,
#8

bl phex

and r0, r11, #7

teq r0, #3

moveq r0,
#' '

bleq putc

and r0, r11, #7

add r11, r11, #1

teq r0, #7

bne 1b

mov r0,
#'\n'

bl putc

cmp r11,
#64

blt 2b

mov pc,
r10

#endif

嘿嘿,还有memdump 这个函数可以用,不错。

好了,言归正传,再往下看,代码如下:

.macro debug_reloc_start

#ifdef DEBUG

kputc #'\n'

kphex r6,
8

kputc #':'

kphex r7,
8

#ifdef CONFIG_CPU_CP15

kputc #':'

mrc p15,
0, r0, c1, c0

kphex r0,
8

#endif

kputc #'\n'

kphex r5,
8

kputc #'-'

kphex r9,
8

kputc #'>'

kphex r4,
8

kputc #'\n'

#endif

.endm

.macro debug_reloc_end

#ifdef DEBUG

kphex r5,
8

kputc #'\n'

mov r0,
r4

bl memdump

#endif

.endm

debug_reloc_start

用来打印出一些代码重定位后的信息,关于重定位,后面会说, debug_reloc_end

用来把解压后的内核的256字节的数据dump出来,查看是否正确。很不幸的是,这个不是必须调用的,调试的时候,这些都是要自己把这些调试函数加上去的。好debug部分到这里就完了。

2. head.s的.start部分,进入或保持在svc模式,并关中断

继续向下分析,下面是定义.start段,这段在链接时被链接到代码的最开头,那么zImage启动时,最先执行的代码也就是下面这段代码start开始的,如下:

.section ".start",
#alloc, #execinstr

.align

.arm @
Always enter in ARM state

start:

.type start,#function

.rept 7

mov r0,
r0

.endr

ARM( mov r0,
r0 )

ARM( b 1f )

THUMB( adr
r12, BSYM(1f) )

THUMB( bx r12 )

.word 0x016f2818 @
Magic numbers to help the loader

.word start @
absolute load/run zImage address

.word _edata @
zImage end address

THUMB( .thumb )

1: mov r7,
r1 @ save architecture ID

mov r8,
r2 @ save atags pointer

#ifndef __ARM_ARCH_2__

mrs r2,
cpsr @ get current mode

tst r2,
#3 @ not user?

bne not_angel

mov r0,
#0x17 @ angel_SWIreason_EnterSVC

ARM( swi
0x123456 ) @ angel_SWI_ARM

THUMB( svc
0xab ) @ angel_SWI_THUMB

not_angel:

mrs r2,
cpsr @ turn off interrupts to

orr r2,
r2, #0xc0 @ prevent angel from running

msr cpsr_c,
r2

#else

teqp pc,
#0x0c000003 @ turn off interrupts

#endif

为何这个会先执行呢?问的好。那么来个中断吧:这个是由arch/arm/boot/compressed/vmlinux.lds的链接脚本决定的,如下:

.text : {

_start = .;

*(.start)

*(.text)

*(.text.*)

*(.fixup)

*(.gnu.warning)

*(.rodata)

*(.rodata.*)

*(.glue_7)

*(.glue_7t)

*(.piggydata)

. = ALIGN(4);

}

怎么样,看到没,.text段最开始的一部分就是.start段,所以这就注定了它就是最先执行的代码。

好了,中断结束,再回到先前面的代码,这段代码的最开始是会被编译器编译成8个nop, 这个是为了留给ARM的中断向量表的,但是整个head.s都没有用到中断啊,谁知道告诉我一下,谢了。

然后呢,把u-boot 传过来的放在r1,r2的值,存在r7,r8中,r1存是的evb板的ID号,而r2存的是内核要用的参数地址,这两个参数在解压内核的时候不要用到,所以暂时保存一下,解压内枋完了,再传给linux内核。

再然后是几个宏定义的解释,ARM(),BSYM(),THUMB(),再加上 W()吧,这几个个宏定义都是在arch/arm/include/asm/unified.h里面定义的,好了,这里也算个中断吧,如下:

#ifdef CONFIG_THUMB2_KERNEL

......

#define ARM(x...)

#define THUMB(x...) x

#ifdef __ASSEMBLY__

#define W(instr) instr.w

#endif

#define BSYM(sym) sym
+ 1

#else

......

#define ARM(x...) x

#define THUMB(x...)

#ifdef __ASSEMBLY__

#define W(instr) instr

#endif

#define BSYM(sym) sym

#endif

好的看到上面的定义你就会明白了,这里是为了兼容THUMB2指令的内核。

关于#define ARM(x...) 里面的“...”,没有见过吧,这个是C语言的C99的新标准,变参宏,就是在x里,你可以随便你输入多少个参数。别急还没有完,因为没有看见文件里有什么方包含这个头文件。是的文件中确实没有包含,它的定义是在:arch/arm/makefile中加上的:

KBUILD_AFLAGS += -include asm/unified.h

行,这些宏解释到此,下面再出现,我就无视它了。

好了,再回来,读取cpsr并判断是否处理器处于supervisor模式——从u-boot进入kernel,系统已经处于SVC32模式;而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入。

注:Angel是ARM公司的一种调试方法,它本身就是一个调试监控程序,是一组运行在目标机上的程序,可以接收主机上调试器发送的命令,执行诸如设置断点、单步执行目标程序、观察或修改寄存器、存储器内容之类的操作。与基于jtag的调试代理不同,Angel调试监控程序需要占用一定的系统资源,如内存、串行端口等。使用angel调试监控程序可以调试在目标系统运行的arm程序或thumb程序。

好了,里面有一句:teqp pc,
#0x0c000003 @ turn off interrupts

是否很奇怪,不过大家千万不要纠结它,因为它是ARMv2架构以前的汇编方法,用于模式变换,和中断关闭的,看不明白也没关系,因为我们以后也用不到。这里知道一下有这个事就行了。

行,到这里.start段就完了,代码那么多,其实就是做一件事,保证运行下面的代码时已经进入了SVC模式,并保证中断是关的,完了.start部分结束。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: