<?xml version="1.0" encoding="gbk"?> <rss version="2.0"><channel> <title>定阅帖子更新</title> <link>http://www.broadkey.com.cn/XML.ASP</link><description>TEAM Board - 意得法电子</description> <copyright>TEAM 2.0.5 Release</copyright><generator>TEAM Board by TEAM5.Cn Studio</generator> <ttl>30</ttl><item><link>http://www.broadkey.com.cn/Thread.asp?tid=74 </link><title>Linux内核配置系统浅析</title><author>page</author><pubDate>2009-11-26 21:16:42</pubDate><description><![CDATA[<blockquote><font size="3">随着 Linux 操作系统的广泛应用，特别是 Linux 在嵌入式领域的发展，越来越多的人开始投身到 Linux 内核级的开发中。面对日益庞大的 Linux 内核源代码，开发者在完成自己的内核代码后，都将面临着同样的问题，即如何将源代码融入到 Linux 内核中，增加相应的 Linux 配置选项，并最终被编译进 Linux 内核。这就需要了解 Linux 的内核配置系统。</font></blockquote>
<p><font size="3">随着 Linux 操作系统的广泛应用，特别是 Linux 在嵌入式领域的发展，越来越多的人开始投身到 Linux 内核级的开发中。面对日益庞大的 Linux 内核源代码，开发者在完成自己的内核代码后，都将面临着同样的问题，即如何将源代码融入到 Linux 内核中，增加相应的 Linux 配置选项，并最终被编译进 Linux 内核。这就需要了解 Linux 的内核配置系统。</font></p>
<p><font size="3">众所周 知，Linux 内核是由分布在全球的 Linux 爱好者共同开发的，Linux 内核每天都面临着许多新的变化。但是，Linux 内核的组织并没有出现混乱的现象，反而显得非常的简洁，而且具有很好的扩展性，开发人员可以很方便的向 Linux 内核中增加新的内容。原因之一就是 Linux 采用了模块化的内核配置系统，从而保证了内核的扩展性。</font></p>
<p><font size="3">本文首先分析了 Linux 内核中的配置系统结构，然后，解释了 Makefile 和配置文件的格式以及配置语句的含义，最后，通过一个简单的例子--TEST Driver，具体说明如何将自行开发的代码加入到 Linux 内核中。在下面的文章中，不可能解释所有的功能和命令，只对那些常用的进行解释，至于那些没有讨论到的，请读者参考后面的参考文献。</font></p>
<p><font size="3"><a name="N10043"><span class="atitle">1． 配置系统的基本结构</span></a></font></p>
<p><font size="3">Linux内核的配置系统由三个部分组成，分别是：</font></p>
<ol>
    <li><font size="3">Makefile：分布在 Linux 内核源代码中的 Makefile，定义 Linux 内核的编译规则；</font></li>
    <li><font size="3">配置文件（config.in）：给用户提供配置选择的功能；</font></li>
    <li><font size="3">配 置工具：包括配置命令解释器（对配置脚本中使用的配置命令进行解释）和配置用户界面（提供基于字符界面、基于 Ncurses 图形界面以及基于 Xwindows 图形界面的用户配置界面，各自对应于 Make config、Make menuconfig 和 make xconfig）。</font></li>
