【嵌入式Linux】Uboot初步认识 笔记

整体简介

本笔记为记录嵌入式Linux的uboot部分基础知识,结合源码对uboot的实现原理和应用展开学习

授课老师:朱有鹏
时间:2017年10月1日

本part讲uboot的使用和相关概念。
后面会讲uboot的设计和组成原理,再往后就是移植过程。
我们的目的就是把ARM端的操作系统搭建起来,以后就不玩裸机了。


笔记正文

一、学习前传

1.1为什么要有uboot

uboot是用来 启动操作系统内核 的!还有别的辅助功能。
1启动操作系统内核
2部署计算机系统
3操作Flash等上硬盘驱动
4提供命令行界面

uboot类似于PC机系统中的BIOS设计。典型的嵌入式系统的部署是uboot在一块支持启动的Flash上,OS部署在Flash(通常是另一块Flash,NandFlash or iNand,慢慢会发展成一块儿)。

其中,环境变量和命令是uboot的重要部分。此外uboot对于Flash和DDR有管理功能。

启动过程
step1: 先执行uboot
step2: uboot初始化DDR,初始化Flash
step3: 将OS从Flash读取到DDR中,启动OS。启动完成后uboot就无用了。

总结:嵌入式系统启动其实和PC没两样
只是BIOS代替成uboot
硬盘替换成了Flash


1.2 uboot之发展历史

uboot经过多年发展,已经成为事实上业内的bootloader标准。

uboot的版本号问题
早期的uboot版本号类似于:

uboot1.3.4	//这个1.3.4是最后的老版本

从2008左右开始就不是这样子了,后来变成了类似于:

uboot-2010-06
(后缀带有 -rc 的是非正式版本)

核心部分就几乎没变化,越新版本支持的开发板越多。当然也不是越新越好,新的东西有很多冗余。

uboot可移植理解
universal bootloader(通用的启动代码),具有可移植性。
注意:并不是说在哪个开发板都可以随便用。而是具有在源代码级别的移植能力。如果没有移植的话就可以直接用的。代码必然是要修改的!不是下载下来就能用。

uboot的成功:时势造英雄,他的出现是一种必然。如果没有uboot也会有另一个bootloader代替。
在那个年代嵌入式疯狂的发展,很需要一种bootloader能够移植上去,启动操作系统。所以uboot站出来了


1.4 uboot工作方式

uboot是一个单线程裸机程序。一旦运行uboot就不能运行别的 程序。
入口是开机自动启动
出口是唯一出口,启动内核

内核启动之后,uboot就会结束,而且不再重生。除非下一次开机。
uboot没有运行时表现为uboot.bin,放在存储介质里,运行时被加载入内存,逐步执行。


1.5 常用命令

uboot的命令和环境变量机制几乎和OS完全一样,连题目的名称都不带变的。

命令使用行缓冲。		//linux缓冲方式还有无缓冲和全缓冲
简写命令:
	printenv 	= print	//打印环境变量
	setenv	= set	//设置环境变量
	注意:自己尝试过,pri就能自己打印出printenv的内容


命令族
	有些命令有衍生的 多个命令
	如 movi
	movi init,movi read等。命令一样,参数不一样。

环境变量和全局变量的不同
环境变量生命周期长,他存储在FLash的一块专门区域。而全局变量在程序结束后消亡。

理论上讲,环境变量在更改后,下一次开机依然保留才对,但是我试过的和老朱说的不一样。
是因为我们没有保存!命令:save/saveenv
我们操作的环境变量的是DDR中的环境变量,
DDR断电不保存,save命令是就把运行时内存中的环境变量写回Flash

注意这里的 save,会让原来的Flash中环境变量完全重写。

Flash里面有好多分区

完整Linux系统分区简表(按顺序,uboot为最开始)

序号分区名简写名
1ubootuboot
2环境变量env
3操作系统内核kernel
4根文件系统rootfs

1.9 uboot指令

指令规范
movi read {aaa | bbb} [xxxx]
//movi read 是必填,{ }里面的内容多选一,[ ]的内容是选填

uboot默认数字问题
命令行中,uboot中所有的数字都会被当做16进制处理!!
uboot中没有默认十进制的。uboot中出现数字90%的情况都是地址,是有意义的。

