BootLoader有很多种,u-boot是BootLoader中的其中一种,在嵌入式Linux中我们使用u-boot。最终的目的是启动内核。
PC | 嵌入式 |
---|---|
BIOS | BootLoader |
引导操作系统Windows | Linux内核 |
识别C,D盘 | 挂载根文件系统 |
运行应用程序QQ,微信等 | 应用程序 |
表中的作用是一一对应的。BootLoader的最终目的是启动内核。
uboot要实现的功能:
硬件相关初始化(初始化时钟、关看门狗)
- 从flash中读出内核
- 能够初始化sdram
- 启动内核
- 其他(开发功能):烧写flash、网卡、USB、串口
可以理解uboot为一个单片机程序。
首先把uboot下载到nor flash,启动程序。
启动之后再倒数计时的时候,按空格键,进入uboot,不然会直接启动Linux系统。进入uboot的命令行,如图所示:
当使用命令make 100ask24x0_config进行配置的时候,就相当于执行如下命令:
对于MKCONFIG:
MKCONFIG := $(SRCTREE)/mkconfig
表示在SRCTREE目录下mkconfig文件。
对于$()表示目标:则他们可替换为
所以在这行make 100ask24x0_config命令的时候,就相当于执行上面蓝色部分的那句命令。
# 传入:
# mkconfig 100ask24x0 arm arm920t 100ask240 NULL s3c24x0
# $0 $1 $2 $3 $4 $5 $6
APPEND=no # Default: Create new config file
BOARD_NAME="" # Name to print in make output
# 个部分是判断传入的文件有灭有--、-a等等分析
while [ $# -gt 0 ] ; do
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
*) break ;;
esac
done
# 判断BOARD_NAME是否已经定义,最开始BOARD_NAME="",所以执行BOARD_NAME="$1",BOARD_NAME = 100ask24x0
[ "${BOARD_NAME}" ] || BOARD_NAME="$1"
# $#表示出入参数的个数,当传入小于4个或者大于6个也会退出
[ $# -lt 4 ] && exit 1
[ $# -gt 6 ] && exit 1
# 打印
echo "Configuring for ${BOARD_NAME} board..."
#
# Create link to architecture specific headers
#
# 判断"$SRCTREE" != "$OBJTREE" 是否相等,在Makefile中有这个参数,但没有人去定义,所以这个部分不成立,会执行else部分
if [ "$SRCTREE" != "$OBJTREE" ] ; then
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/include/asm-$2 asm
LNPREFIX="../../include2/asm/"
cd ../include
rm -rf asm-$2
rm -f asm
mkdir asm-$2
ln -s asm-$2 asm
else
cd ./include
rm -f asm
ln -s asm-$2 asm # 建立一个连接文件:ln -s asm-arm asm ,asm指向asm-arm
# 例如在源码的时候,我们使用#include <asm/type.h>就相当于#include <asm-arm/type.h>
fi
rm -f asm-$2/arch
## -z 在if里的意思是空,这里是表示第六个参数为空或者为NULL的话,就会执行,这里不成立,会执行else分支
if [ -z "$6" -o "$6" = "NULL" ] ; then
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
ln -s ${LNPREFIX}arch-$6 asm-$2/arch # LNPREFIX没有定义,为空,所以:ln -s arch-s3c24x0 asm-arm/arch
# 在asm-arm/arch中建立指向 arch-s3c24x0
fi
if [ "$2" = "arm" ] ; then
rm -f asm-$2/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc # 建立连接文件:ln -s proc-armv asm-arm/proc
fi
#
# Create include file for Make
#
# 创建一个新的配置文件,> 表示建立,>>表示追加内容
echo "ARCH = $2" > config.mk
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
# 内容:
# ARCH = arm
# CPU = arm920t
# BOARD = 100ask240
# 如果第五个参数存在,并且第五个参数不等于NULL的话
# 再往config.mk中增加一项内容,但是这里的$5为NULL.
[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
#
[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
# 再往config.mk中加入:SOC = s3c24x0
# Create board specific header file
# 创建一个单板相关的头文件
# 这里是不等于yes的所以,这些else分支,创建一个config.h文件
if [ "$APPEND" = "yes" ] # Append to existing config file
then
echo >> config.h
else
> config.h # Create new config file
fi
# 把信息追加到config.h文件中
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h # #include<configs/100ask24x0.h>
exit 0
当我们编译的时候,执行执行make命令,所以还是要对Makefile进行分析。
# 包含了以上生成的文件
include $(OBJTREE)/include/config.mk
# config.mk
# ARCH = arm
# CPU = arm920t
# BOARD = 100ask240
# SOC = s3c24x0
OBJS = cpu/$(CPU)/start.o
# OBJS = cpu/arm920t/start.o
LIBS = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
# LIBS += board/100ask240/lib100ask240.a
# LIBS += cpu/arm920t/libarm920t.a
这里还有很多lib文件,这里表示把所涉及的文件编译好之后打包成.a文件。
# 这里的目的是想生成一个u-boot.bin
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
# 我们在执行make的时候如果不指定目标,那么这里的all就会使用以上ALL的定义
all: $(ALL)
$(obj)u-boot.hex: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
# 生成u-boot.bin它依赖于u-boot(els)格式的
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(obj)u-boot.img: $(obj)u-boot.bin
./tools/mkimage -A $(ARCH) -T firmware -C none \
-a $(TEXT_BASE) -e 0 \
-n $(shell sed -n -e 's/.*U_BOOT_VERSION//p' $(VERSION_FILE) | \
sed -e 's/"[ ]*$$/ for $(BOARD) board"/') \
-d $< $@
$(obj)u-boot.dis: $(obj)u-boot
$(OBJDUMP) -d $< > $@
# 而u-boot(els)格式依赖于以下,对于这个部分,相对复杂,如果想查看,可以使用make进行编译
# 然后再编译的结尾会生成这些过程
$(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
-Map u-boot.map -o u-boot
例如,通过编译的输出来看:
对于这些材料是如何组织成u-boot的:
可以看放在最前面的那一个,u-boot.lds这个是一个链接脚本:
定义u-boot存放的位置,这样可以直接修改TEXT_BASE的地址就可以直接修改u-boot的存放位置。
当前地址0会加上0x33f80000这个地址,表示后面要存放的代码和变量要存放在这个地址后面,并且在uboot运行的开始地址是0x33f80000,因为这个单板有64M SDRAM,如果使用这个地址的话,从0x33f80000到最高的地址有512k,如果uboot更大的话,可以把地址往下调,以至于下载其他文件的时候不覆盖掉uboot,如图:
存放的数据顺序为:
同链接脚本来看,最闲开始执行的是start.s文件
总结:通过Makefile得到的知识
第一阶段一般用汇编写的。
以上分析过,u-boot的启动是从cpu/arm920t/start.S开始的,所以先分析start.S文件。
以上这些都是硬件相关的初始化。这些是u-boot的第一阶段,第二阶段是从第9个调用C函数开始,这些要实现一些串口、网卡、USB、烧写Flash等一些设备的初始化。
重定位部分:
目标:
从源码上来看,经过一系列的初始化以后,进入一个main_loop进行死循环。
u-boot的核心是run_command,主要是命令。
主要是run_command的实现,对于这个函数它会接收在从串口发送过来的命令,然后这行命令是保存在结构体当中的,当接收到命令后悔保存在一个结构体当中,然后与u-boot中定义的命令结构体进行对比,如果相同,就会调用相应的函数进行处理,这个结构体包含着命令的名字和回调的函数等。
flash上存的内核:UImage(包括头部64字节+真正的内核)
从nand flash中读出内核的时候放到哪里?
地址是可以随便选择的,只要不覆盖uboot的代码。如下图所示,在这块开发板sdram有64M。只要不破坏如下的参数:0x33f80000往上是用来存储uboot的,往下就是存放uboot传递给linux内核的参数。
nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000); // 从分区中把内核写入到sdram,这个分区的起始地址是0x60000,复制到0x30008000,大小为0x200000(2M)
bootm的作用:
读取头部
移动内核到加载地址
启动(do_bootm_linux)
do_bootm_linux:uboot告诉内核一些参数,设置启动参数, 跳到启动入口地址,启动内核theKernel
启动内核bootm要做的事:
分区:我们电脑都会有分区,但对于嵌入式来说是没有分区的,所以在嵌入式中,对于分区,直接在源码中写死了,用一段内存来表示分区。所以我们要关心的不是分区的名字,而是分区的区域。
总结:启动过程