</ol>
<p><font size="3">这 些配置工具都是使用脚本语言，如 Tcl/TK、Perl 编写的（也包含一些用 C 编写的代码）。本文并不是对配置系统本身进行分析，而是介绍如何使用配置系统。所以，除非是配置系统的维护者，一般的内核开发者无须了解它们的原理，只需 要知道如何编写 Makefile 和配置文件就可以。所以，在本文中，我们只对 Makefile 和配置文件进行讨论。另外，凡是涉及到与具体 CPU 体系结构相关的内容，我们都以 ARM 为例，这样不仅可以将讨论的问题明确化，而且对内容本身不产生影响。</font></p>
<p><font size="3"><a name="N1005B"><span class="atitle">2． Makefile</span></a></font></p>
<p><font size="3"><a name="N10061"><span class="smalltitle">2.1 Makefile 概述</span></a></font></p>
<p><font size="3">Makefile 的作用是根据配置的情况，构造出需要编译的源文件列表，然后分别编译，并把目标代码链接到一起，最终形成 Linux 内核二进制文件。</font></p>
<p><font size="3">由于 Linux 内核源代码是按照树形结构组织的，所以 Makefile 也被分布在目录树中。Linux 内核中的 Makefile 以及与 Makefile 直接相关的文件有：</font></p>
<ol>
    <li><font size="3">Makefile：顶层 Makefile，是整个内核配置、编译的总体控制文件。</font></li>
    <li><font size="3">.config：内核配置文件，包含由用户选择的配置选项，用来存放内核配置后的结果（如 make config）。</font></li>
    <li><font size="3">arch/*/Makefile：位于各种 CPU 体系目录下的 Makefile，如 arch/arm/Makefile，是针对特定平台的 Makefile。</font></li>
    <li><font size="3">各个子目录下的 Makefile：比如 drivers/Makefile，负责所在子目录下源代码的管理。</font></li>
    <li><font size="3">Rules.make：规则文件，被所有的 Makefile 使用。</font></li>
</ol>
<p><font size="3">用 户通过 make config 配置后，产生了 .config。顶层 Makefile 读入 .config 中的配置选择。顶层 Makefile 有两个主要的任务：产生 vmlinux 文件和内核模块（module）。为了达到此目的，顶层 Makefile 递归的进入到内核的各个子目录中，分别调用位于这些子目录中的 Makefile。至于到底进入哪些子目录，取决于内核的配置。在顶层 Makefile 中，有一句：include arch/$(ARCH)/Makefile，包含了特定 CPU 体系结构下的 Makefile，这个 Makefile 中包含了平台相关的信息。</font></p>
<p><font size="3">位于各个子目录下的 Makefile 同样也根据 .config 给出的配置信息，构造出当前配置下需要的源文件列表，并在文件的最后有 include $(TOPDIR)/Rules.make。</font></p>
<p><font size="3">Rules.make 文件起着非常重要的作用，它定义了所有 Makefile 共用的编译规则。比如，如果需要将本目录下所有的 c 程序编译成汇编代码，需要在 Makefile 中有以下的编译规则：</font></p>
<pre><font size="3">%.s: %.c<br />$(CC) $(CFLAGS) -S $&lt; -o $@<br /></font></pre>
<p><font size="3">有很多子目录下都有同样的要求，就需要在各自的 Makefile 中包含此编译规则，这会比较麻烦。而 Linux 内核中则把此类的编译规则统一放置到 Rules.make 中，并在各自的 Makefile 中包含进了 Rules.make（include Rules.make），这样就避免了在多个 Makefile 中重复同样的规则。对于上面的例子，在 Rules.make 中对应的规则为：</font></p>
<pre><font size="3">%.s: %.c<br />$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F)) $(CFLAGS_$@) -S $&lt; -o $@<br /></font></pre>
<p><font size="3"><a name="N10091"><span class="smalltitle">2.2 Makefile 中的变量</span></a></font></p>
<p><font size="3">顶层 Makefile 定义并向环境中输出了许多变量，为各个子目录下的 Makefile 传递一些信息。有些变量，比如 SUBDIRS，不仅在顶层 Makefile 中定义并且赋初值，而且在 arch/*/Makefile 还作了扩充。</font></p>
<p><font size="3">常用的变量有以下几类：</font></p>
<p><font size="3">1） 版本信息 <br />
版本信息有：VERSION，PATCHLEVEL, SUBLEVEL, EXTRAVERSION，KERNELRELEASE。 版本信息定义了当前内核的版本，比如 VERSION=2，PATCHLEVEL=4，SUBLEVEL=18，EXATAVERSION=-rmk7，它们共同构成内核的发行版本KERNELRELEASE：2.4.18-rmk7 </font></p>
<p><font size="3">2） CPU 体系结构：ARCH <br />
在顶层 Makefile 的开头，用 ARCH 定义目标 CPU 的体系结构，比如 ARCH:=arm 等。许多子目录的 Makefile 中，要根据 ARCH 的定义选择编译源文件的列表。 </font></p>
<p><font size="3">3） 路径信息：TOPDIR, SUBDIRS <br />
TOPDIR 定义了 Linux 内核源代码所在的根目录。例如，各个子目录下的 Makefile 通过 $(TOPDIR)/Rules.make 就可以找到 Rules.make 的位置。 <br />
SUBDIRS 定义了一个目录列表，在编译内核或模块时，顶层 Makefile 就是根据 SUBDIRS 来决定进入哪些子目录。SUBDIRS 的值取决于内核的配置，在顶层 Makefile 中 SUBDIRS 赋值为 kernel drivers mm fs net ipc lib；根据内核的配置情况，在 arch/*/Makefile 中扩充了 SUBDIRS 的值，参见4）中的例子。 </font></p>
<p><font size="3">4） 内核组成信息：HEAD, CORE_FILES, NETWORKS, DRIVERS, LIBS <br />
Linux 内核文件 vmlinux 是由以下规则产生的： </font></p>
<p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode"><font size="3">  vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs<br /> $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o \<br />  --start-group \<br />  $(CORE_FILES) \<br />  $(DRIVERS) \<br />  $(NETWORKS) \<br />  $(LIBS) \<br />  --end-group \<br />  -o vmlinux<br />  </font></pre>
            </td>
        </tr>
    </tbody>
</table>
<font size="3"><br />
</font></p>
<p><font size="3">可 以看出，vmlinux 是由 HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 组成的。这些变量（如 HEAD）都是用来定义连接生成 vmlinux 的目标文件和库文件列表。其中，HEAD在arch/*/Makefile 中定义，用来确定被最先链接进 vmlinux 的文件列表。比如，对于 ARM 系列的 CPU，HEAD 定义为：</font></p>
<p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode"><font size="3">                   HEAD            := arch/arm/kernel/head-$(PROCESSOR).o \<br />                   arch/arm/kernel/init_task.o<br />                   </font></pre>
            </td>
        </tr>
    </tbody>
</table>
<font size="3"><br />
</font></p>
<p><font size="3">表明 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 进行了扩充：</font></p>
<p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode"><font size="3"># arch/arm/Makefile<br /># If we have a machine-specific directory, then include it in the build.<br />MACHDIR         := arch/arm/mach-$(MACHINE)<br />ifeq ($(MACHDIR),$(wildcard $(MACHDIR)))<br />SUBDIRS         += $(MACHDIR)<br />CORE_FILES      := $(MACHDIR)/$(MACHINE).o $(CORE_FILES)<br />endif<br />HEAD            := arch/arm/kernel/head-$(PROCESSOR).o \<br />                   arch/arm/kernel/init_task.o<br />SUBDIRS         += arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpe<br />CORE_FILES      := arch/arm/kernel/kernel.o arch/arm/mm/mm.o $(CORE_FILES)<br />LIBS            := arch/arm/lib/lib.a $(LIBS)<br /></font></pre>
            </td>
        </tr>
    </tbody>
</table>
<font size="3"><br />
</font></p>
<p><font size="3">5） 编译信息：CPP, CC, AS, LD, AR，CFLAGS，LINKFLAGS <br />
在 Rules.make 中定义的是编译的通用规则，具体到特定的场合，需要明确给出编译环境，编译环境就是在以上的变量中定义的。针对交叉编译的要求，定义了 CROSS_COMPILE。比如： </font></p>
<p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode"><font size="3">CROSS_COMPILE   = arm-linux-<br />CC              = $(CROSS_COMPILE)gcc<br />LD              = $(CROSS_COMPILE)ld<br />......<br /></font></pre>
            </td>
        </tr>
    </tbody>
