Linux内核配置及编译过程

时间:2021-03-23 16:35:21

几个重要的内核文件介绍

Linux服务器内核编译基础
--几个重要的RedHat Linux内核文件介绍

在网络中,不少服务器采用的是Linux系统。为了进一步提高服务器的性能,可能需要根据特定的硬件及需求重新编译Linux内核。编译Linux内核,需要根据规定的步骤进行,编译内核过程中涉及到几个重要的文件。比如对于RedHat Linux,在/boot目录下有一些与Linux内核有关的文件,进入/boot执行:ls –l,如图所示。编译过RedHat Linux内核的人对其中的System.map 、vmlinuz、initrd-2.4.7-10.img印象可能比较深刻,因为编译内核过程中涉及到这些文件的建立等操作。那么这几个文件是怎么产生的?又有什么作用呢?本文对此做些介绍。

一、vmlinuz
vmlinuz是可引导的、压缩的内核。“vm”代表“Virtual Memory”。Linux 支持虚拟内存,不像老的操作系统比如DOS有640KB内存的限制。Linux能够使用硬盘空间作为虚拟内存,因此得名“vm”。vmlinuz是可执行的Linux内核,它位于/boot/vmlinuz,它一般是一个软链接,比如图中是vmlinuz-2.4.7-10的软链接。
vmlinuz的建立有两种方式。一是编译内核时通过“make zImage”创建,然后通过:
“cp /usr/src/linux-2.4/arch/i386/linux/boot/zImage /boot/vmlinuz”产生。zImage适用于小内核的情况,它的存在是为了向后的兼容性。二是内核编译时通过命令make bzImage创建,然后通过:“cp /usr/src/linux-2.4/arch/i386/linux/boot/bzImage /boot/vmlinuz”产生。bzImage是压缩的内核映像,需要注意,bzImage不是用bzip2压缩的,bzImage中的bz容易引起误解,bz表示“big zImage”。 bzImage中的b是“big”意思。
zImage(vmlinuz)和bzImage(vmlinuz)都是用gzip压缩的。它们不仅是一个压缩文件,而且在这两个文件的开头部分内嵌有gzip解压缩代码。所以你不能用gunzip 或 gzip –dc解包vmlinuz。
内核文件中包含一个微型的gzip用于解压缩内核并引导它。两者的不同之处在于,老的zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么可以采用zImage 或bzImage之一,两种方式引导的系统运行时是相同的。大的内核采用bzImage,不能采用zImage。
vmlinux是未压缩的内核,vmlinuz是vmlinux的压缩文件。
二、 initrd-x.x.x.img
initrd是“initial ramdisk”的简写。initrd一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态。图中的initrd-2.4.7-10.img主要是用于加载ext3等文件系统及scsi设备的驱动。比如,使用的是scsi硬盘,而内核vmlinuz中并没有这个scsi硬件的驱动,那么在装入scsi模块之前,内核不能加载根文件系统,但scsi模块存储在根文件系统的/lib/modules下。为了解决这个问题,可以引导一个能够读实际内核的initrd内核并用initrd修正scsi引导问题。initrd-2.4.7-10.img是用gzip压缩的文件,下面来看一看这个文件的内容,操作步骤如下图所示:

从图中linuxrc这个脚本的内容可以看到,initrd实现加载一些模块和安装文件系统等。
initrd映象文件是使用mkinitrd创建的。mkinitrd实用程序能够创建initrd映象文件。这个命令是RedHat专有的。其它Linux发行版或许有相应的命令。这是个很方便的实用程序。具体情况请看帮助:man mkinitrd

下面的命令创建initrd映象文件:

三、 System.map
System.map是一个特定内核的内核符号表。它是你当前运行的内核的System.map的链接。
内核符号表是怎么创建的呢? System.map是由“nm vmlinux”产生并且不相关的符号被滤出。对于本文中的例子,编译内核时,System.map创建在/usr/src/linux-2.4/System.map。像下面这样:
nm /boot/vmlinux-2.4.7-10 > System.map
下面几行来自/usr/src/linux-2.4/Makefile:
nm vmlinux | grep -v '(compiled)|(.o$$)|( [aUw] )|(..ng$$)|(LASH[RL]DI)' | sort > System.map
然后复制到/boot:
cp /usr/src/linux/System.map /boot/System.map-2.4.7-10
下图是System.map文件的一部分:

在进行程序设计时,会命名一些变量名或函数名之类的符号。Linux内核是一个很复杂的代码块,有许许多多的全局符号。
Linux内核不使用符号名,而是通过变量或函数的地址来识别变量或函数名。比如不是使用size_t BytesRead这样的符号,而是像c0343f20这样引用这个变量。
对于使用计算机的人来说,更喜欢使用那些像size_t BytesRead这样的名字,而不喜欢像c0343f20这样的名字。内核主要是用c写的,所以编译器/连接器允许我们编码时使用符号名,当内核运行时使用地址。
然而,在有的情况下,我们需要知道符号的地址,或者需要知道地址对应的符号。这由符号表来完成,符号表是所有符号连同它们的地址的列表。上图就是一个内核符号表,由上图可知变量名checkCPUtype在内核地址c01000a5。
Linux 符号表使用到2个文件:
/proc/ksyms
System.map
下图是/proc/ksyms的一部分。
/proc/ksyms是一个“proc file”,在内核引导时创建。实际上,它并不真正的是一个文件,它只不过是内核数据的表示,却给人们是一个磁盘文件的假象,这从它的文件大小是0可以看出来。然而,System.map是存在于你的文件系统上的实际文件。当你编译一个新内核时,各个符号名的地址要发生变化,你的老的System.map具有的是错误的符号信息。每次内核编译时产生一个新的System.map,你应当用新的System.map来取代老的System.map。


虽然内核本身并不真正使用System.map,但其它程序比如klogd, lsof和ps等软件需要一个正确的System.map。如果你使用错误的或没有System.map,klogd的输出将是不可靠的,这对于排除程序故障会带来困难。没有System.map,你可能会面临一些令人烦恼的提示信息。
另外少数驱动需要System.map来解析符号,没有为你当前运行的特定内核创建的System.map它们就不能正常工作。
Linux的内核日志守护进程klogd为了执行名称-地址解析,klogd需要使用System.map。System.map应当放在使用它的软件能够找到它的地方。执行:man klogd可知,如果没有将System.map作为一个变量的位置给klogd,那么它将按照下面的顺序,在三个地方查找System.map:
/boot/System.map
/System.map
/usr/src/linux/System.map
System.map也有版本信息,klogd能够智能地查找正确的映象(map)文件。
 Makefile分析(2.4内核arm版)
一、内核源码中makefile文件的分类
      
       Linux-roy内核源码中的makefiles(不含动态生成的.flags文件)主要分为以下四类:
 
1. 主目录下的Makefile(不妨称为main-makefile)
它主要有两个作用:生成vmlinux(内核映像)和modules(模块)。
 