【movi read】
movi read u-boot 0x30000000
//将iNand中的u-boot分区读到DDR的0x30000000处,注意读的是分区
//也许uboot本身不大,但分区有1M那么这里还是读1M大小过去
//在uboot代码中,iNand被分成了很多个分区,每个分区都有独自的地址和名称。
movi read {sector#} {byte(hex)} {addr}
//sector是扇区,读某扇区多少字节,读到哪个地址。

【nand】
//完全类似movi,操作NandFlash

【内存操作指令:mm mw md】
内存是没有分区的,我们要注意防止越界,越界会踩到别人。操作系统中不容易越界,但uboot是裸机程序,因此我们使用uboot时不注意,就可能发生自己把自己数据给覆盖了的问题。
我们之前usb下载放在0x23E00000地址处就是放在了一个低地址。(也和虚拟地址映射有关)

md,memory display
mw,写内存,一般用的很少
mm,批量写内存

【启动内核指令:bootm, go】
bootm是能够传参的,他其实是正宗的启动命令

go不能传参,他本身不是为了启动内核用的,内部就是吧pc跳转到一个内存地址去执行。
go可以在uboot中执行任何的裸机程序
有一种调试裸机程序的方法就是事先启动uboot,然后再uboot中下载裸机程序 ,用go命令执行裸机程序。


1.10 uboot常用环境变量

ipaddr:开发板ip地址
serverip:tftp服务器的ip地址
gatewayip:开发板的本地网关地址
ethaddr:开发板本地网卡的MAC地址。

Tips:重装系统为什么MAC地址会变?是因为驱动重新安装了

启动内核的最关键环境变量
bootcmd(重要)自动运行命令设置
uboot启动后会倒数秒数,如果没有人按下回车键打断则会启动内核
这个 启动内核的 功能其实内部就是执行了bootcmd

查一下:

bootcmd=movi read kernel 30008000

//读iNand中内核分区到30008000
movi read rootfs 30B00000 300000; //读根文件系统
bootm 30008000 30B00000 //启动内核和根文件系统
//iNand,可以简单的看成SD卡或MMC卡芯片化

bootargs(重要)
bootargs是uboot给kernel传参用的。linux内核与uboot有约定好的参数。这样的设计是为了灵活,为了内核在不重新编译的条件下以不同方式启动。

    bootargs=console=ttySAC2,115200 	//控制台使用SAC2,即串口2,波特率115200
	root=/dev/mmcblk0p2 rw	            //rootfs使用mmc,block端口0,第二分区
										//rw表示可读可写
	init=/linuxrc 	                    //linux的进程1(init进程)的路径
	rootfstype=ext3		                //rootfs类型,ext3是一种文件系统类型。

内核传参是非常重要的,新手经常忘记和内核传参,或者传参不对,造成内核启动失败。

【新建、删除环境变量】

set var value	//新建、更改
set var			//删除

1.12.uboot中对Flash和DDR的管理

uboot对Flash和内存进行分区。

分区方法不是一定的,不是固定的,是可以变动的。但是在一个移植中必须事先设计好定死,一般在设计系统移植时就会定好,定的标准是:

uboot:uboot必须从Flash起始地址开始存放(也许是扇区0,也许是扇区1,也许是其他,取决于SoC的启动设计),uboot分区的大小必须保证uboot肯定能放下,一般设计为512KB或者1MB(因为一般uboot肯定不足512KB,给再大其实也可以工作,但是浪费);

环境变量:环境变量分区一般紧贴着uboot来存放,大小为32KB或者更多一点。

kernel:kernel可以紧贴环境变量存放,大小一般为3MB或5MB或其他。

rootfs:rootfsl可以紧贴kernel存放

再往后面:则是自由分区。

总结:一般规律如下:
(1)各分区彼此相连,前面一个分区的结尾就是后一个分区的开头。
(2)整个flash充分利用,从开头到结尾。
(3)uboot必须在Flash开头,其他分区相对位置是可变的。
(4)各分区的大小由系统移植工程师自己来定,一般定为合适大小(不能太小,太小了容易溢出;不能太大,太大了浪费空间)
(5)分区在系统移植前确定好,在uboot中和kernel中使用同一个分区表。将来在系统部署时和系统代码中的分区方法也必须一样。

12.2、uboot阶段DDR的分区

(1)DDR的分区和Flash的分区不同,因为Flash是掉电存在的,而DDR是掉电消失,因此可以说DDR是每次系统运行时才开始部署使用的。
(2)内存的分区主要是在linux内核启动起来之前,linux内核启动后内核的内存管理模块会接管整个内存空间,那时候就不用我们来管了。
(3)注意内存分区关键就在于内存中哪一块用来干什么必须分配好,以避免各个不同功能使用了同一块内存造成的互相踩踏。譬如说我们tftp 0x23E00000 zImage去下载zImage到内存的0x23E00000处就会出错,因为这个内存处实际是uboot的镜像所在。这样下载会导致下载的zImage把内存中的uboot给冲掉。


2.1.shell介绍

shell是操作系统的终端命令行
系统提供给用户操作的命令行界面,是人机交互的一种方式
shell用来干什么?
在linux下创建100个文件,分别为a1.c a2.c…a100.c。最好的做法就是把创建过程写成一 个shell脚本程序。

shell是一类编程语言,而不是一个语言。
shell语言,又叫脚本语言。常用shell语言:sh、bash、csh、ksh、perl、python等

perl、python等高级shell语言,常用在网络管理配置等领域,系统运维人员一般要学习这些。
脚本语言一般在嵌入式中应用,主要是用来做配置。
linux下最常用的脚本就是bash,我们学习也是以bash为主。

shell脚本的运行机制:解释运行
C语言(C++)这种编写过程是:编写出源代码(源代码是不能直接运行的)然后编译链接形成可执行二进制程序,然后才能运行;脚本程序编写好后源代码即可直接运行(没有编译链接过程)
所谓解释运行就是说当我们执行一个shell程序时,shell解析器会逐行的解释shell程序代码,然后一行一行的去运行。
CPU实际只认识二进制代码,根本不认识源代码。脚本程序源代码其实也不是二进制代码,CPU也不认识,也不能直接执行。【shell程序的编译链接过程不是以脚本程序源代码为单位进行的,而是在脚本运行过程中逐行的解释执行时完成二进制转化,进而无需编译链接,而是调用他。】


2.2.动手写第一个shell

编辑器		vim
编译器		无

运行方法如下
第一种:./xx.sh,这样运行shell要求shell程序必须具有可执行权限。	
第二种:source xx.sh,source是linux的执行脚本程序命令。不需要脚本具有可执行权限。
第三种:bash xx.sh,bash是一个脚本程序解释器,本质上是一个可执行程序。这样执行相当于我们执行了bash程序,然后把xx.sh作为argv[1]传给他运行。

shell程序的第一行一般都是: #!/bin/sh	
    指定shell程序执行时被哪个解释器解释执行
    可以将第一行写为:#!/bin/bash来指定使用bash执行该脚本。

注意:在ubuntu上面默认使用的默认脚本解释器sh其实不是bash,而是dash!

脚本中的注释使用#,例如“#same as ‘//’ ”,和C语言的//是一样的


2.3.shell编程学习1

shell中的变量定义和引用
shell是弱类型语言(语言中的变量如果有明确的类型则属于强类型语言)
和C语言不同。在shell编程中定义变量不需要制定类型,也没有类型这个概念。

变量定义时可以初始化,使用=进行初始化赋值。在shell中赋值的=两边是不能有空格的
注意:shell对语法非常在意,非常严格。很多地方空格都是必须没有或者必须有,而且不能随意有没有空格。

变量引用
shell中引用一个变量必须使用 符号, 符号, 符号,符号就是变量解引用符号。
注意:$符号后面跟一个字符串,这个字符串就会被当作变量去解析。如果这个字符串本身没有定义,执行时并不会报错,而是把这个变量解析为空。

注意:变量引用的时候可以 v a r ,也可以 var,也可以 var,也可以{var}。这两种的区别是在某些情况下只能用 v a r 而不能简单的 {var}而不能简单的 var而不能简单的var。 v a r 可用的情况下, var可用的情况下, var可用的情况下,{var}是肯定能用的。
什么情况下$var是不能用的呢?
eg:

var="hello"
echo "$varyou"

则打印不出东西,因为找不到varyou这个变量

正确写法:

echo "${var}you"

打印出:helloyou

变量定义、初始化

string="hello world"
echo $string

如果echo string(不加$),那么解释器就会将string当成一个新的变量,打印一个string出来。

单引号:完全字面替换 ‘23\"33’	就会打印出23\"33
双引号:
	$加变量名可以取变量的值
	\$表示$的字面值		    输出$符号
	\`表示`的字面值
	\"表示"的字面值
	\\表示\的字面值

示例				打印结果
echo new string			new string
echo 'new \"string'		new \"string
echo "new \"string"		new "string
echo "$string"			 hello world

PWD=`pwd`
echo $PWD

//打印命令pwd的返回值。返回当前路径

反引号括起来执行。有时候我们在shell中调用linux命令是为了得到这个命令的返回值(结果值),这时候就适合用一对反引号(键盘上ESC按键下面的那个按键,和~在一个按键上)来调用执行命令。

shell中的分支结构
if语法很多,只介绍重要部分。

典型if语言格式。

if [表达式]; then
	xxx
else
	xxx
fi

判断文件是否存在。(-f),注意[]里面前后都有空格,不能省略。 file
判断目录是否存在 (-d) diractory
判断字符串是否相等(“str1” = “str2”),注意用一个等号而不是两个
判断数字是否相等(-eq)、大于(-gt)、小于(-lt)、大于等于(-ge)、小于等于(-le)
while [ $# -gt 0 ] ; do
判断式中使用“-o”表示逻辑或

if [ -f a.c ]; then
	ehco yes

【注意】:[ -f a.c ]
-f前面有空格!!!
a.c后面有空格!!! 不写空格会报错

if [ 12 - eq 12 -o "abcd" = "abcd"  ]; then
	ehco "yes"
else ...
fi

逻辑&&和逻辑||在shell中的独特用法
话说&& 和 || 在shell中也有,出现在shell中简写的if表达式:没有if,只有中括号

[ -z $str ] || echo "233"

//str如果为空,就不显示233;如果不为空则显示233


2.5.shell中的循环结构

for循环

能看懂、能改即可。不要求能够完全不参考写出来

for i in 1 2 3 4 5
do
	echo $i
done

//循环打印12345。1 2 3 4 5,这里的数字是我们遍历的集合,没有边界,不需要用()、[]和{}

for i in `ls`		#当前目录文件名的集合
do
	echo $i
done

while循环

(1)和C语言的循环在逻辑上无差别。while后面的[]两边都有空格。i++的写法中有两层括号。

i=1
j=11
while [ $i -lt $j ]; do
	echo $i
	i=$(($i + 1))	#这是C语言里面的 i++,看起来有点怪异。 +两边可以有空格
done

打印信息传入一个文件

echo "start" > a.txt		//创建一个a.txt,把start写进去
i=1
j=11
while [ $i -lt $j ]; do
	echo $i >> a.txt	#追加$i的内容,写入已经存在的a.txt里面末尾去。
	i=$(($i + 1))	
done

打印1到10开始,大于8和等于4 的数

#!/bin/sh
str="deep"
str="打印出的是:"

i=0

touch test.txti
while [ $i -lt 10 -o $i -eq 10 ]; do
        if [ $i -eq 4 -o $i -gt 8 ]; then
                echo $str$i >> test.txt
        fi
        i=$(($i + 1))
done

case的用法

var=1
case var in
1) echo "1" ;;
2) echo "2" ;;
esac

