三、第三部分
3.1board_init_f函数详解
首先我们还是要明确调用此函数前,一些关键量的值:如gd>malloc_base=0X0091FB00,这个也就是 early malloc的起始地址,其它在下图有标记:
_main中会 调用 board_init_f函数, board_init_f函数主要有两个工作:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化 gd的各个成员变量, uboot会将自己重定位到 DRAM最后面的地址区域,也就是将自己拷贝到DRAM最后面的内存区域中。这么做的目的是给 Linux腾出空间,防止 Linux kernel覆盖掉 uboot,将 DRAM前面的区域完整的空出来。在拷贝之前肯定要给 uboot各部分分配好内存位置和大小,比如 gd应该存放到哪个位置, malloc内存池应该存放到哪个位置等等。这些信息都保存在 gd的成员变量中,因此要对 gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot的时候就会用到这个内存“分配图”。
我们来截取一些重点源码来分析:
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
在初始化两个变量之后,重点来了:通过函数 initcall_run_list来运行初始化序列 init_sequence_f里面的一系列函数, init_sequence_f里面包含了一系列的初始化函数, init_sequence_f也是定义在文件common/board_f.c ,为了紧贴主线,我们重点了解跟gd有关的几行代码,其他初始化外设的代码我们只用知道他在这个函数里面完成即可。
setup_mon_len,
initf_malloc,
init_baud_rate, /* initialze baudrate settings */
setup_dest_addr,
reserve_round_4k
reserve_mmu,
reserve_uboot,
reserve_malloc,
reserve_board,
reserve_global_data,
reserve_stacks,
setup_dram_config,
setup_reloc,
setup_mon_len函数设置 gd的 mon_len成员变量,此处为 __bss_end -_start,也就是整个代码的长度。 这就要用到我们之前定义过的,我们将表格复制过来,计算__bss_end -_start=0x9A194,这个就是代码长度。
initf_malloc函数初始化gd中跟malloc有关的成员变量,比如 malloc_limit,此函数会设置 gd>malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0X400。 malloc_limit表示 malloc内存池大小。这其实就是我们上节设置的early malloc大小
init_baud_rate函数用于初始化波特率,根据环境变量baudrate来初始化 gd->baudrate。
setup_dest_addr函数,设置目的地址,设置 gd->ram_size gd->ram_top gd->relocaddr这三个的值。(因为博主的网线没到,所以两个操作系统切来切去太麻烦,这里偷个小懒,将这三个值直接复制文档的值了,以后有机会补上。)
gd->ram_size = 0X20000000 //ram大小为 0X20000000=512MB
gd->ram_top = 0XA0000000 //ram最高地址为 0X80000000+0X20000000=0XA0000000
gd->relocaddr = 0XA0000000 //重定位后最高地址为 0XA0000000
reserve_round_4k函数用于对 gd->relocaddr做4KB对齐,因为gd->relocaddr=0XA0000000,已经是4K对齐了,所以调整后不变。
reserve_mmu,留出 MMU的 TLB表的位置,分配 MMU的 TLB表内存以后会对 gd->relocaddr做 64K字节对齐。完成以后 gd->arch.tlb_size、 gd->arch.tlb_addr和 gd->relocaddr为:
gd->arch.tlb_size= 0X4000 //MMU的 TLB表大小
gd->arch.tlb_addr=0X9FFF0000 //MMU的 TLB表起始地址, ,64KB对齐以后
gd->relocaddr=0X9FFF0000 //relocaddr地址
到目前为止,这些变量的值都在最后正点的图可以直观看到。
reserve_uboot函数留出重定位后的 uboot所占用的内存区域, uboot所占用大小由gd->mon_len所指定,留出uboot的空间以后还要对gd->relocaddr做4K字节对齐,并且重新设置gd>start_addr_sp ,但这里的值和正点的图肯定就开始不一样了,因为每个人编译出来的代码长度即这个mon_len不一定一样啊,所以这个图只给大家一个清晰的分配流程参考而已。因为3.2节会用到,所以我们算出gd->relocaddr=0x9FF55E6C。
reserve_malloc函数留出 malloc区域,调整 gd->start_addr_sp位置, malloc区域由宏
TOTAL_MALLOC_LEN定义,这里的大小和图中一致TOTAL_MALLOC_LEN=0X1002000=16MB+8KB
reserve_board函数,留出板子 bd所占的内存区, bd是结构体 bd_t bd_t大小为80字节,结果如图
reserve_global_data函数,保留出 gd_t的内存区域, gd_t结构体大小为 248B。
reserve_stacks函数留出栈空间,先对 gd->start_addr_sp减去 16,然后做 16字节对齐。如果使能
IRQ的话还要留出 IRQ相应的内存,在本uboot中并没有使用到 IRQ,所以不会留出 IRQ相应的内存区域
setup_dram_config函数设置 dram信息,就是设置 gd->bd->bi_dram[0].start和gd->bd->bi_dram[0].size,后面会传递给 linux内核,告诉 linux DRAM的起始地址和大小。
setup_reloc,设置 gd的其他一些成员变量,供后面重定位的时候使用,并且将以前的gd拷贝到 gd->new_gd处。需要使能
变量 | 数值 | 描述 |
__image_copy_start | 0x87800000 | uboot拷贝的首地址 |
__image_copy_end | 0x8784f1a4 | uboot拷贝的结束地址 |
__rel_dyn_start | 0x8784f1a4 | .rel.dyn段起始地址 |
__rel_dyn_end | 0x8785794c | .rel.dyn段结束地址 |
_image_binary_end | 0x8785794c | 镜像结束地址 |
__bss_start | 0x8784f1a4 | .bss段起始地址 |
__bss_end | 0x8789a194 | .bss段结束地址 |
综上才完成了内存分配。
3.2relocate_code 函数详解
relocate_code 函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S 中,首先我们还是先不厌其烦地查看调用前的准备工作,r0 = gd->relocaddr=0x9FF55E6C
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
这里r4=r0-r1=0x9FF55E6C-0X87800000=18755E6C,保存偏移量。如果偏移量等于 0,说明 r0和 r1相等,也就是源地址和目的地址是一样的,那肯定就不需要拷贝了!执行 relocate_done函数
r2=__image_copy_end,r2中保存拷贝之前的代码结束地址,由表知,__image_copy_end =0x8784f1a4。ok,接下来我们就要进循环了,当r1值不等于代码结束地址时,每次从原来的地方取两个4字节的值到现在地方,地址自增。
ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 /* r4 <- relocation offset */
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop
........
........
relocate_done:
接下来就是重定位 .rel.dyn段,.rel.dyn段是存放.text段中需要重定位地址的集合。重定位就是 uboot将自身拷贝到 DRAM的另一个地放去继续运行 (DRAM的高地址处 )。我们知道,一个可执行的 bin文件,其链接地址和运行地址要相等,也就是链接到哪个地址,在运行之前就要拷贝到哪个地址去。现在我们重定位以后,运行地址就和链接地址不同了。这里我拿正点原子的例子和大家一起理解。
board_init函数会调用 rel_test rel_test会调用全局变量 rel_a并进行赋值。
第 12行是 borad_init调用 rel_test函数,用到了 bl指令,而 bl指令是位置无关指令,bl指令是相对寻址的 (pc+offset),因此 uboot中函数调用是与绝对位置无关的。好,现在我们开始看rel_test函数,我们要调用全局变量,第 2行设置 r3的值为 pc+12地址处的
值,因为因为 ARM流水线的原因(三步骤,取指、译码、执行),所以pc寄存器的值为当前地址 +8,pc=0X87804184+8=0X8780418C
r3=0X8780418C+12=0X87804198(大家一定要注意这是16进制计算,12也就是C)
0X87804198处的值为0X8785DA50。根据第 17行可知, 0X8785DA50正是变量 rel_a的地址,最终 r3=0X8785DA50,然后才进行赋值。
函数rel_test对变量 rel_a的访问没有直接进行,而是使用了一个第三方偏移地址 0X87804198,专业术语叫做 Label。这个第三方偏移地址就是实现重定位后运行不会出错的重要原因!
uboot重定位后偏移为 0x18755E6C,那么重定位后函数 rel_test的首地址就是0X87804184+0X18755E6C=0X9FF59FF0。保存变量 rel_a地址的 Label就是0X9FF59FF0+8+12=0X9FF5A004,变量 rel_a的地址就为0X8785DA50+0X18755E6C=0X9FFB38BC。重定位后函数 rel_test要想正常访问变量 rel_a就得
设置 0X9FF5A004(重定位后的 Label)地址出的值为 0X9FFB38BC重定位后的变量 rel_a地址 )。
这样就解决了重定位后链接地址和运行地址不一致 的问题。大家一定要掏出计算机一起算一下,你会顿悟的。
先来看一下.rel.dyn段的格式,类似第7行和第8行这样的是一组,也就是两个 4字节数据为一组。高4字节是Label地址标识 0X17,低4字节就是Label的地址,首先判断 Label地址标识是否正确,也就是判断高4字节是否为 0X17,如果是的话低4字节就是 Label地址值。
.rel.dyn段的重定位代码如下:
/*
* fix .rel.dyn relocations
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop
r2=__rel_dyn_start,也就是 .rel.dyn段的起始地址。r3=__rel_dyn_end,也就是 .rel.dyn段的终止地址。从 .rel.dyn段起始地址开始,每次读取两个4字节的数据存放到 r0和 r1寄存器。r0存放低4字节的数据,也就是Label地址;r1存放高 4字节的数据,也就是 Label标志。r1中给的值与0xff进行与运算,其实就是取 r1的低 8位。判断r1中的值是否等于23(0X17)。如果 r1不等于 23的话就说明不是描述 Label的,执行函数 fixnext,否则的话继续执行下面的代码。
r0保存着Label值,r4保存着重定位后的地址偏移,r0+r4就得到了重定位后的Label值。此时r0保存着重定位后的 Label值,读取重定位后Label所保存的变量地址,此时这个变量地址还是重定位前的(rel_a重定位前的地址),将得到的值放到 r1寄存器中。r1+r4即可得到重定位后的变量地址,相当于rel_a重定位后地址值,重定位后的变量地址写入到重定位后的 Label中。比较 r2和 r3,查看 .rel.dyn段重定位是否完成。如果 r2和 r3不相等,说明 .rel.dyn重定位还未完成,因此跳到 fixloop继续重定位 .rel.dyn段。
3.3relocate_vectors函数详解
函数relocate_vectors用于重定位向量表,此函数定义在文件 relocate.S中, 函数源码如下:
这个较为简单,就是把中断向量表定位到新的u-boot首地址下,就不多叙述了。
#ifdef CONFIG_CPU_V7M
/*
* On ARMv7-M we only have to write the new vector address
* to VTOR register.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
ldr r1, =V7M_SCB_BASE
str r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR
/*
* If the ARM processor has the security extensions,
* use VBAR to relocate the exception vectors.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
#else
/*
* Copy the relocated exception vectors to the
* correct address
* CP15 c1 V bit gives us the location of the vectors:
* 0x00000000 or 0xFFFF0000.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
ands r2, r2, #(1 << 13)
ldreq r1, =0x00000000 /* If V=0 */
ldrne r1, =0xFFFF0000 /* If V=1 */
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
#endif
#endif
bx lr
ENDPROC(relocate_vectors)
接下来的内容有些杂,就不再往下记录了,做为初学者我认为已经够了,我们下期再见doge。这里还是得佩服写文档的人,太牛了,还是跟大佬有很大的差距。