上文讲到,如果需求仅略微修改,整个从编译到仅保留二进制文件到添加HeaderInfo
到烧写到SD卡的一系列命令都需要重新再输入一遍,这很繁琐。
如何解决这个问题呢?
制作一个bash脚本文件
制作一个bash
脚本文件,也就是制作一个批处理文件
:
#!/bin/bash
arm-linux-gcc -c mystart.s
arm-linux-gcc -c mylowlevel_init.s //.s文件生成.o文件
arm-linux-ld -T myboot.lds -o myboot mystart.o mylowlevel_init.o //.o文件生成可执行文件
arm-linux-objcopy -O binary myboot myboot.bin //只保留二进制文件
./mkv210 u-boot.bin u-boot.16k //添加HeaderInfo
sudo dd iflag=dsync oflag=dsync if=u-boot.16k of=/dev/sdb seek=1 //烧写到SD卡
然后运行这个文件:
bash genmyboot.sh
虽然,这样也能够实现功能,但是某种程度上可以看出并不智能。什么意思呢?可能某次修改,只修改了其中的某几个文件,其他的很多文件都没有修改。但如果运行这个bash
,所有的程序都要走一遍,也就是无论文件是否被修改,都会被处理,会浪费很多的时间。
Makefile
无需畏惧Makefile
Makefile
是一个能够让初学者很挫败的文件,初看会让人头昏眼花,感觉在看“天书”。比如下面是U-Boot
的Makefile
中很少的一部分:
$(obj)u-boot.img: $(obj)u-boot.bin
$(obj)tools/mkimage -A $(ARCH) -T firmware -C none \
-O u-boot -a $(CONFIG_SYS_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.ubl: $(obj)spl/u-boot-spl.bin $(obj)u-boot.bin
$(OBJCOPY) ${OBJCFLAGS} --pad-to=$(PAD_TO) -O binary $(obj)spl/u-boot-spl $(obj)spl/u-boot-spl-pad.bin
cat $(obj)spl/u-boot-spl-pad.bin $(obj)u-boot.bin > $(obj)u-boot-ubl.bin
$(obj)tools/mkimage -n $(UBL_CONFIG) -T ublimage \
-e $(CONFIG_SYS_TEXT_BASE) -d $(obj)u-boot-ubl.bin $(obj)u-boot.ubl
rm $(obj)u-boot-ubl.bin
rm $(obj)spl/u-boot-spl-pad.bin
$(obj)u-boot.ais: $(obj)spl/u-boot-spl.bin $(obj)u-boot.img
$(obj)tools/mkimage -s -n $(if $(CONFIG_AIS_CONFIG_FILE),$(CONFIG_AIS_CONFIG_FILE),"/dev/null") \
-T aisimage \
-e $(CONFIG_SPL_TEXT_BASE) \
-d $(obj)spl/u-boot-spl.bin \
$(obj)spl/u-boot-spl.ais
$(OBJCOPY) ${OBJCFLAGS} -I binary \
--pad-to=$(CONFIG_SPL_MAX_SIZE) -O binary \
$(obj)spl/u-boot-spl.ais $(obj)spl/u-boot-spl-pad.ais
cat $(obj)spl/u-boot-spl-pad.ais $(obj)u-boot.img > \
$(obj)u-boot.ais
是不是很费解?Makefile
其实语法比较简单,但复杂就复杂在其中运用了许多shell
脚本和各种正则表达式等,这些混杂在一起,就会让人摸不着头脑。
如果需要详细了解Makefile
的内容,可以参考链接:跟我一起写Makefile。
Makefile的基本语法
target ... : prerequisites ...
command
...
...
具体含义为:
target
:可以是一个object file
(目标文件),也可以是一个执行文件,还可以是一个标签(label
);prerequisites
:生成该target所依赖的文件和/或target;command
:该target要执行的命令(任意的shell命令)。
这是一个文件的依赖关系,也就是说,target
这一个或多个的目标文件依赖于prerequisites
中的文件, 其生成规则定义在command
中。说白一点就是说:prerequisites
中如果有一个以上的文件比target
文件要新的话,command
所定义的命令就会被执行。
这就是Makefile
的规则,也就是Makefile
中最核心的内容。
Makefile案例
既然大致了解了Makefile
的规则,那么如何才能完成制作描述U-Boot
烧写镜像到SD卡的过程的Makefile
呢?
.s文件生成.o文件
先根据这个简单的规则,写一个简单的Makefile
。这里首先注明一下make
命令的寻找优先级,make
会在当前目录下寻找以下的Makefile
文件,优先级由高到低为:
GUNMakefile > makefile > Makefile
一般,文件命名为Makefile
。
制作一个Makefile
文件,用于将mystart.s
汇编成mystart.o
文件:
mystart.o: mystart.s
arm-linux-gcc -c mystart.s
这很简单,如果同时还需要将mylowlevel_init.s
汇编成mylowlevel_init.o
文件:
mystart.o: mystart.s
arm-linux-gcc -c mystart.s
mylowlevel_init.o: mylowlevel_init.s
arm-linux-gcc -c mylowlevel_init.s
如果此时,还是运行Makefile
,会发现只运行了前一句指令,但后一句的指令并没有被运行。这是因为:Makefile
只运行第一个目标target
,其余的目标target
都不会被运行。
怎么解决呢?利用Makefile
生成一个伪目标target
,再利用伪目标target
去生成接下来的两个目标target
。方式如下:
.PHONY:all //注明all为伪目标(可写可不写)
all: mystart.o mylowlevel_init.o
mystart.o: mystart.s
arm-linux-gcc -c mystart.s
mylowlevel_init.o: mylowlevel_init.s
arm-linux-gcc -c mylowlevel_init.s
当make
的时候,目标all
本身不运行任何命令,但是它依赖于mystart.o
和mylowlevel_init.o
。但当前目录下并没有这两个文件,因此它就会自动在当前Makefile
中寻找是否存在目标target
,如果存在,就自动执行该目标的命令。
其余步骤
.PHONY:all //注明all为伪目标(可写可不写)
all: mystart.o mylowlevel_init.o
arm-linux-ld -T myboot.lds -o myboot mystart.o mylowlevel_init.o
arm-linux-objcopy -O binary myboot myboot.bin
./mkv210 u-boot.bin u-boot.16k
mystart.o: mystart.s
arm-linux-gcc -c mystart.s
mylowlevel_init.o: mylowlevel_init.s
arm-linux-gcc -c mylowlevel_init.s
由于烧写到SD卡的命令,需要SD卡已经插入的状态,一般而言,不会将它和这些命令都写在一起。但是,每次都输入这么麻烦的代码,也是很麻烦的事情。于是,可以用另一种方式:
.PHONY:all //注明all为伪目标(可写可不写)
all: mystart.o mylowlevel_init.o
arm-linux-ld -T myboot.lds -o myboot mystart.o mylowlevel_init.o
arm-linux-objcopy -O binary myboot myboot.bin
./mkv210 u-boot.bin u-boot.16k
mystart.o: mystart.s
arm-linux-gcc -c mystart.s
mylowlevel_init.o: mylowlevel_init.s
arm-linux-gcc -c mylowlevel_init.s
.PHONY:mksd
mksd:
sudo dd iflag=dsync oflag=dsync if=u-boot.16k of=/dev/sdb seek=1
创造一个伪目标,专门用于烧写镜像到SD卡中。但是,一般情况下,这句伪目标是运行不到的。那怎么样才能只运行这句伪目标呢?
只需要在make的时候,人为地指定伪目标即可:
make mksd
Makefile的改进
尽管此时Makefile
的运行没有问题,但是还是会发现一个问题。如果修改了mylowlevel_init.s
文件,只会将mylowlevel_init.s
文件会变成mylowlevel_init.o
文件,mystart.s
并不会重新汇编,这很不错;但是如果两个都不修改,此时两个.s
文件都不会会变成.o
文件,但是后面链接、只保留二进制文件、添加HeaderInfo
三句依然还是会运行!
这是为什么呢?
由于all是一个伪目标,没有办法进行目标与依赖之间的新旧关系,因此就会一直都会运行后面的三句。
改进后的代码为:
.PHONY:all //注明all为伪目标(可写可不写)
all: myboot
mystart.o: mystart.s
arm-linux-gcc -c mystart.s
mylowlevel_init.o: mylowlevel_init.s
arm-linux-gcc -c mylowlevel_init.s
myboot: mystart.o mylowlevel_init.o myboot.lds
arm-linux-ld -T myboot.lds -o myboot mystart.o mylowlevel_init.o
arm-linux-objcopy -O binary myboot myboot.bin
./mkv210 u-boot.bin u-boot.16k
.PHONY:mksd
mksd:
sudo dd iflag=dsync oflag=dsync if=u-boot.16k of=/dev/sdb seek=1
如此便好,也就是说,最好让伪目标没有命令可以执行。
Makefile的自动化变量
尽管上文的Makefile
看起来比较“优雅”了,但是还是存在问题的:如果存在100个.s
文件需要汇编成.o
文件,那么需要写100条类似于如下的代码。
mystart.o: mystart.s
arm-linux-gcc -c mystart.s
这想一想,也是非常繁琐。于是,Makefile
就引进了:
- 自动化变量:$@(所有目标target集合)、$^(所有依赖集合)、$<(所有依赖集合中的第一个)
- 模式匹配:%.x(当前目录下所有.x结尾的文件)
有了自动化变量和模式匹配,就可以写出更加简洁的Makefile
了:
.PHONY:all //注明all为伪目标(可写可不写)
all: myboot
%.o: %.s
arm-linux-gcc -c $<
myboot: mystart.o mylowlevel_init.o myboot.lds
arm-linux-ld -T myboot.lds -o myboot mystart.o mylowlevel_init.o
arm-linux-objcopy -O binary myboot myboot.bin
./mkv210 u-boot.bin u-boot.16k
.PHONY:mksd
mksd:
sudo dd iflag=dsync oflag=dsync if=u-boot.16k of=/dev/sdb seek=1
同样,可以使用变量来代替某些内容,有点类似于宏定义的样子。一般采用:=
来赋值,引用的时候需要用$()
来引用:
CC := arm-linux-gcc
.PHONY:all //注明all为伪目标(可写可不写)
all: myboot
%.o: %.s
$(CC) -c $<
myboot: mystart.o mylowlevel_init.o myboot.lds
arm-linux-ld -T myboot.lds -o myboot mystart.o mylowlevel_init.o
arm-linux-objcopy -O binary myboot myboot.bin
./mkv210 u-boot.bin u-boot.16k
.PHONY:mksd
mksd:
sudo dd iflag=dsync oflag=dsync if=u-boot.16k of=/dev/sdb seek=1
当然,U-Boot
的Makefile
肯定比本文的要复杂得多的多,之后的博文会对此进行详细分析。