linux 重定位arm,Arm linxu启动过程分析(一)

本文着重分析 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

uid-30617037-id-5567816.html

我们的分析集中在 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 :

0: e1a00000       nop                (mov r0,r0)

….          ….           …..

1c: e1a00000       nop                (mov r0,r0)

20: ea000002       b     30

….          ….           …..

***** 保存参数

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

uid-30617037-id-5567816.html

//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

***** 解压缩内核 , 分三种情况,请看下篇...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值