您的位置:首页 > 其它

Arm linxu启动过程分析(一)

2010-06-12 22:27 253 查看

本文着重分析
FS2410
平台
linux-2.6.14
内核启动的详细过程,主要包括:
zImage
解压缩阶段、
vmlinux
启动汇编阶段、
startkernel
到创建第一个进程阶段三个部分,一般将其称为
linux
内核启动一、二、三阶段,本文也将采用这种表达方式。对于
zImage
之前的启动过程,本文不做表述,可参考作者


u-boot-1.3.1
启动过程分析”一文。

-------------------------------------------------------------------------------------------------------

硬件平台:优龙
FS2410
开发板

CPU: S3C2410(Arm920T);
NOR Flash: SST39VF1601(2MB);
SDRAM: K4S561632D-TC/L75 (32M)x2);Nand Flash: K9K2G08U0M-YCB0(64MB);
Net card: CS8900A)。
软件平台:u-boot-1.3.1、linux-2.6.14
-------------------------------------------------------------------------------------------------------

本文中涉及到的术语约定如下:



基本内核映像:即内核编译过程中最终在内核源代码根目录下生成的
vmlinux
映像文件,并不包含任何内核解压缩和重定位代码;



zImage
内核映像:包含了内核及压缩和重定位代码,以及基本内核映像
vmlinux
的压缩挡
piggy.gz
的一个映像文件,通常是目标板
bootloader
加载的对象;



zImage
下载地址:即
bootloader

zImage
下载到目标板内存的某个地址或者
nand read

zImage
读到内存的某个地址;



zImage
加载地址:由
Linux

bootloader
完成的将
zImage
搬移到目标板内存的某个位置所对应的地址值,默认值
0x30008000


关于内何映像的生成过程可以参考前一篇"Arm linux内核映像生成过程"

1.


Linux


内核启动第一阶段:内核解压缩和重定位


该阶段是从
u-boot

引导进入内核执行的第一阶段,我们知道
u-boot

引导内核启动的最后一步是:通过一个函数指针
thekernel

()带三个参数跳转到内核(
zImage

)入口点开始执行,此时,
u-boot

的任务已经完成,控制权完全交给内核(
zImage

)。

稍作解释,在
u-boot

的文件
lib_arm/armlinux.c(u-boot-1.3.1)

或者
lib_arm/bootm.c (u-boot-1.3.4)

中定义了
thekernel,

并在
do_bootm_linux

的最后执行
thekernel,

如下:

void (*theKernel)(int zero, int arch, uint params);

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

//hdr->ih_ep----Entry Point Address uImage

中指定的内核入口点,这里是
0x30008000



theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

其中第二个参数为机器
ID,

第三参数为
u-boot

传递给内核参数存放在内存中的首地址,此处是
0x30000100



由上述
zImage

的生成过程我们可以知道,第一阶段运行的内核映像实际就是
arch/arm/boot/compressed/vmlinux

,而这一阶段所涉及的文件也只有三个:

arch/arm/boot/compressed/vmlinux.lds

arch/arm/boot/compressed/head.S

arch/arm/boot/compressed/misc.c



我们的分析集中在
arch/arm/boot/compressed/head.S,
适当参考
vmlinux.lds



arch/arm/boot/compressed/vmlinux
的反汇编代码可一看出,内核执行的第一个代码段位
start,

*****start


vmlinux: file format elf32-littlearm

Disassembly of section .text:

00000000 <start>:


0: e1a00000 nop (mov r0,r0)

…. …. …..

1c: e1a00000 nop (mov r0,r0)

20: ea000002 b 30 <.text+0x30>

…. …. …..

*****

保存参数


30: e1a07001 mov r7, r1

u-boot

向内传递参数分析


//
由thekernel
传递的三个参数分别保存在r0,r1,r2
中。

//
将机器ID
保存在r7


34: e3a08000 mov r8, #0 ; 0x0

//
保存
r0
,这里似乎没有太大的意义,

这里没有保存
r2
,也就是
u-boot
传递给内核参数的首地址
0x30000100
,看来
linux-2.6.14
启动时是不需要传递该参数的而是通过
struct machine_desc

