uboot前转
uboot 2种编译方法
(1)编译复杂项目,Makefile提供两种编译管理方法,默认情况下是当前文件夹中的.c文件编译出来的.o文件会放在同一文件夹下。这种方式就叫做原地编译。原地编译的好处就是处理起来简单。
(2)原地编译有一些坏处:1.污染了源文件目录,原来源文件里面只有.c .s文件,现在被污染了,有了很多.o文件,所以就会出现编译完了之后忘了清理,把它打包,就会特别大。2.一套源代码只能按照一种配置和编译方法就行方法,无法同时维护2个或2个以上的配置编译方法。我有一套源代码,但是我们公司可能有3款产品,这三块产品可能用的同一源代码来弄的,配置方法不一样,比如说一个高配,一个中配,一个低配。你就有三种不同的配置和方法。我们原地编译的话只能先清理掉前面的然后在编译,很麻烦
(3)为了解决以上缺陷,我们uboot支持单独输出文件夹方式的编译(linux kernel也支持,而且uboot的这种技术就是从Linux kernel移植过来的)。基本思路就是在编译的时候另外指定一个输出目录将来所有的编译生成的.o文件和生成的其他文件全部丢到那个输出目录下面去。我们源代码目录不做任何污染,这样的话输出目录就承载了本次编译配置的所有结果。比如说你对高版本的进行了源代码的修改,你只需要编译输出到原来高版本的目录就可以了。
(4)具体用法有两种:默认的就是原地编译。如果需要指定具体的输出目录编译有2种方式来指定输出目录。
第一种是 make O=输出目录
第二种先export KBUILD_SRC=后面接的输出目录
如果两个都指定了(既有KBUILD_SRC)环境变量,然后在make的时候接了 O=xx。则O=xxx具有更高的优先级
代码实现如下
第一行就是 如果O在命令行中定义,则将O赋值给KBUILD_OUTPUT
范例
请看README
当我要编译ARM架构的时候输入这三行命令 我想输出到output目录下
make O=output distclean
make O=output s5p_goni_defconfig
make O=output all ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
便可以完成
在我们include下面有个autoconfig.mk,这个文件不是源码提供的,是配置过程自动生成的
这个文件的作用就是用来指导我们这个uboot的编译过程,这个文件的内容其实就是很多config_开头的一个宏,这些宏会影响我们uboot编译过程的走向(原理就是条件编译)。在uboot代码中有很多地方使用条件编译进行编译的
这个文件不是凭空产生的,配置过程也是需要原材料来产生的,原材料在我们源码目录的include的configs/xxx.h头文件,这个h头文件里面全都是宏定义,这些宏定义就是我们对我们当前开发板的移植。每一个开发板的移植都对应这个目录下的一个头文件,这个头文件里面的配置项就是我们开发板里面的配置项,每一个宏定义都很重要,这个配置的宏定义就是我们移植uboot的关键所在。我们很大一部分工作就在配置这个头文件
这里-Ttext是链接地址,在uboot里面使用了 -Ttext和-Tlds
用lds这个脚本指定了一个链接,然后用-Ttext把这个脚本移到了这个地方
我们uboot.lds里面都是从0地址开始的,链接脚本来看它连接的地址好像是0地址,但是实际上用-Ttext连接到了另外一个地址,就是我们-Ttest后面的那个地址
我们目标里面有一些是重要的,譬如说我们的u-boot是我们最终编译链接生成的elf格式的可执行文件
我们最终要烧入的是u-boot.bin 它是u-boot通过objcopy工具把elf文件转变成.bin文件了
Uboot源码学习,在uboot阶段中因为有汇编阶段的参与,因此不能直接找main.c。这个程序的入口取决于链接脚本中ENTRY声明的地方。 ENTRY声明的地方就是我们整个程序的起始地方。_start符号所在的文件就是整个程序的起始文件,_start所在处的代码就是整个程序的起始代码。
uboot和内核到底是什么
uboot其实是裸机程序,uboot的本质其实是复杂的裸机程序,跟我们写的LED什么本质没什么区别。
Linux内核其实也是个”裸机程序”,和我们Uboot和我们其他的裸机程序没有本质区别。区别就是我们操作系统在运行起来之后在软件上分为内核层和我们的应用层,分层后,两层的权限不同,内存访问和设备操作的管理上更加精细。我们内核可以随便访问各种硬件,而应用程序只能被限制的访问硬件和内存地址。
直观来看,uboot的镜像是u-boot.bin,我们Linux系统的镜像是zImage,这两个其实都是两个裸机程序镜像。上面是从系统启动的角度来讲
部署在SD卡的中特定分区内
一个完整的软件+硬件的嵌入式系统,静止时(未上电时)bootloader,kernel,rootfs等必须的软件都已镜像的形式存储在启动介质中,运行的时候都是在我们DDR内存中运行的,与存储介质无关,上面两个状态是稳定状态,稳定状态就是你一年不运行它还是那个样子,你运行了几个小时他还是那个样子。第三种状态是个动态过程,即从静止态到运行态的过程,也就是启动过程
动态启动过程就是从SD卡等介质逐步搬移到DDR内存,并且运行启动代码进行相关硬件的初始化和软件架构的建立,最终达到我们运行的时候的稳定状态。
静止时我们的Uboot.bin和内核的zImage,都在我们SD卡中,它们不可能存在任意位置,所以我们需要对这些介质进行一个分区,然后将各种镜像,各自存在各自的分区中,这样我们在启动过程中,我们Uboot,内核等就知道到哪里去找谁。
运行的时候必须加载到DDR中的链接地址处
uboot在第一阶段中进行重定位时将第二阶段(整个Uboot镜像)加载到DDR的0xc3e00000地址处,这个地址就是Uboot的链接地址
内核也有类似的要求,uboot启动内核时,将内核从SD卡读取放到DDR中,其实就是个重定位的过程,不能随意放,必须放到内核的链接地址处,不然你是起不来的。譬如说我们用的内核链接地址是0x30008000。
内核启动需要参数,但是我们的Uboot是无条件启动的,从0开始启动的,内核是不能开机从0开始完全自动启动的,内核启动要别人帮忙,这个别人就是Uboot,这个Uboot要帮助内核实现重定位,要从SD卡到DDR,因为内核一开始就在内存中运行的,所以不存在这个问题
,uboot还要给内核提供参数
在uboot中启动内核的代码是do_bootm,其实就是bootm前面加个do,很多命令都是这样的
uboot命令体系实现的代码在哪里
在我们uboot下面有个uboot/common/cmd_xxx.c,有若干个.c文件
还有command.c和main.c也和它相关的
其实每一个uboot的命令都对应了一个函数,实际上你在命令行上敲的每一个命令都对应了一个函数,这就是Uboot实现命令体系的一种思路和方法
我们要找到每一个命令背后所对应的那个函数,而且要分析这个函数和这个命令是怎么对应起来的。我们有些uboot的命令还支持传参,也就是说命令背后对应的函数接收的参数列表中有argc和argv,然后我们的命令体系会把我们执行命令时的命令和参数以argc和argv的方式传递给执行命令的函数,
main_loop里面的run_command函数就是用来执行命令的函数
我们命令的结构体是cmd_tbl_t这个结构体
uboot实现命令管理的思路
(1) 填充一个结构体实例构成一个命令
(2) 给命令结构体实例附加特定段的属性,链接的时候将带有该段属性的内容链接在一起排列。
段有个特性是我们给一个特性就是我们给一个数据附加一个段属性之后,将来在链接的时候,链接器可以把有这个段属性的东西可以简释出来,这个段属性有点像一个标签一样,链接时,链接器将带有该属性的内容链接在一起排列。
(3) uboot重定位时将该段整体加载到DDR中。加载到DDR中的Uboot的镜像中带有特定段属性的这一段其实就是命令结构体的集合,有点像一个命令结构体数组。
(4) 段的起始地址和结束地址(链接地址、定义在u-boot.lds中)决定了这些命令集的开始和结束地址。
我们可以研究一下Uboot定义一个命令是怎么定义的
U_BOOT_CMD这个宏是个关键
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
这里传了6个参数
find_cmd函数查找我们当前uboot是否支持命令,从当前的uboot的命令集中查找是否有某个命令。
如果你想在原有的基础上增加命令,可以在uboot的uboot/common/command.c中加载命令
譬如我添加如下代码
Int do_eight (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
printf(“eight love eight”)
}
U_BOOT_CMD(
eight, 1, 1, do_eight,
"eight - eight\n",
" - eight hello \n"
);
还一种比较好的办法就是自建一个C文件并且添加命令,一般叫做cmd_加你的命令名
比如我这里就叫cmd_eight.c
对应的命令名就叫eight,对应的函数就叫do_eight 所以这个一出来,其他的全部都决定了,然后在我们C文件中添加命令所对应的U_BOOT_CMD宏和函数 。其实就是加头文件罢了
如下
然后在我们uboot/common/Makefile中添加上添加cmd_eight.o,目的是让我们make编译的时候能否把cmd_eight.o编译链接进去
你在这里加上就可以了
uboot的环境变量基础
环境变量的作用:让我们不用修改uboot的源代码,而是通过我们修改环境变量来影响我们Uboot运行的一些数据和特性。譬如说通过修改bootdelay环境变量就可以影响我们系统开机自动启动时倒数的秒数。
环境变量的优先级:我们Uboot代码当中有一个值,环境变量中也有一个值,uboot实际运行时规则就是如果环境变量为空,则使用代码中的值,如果环境变量不为空则优先使用环境变量对应的值
环境变量在uboot中的工作方式:默认环境变量,在uboot/common/env_common.c中的default_enviroment。这个东西本质是一个字符数组,大小为CFG_ENV_SIZE,内容就是很多个环境变量连续分布组成的,每个环境变量最末端以”\0”结束。SD卡中的环境变量分区,在uboot的raw分区中。DDR中的环境变量,在default_enviroment中,实质是字符串
刚烧录的系统中环境变量分区是空白的,uboot第一次运行时加载的是由uboot代码中自带的一份环境变量,叫默认环境变量。我们在saveenv时这个DDR环境变量会被更新到存储介质环境变量中,下次开机时环境变量会被重定位加载到内存里面去