2. 主目录下的Rules.make
Rules.make中定义通用规则供main-makefile和subdir-makefiles调用。
变量subdir-y为用于构建vmlinux(内核)的目录,变量subdir-m为用于构建modules(模块)的目录;subdir-n和subdir-中的目录将不参加构建工作。
变量obj-y为内核的目标文件列表,在各subdir-y中会对其赋相应的值。如:lib/Makefile的第13-14行。变量obj-m为各模块的目标文件列表,在各subdir-m中会对其赋相应的值。如在drivers/Makefile中,在第13行对subdir-y赋值,并在第14行将此列表复制给subdir-m。从第17行开始对subdir-y和subdir-m进行补充,这两个列表的建立完成。obj-n和obj-中所有的目标将被忽略。
 
3. arch/*/目录下的Makefile(不妨称为arch-makefiles)
arch-makefiles是与特定的体系结构相关的,它们分别是针对特定CPU的makefiles。
 
4. 除arch外的子目录下的Makefile(不妨称为subdir-makefiles)
subdir-makefiles只负责在自己的目录中生成目标文件。它们接受来自上层Make传递下来的信息,并根据这些信息来构造一个需要编译的文件列表,并交由Rules.make处理。subdir-makefiles都比较简短,因为通用的规则都已在Rules.make定义了。一般subdir-makefiles的主要工作是对obj-*或subdir-*变量赋值。
 
二、内核和模块构建的过程
 
       下面分析内核编译的动态过程。
 
第一步:make ARCH=arm menuconfig
目标menuconfig的定义在main-makefile的第306行。main-makefile的第308行调用Menuconfig 工具(scripts/Menuconfig),让它根据arch/$(ARCH)/config.in文件的内容在主目录下生成.config文件供以后调用(main-makefile的第37行)。
 
第二步:make ARCH=arm CROSS_COMPILE=arm-linux- dep
include/linux/autoconf.h根据.config文件生成,其内容是若干#define和#undef宏。这些宏通知编译器哪些组件需要编译而哪些不需要,是编译进内核还是编译成模块。这两个文件中的变量是相互对应的。例如.config的第4行是:
CONFIG_ARM=y
这是编译进内核的情况。相应地,include/linux/autoconf.h的第5行是:
#define CONFIG_ARM 1
       编译成模块的例子见.config的第216行:
CONFIG_BINFMT_AOUT=m
       相应地,autoconf.h的第217和218行是:
#undef CONFIG_BINFMT_AOUT
#define CONFIG_BINFMT_AOUT_MODULE 1
 
接下来main-makefile调用split-include工具(scripts/split-include.c)(main-makefile的第314行),分割解析autoconf.h并在include/config目录下生成相应的头文件。autoconf.h的路径通过第一个参数传递给split-include(scripts/split-include.c的第71行)根据刚才提到的autoconf.h的第5行的内容,split-include工具会在include/config/目录下生成arm.h文件,其内容就是一行:
#define CONFIG_ARM 1
 
include/linux/config.h的第4行嵌入了autoconf.h,而内核源码中很多c文件又嵌入这个config.h(例如kernel/printk.c的第28行),于是间接地嵌入了autoconf.h,可见config.h起到的是一个桥梁的作用。
 
在这一步骤中,makefile会调用mkdep工具(scripts/mkdep.c)生成两种包含依赖关系的文件:.hdepend和.depend。
.hdepend只有一个,在主目录下,它表示相应的头文件又依赖于哪些其它的头文件。.hdepend是在main-makefile的第496行执行后生成的。如果存在.hdepend文件,Rules.make会嵌入它(Rules.make第295-297行)。
.depend文件有很多,它表示相应的目标文件依赖哪些源文件(*.c)和头文件(*.h)。主目录下的.depend文件是在main-makefile的第497行执行后生成的。mkdep读取*.c文件,分析出其所依赖的文件。下面以mm/page_alloc.c举例。page_alloc.c的第15-23行为(“…”表示省略):
#include <linux/config.h>
#include <linux/mm.h>

#include <linux/module.h>
mkdep就会在mm/.depend中的第110-118行中写入:
page_alloc.o: page_alloc.c /
   /usr/src/arm-linux/linux-roy/include/linux/mm.h /
   …
   /usr/src/arm-linux/linux-roy/include/linux/module.h /
但接下来的第119行和上面的写法有些不同:
$(wildcard /usr/src/arm-linux/linux-roy/include/config/discontigmem.h) /
   这是因为page_alloc.c中的第244行的内容如下:
#ifndef CONFIG_DISCONTIGMEM
这需要判断discontigmem.h的存在性,而mm目录下的.depend文件的第119行正是起到了这样的作用(若存在则返回文件名,若不存在则返回空)。
 
第三步:make ARCH=arm CROSS_COMPILE=arm-linux- clean
目标clean在main-makefile的第444-449行定义,它所清除文件的文件和目录列表(CLEAN_FILES和CLEAN_DIRS)在203-229行被赋值。
 
第四步:make ARCH=arm CROSS_COMPILE=arm-linux- zImage
make zImage执行后会生成一个用gzip压缩过的内核。对于arm类型的CPU,目标zImage是在arch/arm/boot/Makefile的第146行定义的。它依赖于compressed/vmlinux。而在第152行,又说明了compressed/vmlinux依赖于主目录下的vmlinux。它的生成规则在main-makefile的第281-289行:
vmlinux: include/linux/version.h $(CONFIGURATION) init/main.o init/version.o /
init/do_mounts.o linuxsubdirs
       $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o init/do_mounts.o /
              --start-group /
              $(CORE_FILES) /
              $(DRIVERS) /
              $(NETWORKS) /
              $(LIBS) /
              --end-group /
              -o vmlinux
ZLDFLAGS是在arch/arm/boot/Makefile的第14行定义的。如果我们指定CROSS_COMPILE 为arm-linux-,则LD为arm-linux-ld(main-makefile的第29行)。LINKFLAGS在arch/arm/Makefile的第10行定义:
LINKFLAGS :=-p -X -T arch/arm/vmlinux.lds
       它指定了链接脚本为arch/arm/vmlinux.lds。
 
make从主目录Makefile开始执行,从中获得与体系结构无关的变量和依赖关系,并同时从arch-makefiles中获得体系特定的变量等信息,这些信息继承并扩展了主目录Makefile提供的变量。先是所有通过“:=”赋值的变量在这个过程中被赋值,然后是所有通过“=”赋值的变量被赋值(实际上类似于宏替换)。然后,main-makefile向下递归调用子目录中的Makefile,把部分变量传递给子目录Makefile。此时构建内核需要的子目录Makefile根据配置信息决定编译哪些源文件,从而构建出一个需要编译的文件列表。在make zImage的时候make会递归进入列表subdir-y里的目录,根据其定义的编译规则决定这些文件的编译方式。然后,make会根据依赖关系树执行命令,编译生成一系列的目标文件,最后根据vmlinux的生成规则(main-makefile的第281-289行),通过链接脚本把这些目标文件链接成vmlinux(例如arch/arm/Makefile第267行)。
 
第五步:make ARCH=arm CROSS_COMPILE=arm-linux- modules
       make module用于生成内核模块(modules),它的规则在main-makefile的388-389行定义。在make modules的时候make会递归进入列表subdir-m里的目录。
vmlinux:
       第六步:make ARCH=arm CROSS_COMPILE=arm-linux- INSTALL_MOD_PATH=<root fs>modules_install
       make module用于安装内核模块,它的规则在main-makefile的396行定义。
 
三、几点说明
 
main-makefile的第37行是:
MAKEFILES = $(TOPDIR)/.config
       MAKEFILES是一个环境变量,如果你的当前环境中定义了MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include 不同的是,从这个环境变中引入的Makefile 中的目标不会起作用,如果环境变量中定义的文件发现错误,make 也会忽略。子目录中的Makefile与Rules.make都没有嵌入.config文件,它们就是通过main-makefile向下传递MAKEFILES变量得到相关信息的。
 
       在main-makefile的第48-67行所完成的工作是这样的:首先all被定义为第一个目标,然后在55-58行判断.config文件和.depend文件的存在性,如果存在就包含进来。如果.config文件不存在,则把do-it-all设为config(第66行)。目标conifg在第310行定义。如果.config文件存在而.depend文件不存在,则把do-it-all设为depend(第62行)。目标depend在第506行定义。在执行make后,主目录下会生成配置文件.config(如果原来存在.config文件,则被更名为.config.old)。
      
makemrproper用于重新构建内核,它做的清除工作比make clean更加彻底:除了做makeclean的工作外,还要删除.config,.depend等文件,把核心源码恢复到最原始的状态。它在main-makefile的第451-455行定义,它所清除文件的文件和目录列表(MRPROPER_FILES和MRPROPER_DIRS)在232-256行被赋值。
 
递归的make 必须使用MAKE变量,而不是直接用make 命令。如main-makefile的第279行:
@$(MAKE) CFLAGS="$(CFLAGS) $(CFLAGS_KERNEL)" -C arch/$(ARCH)/boot
当使用“-C”参数来make下层Makefile时,“-w”参数就会被自动打开,即在处理makefile
之前和之后,显示工作目录,所以在make的时候会看到屏幕上反复出现Entering directory[some dir]和Leaving directory [some dir]。
 
       一对反引号(`)的作用相当于shell函数,里面的内容是系统shell命令(例如main-make的第496行)。
 
如果在编译的第一步用的是xconfig,则可以参见第302行的定义。在xconfig中,会使用wish程序调用scripts目录下的tk脚本形成图形界面供用户配置。
 
在编译的第二步中,为什么要通过mkdep和split-include工具,而不是通过gcc加上-MM参数来建立依赖关系呢?上文提到过,很多c源文件都会通过linux/config.h而嵌入autoconf.h,如果按照通常方法建立文件依赖关系(.depend),只要更新过autoconf.h,就会造成所有源代码的重新编译。为了优化make过程,减少不必要的重新编译,linux开发了专用的mkdep工具,用它来生成.depend文件。配置内核时,split-include会检查旧的子文件的内容,确定是不是要更新它们。这样,不管autoconf.h修改日期如何,只要其配置不变,make就不会重新编译内核。
 
Rules.make把当前目录下所有的目标文件称为O_TARGET,它的生成规则在第96-102行,最终生成一系列的.o文件;把当前目录下所有的库文件称为L_TARGET,它的生成规则在第114-116行,最终生成一系列的.a文件。注意第62-65行、第104-107行、118-121行、279-282行和304-317行实现了一种增量编译的机制。make每编译一个源文件时会生成一个.flags文件。例如编译mm/swap.c时,会在相同的目录下生成.swap.o.flags文件。它本质上也是Makefile文件,其代码的功能是测试当前的编译选项与上次相比是否作过改动,若改动过就将自己对应的目标文件加入FILES_FLAGS_UP_TO_DATE列表。生成这个文件是供下一次编译用的。下次编译时,当make进入某个子目录,会搜索其中的.flags文件,如果存在则将它们嵌入(第309-312行)。Make会得到FILES_FLAGS_UP_TO_DATE列表,它的内容是不需要更新的目标。于是,系统从编译对象表中删除它们,从而得到FILES_FLAGS_CHANGED列表(第314-317行),这个列表的内容就是需要更新的目标。
 
       Main-makefile的第281-289行起定义vmlinux的生成(链接)规则。vmlinux 是由 HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 组成的。其中,CORE_FILES、NETWORKS、DRIVERS 和LIBS在main-makefile中定义,由 arch-makefiles扩充。而HEAD在arch-makefiles 中定义(例如arch/arm/Makefile的第193行)。HEAD列表中的目标文件需要最先被链接到 vmlinux 中。
       链接脚本(*.lds文件)是通过*.lds.in文件生成的,对于arm,就是vmlinux-armo.lds.in。arch/arm/Makefile的第269-274行定义了.lds的生成规则。vmlinux-armo.lds.in实际上是一个链接脚本模板,只不过相关参数没有确定。arch/arm/Makefile的第274行就是完成“填空”工作(为TEXTADDR、DATAADDR等赋上具体的值),并把完整的信息保存到arch/arm/vmlinux.lds中。
Linux 内核配置系统浅析 
 
 
  
内容:
 
配置系统的基本结构
Makefile
配置文件
实例
参考资料
关于作者
 
 
在 Linux 专区还有:
 
教程
工具与产品
代码与组件
文章
 
 
 
 
随着 Linux 操作系统的广泛应用,特别是 Linux 在嵌入式领域的发展,越来越多的人开始投身到 Linux 内核级的开发中。面对日益庞大的 Linux 内核源代码,开发者在完成自己的内核代码后,都将面临着同样的问题,即如何将源代码融入到 Linux 内核中,增加相应的 Linux 配置选项,并最终被编译进 Linux 内核。这就需要了解 Linux 的内核配置系统。

众所周知,Linux 内核是由分布在全球的 Linux 爱好者共同开发的,Linux 内核每天都面临着许多新的变化。但是,Linux 内核的组织并没有出现混乱的现象,反而显得非常的简洁,而且具有很好的扩展性,开发人员可以很方便的向 Linux 内核中增加新的内容。原因之一就是 Linux 采用了模块化的内核配置系统,从而保证了内核的扩展性。

本文首先分析了 Linux 内核中的配置系统结构,然后,解释了 Makefile 和配置文件的格式以及配置语句的含义,最后,通过一个简单的例子--TEST Driver,具体说明如何将自行开发的代码加入到 Linux 内核中。在下面的文章中,不可能解释所有的功能和命令,只对那些常用的进行解释,至于那些没有讨论到的,请读者参考后面的参考文献。

1. 配置系统的基本结构

Linux内核的配置系统由三个部分组成,分别是:

Makefile:分布在 Linux 内核源代码中的 Makefile,定义 Linux 内核的编译规则;
配置文件(config.in):给用户提供配置选择的功能;
配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供基于字符界面、基于 Ncurses 图形界面以及基于 Xwindows 图形界面的用户配置界面,各自对应于 Make config、Make menuconfig 和 make xconfig)。
这些配置工具都是使用脚本语言,如 Tcl/TK、Perl 编写的(也包含一些用 C 编写的代码)。本文并不是对配置系统本身进行分析,而是介绍如何使用配置系统。所以,除非是配置系统的维护者,一般的内核开发者无须了解它们的原理,只需要知道如何编写 Makefile 和配置文件就可以。所以,在本文中,我们只对 Makefile 和配置文件进行讨论。另外,凡是涉及到与具体 CPU 体系结构相关的内容,我们都以 ARM 为例,这样不仅可以将讨论的问题明确化,而且对内容本身不产生影响。

2. Makefile

2.1 Makefile 概述

Makefile 的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成 Linux 内核二进制文件。

由于 Linux 内核源代码是按照树形结构组织的,所以 Makefile 也被分布在目录树中。Linux 内核中的 Makefile 以及与 Makefile 直接相关的文件有:

Makefile:顶层 Makefile,是整个内核配置、编译的总体控制文件。
.config:内核配置文件,包含由用户选择的配置选项,用来存放内核配置后的结果(如 make config)。
arch/*/Makefile:位于各种 CPU 体系目录下的 Makefile,如 arch/arm/Makefile,是针对特定平台的 Makefile。
各个子目录下的 Makefile:比如 drivers/Makefile,负责所在子目录下源代码的管理。
Rules.make:规则文件,被所有的 Makefile 使用。
用户通过 make config 配置后,产生了 .config。顶层 Makefile 读入 .config 中的配置选择。顶层 Makefile 有两个主要的任务:产生 vmlinux 文件和内核模块(module)。为了达到此目的,顶层 Makefile 递归的进入到内核的各个子目录中,分别调用位于这些子目录中的 Makefile。至于到底进入哪些子目录,取决于内核的配置。在顶层 Makefile 中,有一句:include arch/$(ARCH)/Makefile,包含了特定 CPU 体系结构下的 Makefile,这个 Makefile 中包含了平台相关的信息。