include/asm-arm/mach/arch.h
)来确定,但是这个文件只是该结构的定义,真正的参数赋值在哪呢?实际上,这就是在内核移植是需要做的工作了,内核移植最主要的一个文件就是
arch/arm/mach-s3c2410/mach-fs2410.c
,通过下面的宏来实现对
machine_desc
结构体的赋值,并且在该文件中对所涉及到的函数进行了具体的代码实现,这是内核移植方面的内容,与我们的主题无关,这里不再多说。

MACHINE_START(SMDK2410, "SMDK2410")


/* @TODO: request a new identifier and switch to SMDK2410 */


.phys_ram = S3C2410_SDRAM_PA,


.phys_io = S3C2410_PA_UART,


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


.boot_params = S3C2410_SDRAM_PA + 0x100,


//

指定

u-boot

传递给内核的参数存放的位置


.map_io = smdk2410_map_io,


.init_irq = smdk2410_init_irq,


.init_machine = sdmk2410_init,


.timer = &s3c24xx_timer,


MACHINE_END


下面我们采用汇编代码来进行分析:
arch/arm/boot/compressed/head.S

.align

start: //u-boot first jump to this execute

.type start,#function

.rept 8

mov r0, r0

.endr

b 1f

.word 0x016f2818 @ Magic numbers to help the loader

.word start @ absolute load/run zImage address

.word _edata @ zImage end address

1: mov r7, r1 @ save architecture ID

mov r8, #0 @ save r0

*****

判定是否是超级用户模式


mrs r2, cpsr @ get current mode

tst r2, #3 @ not user? //
判断当前是否为超级用户权限模式

bne not_angel //
如果是超级用户权限模式,
jump to not_angel

mov r0, #0x17 @ angel_SWIreason_EnterSVC
如果是普通用户模式,则通过软中断进入超级用户权限模式

swi 0x123456 @ angel_SWI_ARM

*****

关中断


not_angel:

mrs r2, cpsr @ turn off interrupts to //
关中断

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

msr cpsr_c, r2

*****

将编译时指定的一些变量加载到相应的寄存器中


/* some architecture specific code can be inserted by the linker here, but it should preserve r7 and r8. zImage
的连接首地址为
0x0 zImage
的运行时首地址一般为
0x30008000
,当然可以不同
*/

.text

adr r0, LC0 //
读取
LC0
的当前运行时地址,应当为
zImage
的运行时起始地址
+(LC0

zImage
链接地址的首地址
(0x0)
的偏移
)

ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}//

LC0
中的变量值加载到
r1, r2, r3, r4, r5, r6, ip, sp

subs r0, r0, r1 @ calculate the delta offset //
计算当前运行地址与链接地址的偏移

beq not_relocated @ if delta is zero, we are running at the address we were linked at.

//
如果运行地址等于链接地址,则跳过重定位部分代码,否则继续执行
relocate

/* .type LC0, #object

LC0: .word LC0 @ r1

.word __bss_start @ r2

.word _end @ r3

.word zreladdr @ r4

.word _start @ r5

.word _got_start @ r6

.word _got_end @ ip

.word user_stack+4096 @ sp

LC1: .word reloc_end - reloc_start

.size LC0, . - LC0 */

/* We're running at a different address. We need to fix up various pointers:

* r5 - zImage base address

* r6 - GOT start

* ip - GOT end

GOT
(global offset table


GOT
是一个数组,存在ELF image
的数据段中,他们是一些指向objects
的指针(
通常

是数据objects).
动态连接器将重新修改那些编译时还没有确定下来地址的符号的

GOT
入口。所以说GOT
在i386
动态连接中扮演着重要的角色。*/

*****

将上面的变量进行重定位,转换为当前的运行时地址


add r5, r5, r0 //zImage
的链接时首地址重定位为运行时首地址

add r6, r6, r0 //GOT
的链接时首地址重定位为运行时首地址

add ip, ip, r0

#ifndef CONFIG_ZBOOT_ROM

/* If we're running fully PIC === CONFIG_ZBOOT_ROM = n,

* we need to fix up pointers into the BSS region.

* r2 - BSS start

* r3 - BSS end

* sp - stack pointer */

add r2, r2, r0 //__bss_start
的链接时首地址重定位为运行时首地址

add r3, r3, r0 //_end
的链接时地址重定位为运行时地址

