通过对makefile的分析,对整一个依赖关系有一个大致的了解。接下来准备对每一个模块进行分析。首先看第一个子目录arch。
arch目录是针对不同目标开发板设计的,存放着处理器架构相关的代码,比如可能针对的是s3c2440,s3c2410等,不同的处理器架构有着不一样的硬件模块。而整个vivi启动包括两个部分,第一部分就是完成含依赖于CPU的体系结构硬件初始化的代码,包括禁止中断、初始化串口、复制自身到RAM等。这部分代码需要根据不同的处理器来编写,该目录下就保存了针对不同处理器的第一阶段的代码。(后面的讨论针对2440来进行)
打开该文件夹可以看到:def-configs,s3c2440,config.in,Makefile,vivi.lds.in.
同样我们也从Makefile开始看起。
1、Makefile:
通过Makefile中开头的注释我们就可以大概知道Makefile中的内容了:# Select CPU dependent flags. Note that order of declaration is important;# the options further down the list override previous items.依赖于CPU的相关选项的设置,比如编译的时候需要指定的跟CPU相关的选项,根据CPU相关的SDRAM的地址来指定程序运行地址等。其中在写法上保持了OPTION-y这样的写法(在vivi Makefile中由配置决定的优化选项好象基本用了这种写法,我个人觉得这个写法是很好的),
按照CPU体系结构的配置信息追加了一些编译选项:
CFLAGS +=$(apcs-y) $(arch-y) $(tune-y) -mshort-load-bytes -msoft-float
AFLAGS +=$(apcs-y) $(arch-y) -mno-fpu -msoft-float
。。。。。。
从名称就可以看出,与连接脚本是密切相关的(我们在看总Makefile的时候看到LINKFLAGS = -Tarch/vivi.lds -Bstatic ),但是是.lds后缀的,所以估计下面要在LDSCRIPT = arch/vivi.lds.in这个上做文章来产生这个.lds脚本。
LDSCRIPT = arch/vivi.lds.in(等下会分析这个小文件的内容)
又比如我的开发板是2440:
ifeq ($(CONFIG_ARCH_S3C2440),y)
MACHINE = s3c2440
ifeq ($(CONFIG_S3C2440_NAND_BOOT),y)
TEXTADDR = 0x33f00000
else
TEXTADDR = 0x00000000
endif
endif
ifeq ($(CONFIG_VIVI_ADDR),y)
TEXTADDR = 0x$(CONFIG_VIVI_TEXTADDR)
endif
export MACHINE PROCESSOR TEXTADDR
其中CONFIG_ARCH_S3C2440 CONFIG_VIVI_ADDR等是在配置过程中产生的(这可以从同目录下的config.in的选项看到,后面将对config.in进行讨论,配置结果最后保存在.config中,.config被总Makefile包含)。最后的地址将由我们的设定值和配置结果决定。对于具体的地址我们可以按照自己的实际情况来设定。比如我最后只配置了CONFIG_ARCH_S3C2440,那么最后我指定的连接的运行地址是0x33f00000,这个值将传递给连接脚本,可以看到在该Makefile中(后面一点的部分):
arch/vivi.lds: $(LDSCRIPT) dummy
@sed s/TEXTADDR/$(TEXTADDR)/ $(LDSCRIPT) >$@
定义了HEAD := arch/$(MACHINE)/head.o,这就是我们总Makefile(include了这个Makefile)中用到的HEAD的定义。不过我认为这句是不是应该放在下面那段的后面,或者是在下面那个ifeq里面,因为这个目录还在进行判断是否存在,这里却在用了?
然后我们看到:
# If we have a machine-specific directory, then include it in the build.
MACHDIR := arch/$(MACHINE)
ifeq ($(MACHDIR),$(wildcard $(MACHDIR)))
SUBDIRS += $(MACHDIR)
CORE_FILES := $(MACHDIR)/$(MACHINE).o $(CORE_FILES)
endif
如果我们在看总Makefile时还纠结于为什么vivi依赖文件里面vivi: include/version.h $(CONFIGURATION) init/main.o init/version.o linuxsubdirs没有直接看到head.o后者说head.o为什么没有被作为CORE_FILES的话,那么这里就清晰了,在主Makefile中因为 linuxsubdirs: $(patsubst %, _dir_%, $(SUBDIRS))
$(patsubst %, _dir_%, $(SUBDIRS)) : include/version.h
$(MAKE) CFLAGS="$(CFLAGS) $(CFLAGS_KERNEL)" -C $(patsubst _dir_%, %, $@)
此时的SUBDIRS已经+= $(MACHDIR),在按照SUBDIRS目录执行子makefile的时候同样也执行了arch/s3c2440目录下的Makefile,生成head.o文件。可以猜想head.o和其他SUBDIRS包含的文件对应的.o文件可能都是用这种办法形成的(如果在主Makefile中没有出现在任何以来中的话)。(那么那些文件夹里面应该有一个Makefile,并且需要有实现规则,这个规则可能是借用调用Rules.make来实现。暂时的猜想。)
CLEAN_FILES += arch/vivi.lds
也许应该继续往下看来分析为什么可以删除这个连接脚本(当然它肯定是要删除的,因为连接的内容是根据每一次配置而不同的,如运行地址),不过其实我们先面已经分析过了这个规则(红色部分),当然在这个Makefile中这部分其实是放在下面的。由此我们可以知道删除它自然有生成它的办法。这个也是黄色部分的解答。
# Configuration targets. Use these to select a
# configuration for your architecture
%_config:
@( /
CFG=$(@:_config=); /
if [ -f arch/def-configs/$$CFG ]; then /
[ -f .config ] && mv -f .config .config.old; /
cp arch/def-configs/$$CFG .config; /
echo "*** Default configuration for $$CFG installed"; /
echo "*** Next, you may run 'make oldconfig'"; /
else /
echo "$$CFG does not exist"; /
fi; /
) ----------------------------------------------------------------------这里有些符号我还需要弄清楚。
这部分提供CREAT一个自己的定义的配置文件并成为.config文件,因为在总Makefile中我们已经知道配置方式有config,oldconfig,menuconfig等,其中config将逐个询问配置选项默认值,这样在没有.config时相当于询问了全部选择,而存在.config时就根据它来设置默认值;而oldconfig则是询问.config里面还没有配置的选项,当然它需要一个已经存在的.config文件。menuconfig 是图形化了的询问方式,如果.config存在那么用该文件配置默认值。这些都是跟LINUX内核配置方式一致的。
所以如果我们需要用基于自己定义的文件来进一步配置,则需要将其替换存在的.config文件,并执行make oldconfig进一步配置。上一次在分析总Makefile的时候,最后也是有一个:
%: ./arch/def-configs/%
$(MAKE) distclean
cp arch/def-configs/$* ./.config -f
$(MAKE) oldconfig
$(MAKE)
我觉得原理差不多,这里相当于包括了CREAT了.config并且自动运行了整个Makefile。另外这些配置文件都需要放在arch/def-configs这个地方。
这样这个Makefile就分析完了,回头想想,主要做了3件事情:完成一些CPU体系相关的选项和定义的追加,定义了运行域并以次CREAT了一个连接脚本,提供了一个用自己的配置文件来生成.config文件的途径。
可能有一个疑问,到目前只出现了head.o,但是并没有生成它的规则。我想这个我们应该去看arch/s3c2440文件夹的内容(也就是所谓的MACHDIR 或者arch/$(MACHINE)),看看要生成head.o到底需要那些依赖以及操作再下定论。而且因为现在已经把这个目录追加到了SUBDIRS中,这就保证了运行其中的Makefile,并且CORE_FILES 也追加了$(MACHDIR)/$(MACHINE).o(也就是head.o),不过这在vivi的连接过程中好象有点重复,因为里面已经有对HEAD的连接了。 不过这之前先分析一下:config.in和 vivi.lds.in,因为前面的过程依赖于它们。
2、config.in (怎么自己写这个文件其中的语法还需要学习)
在分析总Makefile的时候,已经接触了这个配置选项的文件,大概知道里面是对一些配置选项的罗列,提示以及操作。
打开看一下,提示了Documentation/kbuild/config-language.txt,这应该是LINUX下的参考文件,以后可以按照这个学学习一下config的语法。
如果按照2440的思路来,一种选择将包括:
ARM system type ------>CONFIG_ARCH_S3C2440
Platform------------------>CONFIG_S3C2440_SMDK
General setup----------->CONFIG_VIVI_ADDR(未选),因为根据前面的Makefile中可知道,这将影响TEXTADDR的最后取值,这里没有选中。
CACHE Enable 没有打开任何一个cache
接下来的都没有选择。。。
其他还调入了其他一些文件夹下的config.in文件,因为这里只是根据CPU和开发平台选择的一些配置。
根据交互时选择的配置,生成的信息将存入.config文件。
3、vivi.lds.in (连接脚本的书写格式)
SECTIONS {
. = TEXTADDR;
.text : { *(.text) }
.data ALIGN(4) : { *(.data) }
.bss ALIGN(4) : { *(.bss) *(COMMON) }
}
三个段和起始地址,第二行用了通配符*表示所有字符,这里的意思就是说指定的每个目标文件的text section的内容都放到同一个.text中。第三行表示指定的每个目标文件的data section的内容都放到同一个.data中,而且要四字节边界对齐。第四行表示指定的每个目标文件的bss section的内容都放到同一个.bss中,所有的普通符号都放到COMMON中,而且要四字节边界对齐。涉及到的这些段在以前分析linker&loader时已经接触过,可以参考。根据前面的配置信息,在Makefile中产生的TEXTADDR的值将用于此。生成vivi的连接操作就按照这个文件生成的.lds脚本进行。
因为linux是需要MMU的,所以这将被作为VMA,关于虚拟地址,转换后的虚拟地址,物理地址这些需要去研究MMU和上下文切换。
4、s3c2440
现在终于要看这个文件夹了,我们很早已经知道它里面存放着一个关键文件head.S。一上来这里就有讲究:.S。这个知识点是根据编译器而来,因为打开这个文件来看,可以发现它并不只有汇编的语法,还有预处理信息。这样我们就需要用到预处理工具:cpp.查看GNU的as手册:You can use the gnu C compiler driver to get other “CPP” style preprocessing, by giving the input file a ‘.S’ suffix.
或者查看gcc manue:file.S file.sx Assembler code which must be preprocessed. 等等。这样就很明白了。
题外话:熟悉GNU工具是非常有挑战但是非常非常重要的,比如在as手册中也简单介绍了ld,详细介绍了Sections and Relocation,这些就是就是后面理解linker&loader的基础。再去看ld手册那么就很清楚了。我想这些对于嵌入式linux开发都是致关重要的。
不过同样首先打开Makefile看了一下:
O_TARGET := s3c2440.o
obj-y :=
ifeq ($(CONFIG_S3C2440_NAND_BOOT),y)
obj-y += nand_read.o
endif
obj-y += mmu.o proc.o
obj-$(CONFIG_S3C2440_SMDK) += smdk.o
ifeq ($(CONFIG_S3C2440_SMDK),y)
obj-$(CONFIG_TEST) += smdk2440_test.o
endif
include $(TOPDIR)/Rules.make
初看只是定义了两个变量,而第二个变量的追加内容对应的就是该文件夹下的除head.S以外的其他文件的目标文件。但是定义这两个变量到底用在哪,最后一句include给出了解释,看了一下Rules.make,确实在里面有O_TARGET等的规则定义:$(O_TARGET): $(obj-y)。这样看来这些文件一起生成了一个s3c2440.o文件。而这些(obj-y中的.o文件在成为依赖的同时,也将由Rules.make中的相应规则所产生。
那么s3c2440.o这个文件到底有什么用,继续看Rules.make内容:all_targets: $(O_TARGET) $(L_TARGET),是与另外一个L_TARGET一起成为all_targets的依赖,并且没有命令,定位all_targets: first_rule: sub_dirs
$(MAKE) all_targets
是first_rule的执行命令,可见这条命令的唯一作用就是生成$(O_TARGET) $(L_TARGET)这两个文件。具体first_rule是用来什么的,sub_dirs指什么,以后将专门来分析Rules.make时具体分析。因为我认为Rules.make在整个vivi中的角色非常重要。
可见,Rules.make定义了很多通用的规则,用以各种源文件生成目标文件。由此想到我们的总Makefile中也是include了这个文件,并且head.o在总Makefile中已经成为依赖(在上一层的Makefile中实现),加上head.o并未在其他地方定义生成规则,所以可能也是用这种方法生成的,那么来看一下head.S的内容(这个文件将在专门一篇文章中分析):首先include了3个头文件,然后就是一系列汇编指令,因此需要一个预编译和一个编译来实现。要由.S文件编译生成.o文件,可以先由cpp变成.s,再由as变成.o实现;或者直接由gcc来实现。而按照Rules.make中提供的编译选项,两种方式都能实现。这里的一个问题是到底由哪种方式生成,我不是很清楚,因为两种生成方式会相差一个中间结果head.s。这里还可以分析的是include的头文件并不与head.s在同一个目录下,因此这肯定需要在编译预处理的时候指示这些头文件的搜索目录,看到无论哪种编译方式,编译选项里面都有$(AFLAGS),定位到总Makefile中这个便宜选项的定义AFLAGS := -D__ASSEMBLY__ $(CPPFLAGS),再定位到预编译选项CPPFLAGS,顺利发现包含两个搜索目录:CPPFLAGS := -I$(VIVIPATH) -I$(LINUX_INCLUDE_DIR),其中VIVIPATH = $(TOPDIR)/include,这些头文件确实在这个目录下,因此对上了,至于另一个LINUX_INCLUDE_DIR = /usr/local/arm/2.95.3/include我想应该是预编译类似arm linux类似文件时候用到的搜索路径,这里只编译vivi的文件的话,应该是用不到的,但是我想有一种情况如果是将内核跟vivi一起来编译,可能有用,这个以后再研究。顺便看一下另外一个编译选项-D__ASSEMBLY__:
-D Ignored. This option is accepted for script compatibility with calls to other assemblers.
--defsym sym=value
Define the symbol sym to be value before assembling the input file. value must be an integer constant. As in C, a leading ‘0x’ indicates a hexadecimal value, and a leading ‘0’ indicates an octal value.我的理解是相当于定义了__ASSEMBLY__,但并不影响编译。而且在调用的头文件linkage.h中可以看到:
#ifdef __ASSEMBLY__
#define ALIGN __ALIGN
#define ALIGN_STR __ALIGN_STR
#define ENTRY(name) /
.globl SYMBOL_NAME(name); /
ALIGN; /
SYMBOL_NAME_LABEL(name)
#endif
我想这应该就是这个定义的作用,这些在专门分析head.S的时候来具体分析。分析到此:head.o由Rules.make的规则编译生成。这样所有arch文件夹里的文件对应生成的.o文件,都由Rules.make实现。
5、def-configs
这个文件夹也不陌生了。
a、总Makefile分析的时候,知道了它存放着默认的配置文件,打开可以看到一个smdk2440文件,如果这个文件的配置信息可以满足需要,那么通过make smdk2440自动完成vivi的生成。回顾一下:
%: ./arch/def-configs/%
$(MAKE) distclean
cp arch/def-configs/$* ./.config -f
$(MAKE) oldconfig
$(MAKE)
b、在前面分析arch文件夹中的Makefile中也有一段使用自己定义的配置信息文件做母板,通过make %_config加上make oldconfig来CREAT新的.config文件的定义,这个母板也需要存放在这里,这个前面已经分析过。
又一次提到.config文件,想到另外一个内容相同的文件include/autoconfig.h文件。但是我认为这两个的作用又有点区别。因为正好在head.S文件中通过逐层的包含关系涉及到了这个文件,所以干脆在那个时候去分析了。
到这里为止,已经把arch文件夹的结构理了一遍,接下来就详细分析里面几个重要文件的内容了。