bash中的传参

$#			传参个数,注意只考虑真正的参数个数。
$1 $2 $3	 参数

eg

echo $# $0 $1 $2

sh a.sh aa bb cc 执行sh :$# = 3。
$0是执行shell的应用程序名字,如bash

break跳出在shell中不是用于case的,而是跳出循环的。(因为case是不用break的)
C语言的argv是 只读的,是不可改的。而在shell中,$1是可以用shift改的。

echo $# $1
shift;
echo $# $1

上例中输入source a.sh aa bb cc
打印
3 aa
2 bb
可见shift有点像左移运算符。把shell的传参左移了一个移出去。原来的$2变成了原来的$1。


2.7 Makefile

kernel 的 本质是C语言的项目,由很多个文件组成,因此都需要Makefile的管理。
分析uboot必须对Makefile有所了解

目标、依赖、命令是Makefile中的三个最主要的成分

目标是我们要去make xxx的那个,是最后生成的东西
依赖是用来生成目标的原材料。
命令就是对原材料的加工方法。

%通配符 和 自动推导

示例:ARM裸机中的makefile

led.bin: start.o 
	//要生成bin,但当前没有led.o,看规则
	arm-linux-ld -Ttext 0x0 -o led.elf $^
	arm-linux-objcopy -O binary led.elf led.bin
	arm-linux-objdump -D led.elf > led_elf.dis
	gcc mkv210_image.c -o mkx210
	./mkx210 led.bin 210.bin

