当完成对Linux内核的配置以后,此时仍然以源代码的方式存在,不能直接下载到嵌入式系统中运行,因此,需要对内核进行编译,生成最终可以在嵌入式系统上运行的可执行代码。
嵌入式系统Linux内核编译步骤如下。
(1)执行如下命令,删除过时的文件:
# make clean
make clean会删除原来的编译结果,以及一些旧的数据文件。
(2)执行如下命令进行依赖性编译:
# make dep
例如,如果使用了程序A,而A需用到程序B(比如B是A的一个子程度)。B需用到程序C。如果当进行内核配置时,添加或删除了某个功能,这个功能刚好会对程序C造成影响,显然就需要重新编译程序C、B和A,如果程序数量非常多,是很难手工完成的,而采用依赖性编译则可以自动完成该项工作。
(3)执行如下命令,生成可执行的内核映像文件:
# make
这一步是实际的编译过程,最终会生成可运行在嵌入式系统上的内核映像文件。当执行完这一步后,会在当前目录下生成一个内核映像文件zImage。
Rules.make文件中包含了各级目录下的Makefile共同遵循的编译规则,比如将c文件编译成目标文件的规则、将汇编文件生成目标文件的规则等。各级子目录下的Makefile通过语句include $(TOPDIR) /Rules. make将其包含,用以识别各Makefile中所定义的一些变量,比如.config文件中有CONFIG_ARCH_S3C2410 = y,则在Makefile中obj - CONFIG_ ARCH_S3C2410就成为obj -y变量。
下面分析一下Rules. make中的变量所代表的意义。
n obj –y、obj –m、bj –n、obj -:变量obj -y表示需编译到Linux内核中的目标文件名集合,obj -m表示需要编译成模块的目标文件名集合,obj -n表示不需要编译的文件,obj -表示为空。
n O_TARGET、L_TARGET:每个Makefile中都有1个O_TARGET或L_TARGET变量,O_TARGET或L_TARGET的生成规则在Rules.make中规定。目标文件O_TARGET表示由obj -y链接(ld)而成,后缀为.o,静态库文件L_TARGET是由obj -y打包(ar)而成,后缀为.a。
Makefile根据配置文件.config构造出需要进入的子目录及其需要编译的源文件列表,然后对该源文件列表分别进行编译,并把目标代码链接到一起,最终形成Linux内核二进制文件。各级子目录下都有Makefile文件,分别控制其所在目录下的源代码编译。
在这里我们也用一个现实中的例子来介绍格式内容(同样有删减):
#
# Makefile for the Linux networking
#
obj -$ (CONFIG_COMPAT) += compat.o
obj -$ (CONFIG_ECONET) += econet/
obj -$ (CONFIG_VLAN 8021Q) += 8021q/
obj -$ (CONFIG_IP_SCTP) += sctp/
obj -$ (CONFIG_SYSCTL) += sysctl_net.o
endif
obj -$ (CONFIG_IP_SCTP) += sctp/表示如果配置结果中包含CONFIG_IP_SCTP宏,那么需要继续读取sctp/目录下的Makefile文件。
obj -$ (CONFIG_SYSCTL) += sysctl_net.o表示如果配置结果中包含CONFIG_SYSCTL宏,那么需要编译sysctl_net.c文件来得到sysetl_net.o。
顶层Makefile为内核编译的总体控制文件,它的主要任务是生成内核二进制文件vmLinux和内核模块module。以内核文件vmLinux的生成规则为例,分析一下顶层Makefile如何进入子目录并调用其下的各Makefile。
在顶层Makefile中vmLinux的生成规则设定如下:
vmLinux:include/Linux/version. h $(tCONFIGURATION)
init/main.o init/version.o Linuxsubdirs
$ (LD) $ (LINKFLAGS) $ (HEAD) init/main.o init/version.o\.
--start -group\
$ (CORE_FILES)\
$ (DRIVERS)\
$ (NETWORKS)\
$ (LIBS)\
--end -group\
-o vmLinux
从上面可以看出内核vmLinux是由HEAD、main.o、ver-sion.o、CORE_FILES、DRIVERS、NETWORKS、LIBS组成的。其中CORE_FILES、DRIVERS、NETWORKS、LIBS在顶层Makefile中定义,例如在该Makefile中DRIVERS的定义如下:
DRIVERS -y+= drivers/serial/serial.o drivers/char/char.o
…
DRIVERS -$ (CONFIG_PCI) += drivers/pci/driver.o
…
DRIVERS := $ (DRIVERS –y)
HEAD在arch /arm/Makefile中定义,该Makefile是针对特定平台ARM的。它包含了与平台相关的一些信息。
HEAD := arch /arm / kernel/ head -$ (PROCESSOR).o\
arch/arm/kernel/init_ask.o
从内核vmLinux的产生过程所依赖的目标文件及变量可以看出,欲生成vmLinux,顶层Makefile会根据.config文件中的配置,分别进入drivers/serial、drivers/char/及arch/ arm /等各个子目录并凋用其中的Makefile,从而生成serial.o、char.o、init task.o等。如果这时我们选择了系统支持PCI设备,那么DRIVERS -$ (CONFIG_PCI) =DRIVERS -y,make就会进入drivers/pci /目录并根据该目录下的Makefile来生成driver.o。所有的这些目标文件构成了DRIVERS和HEAD,从而构成内核vmLinux中的DRIVERS和HEAD。其他的变量NETWORKS、LIBS等的产生过程类似。
通过上例总结一下Makefile之间的调用关系:在顶层Makefile中定义了变量SUBDIRS,在编译内核或模k时,顶层Makefile根据SUBDIRS的值来决定进入哪些子目录。SUBDIRS的值取决于内核的配置在顶层Makefile中将SUBDIRS赋值为:
SUBDIRS = kernel drivers mm fs net ipc lib
根据内核的配置情况,在arch/arm/Makefile中对变量SUBDIRS的值进行了扩充。假设现在进入了drivers/目录,在该目录下的Makefile会根据其中的变量subdir -y的值来决定进入drivers/下的哪个子目录进行编译,subdir -y的定义如下:subdir - y = parport serial char block net sound mist media cdrom hotplug。这是默认需要进入的子目录,根据需要可以对其进行扩充。比如我们在make menuconfig的配置菜单中选择了系统支持PCI设备,在.config中$ (CONFIG PCI)的值就为y,因此在该Makefile中可以通过subdir -$ (CONFIG PGI) += pci语句对subdir - y进行扩充。也就是还需要进入drivers/pci/子目录下进行make。然后在drivers / pci / Makefile中根据变量obj -y来定义drivers/pci/下需要编译的c源文件:
obj -$ (CONFIG_PCI) += pci.o quirks.o compat.o names.o bridge.o
也就是需要编译pci.c、bridge.c源文件等。当然根据配置也可以对需要编译的源文件进行扩充,比如我们选择了系统支持proc文件系统,通过下面语句对drivers/pci/proc.c文件进行编译:
obj -$ (CONFIG_PROC_FS) += proc.o
另外,顶层Makefile中定义了许多环境变量,子目录中的Makefile可以使用这些变量。前面已经提到了一些,比如HEAD、CORE_FILES、DRIVERS等。这里需要补充一点:当进行交叉编译时,需要在顶层Makefile中修改变量CROSS_COMPILE的值,将其改为用户自己的交叉编译器所在的目录,比如:
CROSS COMPILE = /usr/local/arm/2.95.3 /bin/arm - Linux -
子Makefile比较简单,它负责控制该Makefile所在目录下的子目录及其源文件的编译。
配置、编译Linux内核需要使用多个命令,下面逐一说明。
如果内核已经过多次编译,该命令可将内核源代码恢复到“干净”的初始状态。
该命令用于配置内核。在顶层Makefile中有对应的执行语句:
menuconfig:include/Linux/version. h symlinks
$ (MAKE) -c scripts/Ixdialog all
$ (CONFIG_SHELL) scripts/Menuconfig arch/$ (ARCH)/config.in
当执行该命令时,顶层Makefile会调用该目录下的scripts/menuconfig脚本程序来解释arch/arm/config.in文件,执行完后产生配置文件config,它保存了配置变量。
对于特定的嵌入式Linux应用系统,并不会用到所提供的Linux内核的所有部分,这就需要对内核进行裁剪。所谓裁剪就是只将应用系统需要用到的部分功能保留,将其他不需要的功能删除,这样会节省系统资源,并减少系统出错的机率。所有这些都需要在make menuconfig所弹出的菜单选项中进行取舍,选择方法很简单,就是将光标移到每个选项上,通过按空格键进行选择切换,当出现[*]时,表示系统需要此功能,当出现[m]时,表示系统需要此功能并把它作为模块进行编译,当出现[ ]时,表示系统不需要此功能,在编译时并不会将该功能编译进内核中。
该命令用于建立依赖关系。内核源码树中大多数文件都会与一些头文件有依存关系。要建立内核,各个Makefile必须知道这些依存关系。执行该命令后会在内核源码树中每个子目录里产生一个隐藏的.depend文件,此文件中含有子目录下各文件所依存的头文件清单。
该命令用于编译内核。不管用哪个命令,在顶层目录下都会生成vmLinux文件,vmLinux为未经压缩的内核映像,vm代表virtual memory,即表示它支持虚拟内存。使用make zImage或make bzImage都会在arch/arm/boot/下产生内核映像名为zImage的文件,它是经过gzip压缩的内核映像,不同之处在于:前者生成的内核为不超过512KB的小内核映像;后者bzImage是big zImage的简写,它不受大小限制,生成的内核映像可以比前者更大一些。
如果需要编译内核模块,则使用该命令。
上面这5个步骤在生成Linux内核映像的过程中不一定全部使用,根据经验一般只需make menuconfig和make bzImage两步即可,然后将arch/arm/boot/下生成的内核映像zImage文件烧写到嵌入式应用系统的板子上。
经过上面的分析,应该对嵌入式Linux内核的配置与编译系统有了比较深入的认识。下面通过一个简单的例子说明如何将开发者自己开发的内核代码加入到Linux内核中,并且增加相应的可供用户选择的配置选项,一旦用户选择了这些选项就可以将相应的代码编译进最终生成的内核映像vmLinux中。这个例子中用到了配置语言和编程,以及Makefile的编写,在例子中都做了相应的解释。
这里使用的内核是杭州立宇泰电子有限公司提供的与开发板配套的基于S3C2410的源码包kernel_armsys_050929.tgz,其内核版本为2.4.18,其他公司提供的源码包和下面的操作过程类似。将kernel_armsys_050929.tgz复制到/opt目录下,然后解压,命令如下:
tar zxvf kernel_armsys_050929.tgz
在当前目录下会生成解压后的kernel源码目录,然后在/opt/kernel/目录下分别进行以下操作。
本例是向Linux内核中增加一个新的驱动程序mydriver,因此应该将该驱动所包含的代码加入到.drivers/目录下,其代码目录树如下:
$cd drivers/mydriver
$tree
| - - Config.in-
| - -Makefile
| - - dir.
| | - -Makefile
| \ - - programl.c
| - -mydriver.c
| - - program2.c
通过在该配置文件的最后加入一行source drivers/test/config.in,将子功能mydriver的配置菜单及其选项加入到Linux内核的配置菜单系统中。因为arch/arm/config.in是总的配置菜单,所有在make menuconfig之后出现的菜单及其选项均在该文件中设置。
因为mydriver是内核驱动中的一项新功能,因此创建了一个相应的菜单Mydriver Driver,接着会显示Mydriver support选项,当选择了该选项(CONFIG_MYDRIVER = y)时,会出现user interface选项供用户选择。同理,当选择了user interface及I2C support(配置菜单中已存在)选项后,会出现Increase dir function选项供用户选择。关键字bool类型只接受y、n,并将其值赋给后面的变量CONFIG_MYDRIVER,关键字tristate可接受y、m、n、dep_ristate表示CONFIG_MYDRIVER_DIR的值依赖于CONFIG_I2C及CONFIG_ MYDRIVER_PROGRAM2二者的值,只要二者中有一个为n,则另一个值也为n。
# mydriver driver configuration
#
mainmenu_option next_comment
comment 'Mydriver Driver'
bool 'Mydriver support' CONFIG_MYDRIVER
if ["$CONFIG_MYDRIVER" = "y"]; then
dep_tristate 'user interface' CONFIG_MYDRIVER_PROGRAM2 \$CONFIG_MYDRIVER
if ["$CONFIG_MYDRIVER_PROGRAM2" != "n"]; then
dep_tristate 'Increase dir funtion' CONFIG_MYDRIVER_DIR
\$CONFIG_MYDRIVER_PROGRAM2 $CONFIG_I2C
fi
fi
endmenu
/drivers/mydriver目录下最终生成的文件为mydriver.a。将program2.o放在export -objs列表中,是因为program.c中使用了EXPORT_SYMBOL输出符号(可以是变量或者是函数),以供其他内核模块调用。根据用户的选择构建obj -y、obj -m列表,然后将该列表中的目标文件连接成Mydriver.a。同时根据选择确定是否进入dir目录进行make。subdir -y表示需要进入该列表中的目录进行编译。drivers/mydriver/Makefile内容如下:
#
# Makefile for the mydriver
#
SUB_DIRS :=
MOD_SUB_DIRS := $ (SUB_DIRS)
ALL_SUB_DIRS := $ (SUB_DIRS) dir
L_TARGET := mydriver.a
export - objs := program2.o
obj -$ (CONFIG_MYDRIVER) += mydriver.o
obj -$ (CONFIG_MYDRIVER_PROGRAM2) += program2.o
subdir -$ (CONFIG_MYDRIVER_DIR) += dir
include $ (TOPDIR) /Rules. make
clean:
for dimame in $(ALL_SUB_DIRS); do make -C $$dimame
clean; done
rm -f *.[oa].*.flags
/drivers/mydriver/dir/Makefile内容如下:
#
# Makefile for the dir
#
SUB_DIRS :=
MOD_SUB_DIRS := $ (SUB_DIRS)
ALL_SUB_DIRS := $ (SUB_DIRS)
L_TARGET := programl.a
obj -$ (CONFIG_MYDRIVER_DIR) += programl.o
include $ (TOPDIR) /Rules. make
clean:
rm -f *.[oa].*.flags
同理,在/drivers/mydriver/dir/下生成文件program1.a。
在该Makefile中加入如下语句:
subdir -$ (CONFIG_MYDRIVER) += mydriver
这样在内核编译时才能够进入mydriver目录进行编译。drivers/Makefile的内容如下:
#
#Makefile for the Linux kernel device drivers.
#
…
subdir -$ (CONFIG_MYDRIVER) += mydriver
…
include $ (TOPDIR)/ Rules.make
在顶层Makefile目录下加入如下所示的两条语句:
…
DRIVERS -$ (CONFIG_MYDRIVER) += drivers/mydriver/mydriver.a
DRIVERS -$ (CONFIG_MYDRIVER) += drivers/mydriver/dir/program1.a
…
DRIVERS := $ (DRIVERS –y)
…
如果用户在配置菜单中选择了选项Mydriver support与Increase dir function(选该选项的前提是user interface和I2C support选项都被选择上),那么CONFIG_MYDRIVER和CONFIG_MYDRIVER_DIR的值都为y,mydriver.a和Program1.a就都位于DRIVERS -y列表中,然后通过如下命令把它们又包括在DRIVERS列表中:
DRIVERS := $ (DRIVERS –Y)
由于Linux内核映像文件vmLinux中包括DRIVERS,所以mydriver.a和programl.a最终就可以被链接到vmLinux中。
本文转载自以下链接,http://www.xici.net/d89591804.htm
感谢原文作者的无私奉献。