add sp, sp, r0 //user_stack+4096
的链接时地址重定位为运行时地址

/* Relocate all entries in the GOT table.

重定位
GOT
中的所有链接地址为当前运行时地址
*/

1: ldr r1, [r6, #0] @ relocate entries in the GOT

add r1, r1, r0 @ table. This fixes up the

str r1, [r6], #4 @ C references.

cmp r6, ip

blo 1b

#else

/* Relocate entries in the GOT table. We only relocate

* the entries that are outside the (relocated) BSS region.

重定位
GOT
中的所有链接地址为当前运行时地址但是不重定位

BSS_START

BSS_END
部分
*/

1: ldr r1, [r6, #0] @ relocate entries in the GOT

cmp r1, r2 @ entry < bss_start ||

cmphs r3, r1 @ _end < entry

addlo r1, r1, r0 @ table. This fixes up the

str r1, [r6], #4 @ C references.

cmp r6, ip

blo 1b

#endif

*****

重定位已经完成,清零

BSS




not_relocated: mov r0, #0

1: str r0, [r2], #4 @ clear bss

str r0, [r2], #4

str r0, [r2], #4

str r0, [r2], #4

cmp r2, r3

blo 1b

*****

准备进入

C

程序的相关设置,开启

cache,

设置一些指针


/* The C runtime environment should now be setup sufficiently. Turn the cache on, set up some pointers, and start decompressing. */

cache on
是一个相当复杂的过程,这里简单描述其流程,如有兴趣可参考“
Arm linux
启动第一阶段
cache on
分析”

bl cache_on -------

call_cache_fn----
---


通过查表
proc_types
调用
__armv4_cache_on

---------------------------

__setup_mmu

__setup_mmu: sub r3, r4, #16384 //4k ,r3=0x30004000


@ Page directory size //16384=16kB=0x4000


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


bic r3, r3, #0x3f00


/* Initialise the page tables, turning on the cacheable and bufferable bits for the RAM area only. */


mov r0, r3


mov r8, r0, lsr #18 r8=0x30004000>>18=0xc00


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


//r8=0xc00<<18=0x30000000


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


//r9=0x40000000


mov r1, #0x12


orr r1, r1, #3 << 10 //r1=0xc00|0x12=0xc12


add r2, r3, #16384 //r2=0x30004000+0x4000=0x30008000


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


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


cmp r1, r9 @ if virt > end of RAM


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


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






//r0=0x300040000 ,r1=0xc12


//0x12

表明这是一个段描述符即

bit4



bit1



1




//0xc00



bit11



bit10



11

,即

AP=11,

允许所有读写访问


add r1, r1, #1048576 //1048576=0x100000


teq r0, r2


bne 1b


//

上面代码段实现

0x00000000 ~0x40000000



1GB

)地址空间也表的创建,映射的目的地址也是

0x00000000~0x40000000,

所以没有实际意义,也许是为了打开

cache

以及设置访问属性。


mov r0, #0

mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
清零

mcr p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
清零

mrc p15, 0, r0, c1, c0, 0 @ read control reg
清零

orr r0, r0, #0x5000 @ I-cache enable, RR cache replacement

orr r0, r0, #0x0030

bl __common_cache_on------->

__common_cache_on:

#ifndef DEBUG

orr r0, r0, #0x000d @ Write buffer, mmu

#endif

mov r1, #-1

mcr p15, 0, r3, c2, c0, 0 @ load page table pointer

//
将页表基址写入页表基址寄存器
cp15 c2

mcr p15, 0, r1, c3, c0, 0

//
设置域访问控制寄存器,写入
0xffffffff ,
与控制位为
11
,也就是允许权限检查,可以访问。

mcr p15, 0, r0, c1, c0, 0 @ load control register

mov pc, lr

mov r0, #0

mcr p15, 0, r0, c8, c7, 0 @ flush I,D TLBs

mov pc, r12

返回,
cache on
结束。

//
这里的
r1,r2
之间的空间为解压缩内核程序所使用,也是传递给
decompress_kernel
的第二和第三的参数

mov r1, sp @ malloc space above stack //

SP
的运行时地址存入
r0

add r2, sp, #0x10000 @ 64k max //r2=sp+0x10000

*****

解压缩内核

,

分三种情况,请看下篇...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: