注:本文是对朱老师uboot和系统移植课程的备忘引导性笔记,主要是为了能够在学完后快速回忆起相关内容。本文主要记录了一些关键易忘性知识点并包含少量理解性内容,遵循尽量精简的原则,以尽量少的篇幅概括整个课程的知识点,便于后期能够快速定位知识点。故本文不包含具体的命令及函数使用方法等,同时不要太纠结于字面表达,这些只是为了能够快速回忆起相关知识点,具体的准确描述以及用法等需参考具体的文章。
(未完待续)
uboot学习前传
PC机的启动过程
(1)典型的PC机的部署:BIOS程序部署在PC机主板上(随主板出厂时已经预制了),操作系统部署在硬盘上,内存在掉电时无作用,CPU在掉电时不工作。
(2)启动过程:PC上电后先执行BIOS程序(实际上PC的BIOS就是NorFlash),BIOS程序负责初始化DDR内存,负责初始化硬盘,然后从硬盘上将OS镜像读取到DDR中,然后跳转到DDR中去执行OS直到启动(OS启动后BIOS就无用了)
典型嵌入式linux系统启动过程
(1)嵌入式系统的部署和启动都是参考PC机的。只是设备上有一些差别
(2)典型嵌入式系统的部署:uboot程序部署在Flash(能作为启动设备的Flash)上、OS部署在FLash(嵌入式系统中用Flash代替了硬盘)上、内存在掉电时无作用,CPU在掉电时不工作。
(3)启动过程:嵌入式系统上电后先执行uboot、然后uboot负责初始化DDR,初始化Flash,然后将OS从Flash中读取到DDR中,然后启动OS(OS启动后uboot就无用了)
android系统启动过程
(1)android系统的启动和linux系统(前面讲的典型的嵌入式系统启动)几乎一样。几乎一样意思就是前面完全一样,只是在内核启动后加载根文件系统后不同了。
(1)可以认为启动分为2个阶段:第一个阶段是uboot到OS启动;第二个阶段是OS启动后到rootfs加载到命令行执行;现在我们主要研究第一个阶段,android的启动和linux的差别在第二阶段。
uboot到底是干嘛的
(1)uboot主要作用是用来启动操作系统内核。
(2)uboot还要负责部署整个计算机系统。
(3)uboot中还有操作Flash等板子上硬盘的驱动。
(4)uboot还得提供一个命令行界面供人来操作。
uboot即universal bootloader(通用的启动代码)是最常用的一种bootloader,已经成为事实上的业内bootloader标准。现在大部分的嵌入式设备都会默认使用uboot来做为bootloader。
早期的uboot的版本号类似于这样:uboot1.3.4。后来版本号便成了类似于uboot-2010.06。uboot的核心部分几乎没怎么变化,越新的版本支持的开发板越多而已,对于一个老版本的芯片来说,新旧版本的uboot并没有差异
uboot必须解决哪些问题(待完善)
(1)自身可开机直接启动
- 一般的SoC都支持多种启动方式,譬如SD卡启动、NorFlash启动、NandFlash启动等·····uboot要能够开机启动,必须根据具体的SoC的启动设计来设计uboot。
- uboot必须进行和硬件相对应的代码级别的更改和移植,才能够保证可以从相应的启动介质启动。uboot中第一阶段的start.S文件中具体处理了这一块。
(2)能够引导操作系统内核启动并给内核传参
- uboot的终极目标就是启动内核。
- linux内核在设计的时候,设计为可以被传参。也就是说我们可以在uboot中事先给linux内核准备一些启动参数放在内存中特定位置然后传给内核,内核启动后会到这个特定位置去取uboot传给他的参数,然后在内核中解析这些参数,这些参数将被用来指导linux内核的启动过程。
(3)能提供系统部署功能
- uboot必须能够被人借助而完成整个系统(包括uboot、kernel、rootfs等的镜像)在Flash上的烧录下载工作。
- 裸机教程中刷机(ARM裸机第三部分)就是利用uboot中的fastboot功能将各种镜像烧录到iNand中,然后从iNand启动。
(4)能进行soc级和板级硬件管理
- uboot中实现了一部分硬件的控制能力(uboot中初始化了一部分硬件),因为uboot为了完成一些任务必须让这些硬件工作。譬如uboot要实现刷机必须能驱动iNand,譬如uboot要在刷机时LCD上显示进度条就必须能驱动LCD,譬如uboot能够通过串口提供操作界面就必须驱动串口。譬如uboot要实现网络功能就必须驱动网卡芯片。
- SoC级(譬如串口)就是SoC内部外设,板级就是SoC外面开发板上面的硬件(譬如网卡、iNand)
uboot的“生命周期”
(1)uboot本质上是一个裸机程序
(2)uboot的入口就是开机自动启动,uboot的唯一出口就是启动内核。uboot还可以执行很多别的任务(譬如烧录系统),但是其他任务执行完后都可以回到uboot的命令行继续执行uboot命令,而启动内核命令一旦执行就回不来了。
环境变量如何参与程序运行
(1)环境变量有2份,一份在Flash中,另一份在DDR中。uboot开机时一次性从Flash中读取全部环境变量到DDR中作为环境变量的初始化值,然后使用过程中都是用DDR中这一份,用户可以用saveenv指令将DDR中的环境变量重新写入Flash中去更新Flash中环境变量。下次开机时又会从Flash中再读一次。
(2)环境变量在uboot中是用字符串表示的,也就是说uboot是按照字符匹配的方式来区分各个环境变量的。
uboot阶段Flash的分区
在一个移植中必须事先将分区方法设计好定死 ,定的标准是:
(1)uboot:uboot必须从Flash起始地址开始存放(也许是扇区0,也许是扇区1,也许是其他,取决于SoC的启动设计),uboot分区的大小必须保证uboot肯定能放下,一般设计为512KB或者1MB(因为一般uboot肯定不足512KB,给再大其实也可以工作,但是浪费);
(2)环境变量:环境变量分区一般紧贴着uboot来存放,大小为32KB或者更多一点。
(3)kernel:kernel可以紧贴环境变量存放,大小一般为3MB或5MB或其他。
(4)rootfs:······
(5)剩下的就是自由分区,一般kernel启动后将自由分区挂载到rootfs下使用各分区彼此相连,前面一个分区的结尾就是后一个分区的开头。uboot必须在Flash开头,其他分区相对位置是可变的。分区在系统移植前确定好,在uboot中和kernel中使用同一个分区表。将来在系统部署时和系统代码中的分区方法也必须一样。
补基础之shell和Makefile
shell部分
shell是一类编程语言
(1)编写shell脚本时使用的语言就是shell语言,又叫脚本语言。
(2)shell脚本其实是一类语言而不是一个语言。
常用shell语言:sh、bash、csh、ksh、perl、python等
(1)在linux下常用的脚本语言其实就是bash、sh;
(2)perl、python这样的高级shell脚本语言,常用在网络管理配置等领域,系统运维人员一般要学习这些。
shell脚本的运行机制:解释运行
shell程序运行的三种方法:
(1)./xx.sh,这样运行shell要求shell程序必须具有可执行权限。chmod a+x xx.sh来添加可执行权限。
(2)source xx.sh,source是linux的一个命令,这个命令就是用来执行脚本程序的。这样运行不需要脚本具有可执行权限。
(3)bash xx.sh,bash是一个脚本程序解释器,本质上是一个可执行程序。这样执行相当于我们执行了bash程序,然后把xx.sh作为argv[1]传给他运行。
shell程序模板
#!/bin/sh #指定shell程序执行时被/bin/sh解释器解释执行 #在ubuntu下还可以写成[ #!/bin/bash ] 或 [ #!/bin/dash ] #下面是代码
Makefile部分
目标、依赖、命令
(1)目标就是我们要去make xxx的那个xxx,就是我们最终要生成的东西。
(2)依赖是用来生成目录的原材料
(3)命令就是加工方法,所以make xxx的过程其实就是使用命令将依赖加工成目标的过程。
通配符%和Makefile自动推导(规则)
(1)%是Makefile中的通配符,代表一个或几个字母。也就是说%.o就代表所有以.o为结尾的文件。
(2)所谓自动推导其实就是Makefile的规则。当Makefile需要某一个目标时,他会把这个目标去套规则说明,一旦套上了某个规则说明,则Makefile会试图寻找这个规则中的依赖,如果能找到则会执行这个规则用依赖生成目标。
Makefile中定义和使用变量
(1)Makefile中定义和使用变量,和shell脚本中非常相似。相似是说:都没有变量类型,直接定义使用,引用变量时用$var
伪目标(.PHONY)
(1)伪目标意思是这个目标本身不代表一个文件,执行这个目标不是为了得到某个文件或东西,而是单纯为了执行这个目标下面的命令。
(2)伪目标一般都没有依赖,因为执行伪目标就是为了执行目标下面的命令。既然一定要执行命令了那就不必加依赖,因为不加依赖意思就是无条件执行。
(3)伪目标可以直接写,不影响使用;但是有时候为了明确声明这个目标是伪目标会在伪目标的前面用.PHONY来明确声明它是伪目标。
Makefile的文件名
(1)Makefile的文件名合法的一般有2个:Makefile或者makefile
Makfile中引用其他Makefile(include指令)
(1)有时候Makefile总体比较复杂,因此分成好几个Makefile来写。然后在主Makefile中引用其他的,用include指令来引用。引用的效果也是原地展开,和C语言中的头文件包含非常相似。
Makefile中的注释用#
(1)Makefile中注释使用#,和shell一样
静默执行
(1)在makefile的命令行中前面的@表示静默执行。
(2)Makefile中默认情况下在执行一行命令前会先把这行命令给打印出来,然后再执行这行命令。
(3)如果你不想看到命令本身,只想看到命令执行就静默执行即可。
Makefile中几种变量赋值运算符
(1)= 最简单的赋值
(2):= 一般也是赋值
以上这两个大部分情况下效果是一样的,但是有时候不一样。
用=赋值的变量,在被解析时他的值取决于最后一次赋值时的值,所以你看变量引用的值时不能只往前面看,还要往后面看。
用:=来赋值的,则是就地直接解析,只用往前看即可。(3)?= 如果变量前面并没有赋值过则执行这条赋值,如果前面已经赋值过了则本行被忽略。(实验可以看出:所谓的没有赋值过其实就是这个变量没有被定义过)
(4)+= 用来给一个已经赋值的变量接续赋值,意思就是把这次的值加到原来的值的后面,有点类似于strcat。(在shell makefile等文件中,可以认为所有变量都是字符串,+=就相当于给字符串stcat接续内容)(注意一个细节,+=续接的内容和原来的内容之间会自动加一个空格隔开)注意:Makefile中并不要求赋值运算符两边一定要有空格或者无空格,这一点比shell的格式要求要松一些。
Makefile的环境变量
(1)makefile中用export导出的就是环境变量。一般情况下要求环境变量名用大写,普通变量名用小写。
(2)环境变量和普通变量不同,可以这样理解:环境变量类似于整个工程中所有Makefile之间可以共享的全局变量,而普通变量只是当前本Makefile中使用的局部变量。所以要注意:定义了一个环境变量会影响到工程中别的Makefile文件,因此要小心。
(3)Makefile中可能有一些环境变量可能是makefile本身自己定义的内部的环境变量或者是当前的执行环境提供的环境变量(譬如我们在make执行时给makefile传参。make CC=arm-linux-gcc,其实就是给当前Makefile传了一个环境变量CC,值是arm-linux-gcc。我们在make时给makefile传的环境变量值优先级最高的,可以覆盖makefile中的赋值)。这就好像C语言中编译器预定义的宏__LINE__ __FUNCTION__等一样。
Makefile中使用通配符
(1)* 若干个任意字符
(2)? 1个任意字符
(3)[] 将[]中的字符依次去和外面的结合匹配还有个%,也是通配符,表示任意多个字符,和*很相似,但是%一般只用于规则描述中,又叫做规则通配符。
Makefile的自动变量
(1)为什么使用自动变量。在有些情况下文件集合中文件非常多,描述的时候很麻烦,所以我们Makefile就用一些特殊的符号来替代符合某种条件的文件集,这就形成了自动变量。
(2)自动变量的含义:预定义的特殊意义的符号。就类似于C语言编译器中预制的那些宏__FILE__一样。
(3)常见自动变量:
$@ 规则的目标文件名
$< 规则的依赖文件名
$^ 依赖的文件集合
零距离初体验uboot
uboot可以有3种获取途径:uboot官方、SoC官方、具体开发板的官方。
配置uboot
在uboot源码的根目录下执行:make x210_sd_config。如果出现:Configuring for x210_sd board... 则说明配置成功。
uboot的配置阶段主要解决的问题就是在可移植性方面能够帮助我们确定具体的开发板的文件夹路径等
编译uboot
编译前需确保uboot根目录下的Makefile文件中编译器的设置为正确的交叉编译工具链的路径和名字,此后make进行编译。可使用例如make -j4来进行4线程同时编译
主要文件介绍
- arm_config.mk。后缀是.mk,是一个Makefile文件,将来在某个Makefile中会去调用它。
- config.mk。和arm_config.mk差不多性质。
- COPYING。版权声明,uboot本身是GPL许可证的。
- image_split。一个脚本,看说明是用来分割uboot.bin到BL1的,暂时用不到,先不管。
- MAKEALL。一个脚本,应该是帮助编译uboot的。
- Makefile。这个很重要,是uboot源代码的主Makefile,将来整个uboot被编译时就是用这个Makefile管理编译的,所以我们在下个课程中研究uboot配置编译过程时就要分析这个Makefile。
- mk。快速编译的脚本,其实就是先清理然后配置然后编译而已。
- mkconfig。这个很重要,是uboot配置阶段的主要配置脚本。uboot的可移植性很大程度就是靠这个配置脚本在维护的。我们在下个课程中研究uboot配置编译过程时就要分析这个配置脚本。
- mkmovi。暂时不去管他,一个脚本,和iNand/SD卡启动有关
- rules.mk。这个文件是我们uboot的Makefile使用的规则,本身非常重要,但是我们不去分析他,不去看他。
目录分析
- api。 硬件无关的功能函数的API。uboot移植时基本不用管,这些函数是uboot本身使用的。
- api_examples。 API相关的测试事例代码。
- board。 board是板的意思,板就是开发板。board文件夹下每一个文件都代表一个开发板,这个文件夹下面放的文件就是用来描述这一个开发板的信息的。board目录下有多少个文件夹,就表示当前这个uboot已经被移植到多少个开发板上了(当前的uboot支持多少个开发板)。
- common。common是普遍的普通的,这个文件夹下放的是一些与具体硬件无关的普遍适用的一些代码。譬如控制台实现、crc校验的。但是更多的主要是两类:一类是cmd开头的,是用来实现uboot的命令系统的;另一类是env开头的,是用来实现环境变量的。
- cpu。这个目录是SoC相关的,里面存放的代码都是SoC相关初始化和控制代码(譬如CPU的、中断的、串口等SoC内部外设的,包括起始代码start.S也在这里)。里面很多子文件夹,每一个子文件夹就是一个SoC系列。
注意:这个问价是严格和硬件相关的,因此移植时也是要注意的。但是因为这个文件夹内都是SoC有关的,我们自己的开发板和三星的开发板虽然板子设计不同但是SoC都是同一个,因此实际移植时这个目录几乎不用动。- disk。磁盘有关的,没研究过,没用过。
- doc。文档目录,里面存放了很多uboot相关文档,这些文档可以帮助我们理解uboot代码。但是因为是纯英文的,而且很杂乱,所以几乎没用。
- drivers。顾名思义,驱动。这里面放的就是从linux源代码中扣出来的原封不动的linux设备驱动,主要是开发板上必须用到的一些驱动,如网卡驱动、Inand/SD卡、NandFlash等的驱动。要知道:uboot中的驱动其实就是linux中的驱动,uboot在一定程度上移植了linux的驱动给自己用。但是linux是操作系统而uboot只是个裸机程序,因此这种移植会有不同,让我说:uboot中的驱动其实是linux中的驱动的一部分。
- examples。示例代码,没用过。
- fs。filesystem,文件系统。这个也是从linux源代码中移植过来的,用来管理Flash等资源。
- include。头文件目录。uboot和linux kernel在管理头文件时都采用了同一个思路,就是把所有的头文件全部集中存放在include目录下,而不是头文件跟着自己对应的c文件。所以在uboot中头文件包含时路径结构要在这里去找。
- lib_开头的一坨。(典型的lib_arm和lib_generic)架构相关的库文件。譬如lib_arm里面就是arm架构使用的一些库文件。lib_generic里是所有架构通用的库文件。这类文件夹中的内容移植时基本不用管。
- libfdt。设备树有关的。linux内核在3.4左右的版本的时候更改了启动传参的机制,改用设备树来进行启动传参,进行硬件信息的描述了。
- nand_spl。nand相关的,不讲。
- net。网络相关的代码,譬如uboot中的tftp nfs ping命令 都是在这里实现的。
- onenand开头的,是onenand相关的代码,是三星加的,标准uboot中应该是没有的。
- sd_fusing。这里面代码实现了烧录uboot镜像到SD卡的代码。后面要仔细研究的。
- tools。里面是一些工具类的代码。譬如mkimage。
uboot配置和编译过程详解
Makefile中重要的变量
- U_BOOT_VERSION。uboot版本号
- VERSION_FILE。存储uboot版本号的头文件的pathname
- HOSTARCH。主机的CPU架构
- HOSTOS。主机的操作系统
- BUILD_DIR。编译生成文件的目录
- OBJTREE。编译出的.o文件存放的目录的根目录
- SRCTREE。源代码的根目录,即uboot源码根目录
- TOPDIR。uboot源码根目录
- MKCONFIG。uboot配置阶段的配置脚本的pathname
- ARCH。arm
- CPU。s5pc11x
- BOARD。x210
- VENDOR。samsung
- SOC。s5pc110
- CROSS_COMPILE。交叉编译工具链的前缀
- TEXT_BASE。整个uboot链接时指定的链接地址
$(obj)include/version_autogenerated.h(make时生成)
#define U_BOOT_VERSION "U-Boot 1.3.4"
$(obj)include/config.mk(配置时生成)
ARCH = arm CPU = s5pc11x BOARD = x210 VENDOR = samsung SOC = s5pc110
include/config.h(配置时生成)
#include <configs/x210_sd.h>
开发板配置项目文件:$(OBJTREE)/include/autoconf.mk(make时生成)
CONFIG_CMD_FAT=y CONFIG_USB_OHCI=y CONFIG_SYS_CLK_FREQ=24000000 CONFIG_CMD_ITEST=y CONFIG_S3C_HSMMC=y CONFIG_DISPLAY_BOARDINFO=y ... ... ...
$(obj)board/samsung/x210/config.mk(配置时生成)
TEXT_BASE = 0xc3e00000
uboot配置编译过程及其文件
配置阶段
执行make x210_sd_config
主Makefile中:
x210_sd_config是主Makefile中的一个目标,会调用$(SRCTREE)/mkconfig配置脚本并传递这6个参数:$1:x210_sd、$2:arm、$3:s5pc11x、$4:x210、$5:samsumg、$6:s5pc110。
生成$(obj)board/samsung/x210/config.mk文件并写入TEXT_BASE = 0xc3e00000
$(SRCTREE)/mkconfig配置脚中:
根据传入的参数创建对应的符号链接,具体到一个开发板的编译时用符号连接的方式提供一个具体的名字的文件夹供编译时使用。
创建include/config.mk文件,其内容为:
ARCH = arm
CPU = s5pc11x
BOARD = x210
VENDOR = samsung
SOC = s5pc110
创建include/config.h文件,其内容为:#include <configs/x210_sd.h>
make阶段
创建文件$(obj)include/version_autogenerated.h,其内容为:#define U_BOOT_VERSION "U-Boot 1.3.4"
创建文件$(OBJTREE)/include/autoconf.mk,其内部是一些CONFIG_开头的宏(可以理解为变量),原材料在include/config.h头文件,这些宏/变量会影响我们是否对某些文件进行编译,用来指导整个uboot的编译过程。
...
Makefile主要做了哪些事情
- uboot版本号的确定,存储在变量U_BOOT_VERSION中
- 存储uboot版本号的头文件的确定,存储在变量VERSION_FILE中
- 主机的CPU架构的确定,存储在变量HOSTARCH中
- 主机的操作系统的确定,存储在变量HOSTOS中
- 根据参数,是否静默编译
- 原地编译还是指定目录编译并导出TOPDIR、SRCTREE、OBJTREE、MKCONFIG(存储了配置uboot时所使用的脚本的pathname)
- 包含配置生成的$(obj)include/config.mk文件,文件中包含变量ARCH、CPU、BOARD、VENDOR、SOC。并导出这几个变量。
- 根据ARCH确定交叉编译工具链的前缀,存储在变量CROSS_COMPILE中
- 编译工具定义
- 包含开发板配置项目文件autoconf.mk(配置时上生成)
- 包含各种板级配置文件
- 确定连接脚本
- 自动推到规则
- 主目标all
- 配置目标x210_sd_config
uboot的链接脚本
(1)ENTRY(_start)用来指定整个程序的入口地址。所谓入口地址就是整个程序的开头地址,可以认为就是整个程序的第一句指令。有点像C语言中的main。
(2)之前在裸机中告诉大家,指定程序的链接地址有2种方法:一种是在Makefile中ld的flags用-Ttext 0x20000000来指定;第二种是在链接脚本的SECTIONS开头用.=0x20000000来指定。两种都可以实现相同效果。其实,这两种技巧是可以共同配合使用的,也就是说既在链接脚本中指定也在ld flags中用-Ttext来指定。两个都指定以后以-Ttext指定的为准。
(3)uboot的最终链接起始地址就是在Makefile中用-Ttext 来指定的,具体参见2.4.5.2节,注意TEXT_BASE变量。最终来源是Makefile中配置对应的命令中,在make xxx_config时得到的。
(4)在代码段中注意文件排列的顺序。指定必须放在前面部分的那些文件就是那些必须安排在前16KB内的文件,这些文件中的函数在前16KB会被调用。在后面第二部分(16KB之后)中调用的程序,前后顺序就无所谓了。
(5)链接脚本中除了.text .data .rodata .bss段等编译工具自带的段之外,编译工具还允许我们自定义段。譬如uboot总的.u_boot_cmd段就是自定义段。
uboot源码分析1-启动第一阶段
uboot源码分析2-启动第二阶段
由宏观分析来讲,uboot的第二阶段就是要初始化剩下的还没被初始化的硬件。主要是SoC外部硬件(譬如iNand、网卡芯片····)、uboot本身的一些东西(uboot的命令、环境变量等····)。然后最终初始化完必要的东西后进入uboot的命令行准备接受命令。
uboot源码分析3-uboot如何启动内核
操作系统运行起来后在软件上分为内核层和应用层,分层后两层的权限不同,内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能被限制的访问硬件和内存地址)。
运行时必须先加载到DDR中链接地址处
(1)uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR的0xc3e00000地址处,这个地址就是uboot的链接地址。
(2)内核也有类似要求,uboot启动内核时将内存从SD卡读取放到DDR中(其实就是个重定位的过程),不能随意放置,必须放在内核的链接地址处,否则启动不起来。譬如我们使用的内核链接地址是0x30008000。
内核启动需要必要的启动参数
(1)uboot是无条件启动的,从零开始启动的。
(2)内核是不能开机自动完全从零开始启动的,内核启动要别人帮忙。uboot要帮助内核实现重定位(从SD卡到DDR),uboot还要给内核提供启动参数。
uboot要启动内核,分为2个步骤:第一步是将内核镜像从启动介质中加载到DDR中,第二步是去DDR中启动内核镜像。(内核代码根本就没考虑重定位,因为内核知道会有uboot之类的把自己加载到DDR中链接地址处的,所以内核直接就是从链接地址处开始运行的)
静态内核镜像在