</table>
<font size="3"><br />
</font></p>
<p><font size="3">CROSS_COMPILE 定义了交叉编译器前缀 arm-linux-，表明所有的交叉编译工具都是以 arm-linux- 开头的，所以在各个交叉编译器工具之前，都加入了 $(CROSS_COMPILE)，以组成一个完整的交叉编译工具文件名，比如 arm-linux-gcc。 <br />
CFLAGS 定义了传递给 C 编译器的参数。 <br />
LINKFLAGS 是链接生成 vmlinux 时，由链接器使用的参数。LINKFLAGS 在 arm/*/Makefile 中定义，比如： </font></p>
<p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode"><font size="3"># arch/arm/Makefile<br />LINKFLAGS       :=-p -X -T arch/arm/vmlinux.lds<br /></font></pre>
            </td>
        </tr>
    </tbody>
</table>
<font size="3"><br />
</font></p>
<p><font size="3">6） 配置变量CONFIG_* <br />
.config 文件中有许多的配置变量等式，用来说明用户配置的结果。例如 CONFIG_MODULES=y 表明用户选择了 Linux 内核的模块功能。 <br />
.config 被顶层 Makefile 包含后，就形成许多的配置变量，每个配置变量具有确定的值：y 表示本编译选项对应的内核代码被静态编译进 Linux 内核；m 表示本编译选项对应的内核代码被编译成模块；n 表示不选择此编译选项；如果根本就没有选择，那么配置变量的值为空。 </font></p>
<p><font size="3"><a name="N100E0"><span class="smalltitle">2.3 Rules.make 变量</span></a></font></p>
<p><font size="3">前面讲过，Rules.make 是编译规则文件，所有的 Makefile 中都会包括 Rules.make。Rules.make 文件定义了许多变量，最为重要是那些编译、链接列表变量。</font></p>
<p><font size="3">O_OBJS，L_OBJS，OX_OBJS，LX_OBJS：本目录下需要编译进 Linux 内核 vmlinux 的目标文件列表，其中 OX_OBJS 和 LX_OBJS 中的 &quot;X&quot; 表明目标文件使用了 EXPORT_SYMBOL 输出符号。</font></p>
<p><font size="3">M_OBJS，MX_OBJS：本目录下需要被编译成可装载模块的目标文件列表。同样，MX_OBJS 中的 &quot;X&quot; 表明目标文件使用了 EXPORT_SYMBOL 输出符号。</font></p>
<p><font size="3">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 结尾。</font></p>
<p><font size="3"><a name="N100F2"><span class="smalltitle">2.4 子目录 Makefile</span></a></font></p>
<p><font size="3">子目录 Makefile 用来控制本级目录以下源代码的编译规则。我们通过一个例子来讲解子目录 Makefile 的组成：</font></p>
<p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode"><font size="3">#<br /># Makefile for the linux kernel.<br />#<br /># All of the (potential) objects that export symbols.<br /># This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'.<br />export-objs := tc.o<br /># Object file lists.<br />obj-y  :=<br />obj-m  :=<br />obj-n  :=<br />obj-  :=<br />obj-$(CONFIG_TC) += tc.o<br />obj-$(CONFIG_ZS) += zs.o<br />obj-$(CONFIG_VT) += lk201.o lk201-map.o lk201-remap.o<br /># Files that are both resident and modular: remove from modular.<br />obj-m  := $(filter-out $(obj-y), $(obj-m))<br /># Translate to Rules.make lists.<br />L_TARGET := tc.a<br />L_OBJS  := $(sort $(filter-out $(export-objs), $(obj-y)))<br />LX_OBJS  := $(sort $(filter     $(export-objs), $(obj-y)))<br />M_OBJS  := $(sort $(filter-out $(export-objs), $(obj-m)))<br />MX_OBJS  := $(sort $(filter     $(export-objs), $(obj-m)))<br />include $(TOPDIR)/Rules.make<br /></font></pre>
            </td>
        </tr>
    </tbody>
</table>
<font size="3"><br />
</font></p>
<p><font size="3">a) 注释 <br />
对 Makefile 的说明和解释，由#开始。 </font></p>
<p><font size="3">b) 编译目标定义 <br />
类似于 obj-$(CONFIG_TC) += tc.o 的语句是用来定义编译的目标，是子目录 Makefile 中最重要的部分。编译目标定义那些在本子目录下，需要编译到 Linux 内核中的目标文件列表。为了只在用户选择了此功能后才编译，所有的目标定义都融合了对配置变量的判断。 <br />
前面说过，每个配置变量取值范围是：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- 中的文件列表被忽略。配置系统就根据这些列表的属性进行编译和链接。 <br />
export-objs 中的目标文件都使用了 EXPORT_SYMBOL() 定义了公共的符号，以便可装载模块使用。在 tc.c 文件的最后部分，有 &quot;EXPORT_SYMBOL(search_tc_card);&quot;，表明 tc.o 有符号输出。 <br />
这里需要指出的是，对于编译目标的定义，存在着两种格式，分别是老式定义和新式定义。老式定义就是前面 Rules.make 使用的那些变量，新式定义就是 obj-y，obj-m，obj-n 和 obj-。Linux 内核推荐使用新式定义，不过由于 Rules.make 不理解新式定义，需要在 Makefile 中的适配段将其转换成老式定义。 </font></p>
<p><font size="3">c) 适配段 <br />
适配段的作用是将新式定义转换成老式定义。在上面的例子中，适配段就是将 obj-y 和 obj-m 转换成 Rules.make 能够理解的 L_TARGET，L_OBJS，LX_OBJS，M_OBJS，MX_OBJS。 <br />
L_OBJS := $(sort $(filter-out $(export-objs), $(obj-y))) 定义了 L_OBJS 的生成方式：在 obj-y 的列表中过滤掉 export-objs（tc.o），然后排序并去除重复的文件名。这里使用到了 GNU Make 的一些特殊功能，具体的含义可参考 Make 的文档（info make）。 </font></p>
<p><font size="3">d) include $(TOPDIR)/Rules.make</font></p>
<p><font size="3">--------------------------------------------------------------</font></p>
<p><font class="zw_txt" size="3">本 文主要介绍如何构建在x86平台上交叉编译各平台上的嵌入式Linux内核。要完成一下步骤，首先必须确认有相应的交叉编译工具链。工具链可以自己编译获 得，但是对于初学者我们不推荐这么作，网上大有许多编译好的优秀的工具链。本文将以编译ARM平台上的内核为例子，其他平台基本步骤差异不大。为了显示最 新性，也采用可2.6.26版本的内核。 </font></p>
<p><font class="zw_txt" size="3">首先必须获得最新版本的内核，可以在ftp.kernel.org上下载我们需要的2.6.26的内核。获得相应内核包后，解压：</font></p>
<p><font class="zw_txt" size="3">对于 Linux-2.6.26.tar.gz为<br />
tar zxvf Linux-2.6.26.tar.gz </font></p>
<p><font class="zw_txt" size="3">对于 Linux-2.6.26.tar.bz2为<br />
tar xjvf Linux-2.6.26.tar.bz2 </font></p>
<p><font class="zw_txt" size="3">之后进入Linux-2.6.26文件夹内。<br />
如果机器上已有2.6.26内核代码,也可以直接使用，但要清除上次编译的遗留物，可以<br />
make distclean </font></p>
<p><font class="zw_txt" size="3">准 备结束后，我们开始配置内核。在本机编译内核时，我们可以直接make menuconfig之类，就合根据我们机器当前内核的配置生产.config文件。但对于嵌入式当然就很困难。幸亏Linux支持上百种平台。对于不同 平台都有默认的配置文件，对于我们使用的arm，可以在/arch/arm/configs文件夹下看到<br />
我们要根据自己所使用的平台来选择。如使用PXA270的，可以选择em_x270_defconfig。之后只要运行<br />
make ARCH＝arm CROSS_COMPILE=arm-Linux- em_x270_defconfig </font></p>
<p><font class="zw_txt" size="3">对于我们使用的两个宏ARCH和CROSS_COMPILE，分别是指我们选择的体系结构（Linux在内核代码树arch文件夹中列出支持的体系结构）和交叉编译器的前缀。</font></p>
<p><font class="zw_txt" size="3">之后就可以再运行<br />
<font color="#ff0000">make </font></font><font class="zw_txt" color="#ff0000" size="3">ARCH＝arm </font><font class="zw_txt" size="3"><font color="#ff0000">menucnfig <br />
</font></font></p>
<p><font class="zw_txt" size="3">对于powerpc：</font><font class="zw_txt" size="3"><font color="#ff0000">make </font></font><font class="zw_txt" color="#ff0000" size="3">ARCH＝powerpc </font><font class="zw_txt" size="3"><font color="#ff0000">menucnfig</font></font></p>
<p>而不是<font class="zw_txt" size="3"><font color="#ff0000">make </font></font><font class="zw_txt" color="#ff0000" size="3">ARCH＝ppc </font><font class="zw_txt" size="3"><font color="#ff0000">menucnfig </font></font><br />
<font class="zw_txt" size="3">进行修改。</font></p>
<p><font class="zw_txt" size="3">之后就是编译了，对于嵌入式大多是编译成镜像文件，这里编译成zImage<br />
make ARCH＝arm CROSS_COMPILE=arm-Linux- zImage</font></p>
<p><font class="zw_txt" size="3">如果有选择模块，还要<br />
make ARCH＝arm CROSS_COMPILE=arm-Linux- modules</font></p>
<p><font class="zw_txt" size="3">和<br />
make ARCH＝arm CROSS_COMPILE=arm-Linux- INSTALL_MOD_PATH= modules_install</font></p>
<p><font class="zw_txt" size="3">宏INSTALL_MOD_PATH是选择模块安装的根目录，这里我们选择的是我们为嵌入式准备的寄生文件系统路径，默认是本机的根目录（即/），如果设置这个宏会把模块安装大/lib/modules文件夹中，有时候这是灾难性的。</font></p>
<p><font class="zw_txt" size="3">这 样就搞定了，是不是缺了什么？我们差点忘记了编译好的zImage。他在什么地方？对于一般而言是在 Linux-2.6.26/arch/$ARCH/boot里，本次是在 Linux-2.6.26/arch/arm/boot 找到他了，一看居然有1.5兆这么大。怎么变小能？关键就是在make menucnfig阶段了，这阶段才是技术要求所在。 </font></p>
<p>&nbsp;</p>]]></description></item></channel></rss>