免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1458 | 回复: 0
打印 上一主题 下一主题

arm-linux源码分析之解压内核映像 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-02-09 09:45 |只看该作者 |倒序浏览
linux-2.6.20.6/arch/arm/boot/compressed/head.S
  
开头有一段宏定义,我们只看其中一段,分析一下gnu arm汇编的宏定义
  
#elif defined(CONFIG_ARCH_S3C2410)
              .macro loadsp, rb
              mov \rb, #0x50000000
              add  \rb, \rb, #0x4000 * CONFIG_S3C2410_LOWLEVEL_UART_PORT
              .endm
#else
这里定义了一个宏,宏名是loadsp,rb是这个宏的参数。宏的参数在被引用时必须加”\”,如:
mov \rb, #0x50000000.
  
宏定义结束之后定义了一个段,
              .section ".start", #alloc, #execinstr
  
这个段的段名是 .start,#alloc表示Section contains allocated data, #execinstr表示Section contains executable instructions.
/*
* sort out different calling conventions
*/
              .align
start:
              .type       start,#function /*.type指定start这个符号是函数类型*/
              .rept 8
              mov r0, r0 //将此命令重复8次,相当于nop,这里为什么这样做还不清楚??
              .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, r2                    @ save atags pointer
r1和r2中分别存放着由bootloader传递过来的architecture ID和指向标记列表的指针。这里将这两个参数先保存。
  
  
  
  
  
#ifndef __ARM_ARCH_2__
              /*
               * Booting from Angel - need to enter SVC mode and disable
               * FIQs/IRQs (numeric definitions from angel arm.h source).
               * We only do this if we were in user mode on entry.
               */
读取cpsr并判断是否处理器处于supervisor模式——从u-boot进入kernel,系统已经处于SVC32模式;而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入
  
Angel 是 ARM 的调试协议,现在用的 MULTI-ICE 用的是 RDI 通讯协议, ANGLE 需要在板子上有 驻留程序,然后通过 串口就可以调试了
  
  
这里介绍一下半主机.
  
半主机是用于 ARM 目标的一种机制,可将来自应用程序代码的输入/输出请求
传送至运行调试器的主机。 例如,使用此机制可以启用 C 库中的函数,如
printf() 和 scanf(),来使用主机的屏幕和键盘,而不是在目标系统上配备屏幕和
键盘。
  
半主机是通过一组定义好的软件指令(如 swi)来实现的,这些指令通过程序控
制生成异常。 应用程序调用相应的半主机调用,然后调试代理处理该异常。 调
试代理提供与主机之间的必需通信。
  
              mrs  r2, cpsr          @ get current mode
              tst    r2, #3                    @ not user?
              bne  not_angel
  
下面两行实现了在主机和 ARM 目标之间启用调试 I/O 功能,
  
              mov r0, #0x17              @ angel_SWIreason_EnterSVC
              swi  0x123456              @ angel_SWI_ARM
0x17是angel_SWIreason_EnterSVC半主机操作,将处理器设置为超级用户模式,通过设置新 CPSR 中的两个中断掩码位来禁用所有中断。0x123456是arm指令集的半主机操作编号
  
not_angel:  //不是通过angel调试进入内核
              mrs  r2, cpsr          @ turn off interrupts to
              orr   r2, r2, #0xc0         @ prevent angel from running
              msr  cpsr_c, r2   //这里将cpsr中I、F位分别置“1”,关闭IRQ和FIQ
#else
              teqp pc, #0x0c000003          @ turn off interrupts
常用 TEQP PC,#(新模式编号) 来改变模式
#endif
  
  
  
  
链接器会把一些处理器相关的代码链接到这个位置,也就是arch/arm/boot/compressed/head-xxx.S文件中的代码。在那个文件里会对I/D cache以及MMU进行一些操作
  
/*
               * Note that some cache flushing and other stuff may
               * be needed here - is there an Angel SWI call for this?
               */
  
              /*
               * some architecture specific code can be inserted
               * by the linker here, but it should preserve r7, r8, and r9.
               */
  
              .text
              adr   r0, LC0 //当前运行时LC0符号所在地址位置
              ldmia       r0, {r1, r2, r3, r4, r5, r6, ip, sp}
              subs r0, r0, r1        @ calculate the delta offset //这里获得当前运行地址与链接地址
                                          @ if delta is zero, we are   //的偏移量,存入r0中。
              beq  not_relocated         @ running at the address we
                                          @ were linked at.
  
上面这几行代码用于判断代码是否已经重定位到内存中,LC0这个符号在288行定义。
              .type       LC0, #object
LC0:              .word      LC0               @ r1 //这个要加载到r1中的LC0是链接时LC0的地址
              .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
通过当前运行时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
               */
              add  r5, r5, r0 //修改内核映像基地址
              add  r6, r6, r0
              add  ip, ip, r0 //修改got表的起始和结束位置
  