%.o : %.S
    //规则:做自动推导。	
	//%是通配符,代表一个或几个字母
	//%.o代表所有以 .o 结尾的文件
	//要%.o,就需要依赖%.S。然后就回去找%.S
	arm-linux-gcc -o $@ $< -c -nostdlib


%.o : %.c
	arm-linux-gcc -o $@ $< -c -nostdlib

clean:
	//伪目标,无依赖,无条件执行
	rm *.o *.elf *.bin *.dis mkx210 -f

Makefile知道要得到什么,需要先得到什么,他会自己一步一步去推导。还是挺聪明的。
自动推导就是把目标文件往规则上套,如果找到了就生成。

$@: 规则的目标文件名
$<: 规则的依赖文件名
$^: 依赖的文件集合

makefile 定义和使用变量
类似shell,没有变量类型,引用时候用 $var

伪目标(.PHONY)
伪目标本身不代表一个文件,而是单纯的执行命令。不生成文件或得到某个东西。
伪目标没有依赖。
为了明确声明这是伪目标,一般会在前面加一个
eg:

.PHONY 
clean	//声明伪目标
clean:
				
	rm *.o *.elf *.bin *.dis mkx210 -f

【makefile的引用】
有时候makefile总体比较复杂,因此makefile有时候 会引用其他makefile
include,和C语言一样,也是原地展开。
例如uboot中有一个:
include $(obj)include/config.mk

【makefile条件语句】

ifeq ($(xxx),var)
xxx = 1
endif

2.8 Makefile 补充学习

【注释用#】
【@命令:静默执行。】

示例:不使用静默执行

all:
	echo "helloworld"

打印信息:
echo “helloworld”
helloworld

可见:makefile是默认打印命令的。如果不想看到命令本身,只想看到执行,那么静默执行即可。

all:
	@echo "helloworld"

变量赋值运算符

(1)=		最简单的赋值	//他的值取决于最后一次赋值时的值
(2):=		一般也是赋值	//就地直接解析
以上这两个大部分情况下效果是一样的,但是有时候不一样。
用=赋值的变量,取决于最后一次赋值时的值,不能只往前面看,还要往后面看。
用:=来赋值的,则是就地直接解析,只用往前看即可。

(3)?=	如果变量未定义则执行这条赋值,若定义了则本行被忽略。【注意空值也算赋值过】
(4)+=  	用来给一个已经赋值的变量接续赋值,意思就是把这次的值加到原来的值的后面,有点类似于strcat。(在shell makefile等文件中,可以认为所有变量都是字符串,+=就相当于给字符串stcat接续内容)(注意一个细节,+=续接的内容和原来的内容之间会自动加一个空格隔开)

注意:Makefile中并不要求赋值运算符两边一定要有空格或者无空格,这一点比shell的格式要求要松一些。

重点:关于=和:=

eg:

A=ABC
B=$(A)DEF	
A=GH

all:
	echo $(B)

结果:GHDEF

说明猜测是正确的。A=ABC要看最后一次A是什么。在被解析时取决最后一次!!

【关于uboot中的makefile】
一共3000多行,真正有意义的差不多就是470行。用=赋值的变量在uboot中真不好分析。
我们能看懂就行了,但是这个=没:=安全。
对于安全的代码做到什么程度呢?其实想保留B赋值A的原始值,只需要B用:=即可

A=ABC
B:=$(A)DEF	
A=GH

all:
	echo $(B)

这种情况打印出的值就是 ABCDEF

【makefile 的环境变量】
用export导出的就是环境变量。一般情况下要求环境变量名用大写,普通变量名用小写。

环境变量	       类似于整个工程中所有Makefile之间可以共享的全局变量
普通变量	       只是当前本Makefile中使用的局部变量

注意定义环境变量可能会影响一个工程中的其他makefile,因此要小心。

有一些环境变量可能是makefile本身自己定义的内部的环境变量

这就好像C语言中编译器预定义的宏__LINE__ __FUNCTION__等一样。

有一些环境变量可能是当前的执行环境提供的环境变量
譬如我们在make执行时给makefile传参。

make CC=arm-linux-gcc

这里给当前Makefile传了一个环境变量CC,值是arm-linux-gcc。用make传参优先级最高。

CC =arm-linux-gcc		//普通变量
export CC			//导出。变成环境变量

关于make给环境变量传参


	CC =gcc
	all:
		echo $(CC)

1 make打印结果:gcc
2 make CC=arm-linux-gcc 打印结果:arm-linux-gcc (覆盖前环境变量值)


makefile常见通配符
*	若干个任意字符
?	1个任意字符
[]	将[]中的字符依次去和外面的结合匹配
%   也是通配符,表示任意多个字符,和*很相似,但是%一般只用于规则描述中,又叫做规则通配符。
all :1.c 2.c 12.c test.h
	echo *.c
	echo ?.c
	echo [12].c		#中括号内的字符依次去外面去匹配

打印
1.c 2.c 12.c
1.c 2.c
1.c 2.c

【自动变量】
自动变量的含义:预定义的特殊意义的符号。就类似于C语言编译器中预制的那些宏__FILE__一样。

文件集合中文件非常多,描述的时候很麻烦。

@ 、 @、 @<、$^ 就是典型自动变量

led.bin: start.o 
				
	arm-linux-ld -Ttext 0x0 -o led.elf $^
	#$^代表start.o
%.o : %.c
	
	arm-linux-gcc -o $@ $< -c -nostdlib

					#$@代表.o文件
					#$<代表.c文件

常见自动变量:
$@ 规则的目标文件名
$< 规则的依赖文件名
$^ 依赖的文件集合

all :1.c 2.c 12.c test.h
	echo $@
	echo $<
	echo $^

打印结果:
all
1.c
1.c 2.c 12.c test.h

【关于 < 需要注意】在目标和依赖中 < 需要注意】 在目标和依赖中 <需要注意】在目标和依赖中<代表第一个依赖
在规则中$<代表的是所有的依赖!


3.1 uboot实践

【关于tar】

tar -jxvf 	//解压
tar -cjx	//打包

bsp是板级支持包
选用的uboot是 B盘 linux/qt4.8/bsp
uboot在任意文件夹下解压,tar -jxvf qt_x210v3.tar.bz2

注意uboot和linux kernel等复杂项目都不能直接编译,需要事先配置。

进入uboot的根目录
执行:make x210_sd_config。 	//执行了就配置完了

编译得到uboot.bin
编译前要注意:
1 一定要检查arm-linux-gcc,我们用的是arm-2009q3。
2 还有注意makefile中关于交叉编译工具链的设置:
(第147行) CROSS_COMPILE = 。。。
要保证这一行的交叉编译工具路径和我们的工具链路径一致,否则不能工作
我们放置在/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi
我自己防止/usr/local/arm/arm-2009q3/bin,路径相同
以上做完之后即可编译

make
//多线程编译(4核心编译)

du -h u-boot.bin 	
//查看编译生成的uboot.bin大小,视频中是384KB

uboot分uboot官方、SoC级uboot和开发板uboot
smdkv210是三星官方制定的开发板,昂贵且庞大。

三星的uboot有68个对象,九鼎有31个,可以说九鼎包括的功能是三星的子集

gitignore 				版本管理的 
config.mk		arm_config.mk	某个makefile中调用的
COPYING				版权声明,GPL许可,即开源项目
credit				鸣谢
image_split			分割uboot到 BL1的
Makefile			!	很重要,主makefile,我们就是用这个mkfile编译的
mkconfig			!	很重要,uboot的可移植性就是通过此配置脚本维护的
rules.mk				很重要,但不学习他
./mk				快速编译:先清理再设置后编译
mkmovi				SD卡启动相关

文件夹
api.	硬件无关的api,是uboot本身使用的,移植不用管
common	【重要】硬件无关。普遍适用代码。环境变量和命令系统。
board	【重要】子文件夹非常多,每一个文件夹代表一个支持的开发板
			这么多文件夹还能找到,要归功于配置过程。配置就是定要用于哪个文件夹
		【配置】说白了配置就是确定路径,这个路径的确定过程保证了可移植。
		【历史】以前是board下存放芯片型号,后来太多了就改成了厂家目录。但为了向前兼容,	还是把以前放在外面的芯片没有动。因为挪了位置很可能就不能用了。

	smdkc110:是遗留问题。uboot中的borad下没有s5pv210是因为c110先出的,服务于手	机。c110和s5pv210有99%的相似度。

cpu		【重要】与SoC有关,初始化和控制代码
drivers	【重要】linux中抠出来 的驱动。如网卡、iNand等
		(uboot的驱动其实是linux驱动的一部分。)
sd_fusing【重要】刷写sd卡的。实现了烧录uboot镜像到SD卡。其实以前在linux下刷机时就用了。
include	【重要】头文件目录
lib_	【重要】架构相关的库文件。如lib_arm就算是arm相关的库文件。移植不用管。

libfdt	设备树相关。
		linux在3.1以前的版本使用传参的方式启动。
		3.1之后的版本采用设备树的机制进行硬件信息描述
		目前用到的芯片使用的内核版本都低于3.4,故暂时不讨论设备树,以后讲通过专题。
net		网络相关的,里面的tftp等功能实现就用这个。程序很精小,学网络相关可以看这个。

fs		也是从linux移植过来的,文件系统,管理Flash的
tools	有用的工具
nand_spl	onenand	post 略

3.6 SourceInsight,简称SI

真正的项目往往有庞大数量的文件。而且代码之间的关联非常复杂。所以读代码是问题。
sourceinsight有方便我们跳转的功能。

【创建工程】
1 首先要创建工程。New project
	工程名字和路径
	注意:工程项目文件和源代码的目录可以不一样,但是建议放在一起
	eg
	;老朱自己有一个uboot的项目,习惯在uboot下创建一个“SI_Proj”文件夹

2 新工程设置,一般不管
3 向项目中添加文件
	左边是被选的文件,右边是添加了的文件。选中整个项目的文件夹,Add Tree
	然后就添加了好多文件。总视窗右边就出现了我们添加了的文件。
4 遗留问题:SI找不到未知文件类型的文件
	比如start.s就不在其中,因为SI不认识。
	解决方法1:选项 - 文档选项,选择C源码,后缀添加*.S
	然后就能搜到了。告诉SI,*.S是一个c文件,随之当做C语言处理
	这还是欺骗SI的小技巧,但是很好使。
	解决方法2:选项 - 装入设置,装入。装入朱有鹏给的一个,自动帮我们加载了.S  .cc
5 再次添加文件
	刚刚漏过的加回来!
	项目,加入和删除项目文件。

【解析工程文件】
SI把我们的全部源代码所有符号存入数据库,查找时不是查文件而是查数据库,所以索引速度非常快。
在此之前应该预先进行解析。
菜单栏:项目,同步文件,选中至少上面2个,确定。这两个选项分别是自动解析、强制解析。
eg:解析uboot工程项目过程大约2s

【使用SI】
试着选中一个变量,很快就出来了
试着右键函数,可以"跳转到定义处"。想要回去回来点→箭头。后来慢慢就会用了。

4.1 uboot主Makefile分析

很多人自己学uboot自己看不懂是因为uboot的文件太多了,我们不得不去研究地图。
不这样的话容易在大量无关代码中迷失自己。
Makefile中的400行我们也不完全去研究清楚,我们研究makefile完了之后能对uboot的整体规划有一个大致了解。
从头开始
【uboot版本确定:24-29行】

VERSION = 1		
//主版本号
PATCHLEVEL = 3
//次版本号,补丁级别
SUBLEVEL = 4
//再次版本号
EXTRAVERSION =		//附加版本信息,我们可以自己标记自己的名字。
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
最终版本号: 1.3.4

VERSION_FILE = $(obj)include/version_autogenerated.h
	//该obj变量在后面定义。“=”的缘故
	//另,version_autogenerated.h源目录中没有。但是编译过后的uboot中有,是宏定义
	//这个宏是我们配置和编译之后的版本号

【hostarch(主机架构) 和 hostos(主机系统)】
这两个是环境变量。有点麻烦买之前没讲过。

HOSTARCH := $(shell uname -m | \
	sed -e s/i.86/i386/ \
	    -e s/sun4u/sparc64/ \...

关于 $(shell xxx)
其实和 pwd = ‘pwd’,echo $(pwd)。是一样的
echo $(shell pwd)与之等同。

“ |”在shell中是管道运算符。把 前一个的输出当做后一个的输入。
我们 uname -m //得到结果是:i686,代表硬件的体系。即CPU型号传给sed -e
sed -e是字符串替换工具

sed -e s/i.86/i386/ \

用后面的替换前面的。eg在这是将“i.86”替换为"i386"

实验:我们此时把上面一段复制,echo $(HOSTARCH),即得到i386。

HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')

uname -s 出的是“Linux”
然后转为小写:即“linux”

【静默编译silent builds,50-54】

ifeq (,$(findstring s,$(MAKEFLAGS)))
    XECHO = echo
else
    XECHO = :
endif

注意ifeq里面的逗号。若后面的为空,那么echo。如果后面不为空则静默编译
即,若MakeFLAG里面有s,那么静默编译。usage:make -s
其中 -s会作为MAKEFLAGS参数。在此代码作用下XECHO变量会变成空(默认是echo)

【原地编译 和 单独输出文件夹编译:56-76,注释】
原地编译
默认.o .c文件都会放在一起,默认都是这样的,这就是原地编译。原地编译处理起来简单,但坏处是污染了源文件。(编译过的uboot7M多,打包给别人会产生误会);其二,一套源代码只能用一次配置。
比如产品有 三种不同的高、中、低性能,我的软件要去变,我希望能做不同的配置。
如果是原地编译我必须要make disclean。如果弄三份,后面维护起来又很麻烦、重复劳动。
解决方法就是使用 单独输出文件夹方式编译。这种方式linux kernel也是支持的。

单独输出文件夹编译
基本思路是另外生成一个其他的生成目录。这样更改了源代码都能应用到,届时配置产生区别

具体用法:	默认原地编译
		第一种 make O=/输出目录		//推荐方法(但注意)
			注意:清除、配置、编译都要加 O=/.......
			即eg:
			make O=/tmp/build disclean
			make O=/tmp/build x210_sd_config
			make O=/tmp/build all
			否则会报错。
			但实际上我们用的uboot是有问题的,我们要手工创建目录。
		第二种 export BUILD_DIR=输出目录 导出环境变量,make
		若两个都指定了,这种情况第一种优先级更高
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif

若命令行中有O,把我们make O=/输出目录中 O的值传给BUILD_DIR.

【单独输出文件夹编译中的环境变量:95】
OBJTREE //编译后的.o文件存放目录。原地编译的情况下,这个变量等于原地
SRCTREE //源代码目录,即当前目录,主makefile所在的目录
TOPDIR //

【MKCONFIG文件】
这里定义了一个MKCONFIG变量,暂时用不到,但非常重要
MKCONFIG的值即为根目录下面的mkconfig配置脚本。

include $(obj)include/config.mk
//这里的obj变量就是OBJTREE,文件存放目录

export	ARCH CPU BOARD VENDOR SOC(No.145行)
		//config.mk内容是配置生成的。源码不含。
		//之所以不直接给出这5个值,是因为便于集中配置
		//这5个值 就在x210_sd_config命令中,调用的就是MKCONFIG这个脚本,这个			过程中这5个值是作为参数传进MKCONFIG脚本去的(No.2589行)
		//移植uboot在一定程度上就是移植这个!

我们x210在iNand情况下配置生成的config.mk为:

ARCH	 	= arm		
CPU		= s5pc11x
BOARD		= x210
VENDOR		= samsung
SOC		= s5pc110

【CROSS_COMPILE 136-182】
由ARCH决定。
CROSS_COMPILE是定义交叉编译前缀的
交叉编译工具不是单独一个工具,共同的特点是共同的前缀。架构不同则前缀不同
因此定义时区分工具前缀,才能实现可移植性

如果未定义CROSS_COMPILE,则进行多种条件编译:
注意:除了在makefile中改变该值,也可make CROSS_COMPILE=/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi- 手动改。当然,这个值也会修改makefile中的值。

【导入config.mk 185】

前面的工作是:编译工具导入、完整工具链补全(补全后段)。我们无需知道细节。

【config.mk112-142行】
主要内容是编译属性类操作,不重点分析
# Load generated board configuration

sinclude $(OBJTREE)/include/autoconf.mk
开发板配置项目,生成autoconf.mk文件,用来指导uboot的编译过程
这个文件里面都是以CONFIG_开头的宏,它们是条件编译的关键,用来指导程序的走向。
原材料:uboot_9ding/include/configs/x210_sd.h (里面也都是宏定义)
【注意!】 这里面出现的宏就是我们对开发板uboot移植的关键!宏是对开发板的配置

【config.mk 142行,指定链接脚本】

ifndef LDSCRIPT


	ifeq ($(CONFIG_NAND_U_BOOT),y)

	LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds

	else

	LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds

如果CONFIG_NAND_U_BOOT = y,那么链接脚本用u-boot-nand.lds
否则用u-boot.lds。
因为我们的x210开发板是用的iNand启动而非nand,因此u-boot.lds是我们的链接脚本。

【config.mk 156行,TEXT_BASE】

ifneq ($(TEXT_BASE),)	
	CPPFLAGS += -DTEXT_BASE=$(TEXT_BASE)
	
endif

	【199行】
	ifneq ($(TEXT_BASE),)
	
LDFLAGS += -Ttext $(TEXT_BASE)
	
endif

若TEXT_BASE不为空,那么CPPFLAGS中添加TEXT_BASE的值
这个TEXT_BASE的值源码中没有,由配置中生成于broad/config.mk中
生成之后他的值是0xc3e0 0000,是整个uboot链接时的地址。
注意:这个地址可能由虚拟地址映射成23E0 0000(主要是忘了是哪儿了)
(我们DNW刷机的时候先下载210_usb.bin,然后下载uboot.bin到0x23e0 0000,这里的下载地址就是为了和现在链接的地址匹配。)

注意:在u-boot.lds脚本中的链接地址依然是零地址。但最后还是会链接到0xc3e0 0000这个位置。这就是我们裸机里学的
“arm-linux-ld -Text 0x0 -o led.elf”中的0x0一样。

【235-256行】
自动推导规则。


4.7 uboot的配置过程详解
x210_sd_config :	unconfig
	
	@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
	
	@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk

	 $(@:_config=)	这个是一个替换符,就等于$@
			$@在前面讲了是代表原材料,
	 		:..=代表冒号 后面的东西用 = 后面的东西替换,
			所以这里就代表x210_sd
			{整句注解:x210_sd_config,把“_config”替换为“”(空)}

	@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110
	//mkconfig 参数1 参数2 参数3 参数4 参数5 参数6
	//$1 = x210_sd		$2 = arm		$3 = s5pc11x
	//$4 = x210 		$5 = samsung	$6 = s5pc110
						//$1 代表第一个参数 , $#=6

【mkconfig脚本:14-21】

while [ $# -gt 0 ] ; 				//当 参数个数$#  大于0
do
	
	case "$1" in
			
	--) shift ; break ;;
			//第一个参数(目前我们的值是x210_sd)
	-a) shift ; APPEND=yes ;;
	
	-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;

	*)  break ;;
	
	esac

done

【mkconfig脚本:23】

[ "${BOARD_NAME}" ] || BOARD_NAME="$1"
简写的if表达式:
	${BOARD_NAME}是Ture,后面就不用执行了。
	我们之前没定义${BOARD_NAME},值为False。那么定义BOARD_NAME=“x210_sd”

【mkconfig脚本:25】
[ $# -lt 4 ] && exit 1
		//若参数个数 <4 脚本退出,返回1
[ $# -gt 6 ] && exit 1		//若参数个数 >6 脚本退出,返回1
				//注意返回0是正常的。返回1其实是出现错误了
echo "Configuring for ${BOARD_NAME} board..."
				//这句来了!使我们配置的时候打印的那句话
				"Configuring for x210_sd board..."

【mkconfig脚本:33-118】创建符号链接
这些符号链接的存在就是整个配置过程的核心。主要作用是给头文件等提供指向性链接。
根本目的是为了移植性。
因为里面有很多重复、平行的代码,他们来自于不同的架构或开发板。
我们提供一个具体名字的文件夹,选代码其中的一部分,供编译使用。
这个选不同的配置、使用不同的文件的过程是符号链接的功劳

【46-48】

if [ "$SRCTREE" != "$OBJTREE" ] ; then
		//若 make -O
mkdir -p ${OBJTREE}/include
....
else	
	cd ./include
	
	rm -f asm
	
	ln -s asm-$2 asm			//step1 创建一个符号链接asm,指向asm_arm。
					//这是一个文件夹
fi

【53:】

ln -s ${LNPREFIX}arch-$6 asm-$2/arch	
    //step2 生成了一个arch,后来在83行被删了
	后来重新又生成了一个。
	samsung相当于打了个粗略的补丁。
        ${LNPREFIX}值是include/asm-arm/

【83】

# create link for s5pc11x SoC

if [ "$3" = "s5pc11x" ] ; then
 
	rm -f regs.h
        //删当前目录下regs.h(includes/)
 	ln -s $6.h regs.h
        //step3 创建 include/regs.h,指向s5pc110.h
 	rm -f asm-$2/arch
	ln -s arch-$3 asm-$2/arch		
        //step4 创建 include/asm-arm/arch,指向arch-s5pc11x
fi

【107】

if [ "$2" = "arm" ] ; then
	
rm -f asm-$2/proc
	
ln -s ${LNPREFIX}proc-armv asm-$2/proc	//5 创建 include/asm-arm/proc
					,指向 include/asm-arm/proc-armv

					${LNPREFIX}值是include/asm-arm/
fi

总结:mkconfig脚本一共创建了4个符号链接:asm arch regs.h proc
将来在写代码时,头文件非常有用。
eg:

#include<asm/xx.h>
如果我们想找这个xx.h,我们不能去asm/下找,应该去include/asm-arm/下找这个文件。

4.8

【mkconfig 123-129】创建include/config.mk文件

mkconfig进行配置,为编译过程提供了 ARCH=arm CPU=s5pv110 这样的变量,起指导编译作用。
mkconfig这个文件不写在一个Makefile文件里是为了便于维护。

注意:在脚本程序中,时刻要注意当前目录是什么。进去了没出来,那就是没出来。

【mkconfig 134】
make -a 追加 include/config.h文件,里面就一句引用了x210_sd.h ,这个x210_sd.h用于生成一个autoconfig.mk,指导编译过程

4.9.uboot的链接脚本

uboot的链接脚本和我们之前裸机中的链接脚本并没有本质区别,只是复杂度高一些,文件多一些,使用到的技巧多一些。
ENTRY(_start)用来指定整个程序的入口地址。所谓入口地址就是整个程序的开头地址,可以认为就是整个程序的第一句指令。有点像C语言中的main

指定程序的链接地址有2种方法:一种是在Makefile中ld的flags用-Ttext 0x20000000来指定;第二种是在链接脚本的SECTIONS开头用.=0x20000000来指定。两种都可以实现相同效果。其实,这两种技巧是可以共同配合使用的,也就是说既在链接脚本中指定也在ld flags中用-Ttext来指定。两个都指定以后以-Ttext指定的为准。

【关于代码段16KB的文件优先排列】
代码段中注意文件排列的顺序。指定必须放在前面部分的那些文件就是那些必须安排在前16KB内的文件,这些文件中的函数在前16KB会被调用。

链接脚本中有很多自定义段
例如 u_boot_cmd段就是自定义段。自定义段很重要。

移植的过程中要非常仔细。对于新手来说心里没有底很正常,后面做移植的时候关键的地方要一遍一遍的背才能不出错。老手不会慌,因为几年前就出过这样的错了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谦谦青岫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值