uboot就是将start.o和大量的built-in.o链接在一起。
built-in.o好像是把所有子目录下的.o文件进行链接到一起。
链接脚本为u-boot.lds ,uboot链接首地址为0x87800000,裸机的时候也是-Ttest 来执行链接首地址
查找一下这个链接的地址
grep -nR "87800000"
在mx6_common.h文件中设置
通过uboot-lds可以看到入口地址为_start
1)设置CPU为管理模式
2)关看门狗
3)关中断
4)设置时钟频率
5)关mmu,初始化各个bank
6)进入board_init_f()函数
初始化DDR,定时器,初始化波特率串口,打印前面暂存在缓冲区的数据 此时sp和gd是存放在DDR上了。而不是内部的RAM。
uboot会将自己重定位到 DRAM最后面的地址区域,也就是将自己拷贝到 DRAM最后面的内存区域中。这么做的目的是给Linux腾出空间,防止 Linux kernel覆盖掉 uboot,将 DRAM前面的区域完整的空出来。在拷贝之前肯定要给uboot各部分分配好内存位置和大小,比如 gd应该存放到哪个位置,malloc内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对 gd的这些成员变量做初始化。最终形成 一个完整的内存“分配图”,在后面重定位uboot的时候就会用到这个内存“分配图”。
(7)重定位
relocate_code代码重定位函数,负责将uboot拷贝到新的地方,完成代码拷贝。
8)清bss
9)跳转到board_init_r()函数,启动流程结束。
前面board_init_f函数里面会调用一系列的函数来初始化一些外设和gd的成员变量。但是
board_init_f并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数
board_init_r(这里的外设更多包括EMMC,NANDFLASH)来完成的,存放于common/board_r.c。
最后运行的是run_main_loop,主循环,处理命令。
从这里开始就是:打断倒计时执行uboot命令要么进入内核的操作。
uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下按下回车键,那么就会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux内 核,这个功能就是由run_main_loop函数来完成的。
A:如果自然结束:run_command_list去执行bootcmd的命令,保存着默认的启动命令,因此linux启动
B:循环处理输入的命令
bootcmd和bootargs的区别
bootcmd: 这个参数包含了一些命令,这些命令将在u-boot进入主循环后执行 示例:
bootcmd=boot_logo;nand read 10000003c0000 300000;bootm 1000000
意思是启动u-boot后,执行boot_logo显示logo信息,然后从nand flash中读内核映像到内存,然后启动内核。bootargs 这个参数设置要传递给内核的信息,主要用来告诉内核分区信息和根文件系统所在的分区。 示例:
root=/dev/mtdblock5 rootfstype=jffs2console=ttyS0,115200 mem=35M
mtdparts=nand.0:3840k(u-boot),4096k(kernel),123136k(filesystem)
对于重定位:
一般片内ROM会根据程序的头部会有一个链接地址,然后程序
通过链接脚本 把源地址 目的地址,长度得到在进行重定位,通过链接脚本得到(至少得到代码段有多长)
什么是位置无关码?什么是绝对跳转指令以及相对跳转指令
通过lds的链接脚本确定链接地址
SECTIONS {
. = 0x80100000;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
data_load_addr = .;
.data 0x900000 : AT(data_load_addr)
{
data_start = . ;
*(.data)
data_end = . ;
}
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
__bss_end = .;
}
.text
.global _start
_start:
/* 设置栈 */
ldr sp,=0x80200000
/* 重定位text, rodata, data段 */
bl copy_data
/* 清除bss段 */
bl clean_bss
/* 跳转到主函数 */
// bl main /* 相对跳转,程序仍在DDR3内存中执行 */
ldr pc, =main /* 绝对跳转,程序在片内RAM中执行 */
halt:
b halt
实现的从链接脚本中读取的地址
/**********************************************************************
* 函数名称: copy_data
* 功能描述: 将整个程序(.text, .rodata, .data)从DDR3重定位到片内RAM
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -------------------------------------------------
* 2020/02/20 V1.0 阿和 创建
***********************************************************************/
void copy_data (void)
{
/* 从链接脚本中获得参数 _start, __bss_start, */
extern int _load_addr, _start, __bss_start;
volatile unsigned int *dest = (volatile unsigned int *)&_start; //_start = 0x900000
volatile unsigned int *end = (volatile unsigned int *)&__bss_start; //__bss_start = 0x9xxxxx
volatile unsigned int *src = (volatile unsigned int *)&_load_addr; //_load_addr = 0x80100000
/* 重定位数据 */
while (dest < end)
{
*dest++ = *src++;
}
}
/**********************************************************************
* 函数名称: clean_bss
* 功能描述: 清除.bss段
* 输入参数: start, end
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -------------------------------------------------
* 2020/02/20 V1.0 阿和 创建
***********************************************************************/
void clean_bss(void)
{
/* 从lds文件中获得 __bss_start, __bss_end */
extern int __bss_end, __bss_start;
volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
volatile unsigned int *end = (volatile unsigned int *)&__bss_end;
while (start <= end)
{
*start++ = 0;
}
}
1、uboot分析之编译体验
-
1、解压缩
-
2、打补丁
通过patch进行打补丁
-p1表示忽略第一个目录
-
3、配置
make 100ask24x0_config -
4、编译
make
uboot的终极目的就是启动内核:
1、从Flash读出内核,放到SDRAM内存中
2、启动内核
因此uboot要实现的功能
1、能读Flash + 为了开发方面提供写Flash功能(通过网络[网卡] 或者串口[USB] 烧写内核)
2、初始化SDRAM(在此之前需要关看门狗、初始化时钟、初始化串口),能够写SDRAM;从Flash中读取内核到SDRAM中
3、启动内核
分析源码的架构最好的方法就是通过Makefile文件去分析。
二、uboot分析之Makefile结构分析
我们需要
- 3、配置
make 100ask24x0_config - 4、编译
make
执行make 100ask24x0_config命令的时候,通过关键字找到,相当于执行下面这一行。
找去找一下MKCONFIG,在makefile中有定义
表示在源码树下的mkconfig
因此,我们配置的时候,实际上执行了这样的一个脚本命令。($@表示替换,结果为:100ask24x0)
$#表示参数的个数
$1表示第一个参数
‘>’ 表示新建一个文件
‘>>’ 表示追加
可以看到确实有config.mk有导入的内容
同时config.h也有
重点部分:
make的时候如果不指定目标,就回去执行第一个目标all
通过make编译后看完整的过程 ,
分析可以得到:arm-linux-ld链接命令:
1、链接脚本 u-boot.lds是在最前面的
2、其次是start.o
3、再是各种lib库
链接脚本0x00000000会加上 0x33F80000,会从这个地址开始排放代码段等等
可以看到整个内存段的排放。
1、第一个文件cpu/arm920t/start.S。
2、链接地址
board/100ask24x0/config.mk我们猜测是不是这个TEXT_BASE?
我们看一下之前Makefile中链接脚本中定义的LDFLAGS
在uboot根目录下的config.mk可以看到
其中TEXT_BASE是在board/100ask24x0/config.mk下定义的
对内存的上面空出512K的大小,如果你的uboot大小超过了512K你也可以修改它的大小,之前前面也提到过uboot一般放在内存的最上方,当linux内核加载的时候后面就会覆盖uboot的代码。
3、uboot分析之源码第一阶段
在裸机的时候启动步骤
1、初始化
- 关看门口
- 初始时钟
- 初始化SDRAM
2、把程序从NAND->SDRAM
3、设置栈(也就是把sp指向某块内存 )
因此uboot在上面基础上再调用c函数从Flash读出内核,再启动内核。
具体步骤如下(第一阶段的全部是硬件部分的初始化)
-
进入reset
-
进入SVC32系统模式
-
关看门狗
-
关中断
-
进入初始化
关flash,清cache
关MMU,初始化存储控制器,初始化后内存控制器才可以使用 -
再设置栈
通过框起来的代码后,内存布局就是这样,最上面是uboot的代码,底下存放的自己实现的malloc的区域以及IRQ、FIQ和SP指针指向的地方,各有各的用处。
栈设置好了后才能去调用C函数 -
接着block_init初始化时钟
-
重定位:把代码从Flash拷贝到SDRAM中,读到SDRAM哪里去呢,读到链接地址中去
-
清BSS段
-
调用start_armboot(C函数),从这个函数开始就是第二阶段了(第二阶段有一些拓展的开发功能例如:烧写Flash、网卡、USB、串口、启动内核等等)
4、uboot分析之源码第二阶段
我们以
1、从Flash读出内核
2、启动
为目的来看。
- 进入start_armboot
分配一个gd结构体存放在在128k的CFG_GBL_DATA_SIZE内存中
- 通过函数指针进行一系列的初始化
cpu初始化,单板初始化。。。
- 在单板初始化中会初始化一些管脚,同时还有机器ID以及启动参数的设置、nor nand的初始化
下一节中我们讲解内核的时候会关注这两个参数为什么会有。再是NORflash初始化:NANDflash可以直接读写(比如我们往0地址写1234如果读出来是1234那就是NAND,读出来还是0的话就是NORflash),而NorFlash的写的时候我们就要配置它,我们因为会有一些拓展的功能就是重新烧写Flash所以我需要对NorFlash读写进行初始化
再是nand flash初始化,还有malloc初始化
现在CPU具备可以读可以写的能力了。
再是环境变量的初始化:
以下内容就是环境变量
环境变量会先去Flash上去找,如果没有设置的话就会去默认的环境变量去看。
网卡、设备、USB的初始化
- 就这就到了一个main_loop死循环的地方
以上的步骤我们知道通过start_armboot:
执行了flash_init
nand_init一些硬件设备的初始化
到达main_loop的循环中。
在main_loop可看到一些环境变量比如:bootdelay
通过获取bootcmd环境变量得到启动命令
后面一段代码是,如果在倒数计时在到达0之前,没有输入空格键。就去打印booting linux并且执行run_command,bootcmd其实就是两个参数,回到我们这一节最开始的地方。
1、我们想要做的就是从flash读出内核也就是对应bootcmd的第一个指令
nand read.jffs2 0x30007FC0 kernel;
读到0x30007FC0(为什么是这个值我们后面再进行分析) 从kernel分区进行读;
2、怎么启动:通过bootm 0x30007FC0
前面已经从NANDflash把内核读到内存中来,启动内核。
因此内核的启动依赖于bootcmd命令
如果我们按了空格,就会跳到下面的代码中:
因此可以分为两类:
1、启动内核
s = getenv(“bootcmd”)
run_command(s,…)
2、u-boot界面
readline(读入串口的数据)
run_command
5、uboot启动内核
1、我们想要做的就是从flash读出内核也就是对应bootcmd的第一个指令
nand read.jffs2 0x30007FC0 kernel;
读到0x30007FC0(为什么是这个值我们后面再进行分析) 从kernel分区进行读;
- kernel分区是什么?
因为用的是Flash没有分区,我们只能通过软件写死分区 ,固定大小区间
所以从NANDFlash中读,那从哪里读呢就是从kernel中读,然后读到SDRAM中
其中kernel就是代表其实地址,当然我们用
效果是一样的,这个就代表从从起始地址0x00060000 ,长度为0x00200000,和图中kenel分区的偏移+大小是一致的。nand read.jffs2 0x30007FC0 0x00060000 0x00200000;
用jffs2是因为这个文件格式不需要页对齐。
2、怎么启动:通过bootm 0x30007FC0
前面已经从NANDflash把内核读到内存中来,启动内核->调用do_bootm。
U-boot在Flash上存的内核叫:uImage
uImage就是一个头部加上一个真正的内核。
这就是头部的结构体
ih_load表示加载地址,表示内核要放在哪里
ih_ep表示入口地址,运行内核的时候直接跳到这个地址就可以了
你可以随便放内存里面,只要不破坏uboot在内存最顶部存放的信息,因为uImage的头部已经包含了加载地址以及入口地址的信息
do_bootm函数做的事情
读出头部
把数据拷贝到加载地址中去
我们真正的加载地址是0x30008000那为什么bootm的地址是0x30007fc0
两个相减后得到的结果是64,所以大小是64字节,刚好是64字节的头部。
启动就是调用do_bootm_linux
那do_bootm_linux也需要做一些事情:
(1) uboot告诉内核一些参数->设置启动参数
在某个地址0x30000100,按某种格式保存数据,这个格式成为TAG
setup_start_tag
setup_memory_tag
setup_commandline_tag
setup_end_tag
第一个setup_start_tag函数:
第二个setup_memory_tag:
此时的内存布局,接下来就是params = tag_next(params);
到了第三个setup_commandline_tag:
来自于commandline
把bootargs的参数传入给内核,其中:
root = 表示根文件系统他位于flash的第四个分区
init = 表示第一个运行的应用程序
console表示内核的打印信息,从串口0打印出来
第四个setup_end_tag
设置0,0
至此整个TAG就填写完了,保存好了后,内核就回到这个地址来读取这些参数
启动的时候带了3个参数,其中后面两个参数在第4节中,我们就提到过,这里刚好对应上了,可以看到启动参数的启动地址也就是0x30000100。同时还要和内核比对机器ID是否匹配。
(2) 跳到入口地址启动内核,调用theKernel