位于各个子目录下的 Makefile 同样也根据 .config 给出的配置信息,构造出当前配置下需要的源文件列表,并在文件的最后有 include $(TOPDIR)/Rules.make。

Rules.make 文件起着非常重要的作用,它定义了所有 Makefile 共用的编译规则。比如,如果需要将本目录下所有的 c 程序编译成汇编代码,需要在 Makefile 中有以下的编译规则:

        %.s: %.c        $(CC) $(CFLAGS) -S ___FCKpd___0lt; -o $@       
有很多子目录下都有同样的要求,就需要在各自的 Makefile 中包含此编译规则,这会比较麻烦。而 Linux 内核中则把此类的编译规则统一放置到 Rules.make 中,并在各自的 Makefile 中包含进了 Rules.make(include Rules.make),这样就避免了在多个 Makefile 中重复同样的规则。对于上面的例子,在 Rules.make 中对应的规则为:

        %.s: %.c        $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F)) $(CFLAGS_$@) -S ___FCKpd___1lt; -o $@       
2.2 Makefile 中的变量

顶层 Makefile 定义并向环境中输出了许多变量,为各个子目录下的 Makefile 传递一些信息。有些变量,比如 SUBDIRS,不仅在顶层 Makefile 中定义并且赋初值,而且在 arch/*/Makefile 还作了扩充。

常用的变量有以下几类:

1) 版本信息
版本信息有:VERSION,PATCHLEVEL, SUBLEVEL, EXTRAVERSION,KERNELRELEASE。版本信息定义了当前内核的版本,比如 VERSION=2,PATCHLEVEL=4,SUBLEVEL=18,EXATAVERSION=-rmk7,它们共同构成内核的发行版本KERNELRELEASE:2.4.18-rmk7

2) CPU 体系结构:ARCH
在顶层 Makefile 的开头,用 ARCH 定义目标 CPU 的体系结构,比如 ARCH:=arm 等。许多子目录的 Makefile 中,要根据 ARCH 的定义选择编译源文件的列表。

3) 路径信息:TOPDIR, SUBDIRS
TOPDIR 定义了 Linux 内核源代码所在的根目录。例如,各个子目录下的 Makefile 通过 $(TOPDIR)/Rules.make 就可以找到 Rules.make 的位置。
SUBDIRS 定义了一个目录列表,在编译内核或模块时,顶层 Makefile 就是根据 SUBDIRS 来决定进入哪些子目录。SUBDIRS 的值取决于内核的配置,在顶层 Makefile 中 SUBDIRS 赋值为 kernel drivers mm fs net ipc lib;根据内核的配置情况,在 arch/*/Makefile 中扩充了 SUBDIRS 的值,参见4)中的例子。

4) 内核组成信息:HEAD, CORE_FILES, NETWORKS, DRIVERS, LIBS
Linux 内核文件 vmlinux 是由以下规则产生的:


vmlinux: $(CONFIGURATION) 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、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 组成的。这些变量(如 HEAD)都是用来定义连接生成 vmlinux 的目标文件和库文件列表。其中,HEAD在arch/*/Makefile 中定义,用来确定被最先链接进 vmlinux 的文件列表。比如,对于 ARM 系列的 CPU,HEAD 定义为:
HEAD            := arch/arm/kernel/head-$(PROCESSOR).o /                   arch/arm/kernel/init_task.o
表明 head-$(PROCESSOR).o 和 init_task.o 需要最先被链接到 vmlinux 中。PROCESSOR 为 armv 或 armo,取决于目标 CPU。 CORE_FILES,NETWORK,DRIVERS 和 LIBS 在顶层 Makefile 中定义,并且由 arch/*/Makefile 根据需要进行扩充。 CORE_FILES 对应着内核的核心文件,有 kernel/kernel.o,mm/mm.o,fs/fs.o,ipc/ipc.o,可以看出,这些是组成内核最为重要的文件。同时,arch/arm/Makefile 对 CORE_FILES 进行了扩充:

# arch/arm/Makefile# If we have a machine-specific directory, then include it in the build.MACHDIR         := arch/arm/mach-$(MACHINE)ifeq ($(MACHDIR),$(wildcard $(MACHDIR)))SUBDIRS         += $(MACHDIR)CORE_FILES      := $(MACHDIR)/$(MACHINE).o $(CORE_FILES)endifHEAD            := arch/arm/kernel/head-$(PROCESSOR).o /                   arch/arm/kernel/init_task.oSUBDIRS         += arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpeCORE_FILES      := arch/arm/kernel/kernel.o arch/arm/mm/mm.o $(CORE_FILES)LIBS            := arch/arm/lib/lib.a $(LIBS)

5) 编译信息:CPP, CC, AS, LD, AR,CFLAGS,LINKFLAGS
在 Rules.make 中定义的是编译的通用规则,具体到特定的场合,需要明确给出编译环境,编译环境就是在以上的变量中定义的。针对交叉编译的要求,定义了 CROSS_COMPILE。比如:


CROSS_COMPILE   = arm-linux-CC              = $(CROSS_COMPILE)gccLD              = $(CROSS_COMPILE)ld......
CROSS_COMPILE 定义了交叉编译器前缀 arm-linux-,表明所有的交叉编译工具都是以 arm-linux- 开头的,所以在各个交叉编译器工具之前,都加入了 $(CROSS_COMPILE),以组成一个完整的交叉编译工具文件名,比如 arm-linux-gcc。
CFLAGS 定义了传递给 C 编译器的参数。
LINKFLAGS 是链接生成 vmlinux 时,由链接器使用的参数。LINKFLAGS 在 arm/*/Makefile 中定义,比如:

# arch/arm/MakefileLINKFLAGS       :=-p -X -T arch/arm/vmlinux.lds

6) 配置变量CONFIG_*
.config 文件中有许多的配置变量等式,用来说明用户配置的结果。例如 CONFIG_MODULES=y 表明用户选择了 Linux 内核的模块功能。
.config 被顶层 Makefile 包含后,就形成许多的配置变量,每个配置变量具有确定的值:y 表示本编译选项对应的内核代码被静态编译进 Linux 内核;m 表示本编译选项对应的内核代码被编译成模块;n 表示不选择此编译选项;如果根本就没有选择,那么配置变量的值为空。

2.3 Rules.make 变量

前面讲过,Rules.make 是编译规则文件,所有的 Makefile 中都会包括 Rules.make。Rules.make 文件定义了许多变量,最为重要是那些编译、链接列表变量。

O_OBJS,L_OBJS,OX_OBJS,LX_OBJS:本目录下需要编译进 Linux 内核 vmlinux 的目标文件列表,其中 OX_OBJS 和 LX_OBJS 中的 "X" 表明目标文件使用了 EXPORT_SYMBOL 输出符号。

M_OBJS,MX_OBJS:本目录下需要被编译成可装载模块的目标文件列表。同样,MX_OBJS 中的 "X" 表明目标文件使用了 EXPORT_SYMBOL 输出符号。

O_TARGET,L_TARGET:每个子目录下都有一个 O_TARGET 或 L_TARGET,Rules.make 首先从源代码编译生成 O_OBJS 和 OX_OBJS 中所有的目标文件,然后使用 $(LD) -r 把它们链接成一个 O_TARGET 或 L_TARGET。O_TARGET 以 .o 结尾,而 L_TARGET 以 .a 结尾。

2.4 子目录 Makefile

子目录 Makefile 用来控制本级目录以下源代码的编译规则。我们通过一个例子来讲解子目录 Makefile 的组成:

## Makefile for the linux kernel.## All of the (potential) objects that export symbols.# This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'.export-objs := tc.o# Object file lists.obj-y  :=obj-m  :=obj-n  :=obj-  :=obj-$(CONFIG_TC) += tc.oobj-$(CONFIG_ZS) += zs.oobj-$(CONFIG_VT) += lk201.o lk201-map.o lk201-remap.o# Files that are both resident and modular: remove from modular.obj-m  := $(filter-out $(obj-y), $(obj-m))# Translate to Rules.make lists.L_TARGET := tc.aL_OBJS  := $(sort $(filter-out $(export-objs), $(obj-y)))LX_OBJS  := $(sort $(filter     $(export-objs), $(obj-y)))M_OBJS  := $(sort $(filter-out $(export-objs), $(obj-m)))MX_OBJS  := $(sort $(filter     $(export-objs), $(obj-m)))include $(TOPDIR)/Rules.make
a) 注释
对 Makefile 的说明和解释,由#开始。

b) 编译目标定义
类似于 obj-$(CONFIG_TC) += tc.o 的语句是用来定义编译的目标,是子目录 Makefile 中最重要的部分。编译目标定义那些在本子目录下,需要编译到 Linux 内核中的目标文件列表。为了只在用户选择了此功能后才编译,所有的目标定义都融合了对配置变量的判断。
前面说过,每个配置变量取值范围是:y,n,m 和空,obj-$(CONFIG_TC) 分别对应着 obj-y,obj-n,obj-m,obj-。如果 CONFIG_TC 配置为 y,那么 tc.o 就进入了 obj-y 列表。obj-y 为包含到 Linux 内核 vmlinux 中的目标文件列表;obj-m 为编译成模块的目标文件列表;obj-n 和 obj- 中的文件列表被忽略。配置系统就根据这些列表的属性进行编译和链接。
export-objs 中的目标文件都使用了 EXPORT_SYMBOL() 定义了公共的符号,以便可装载模块使用。在 tc.c 文件的最后部分,有 "EXPORT_SYMBOL(search_tc_card);",表明 tc.o 有符号输出。
这里需要指出的是,对于编译目标的定义,存在着两种格式,分别是老式定义和新式定义。老式定义就是前面 Rules.make 使用的那些变量,新式定义就是 obj-y,obj-m,obj-n 和 obj-。Linux 内核推荐使用新式定义,不过由于 Rules.make 不理解新式定义,需要在 Makefile 中的适配段将其转换成老式定义。

c) 适配段
适配段的作用是将新式定义转换成老式定义。在上面的例子中,适配段就是将 obj-y 和 obj-m 转换成 Rules.make 能够理解的 L_TARGET,L_OBJS,LX_OBJS,M_OBJS,MX_OBJS。
L_OBJS := $(sort $(filter-out $(export-objs), $(obj-y))) 定义了 L_OBJS 的生成方式:在 obj-y 的列表中过滤掉 export-objs(tc.o),然后排序并去除重复的文件名。这里使用到了 GNU Make 的一些特殊功能,具体的含义可参考 Make 的文档(info make)。

d) include $(TOPDIR)/Rules.make

3. 配置文件

3.1 配置功能概述

除了 Makefile 的编写,另外一个重要的工作就是把新功能加入到 Linux 的配置选项中,提供此项功能的说明,让用户有机会选择此项功能。所有的这些都需要在 config.in 文件中用配置语言来编写配置脚本,
在 Linux 内核中,配置命令有多种方式:

配置命令 解释脚本
Make config, make oldconfig scripts/Configure
Make menuconfig scripts/Menuconfig
Make xconfig scripts/tkparse

以字符界面配置(make config)为例,顶层 Makefile 调用 scripts/Configure, 按照 arch/arm/config.in 来进行配置。命令执行完后产生文件 .config,其中保存着配置信息。下一次再做 make config 将产生新的 .config 文件,原 .config 被改名为 .config.old

3.2 配置语言

1) 顶层菜单
mainmenu_name /prompt/ /prompt/ 是用'或"包围的字符串,'与"的区别是'…'中可使用$引用变量的值。mainmenu_name 设置最高层菜单的名字,它只在 make xconfig 时才会显示。

2) 询问语句

     bool            /prompt/ /symbol/        hex             /prompt/ /symbol/ /word/        int             /prompt/ /symbol/ /word/        string          /prompt/ /symbol/ /word/        tristate        /prompt/ /symbol/
询问语句首先显示一串提示符 /prompt/,等待用户输入,并把输入的结果赋给 /symbol/ 所代表的配置变量。不同的询问语句的区别在于它们接受的输入数据类型不同,比如 bool 接受布尔类型( y 或 n ),hex 接受 16 进制数据。有些询问语句还有第三个参数 /word/,用来给出缺省值。
 

3) 定义语句

        define_bool     /symbol/ /word/        define_hex      /symbol/ /word/        define_int      /symbol/ /word/        define_string   /symbol/ /word/        define_tristate /symbol/ /word/
不同于询问语句等待用户输入,定义语句显式的给配置变量 /symbol/ 赋值 /word/。
 

4) 依赖语句

        dep_bool        /prompt/ /symbol/ /dep/ ...        dep_mbool       /prompt/ /symbol/ /dep/ ...        dep_hex         /prompt/ /symbol/ /word/ /dep/ ...        dep_int         /prompt/ /symbol/ /word/ /dep/ ...        dep_string      /prompt/ /symbol/ /word/ /dep/ ...        dep_tristate    /prompt/ /symbol/ /dep/ ...
与询问语句类似,依赖语句也是定义新的配置变量。不同的是,配置变量/symbol/的取值范围将依赖于配置变量列表/dep/ …。这就意味着:被定义的配置变量所对应功能的取舍取决于依赖列表所对应功能的选择。以dep_bool为例,如果/dep/ …列表的所有配置变量都取值y,则显示/prompt/,用户可输入任意的值给配置变量/symbol/,但是只要有一个配置变量的取值为n,则/symbol/被强制成n。
不同依赖语句的区别在于它们由依赖条件所产生的取值范围不同。
 

5) 选择语句


choice          /prompt/ /word/ /word/
choice 语句首先给出一串选择列表,供用户选择其中一种。比如 Linux for ARM 支持多种基于 ARM core 的 CPU,Linux 使用 choice 语句提供一个 CPU 列表,供用户选择:

         choice 'ARM system type' /        "Anakin                 CONFIG_ARCH_ANAKIN /         Archimedes/A5000       CONFIG_ARCH_ARCA5K /         Cirrus-CL-PS7500FE     CONFIG_ARCH_CLPS7500 /   ……         SA1100-based           CONFIG_ARCH_SA1100 /         Shark                  CONFIG_ARCH_SHARK" RiscPC        
Choice 首先显示 /prompt/,然后将 /word/ 分解成前后两个部分,前部分为对应选择的提示符,后部分是对应选择的配置变量。用户选择的配置变量为 y,其余的都为 n。
 

6) if语句


        if [ /expr/ ] ; then          /statement/           ...        fi                if [ /expr/ ] ; then          /statement/          ...        else          /statement/          ...        fi       
if 语句对配置变量(或配置变量的组合)进行判断,并作出不同的处理。判断条件 /expr/ 可以是单个配置变量或字符串,也可以是带操作符的表达式。操作符有:=,!=,-o,-a 等。
 

7) 菜单块(menu block)语句

mainmenu_option next_commentcomment '…..'…endmenu
引入新的菜单。在向内核增加新的功能后,需要相应的增加新的菜单,并在新菜单下给出此项功能的配置选项。Comment 后带的注释就是新菜单的名称。所有归属于此菜单的配置选项语句都写在 comment 和 endmenu 之间。
 

8) Source 语句
source /word/
/word/ 是文件名,source 的作用是调入新的文件。

3.3 缺省配置

Linux 内核支持非常多的硬件平台,对于具体的硬件平台而言,有些配置就是必需的,有些配置就不是必需的。另外,新增加功能的正常运行往往也需要一定的先决条件,针对新功能,必须作相应的配置。因此,特定硬件平台能够正常运行对应着一个最小的基本配置,这就是缺省配置。

Linux 内核中针对每个 ARCH 都会有一个缺省配置。在向内核代码增加了新的功能后,如果新功能对于这个 ARCH 是必需的,就要修改此 ARCH 的缺省配置。修改方法如下(在 Linux 内核根目录下):

备份 .config 文件
cp arch/arm/deconfig .config
修改 .config
cp .config arch/arm/deconfig
恢复 .config
如果新增的功能适用于许多的 ARCH,只要针对具体的 ARCH,重复上面的步骤就可以了。

3.4 help file

大家都有这样的经验,在配置 Linux 内核时,遇到不懂含义的配置选项,可以查看它的帮助,从中可得到选择的建议。下面我们就看看如何给给一个配置选项增加帮助信息。

所有配置选项的帮助信息都在 Documentation/Configure.help 中,它的格式为:

<description><variable name><help file>
<description> 给出本配置选项的名称,<variable name> 对应配置变量,<help file> 对应配置帮助信息。在帮助信息中,首先简单描述此功能,其次说明选择了此功能后会有什么效果,不选择又有什么效果,最后,不要忘了写上"如果不清楚,选择 N(或者)Y",给不知所措的用户以提示。

4. 实例

对于一个开发者来说,将自己开发的内核代码加入到 Linux 内核中,需要有三个步骤。首先确定把自己开发代码放入到内核的位置;其次,把自己开发的功能增加到 Linux 内核的配置选项中,使用户能够选择此功能;最后,构建子目录 Makefile,根据用户的选择,将相应的代码编译到最终生成的 Linux 内核中去。下面,我们就通过一个简单的例子--test driver,结合前面学到的知识,来说明如何向 Linux 内核中增加新的功能。

4.1 目录结构

test driver 放置在 drivers/test/ 目录下:

$cd drivers/test$tree.|-- Config.in|-- Makefile|-- cpu|   |-- Makefile|   `-- cpu.c|-- test.c|-- test_client.c|-- test_ioctl.c|-- test_proc.c|-- test_queue.c`-- test    |-- Makefile    `-- test.c   
4.2 配置文件

1) drivers/test/Config.in

## TEST driver configuration#mainmenu_option next_commentcomment 'TEST Driver'bool 'TEST support' CONFIG_TESTif [ "$CONFIG_TEST" = "y" ]; then  tristate 'TEST user-space interface' CONFIG_TEST_USER  bool 'TEST CPU ' CONFIG_TEST_CPUfiendmenu
由于 test driver 对于内核来说是新的功能,所以首先创建一个菜单 TEST Driver。然后,显示 "TEST support",等待用户选择;接下来判断用户是否选择了 TEST Driver,如果是(CONFIG_TEST=y),则进一步显示子功能:用户接口与 CPU 功能支持;由于用户接口功能可以被编译成内核模块,所以这里的询问语句使用了 tristate(因为 tristate 的取值范围包括 y、n 和 m,m 就是对应着模块)。
 

2) arch/arm/config.in
在文件的最后加入:source drivers/test/Config.in,将 TEST Driver 子功能的配置纳入到 Linux 内核的配置中。

4.3 Makefile

1)drivers/test/Makefile


#       drivers/test/Makefile##       Makefile for the TEST.#SUB_DIRS     :=MOD_SUB_DIRS := $(SUB_DIRS)ALL_SUB_DIRS := $(SUB_DIRS) cpuL_TARGET := test.aexport-objs := test.o test_client.oobj-$(CONFIG_TEST)              += test.o test_queue.o test_client.oobj-$(CONFIG_TEST_USER)         += test_ioctl.oobj-$(CONFIG_PROC_FS)           += test_proc.osubdir-$(CONFIG_TEST_CPU)       += cpuinclude $(TOPDIR)/Rules.makeclean:        for dir in $(ALL_SUB_DIRS); do make -C $dir clean; done        rm -f *.[oa] .*.flags       
drivers/test 目录下最终生成的目标文件是 test.a。在 test.c 和 test-client.c 中使用了 EXPORT_SYMBOL 输出符号,所以 test.o 和 test-client.o 位于 export-objs 列表中。然后,根据用户的选择(具体来说,就是配置变量的取值),构建各自对应的 obj-* 列表。由于 TEST Driver 中包一个子目录 cpu,当 CONFIG_TEST_CPU=y(即用户选择了此功能)时,需要将 cpu 目录加入到 subdir-y 列表中。
 

2)drivers/test/cpu/Makefile

#       drivers/test/test/Makefile##       Makefile for the TEST CPU #SUB_DIRS     :=MOD_SUB_DIRS := $(SUB_DIRS)ALL_SUB_DIRS := $(SUB_DIRS)L_TARGET := test_cpu.aobj-$(CONFIG_test_CPU)       += cpu.oinclude $(TOPDIR)/Rules.makeclean:        rm -f *.[oa] .*.flags       

3)drivers/Makefile

……subdir-$(CONFIG_TEST)  += test……include $(TOPDIR)/Rules.make
在 drivers/Makefile 中加入 subdir-$(CONFIG_TEST)+= test,使得在用户选择 TEST Driver 功能后,内核编译时能够进入 test 目录。
 

4)Makefile

……DRIVERS-$(CONFIG_PLD) += drivers/pld/pld.oDRIVERS-$(CONFIG_TEST) += drivers/test/test.aDRIVERS-$(CONFIG_TEST_CPU) += drivers/test/cpu/test_cpu.aDRIVERS := $(DRIVERS-y)……
在顶层 Makefile 中加入 DRIVERS-$(CONFIG_TEST) += drivers/test/test.a 和 DRIVERS-$(CONFIG_TEST_CPU) += drivers/test/cpu/test_cpu.a。如何用户选择了 TEST Driver,那么 CONFIG_TEST 和 CONFIG_TEST_CPU 都是 y,test.a 和 test_cpu.a 就都位于 DRIVERS-y 列表中,然后又被放置在 DRIVERS 列表中。在前面曾经提到过,Linux 内核文件 vmlinux 的组成中包括 DRIVERS,所以 test.a 和 test_cpu.a 最终可被链接到 vmlinux 中。 


Linux 内核编译说明

1.Linux内核源码结构:
内核源码中主要包含以下子目录:
arch :包含了与体系结构相关的代码
对应于每一个支持的体系结构,有一个相应的子目录如i386、arm、alpha等。
其每个体系结构子目录下包含几个主要的子目录:
kernel :包含与体系结构相关的内核代码
mm : 包含与体系结构相关的内存管理代码
lib : 包含与体系结构相关的库代码
documentation:包含内核的文档
drivers :包含设备驱动代码。每类设备有相应的子目录,如char 、block、net等
fs : 包含文件系统的代码。每个支持的文件系统有相应的子目录,如 ext2、proc等
include :内核头文件,对每一种体系结构,分别有相应的子目录。
init : 包含内核初始化代码
lib : 包含内核的库代码
mm :包含内存管理代码
kernel :包含内核管理代码
net :包含网络部分的代码

2.系统引导的过程
在pc机上系统启动过程:
系统加电以后bois对系统完成监测设置后将控制权交给硬盘上MBR中的 BootLoader在这里即是lilo或grub等。
BootLoader 将操作系统代码调入内存,然后将控制权交给arch/i386/boot中的Setup.S这段程序。
Setup.S 这段程序在386实模式下对系统进行基本的检测和设置后转入保护模式把控制权交给Head.S
Head.S 建立内存管理和中断管理的框架后调用init/main.c中的start_kernel()函数在start_kernel执行完成后用户就可以登录和使用linux了。Start_kernel()函数在init/main.c 中定义。
Start_kernel的流程中的主要步骤:
setup_arch(&command_line); 用于和处理器、内存等最基本的硬件相关部分的初始化。 在 arch/i386/kernel/setup.c 中定义;
parse_options(command_line); 把启动时得到的参数从命令行的字符串中分离出来并赋给相应的变量。在 init/main.c 中定义;
trap_init(); 对中断向量表进行初始化。在 arch/i386/kernel/trap.c 中定义;
init_IRQ(); 与中断有关的初始化, 在 arch/i386/kernel/i8259.c 中定义;
sched_init(); 进程调度初始化。 在 kernel/sched.c 中定义;
softirq_init(); 在 kernel/softirq.c 中定义;
time_init(); 时间部分初始化。 在 arch/i386/kernel/time.c 中定义;
console_init(); 对终端初始化。 在 drivers/char/tty_io.c 中定义;
buffer_init(mempages); 对用于指示块缓存的buff free list 初始化。 在fs/buffer.c 中定义;
mem_init(); 内存管理初始化。 在 arch/i386/mm/init.c中定义;
rest_init(); 此函数中调用
kernel_thread(init,NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)函数时会调用init/main.c中的init()函数 在init()函数中将会建立dbflush、kswapd两个新的内核线程。初始化tty1设备。寻找/etc/init或/sbin/init 或/bin/init来建立一个init进程。
Init进程根据/etc/inittab文件进行文件系统检查、启动系统守护进程为联机终端建立getty进程,执行/etc/rc下的命令文件。
此后getty会在终端上显示login提示符,以等待用户登录。

3.使用make建立内核
1.使用make menuconfig命令:
使用以下编译选项:
Processor type and features --->
(Pentium-Pro/Celeron/Pentium-II) Processor family
(3GB) Maximum Virtual Memory
General setup --->
(ELF) Kernel core (/proc/kcore) format
[*] Kernel support for ELF binaries
File systems --->
[*] /proc file system support
[*] Second extended fs support
ATA/IDE/MFM/RLL support --->
[*] ATA/IDE/MFM/RLL support
[*] Enhanced IDE/MFM/RLL disk/cdrom/tape/floppy support
[*] Include IDE/ATA-2 DISK support
Character devices --->
[*] Virtual terminal
Console drivers --->
[*] VGA text console

生成内核bzImage 大小为 309892 byte;
此内核可在pc上成功引导系统

此命令生成一个文件 .config 其中根据你在menuconfig中的选择定义了相应的变量。在Makefile文件中将会包含这个文件。

2.使用make dep命令 建立依赖关系。

3.使用make bzImage 命令建立内核。
如设置正确将在arch/i386/boot/目录下生成内核bzImage文件
4.make bzImage的流程简单说明
当我们使用make命令时,make程序将首先找到当前目录下的Makefile文件。根据Makefile文件的语法进行处理。
在主Makefile文件中包含了arch/i386/Makefile我们make的目标bzImage即在该文件中定:
bzImage: vmlinux
@$(MAKEBOOT) bzImage #此命令将解释为:make -C arch/i386/boot bzImage
现在make需要先去建立目标 vmlinux 然后再执行 arch/i386/boot/ 目录下的 make bzImae。我们现在假设vmlinux目标已生成,则 arch/i386/boot目录下的make程序将执行如下操作:
tools/build -b bbootsect bsetup compressed/bvmlinux.out ./ bzImage
即将vmlinux 用 tools/build工具压缩成目标文件 bzImage(在此过程中,还会构建build 程序,将vmlinux转换成bvmlinux.out等,可参考tools 和 compressed目录下的Makefile文件)。

生成vmlinux目标:
在主目录下Makefile文件中vmlinux生成规则如下:
vmlinux: $(CONFIGURATION) 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
$(NM) vmlinux |grep –v /(compiled/)/|/(/.o$$/)/|/( [aUw] /)/|/(/./.ng$$/)/|/(LASH[RL]DI/) | sort > System.map #此命令根据vmlinux生成System.map文件

在当前设置下此ld连接命令被解释为:

ld -m elf_i386 -T /home/arm/linux/arch/i386/vmlinux.lds -e stext arch/i386/kernel/head.o arch/i386/kernel/init_task.o init/main.o init/version.o --start-group arch/i386/kernel/kernel.o arch/i386/mm/mm.o kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o drivers/char/char.o drivers/block/block.o drivers/misc/misc.o drivers/net/net.o drivers/media/media.o drivers/ide/idedriver.o drivers/video/video.o net/network.o /home/arm/linux/arch/i386/lib/lib.a /home/arm/linux/lib/lib.a /home/arm/linux/arch/i386/lib/lib.a --end-group -o vmlinux

即连接程序ld 将各个.o文件连接成目标文件 vmlinux 。
此命令中用到的各个 .o文件make程序会根据Makefile文件的规则去自动生成,下面简单介绍一下由ipc目录生成ipc.o过程:
其ipc目录下Makefile文件内容如下:
O_TARGET := ipc.o
obj-y := util.o
obj-$(CONFIG_SYSVIPC) += msg.o sem.o shm.o
include $(TOPDIR)/Rules.make #包含的Rules.make文件中为通用的规则;

如我们在make menuconfig时选中了SYSVIPC选项,则 .config文件中将定义变量 CONFIG_SYSVIPC=y; 则obj-y 就等于util.o msg.o sem.o shm.o;根据Rules.make将四个.c文件编译为 .o文件。再将四个.o文件连接成 ipc.o文件。在我们的当前设置中没有选择SYSVIPC;将只使用util.o一个文件生成目标 ipc.o ;而util.o由util.c生成。

当所有需要的.o文件生成以后,由ld将其连接生成vmlinux文件,再将其压缩成我们所需要的内核文件 bzImage。Make程序就执行完了。

Linux内核启动地址

内核编译链接过程是依靠vmlinux.lds文件,以arm为例vmlinux.lds文件位于kernel/arch/arm/vmlinux.lds,但是该文件是由vmlinux-armv.lds.in生成的,根据编译选项的不同源文件还可以是vmlinux-armo.lds.in,vmlinux-armv-xip.lds.in。
vmlinux-armv.lds的生成过程在kernel/arch/arm/Makefile中
LDSCRIPT     = arch/arm/vmlinux-armv.lds.in
arch/arm/vmlinux.lds: arch/arm/Makefile $(LDSCRIPT) /
 $(wildcard include/config/cpu/32.h) /
 $(wildcard include/config/cpu/26.h) /
 $(wildcard include/config/arch/*.h)
 @echo '  Generating $@'
 @sed 's/TEXTADDR/$(TEXTADDR)/;s/DATAADDR/$(DATAADDR)/' $(LDSCRIPT) >$@
vmlinux-armv.lds.in文件的内容:
OUTPUT_ARCH(arm)
ENTRY(stext)
SECTIONS
{
    . = TEXTADDR;
    .init : {           /* Init code and data       */
        _stext = .;
        __init_begin = .;
            *(.text.init)
        __proc_info_begin = .;
            *(.proc.info)
        __proc_info_end = .;
        __arch_info_begin = .;
            *(.arch.info)
        __arch_info_end = .;
        __tagtable_begin = .;
            *(.taglist)
        __tagtable_end = .;
            *(.data.init)
        . = ALIGN(16);
        __setup_start = .;
            *(.setup.init)
        __setup_end = .;
        __initcall_start = .;
            *(.initcall.init)
        __initcall_end = .;
        . = ALIGN(4096);
        __init_end = .;
    }
   
其中TEXTADDR就是内核启动的虚拟地址,定义在kernel/arch/arm/Makefile中:
ifeq ($(CONFIG_CPU_32),y)
PROCESSOR    = armv
TEXTADDR     = 0xC0008000
LDSCRIPT     = arch/arm/vmlinux-armv.lds.in
endif

需要注意的是这里是虚拟地址而不是物理地址。
一般情况下都在生成vmlinux后,再对内核进行压缩成为zImage,压缩的目录是kernel/arch/arm/boot。
下载到flash中的是压缩后的zImage文件,zImage是由压缩后的vmlinux和解压缩程序组成,如下图所示:
            |------------------|/    |------------------|
            |                     | /   |                     |
            |                     |  /  | decompress code |
            |     vmlinux     |   / |------------------|    zImage
            |                     |    /|                     |
            |                     |     |                     |
            |                     |     |                     |   
            |                     |     |                     |
            |                     |    /|------------------|
            |                     |   /
            |                     |  /
            |                     | /
            |------------------|/

zImage链接脚本也叫做vmlinux.lds,位于kernel/arch/arm/boot/compressed。
是由同一目录下的vmlinux.lds.in文件生成的,内容如下:
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
 {
   . = LOAD_ADDR;
   _load_addr = .;
 
   . = TEXT_START;
   _text = .;
 
   .text : {
     _start = .;
    
其中LOAD_ADDR就是zImage中解压缩代码的ram偏移地址,TEXT_START是内核ram启动的偏移地址,这个地址是物理地址。
在kernel/arch/arm/boot/Makefile文件中定义了:
ZTEXTADDR   =0
ZRELADDR     = 0xa0008000
ZTEXTADDR就是解压缩代码的ram偏移地址,ZRELADDR是内核ram启动的偏移地址,这里看到指定ZTEXTADDR的地址为0,
明显是不正确的,因为我的平台上的ram起始地址是0xa0000000,在Makefile文件中看到了对该地址设置的几行注释:
# We now have a PIC decompressor implementation.  Decompressors running
# from RAM should not define ZTEXTADDR.  Decompressors running directly
# from ROM or Flash must define ZTEXTADDR (preferably via the config)
他的意识是如果是在ram中进行解压缩时,不用指定它在ram中的运行地址,如果是在flash中就必须指定他的地址。所以这里将ZTEXTADDR指定为0,也就是没有真正指定地址。
在kernel/arch/arm/boot/compressed/Makefile文件有一行脚本:
SEDFLAGS    = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;s/BSS_START/$(ZBSSADDR)/
使得TEXT_START = ZTEXTADDR,LOAD_ADDR = ZRELADDR。
这样vmlinux.lds的生成过程如下:
vmlinux.lds:    vmlinux.lds.in Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config
 @sed "$(SEDFLAGS)" < vmlinux.lds.in > $@
 
以上就是我对内核启动地址的分析,总结一下内核启动地址的设置:
1、设置kernel/arch/arm/Makefile文件中的
   TEXTADDR     = 0xC0008000
   内核启动的虚拟地址
2、设置kernel/arch/arm/boot/Makefile文件中的
   ZRELADDR     = 0xa0008000
   内核启动的物理地址
   如果需要从flash中启动还需要设置
   ZTEXTADDR地址。