#ifndef CONFIG_ZBOOT_ROM
              /*若没有定义CONFIG_ZBOOT_ROM,此时运行的是完全位置无关代码
位置无关代码,也就是不能有绝对地址寻址。所以为了保持相对地址正确,
需要将bss段以及堆栈的地址都进行调整
               * 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
              add  r3, r3, r0
              add  sp, sp, r0
  
              /*
               * Relocate all entries in the GOT table.
               */
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 //若定义了CONFIG_ZBOOT_ROM,只对got表中在bss段以外的符号进行重定位
//为什么要这样做呢??我也不清楚
              /*
               * Relocate entries in the GOT table.  We only relocate
               * the entries that are outside the (relocated) BSS region.
               */
1:            ldr   r1, [r6, #0]            @ relocate entries in the GOT
              cmp r1, r2                    @ entry  start of RAM
              orrhs       r1, r1, #0x0c         @ set cacheable, bufferable
              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
  
上面这段就是对一级描述符表(页表)的初始化,首先比较这个描述符所描述的地址是否在那个256M的空间中,如果在则这个描述符对应的内存区域是cacheable ,bufferable。如果不在则noncacheable, nonbufferable.然后将描述符写入一个一级描述符表的入口,并将一级描述符表入口地址加4,而指向下一个1M section的基地址。如果页表入口未初始化完,则继续初始化。
  
一级描述符表的高12位是每个setcion的基地址,可以描述4096个section。一级页表大小为16K,每个页表项,即描述符占4字节,刚好可以容纳4096个描述符,所以这里就映射了4096*1M = 4G的空间。
  
/*
* If ever we are running from Flash, then we surely want the cache
* to be enabled also for our execution instance...  We map 2MB of it
* so there is no map overlap problem for up to 1 MB compressed kernel.
* If the execution is in RAM then we would only be duplicating the above.
*/
              mov r1, #0x1e
              orr   r1, r1, #3 = r2 -> OK
*   r4 + image length  OK
*/
              cmp r4, r2
              bhs  wont_overwrite
              sub  r3, sp, r5        @ > compressed kernel size
              add  r0, r4, r3, lsl #2      @ allow for 4x expansion
              cmp r0, r5
              bls   wont_overwrite
  
这段代码首先在堆栈上确定了64K的malloc空间,空间的起始地址和结束地址分别存放在r1、r2中。然后判断最终内核地址,也就是解压后内核的起始地址,是否大于malloc空间的结束地址,如果大于就跳到wont_overwrite执行,wont_overwrite函数后面会讲到。否则,检查最终内核地址加解压后内核大小,也就是解压后内核的结束地址,是否小于现在未解压内核映像的起始地址。小于也会跳到wont_owerwrite执行。如两这两个条件都不满足,则继续往下执行。
  
  
              mov r5, r2                    @ decompress after malloc space
              mov r0, r5
              mov r3, r7
              bl     decompress_kernel
  
这里将解压后内核的起始地址设为malloc空间的结束地址。然后后把处理器id(开始时保存在r7中)保存到r3中,调用decompress_kernel开始解压内核。这个函数的四个参数分别存放在r0-r3中,它在arch/arm/boot/compressed/misc.c中定义。
  
              add  r0, r0, #127
              bic   r0, r0, #127           @ align the kernel length
/*
* r0     = decompressed kernel length
* r1-r3  = unused
* r4     = kernel execution address
* r5     = decompressed kernel start
* r6     = processor ID
* r7     = architecture ID
* r8     = atags pointer
* r9-r14 = corrupted
*/
              add  r1, r5, r0        @ end of decompressed kernel
              adr   r2, reloc_start
              ldr   r3, LC1
              add  r3, r2, r3
1:            ldmia       r2!, {r9 - r14}              @ copy relocation code
              stmia       r1!, {r9 - r14}
              ldmia       r2!, {r9 - r14}
              stmia       r1!, {r9 - r14}
              cmp r2, r3
              blo   1b
这里首先计算出解压后内核的大小,然后对它的进行重定位
  
              bl     cache_clean_flush
              add  pc, r5, r0        @ call relocation code
重定位结束后跳到解压后内核的起始处开始执行,在运行解压后内核之前,先调用了
cache_clean_flush这个函数。这个函数的定义在第700行
  
cache_clean_flush:
              mov r3, #16
              b     call_cache_fn
其实这里又调用了call_cache_fn这个函数,注意,这里r3的值为16,call_cache_fn这个函数在前面有讲解,下面看看当r3为16时会调用到哪个函数,回到proc_types这个对像的定义,最终找到处理器相关的处理代码在603行开始
  
              .word      0x00020000           @ ARMv4T
              .word      0x000f0000
              b     __armv4_mmu_cache_on
              b     __armv4_mmu_cache_off
              b     __armv4_mmu_cache_flush
当偏移量为16时,会跳到b       __armv4_mmu_cache_flush这条指令,调用__armv4_mmu_cache_flush这个函数,它的定义在730行
  
__armv4_mmu_cache_flush:
              mov r2, #64*1024         @ default: 32K dcache size (*2)
              mov r11, #32         @ default: 32 byte line size
              mrc p15, 0, r3, c0, c0, 1      @ read cache type
              teq   r3, r6                    @ cache ID register present?
              beq  no_cache_id
              mov r1, r3, lsr #18
              and  r1, r1, #7  //获得Dsize中的size
              mov r2, #1024
              mov r2, r2, lsl r1           @ base dcache size *2//获得dcache字节大小
              tst    r3, #1

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/63991/showart_1820135.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP