这段时间学习的压力要小的多,处理做LFS外,还编译了vivi,vivi是mizi公司一款bootloader,总体说了,这是一个轻量级的bootloader,代码的规模比较小,非常适合用于学习研究,不想uboot,万行的代码,不是那么容易读的,同时,vivi还有一个优点,就是它的Makefile文件结构,完全是仿照kernel的Makefile,所以,学习vivi的Makefile是很有价值的,可以从vivi的 makefile看出,很多的Makefile是直接从kernel的文件中的粘贴过来的。废话不说了,我们开始解析下vivi的Makefile文件吧。
首先需要看一些vivi的文件组织形式:
|-- COPYING //这是一个GPL文件 |-- CVS //这是一个版本控制软件的文件夹 |-- Documentation //相关说明文档 |-- Makefile //Makefile文件 |-- Rules.make //Makefile通用规则 |-- arch //与平台板卡相关的文件 |-- drivers //驱动文件 |-- include //头文件 |-- init // 系统初始化文件 |-- lib //库文件 |-- scripts //一些vivi的配置脚本,同时包含有menuconfig的源代码 |-- util //二进制工具,这里有两个工具:imagewrite 和ecc |
上边就是vivi的文件组织结构,有一些文件是与编译无关的,一些说明文档、版本控制文件以及其他一些文件,我们把不需要的文件删除,util这里边的工具用在其他的地方,可以选择性的编译,但这些文件是与vivi的编译无关的,当然,GPL协议是非常重要的,这里不是说GPL不重要,删除只是为了列举与vivi编译有关的文件。下边是精简的文件组织形式:
|-- Makefile //主Makefile |-- Rules.make //通用Makefile规则 |-- arch //与平台板卡相关的文件 |-- drivers //驱动文件 |-- include //头文件 |-- init //初始化文件 |-- lib //库文件 |-- scripts //配置脚本与menuconfig的源码 |
我们来分析下vivi从配置到最后生成的总体流程:
make menuconfig 或者 make config 或者 make oldconfig
|||||
\ /
生成 vivi/.config文件【保存了配置文件】
||
\/
然后make ,这时候make根据.config的内容进行编译。
这里我们感兴趣的是执行make menuconfig后,出现的图形化菜单选项是怎么完成这个功能的,其实,mizi公司的程序是直接仿照kernel的形式,实质就是COPY过来的。 具体的过程是这样的,当执行make menuconfig ,首先 进入到scripts/lxdialog文件下,用gcc编译一个 menuconfig程序出来,这就是为什么在你输入make menuconfig的时候,会打印出来如下的内容:
make -C scripts/lxdialog all make[1]: Entering directory `/tmp/vivi/scripts/lxdialog' # 跳转入目录 make[1]: Leaving directory `/tmp/vivi/scripts/lxdialog' # 编译完后,离开目录 bin/sh scripts/Menuconfig arch/config.in #执行lxdialog arch/config.in Using defaults found in .config Preparing scripts: functions, parsing.............done.
|
它首先是跳转如scripts/lxdialog文件夹下,然后进行编译,生成
lxdialog的可执行文件,这个主要用于构建可视化图形界面,具体的功能这里不做详细介绍了,这样,编译生成
lxdialog后, 这个可执行文件和menuconfig脚本的相互配合下就可以进行配置了,同时需要说明的时,在图形化配置中,会出现很多的菜单化选项,那么这些菜单化选项是否可以更改,当然是可以的,如果你够仔细,就会发现,在vivi真个目录中,有很多的config.in文件,这些文件就存储了这些的菜单选项,只要修改这些文件就可以任意的添加自己的选项了,这里对这些的菜单配置文件做一些简要的说明。
在arch/config.in中的文件内容:
choice 'ARM system type' \ "SA1100-based CONFIG_ARCH_SA1100 \ PXA250/210-based CONFIG_ARCH_PXA250 \ S3C2400-based CONFIG_ARCH_S3C2400 \ S3C2410-based CONFIG_ARCH_S3C2410"
mainmenu_option next_comment comment 'Implementations' if [ "$CONFIG_ARCH_SA1100" = "y" ]; then choice 'Platform' \ "KINGS CONFIG_SA1100_KINGS \ FORTE CONFIG_SA1100_FORTE \ SUNS CONFIG_SA1100_SUNS \ SUNS_OLD CONFIG_SA1100_SUNS_OLD \ GILL CONFIG_SA1100_GILL \ ENDA CONFIG_SA1100_ENDA \ EXILIEN102 CONFIG_SA1100_EXILIEN102 \ WISMO CONFIG_SA1100_WISMO"
|
这里只截取了一小部分, 这些脚本都是相似的,如果感兴趣可以自己去打开看看,相似的脚本还出现在/driver/mtd driver/mtd/nand driver/mtd/nor driver/mtd/maps driver/serial等文件夹下,这些是和kernel的菜单化配置一样的,都是直接copy过来,给我们一个提示:
学习的第一层是优秀的模仿,学会模仿也是一种学习能力。 |
通过上边的操作,我们认为所有的配置已经完成了,应该开始编译工作了,至于编译的流程需要我们看看makefile文件。由于Makefile文件比较长,不方便全部贴在这里。我们分段进行分析。
VERSION = 0 PATCHLEVEL = 1 SUBLEVEL = 4
VIVIRELEASE=$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)
ARCH := arm
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ else if [ -x /bin/bash ]; then echo /bin/bash; \ else echo sh; fi ; fi) TOPDIR := $(shell /bin/pwd)
# # change this to point to the Linux include directory # LINUX_INCLUDE_DIR = /usr/local/arm/2.95.3/include/
VIVIPATH = $(TOPDIR)/include
HOSTCC = gcc HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
CROSS_COMPILE = /usr/local/arm/2.95.3/bin/arm-linux- #CROSS_COMPILE = /opt/host/armv4l/bin/armv4l-redhat-linux-
# # Include the make variables (CC, etc...) #
AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump MAKEFILES = $(TOPDIR)/.config MD5SUM = md5sum PERL = perl AWK = awk
export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE \ CONFIG_SHELL TOPDIR VIVIPATH HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC \ CPP AR NM STRIP OBJCOPY OBJDUMP MAKE MAKEFILES MD5SUM PERL AWK
|
# 版本号 在生成的vivi中,开头的版本号就是有这个地方的变量来决定
VERSION = 0
PATCHLEVEL = 1
SUBLEVEL = 4
# VIVI 这里设置输出版本信息的格式。
VIVIRELEASE=$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)
# ":=" 与"="的区别: ”:=“为直接展开,”=“为递归展开,详细内容看GNU make中文手册6.2.2
# ”:=“在此处会定义一个变量,直接对其赋值,对于引用会直接找到引用的源,表示立即展开
#”:“只是简单的替换,不会直接找到引用的源,在以后,如果引用的源发生变化,此处的变量也会发生变化。表示递归展开
######### 举例 #########
#
# VAR1=TMP1;
# TMP=$(VAR1)
# VAR1=TMP2
# TMP1:=$(VAR1)
#ECHO $(TMP) ===>显示 TMP2
#ECHO $(TMP1)===>显示 TMP1
#
#ARCH是定义体系结构, VIVI此处的定义是说明该VIVI是针对ARM平台
ARCH := arm
# 这里一处make函数的调用,该函数为shell,该行语句的主要作用是设置shell命令解释器
#这条命令的作用是执行函数把结果给变量
#make函数的调用语法为:$()或者${}
#shell函数的作用是”执行命令,把命令的执行结果作为变量的内容“
#COMFIG_SHELL 指定命令解释器
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)
# 设置当前顶层目录, 该Makefile所在目录 shell函数的用法,把命令pwd的结果赋值给TOPDIR
TOPDIR := $(shell /bin/pwd)
#
# change this to point to the Linux include directory
# 指定LINUX内核的INCLUDE目录,在vivi中,没有起作用,可以删除该语句
#
LINUX_INCLUDE_DIR = /usr/local/arm/2.95.3/include/
# 指定vivi的头文件所在目录
#
VIVIPATH = $(TOPDIR)/include
# 定义本地的编译器 这里指定编译器为gcc
HOSTCC = gcc
#定义本地的编译器的编译参数,vivi的编译需用使用arm-linux-gcc(交叉编译器),那么定义gcc有什么作用?
#解答: 在一次使用make menuconfig的时候,需要使用gcc编译menuconfig选项程序,只为配置服务。
#跳转到scripts/lxdialog
#执行该目录下的make all
# make menuconfig :::---->>
###跳转到scripts/lxdialog
###执行该目录下的make all
###make -C scripts/lxdialog all
###make[1]: Entering directory `/tmp/vivi/scripts/lxdialog' # 跳转入目录
###make[1]: Leaving directory `/tmp/vivi/scripts/lxdialog' # 编译完后,离开目录
###/bin/sh scripts/Menuconfig arch/config.in #执行Menuconfig arch/config.in
###Using defaults found in .config
###Preparing scripts: functions, parsing.............done.
HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
#定义交叉编译器的路径,要注意一些公认的定义方法,一般CROSS_COMPILE就是交叉编译器 ARCH就是于体系结构相关的
CROSS_COMPILE = /usr/local/arm/2.95.3/bin/arm-linux-
#CROSS_COMPILE = /opt/host/armv4l/bin/armv4l-redhat-linux-
#
# Include the make variables (CC, etc...)
# 定义工具链 tools chains
#
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
MAKEFILES = $(TOPDIR)/.config
#MD5
MD5SUM = md5sum
# PERL 脚本
PERL = perl
AWK = awk
# 导出前边定义的变量 主要是因为主makefile需要调用很多子makefile,
# 这样导出后,就可以让子makefile使用这些以前定义的变量了。
export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE \
CONFIG_SHELL TOPDIR VIVIPATH HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC \
CPP AR NM STRIP OBJCOPY OBJDUMP MAKE MAKEFILES MD5SUM PERL AWK
# 定义就宣告结束了
#下边是具体的编译规则