标签 C 下的文章

如何加入Linux内核开发社区(1)

本文翻译自The Linux Foundation的《How to Participate in the Linux Community》(基于2012-03-21最新版本),原作者为Jonathan Corbet(corbet@lwn.net)。下面是该文章第一章节的中译文。

1、内核开发过程指南

本文旨在帮助那些在参与开发社区(community)工作过程中遭遇些许挫折的开发人员(以及他们的管理者)。对于那些不是十分熟悉Linux 内核开发(或通常所说的自由软件开发)的开发人员,本文将以一种易于理解的方式记录社区是如何进行开发工作的。虽然这里会提及一些技术资料,但更 多是面向过程的讨论,这些内容不需要你对内核编程有较深入的了解。

1.1、内容大纲

本文后面章节的内容涵盖了内核开发过程以及开发人员及其雇佣者所遇到的各种挫折。本文还列举了诸多内核代码应该被合并(merge)到官方内核 (主线,mainline)的原因,包括对用户自动可用(automatic availability to users)、社区提供各种形式的支持以及对内核开发演进方向的影响力等。被Linux内核采纳的代码必须使用GPL兼容许可证进行授权。

章节2介绍了Linux内核的开发过程,内核发布的周期以及合并窗口(merge window)机制。该章节还涵盖了补丁开发、评审以及合并周期等各种不同阶段。关于一些工具和邮件列表的讨论也包含在该章节中。我们鼓励那些想要开始内 核开发的开发者们去跟踪和修正bug,并以此作为最初阶段的练习。

章节3涵盖了早期阶段的项目规划(early-stage project planning),并重点强调了开发社区的尽早参与。

章节4中的内容是有关编码过程的;该章节讨论了其他开发人员在开发过程中所遇到的一些陷阱;涵盖了一些对补丁的要求;并介绍了一些用于帮助保证内 核补丁正确性的工具。

章节5谈到了发布补丁评审的过程。为了能让开发社区认真对待发布(post)的补丁,开发者必须对补丁内容进行适当的格式化和描述,并且开发者必 须将补丁发到合适的地方。遵循本章节中的建议可以最大化地提高你的补丁被开发社区接受的可能性。

章节6涵盖了发布补丁后要做的事情;发布补丁那刻离最终完成还差得很远。与评审者的合作是开发过程中的关键环节;这节提供了许多有关如何在这一重 要阶段避免问题的小建议。这里告诫开发者不要想当然的认为当补丁被合并到主线后工作就完成了。

章节7介绍了一些"高级"主题:使用git管理补丁以及评审其他开发人员发布的补丁。

章节8以更多的有关内核开发的信息来源作为结束此文。

1.2、这篇文档是关于什么的

Linux内核是现有最大的并且是最活跃的自由软件项目之一,它拥有600多万行代码以及超过1000名积极贡献者。自从1991年问世以 来,Linux内核已经逐渐演化成一种最佳的操作系统组件,在袖珍数字音乐播放器、桌面个人计算机、现有的超级计算机以及介于个人计算机与超级计 算机之间的所有类型系统上都有Linux内核在运行。它是一种几乎适合所有情况的稳定的、高效的和可伸缩的解决方案。

伴随着Linux内核的发展,希望参与内核开发的开发人员和公司的数量也迎来了一个较大的增长。硬件制造商想要确保Linux可以良好地支持他们 的产品,使得这些产品对Linux用户具有吸引力。那些将Linux作为一个组件集成到它们产品中的嵌入式系统供应商想要Linux能够尽可能地 满足和适合接下来的任务。而产品基于Linux的Linux发行版供应商以及其他软件供应商更是对Linux内核的能力、性能以及可靠性有着明确 的兴趣。最终用户也常常希望通过改变Linux来使得Linux更好地满足他们的需要。

Linux的一个最引入注目的特点就是它对开发者的平易近人;任何具备所需必要的技能的开发者都可以对Linux进行改进,影响Linux的发展 方向。专利产品无法提供这种开放性,这是自由软件开发过程的一个特质。但是,更可能的是,内核要比其他绝大多数自由软件项目更加开放。一个典型的 三个月内核开发周期可能涉及超过来自100多个不同公司的1000多名开发者(或没有受雇佣于任何公司)的开发工作。

在内核开发社区中工作不是特别难。不过,尽管这样,许多潜在的贡献者在尝试进行内核开发工作时都遇到过困难。内核开发社区逐步形成了自己与众不同 的运营方式,这种方式使得Linux内核在每天成千上万行代码被改变的情况下依旧运行顺畅(并且生产出高质量的产品)。因此Linux内核的开发 过程与专利产品的开发方法有着较大区别也就不足为奇了。

对新开发者来说,内核的开发过程可能看上去有些奇怪和咄咄逼人,但是其背后却有着充分的理由和丰富的经验作为支撑。那些不理解内核开发社区工作方 式(或者,更糟糕的是试图无视或规避)的开发者必将经历挫折。内核开发社区会帮助那些主动尝试学习内核开发过程的开发者们,而对那些不听从或不在 乎开发过程的开发者,开发社区的耐心也是有限的。

希望那些读过此篇文章的开发者们都能避免这样的挫折经历。这里虽然有大量资料需要阅读,但用不了多长时间阅读这些资料所付出的努力就会获得回报。 开发社区总是需要那些愿意帮助内核改善的开发者;接下来的内容应该可以帮助你 — 或者那些为你工作的开发者 — 加入到我们的社区。

1.3、贡献

本文由Jonathan Corbet,corbet@lwn.net撰写,并根据James Berry、Alex Chiang、Roland Dreier、Randy Dunlap、Jake Edge、Jiri Kosina、Matt Mackall、Amanda McPherson、Andrew Morton和Jochen VoB等人的评论作了改进。

Linux基金会(Linux Foundation)对这篇文章的撰写提供了支持;特别感谢Amanda McPherson,是她看到了这份努力的价值,并努力使之成为现实。

1.4、将代码合入主线的重要性

一些公司和开发者偶尔也想知道为何他们要这么麻烦地去学习如何参与内核开发社区的工作,并且要将代码合并到主线版本内核(主线版本内核由 Linus Torvalds负责维护,并且被Linux发行商作为基础版本使用)中去。就短期来讲,贡献代码可能看似是一种可避免的开销;并且独立保留代码并直接对 用户提供支持看起来也更加容易。但事情的真相是独立保留代码(树外,out of tree)是一种虚假经济(false economy)。

下面列举一些内核开发过程方面相关的内容,以此说明一下维护离树代码所要付出的代价,其中大部分将在本文后面有更详细的讨论。考虑:

  * 合并到主线内核的代码对所有Linux用户可用。它将自动出现在所有使能它(enable it)的发行版中。你无需考虑驱动盘、下载或支持不同发行版的多个版本的麻烦事;这对于开发者和最终用户而言都是奏效的。代码合入主线版本解决了大量发行 版以及支持的问题。

  * 尽管内核开发者们努力维护一个稳定的对用户空间的接口,但内部的内核API却是不断变化的。内部接口的不稳定性其实是一种蓄意的设计决策;它允许开发者们 随时做出根本性的改进,而这样做的结果将是获得更高质量的代码。不过这样的策略导致的一个结果就是任何离树代码要想和新内核一起工作就必须要有持 续的维护。维护离树代码就需要大量的工作,而这些工作仅仅是为了能让代码正常工作。

    相反,主线中的代码则不需要开发人员去修正那些因API变化而被破坏的代码。因此合并到主线的代码具有更低的维护成本。

  * 除此之外,内核中的代码经常被其他开发人员改进。授权你的用户社区与客户去改进你的产品常常能带来令人惊讶的结果。

  *  内核代码在合入主线前后都要经过评审。无论原开发者的技术水准有多么高超,评审过程总是能找到改进代码的方法。评审过程常常会发现严重bug以及安全问 题。这些结论对那些在封闭环境下开发出来的代码同样是成立的;这样的代码得益于外部开发者们的评审。而未经外部开发者评审的离树代码则是低质量的 代码。

  * 参与内核开发过程是你影响内核开发方向的一种方式。虽然旁观者的抱怨也会被倾听,但积极的开发者发出的声音显然更强健有力-并且他们具备实现这些改变以让 内核更好地满足他们需要的能力。

  * 当你的代码单独维护时,就存在这种可能性:第三方会贡献类似特性的一个不同的实现。一旦出现这种情况,再将你的代码合并到主线将变得更加困难 – 甚至是不可能。那样你就将面临不愉快的选择,(1)要么长期离树维护一个非标准特性,(2)要么放弃你的代码,让你的用户迁移到主线版本。

  * 贡献代码是整个保证开发过程正常运转的基本行为。通过贡献你的代码,你可以为内核添加新功能,提供能力以及那些对其他内核开发者有用的例子。如果你曾为 Linux开发过代码(或正在考虑这么做),你肯定对这个平台的持续成功十分感兴趣;而贡献代码就是帮助Linux成功的一种最佳方式。

上面的所有论证适用于任何离树内核代码,包括那些专有的或仅以二进制形式提供的代码。不过,在考虑发行任何仅二进制形式(binary- only)内核代码之前,你应该考虑下面一些额外因素:

  * 关于发行专有内核模块的法律条款充其量是模糊不清的;相当多的内核版权持有者认为绝大多数仅二进制模块是内核的衍生产品(derived product),因此他们的发行版违背了GNU通用公共许可证(GNU General Public License,下面还有更多关于这个许可证的说明)。笔者不是律师,本文中的内容千万不能被视为法律建议。闭源(closed-source)模块真正 的法律地位只能由法院判决决定。但无论如何困扰这些模块的不确定性是存在的。

  * 二进制模块增加了调试内核问题的难度,甚至于大多内核开发人员都不愿尝试。因此仅二进制模块的发行将增加你的用户获得社区支持的难度。

  * 对于仅二进制模块的发行者而言,支持也是更为困难的,他们必须为每个他们想要支持的发行版以及内核版本提供一个模块版本。一个模块需要几十个构建才能全面 覆盖到所有发行版和不同版本的内核,并且你的最终用户每次升级内核后都需要单独升级你的这个模块。

  * 上面所说的有关代码评审的内容对闭源代码而言更加适用。但由于代码不公开,无法被社区评审,因此毫无疑问将有严重问题。

嵌入式系统制造商特别可能被怂恿而忽视本节前面所说的那些内容,因为他们相信他们交付的是一个完备的产品,产品使用的是一个冻结了的内核版本,发 布后不需要再进行更多的开发了。这种说法忽略了被广受赞同的代码评审的价值以及允许最终用户向你的产品中添加能力的价值。但是这些产品的商业生命 周期也都有限,之后必须发布产品的新版本。在这一点上,代码在主线上且维护良好的制造商将占据更好的位置,并且可以更快地推出满足市场的新产品。

1.5、许可证

代码在若干许可证的授权下被贡献到Linux内核中,但所有代码必须与作为Linux内核整体许可证的GNU通用公共许可证版本2(GPLv2) 兼容。实际上,这意味着所有贡献的代码要么遵照GPLv2许可证(可选的,语言允许在更高版本的GPL许可证下发布),要么遵照三句版BSD许可 证。任何不遵照兼容许可证的贡献代码将不能被内核所接受。

对于贡献到内核中的代码,是不需要进行版权转让的。所有合入主线内核的代码保留其最初的所有权;因此内核目前已经有成千上万个所有者了。

这种所有权结构的一个含义是任何修改内核许可证的尝试是几乎注定会失败的。几乎没有什么实际情况可以得到所有版权所有者的同意(或者将他们的代码 从内核中移除)。因此,在可见的未来,看不到将许可证迁移到GPL版本3的希望。

所有贡献到内核的代码必须是正当的自由软件。因此,来自匿名(或笔名)的贡献者的代码将不会被接受。所有贡献者都被要求在他们的代码上"签别", 声明代码可与内核一起在GPL许可证下发行。那些没有被其原作者授权为自由软件的代码或存在版权相关问题风险的代码(例如那些从通过反向工程努力 获得的缺少适当保障的代码)将不能被贡献到内核中。

在Linux开发邮件列表中经常看到有关版权事宜相关的问题。这些问题一般不会缺少回答,但大家应该牢记回答这些问题的人不是律师,不能提供法律 建议。如果你有任何与Linux源代码相关的法律问题,你唯一的选择是与熟知这一领域的律师谈谈。依赖从技术邮件列表中获得的答案是一个危险的事情。

也谈Linux Kernel Hacking – Kconfig与Kbuild

挖掘简单现象背后的复杂本质。– Tony Bai^_^

 
上文讲到Linux Kernel的配置和编译十分简单,甚至简单到可以与一个用户层应用相媲美。这一切都是因为Linux Kernel实现了一套易于使用、变更和后期维护的配置和编译体系。要知道最新Linux Kernel版本的代码量可是千万级别的,并且模块众多,其背后的配置和编译体系一定不那么简单,这次我们就来尝试Hack一下这套体系。
 
作为操作系统内核级系统软件,Linux Kernel在设计配置和编译体系时至少应该有如下几点考虑:
* 满足配置和编译内核以及内核模块的所有需求
* 较高的运行效率
* 配置阶段和编译阶段平滑结合
* 对内核开发者来说,这套体系应该易用、易变、易维护
* 其设计本身应该做到层次清晰
 
从配置和编译Linux Kernel所使用的命令来看,Linux Kernel的配置和编译体系总体上还是基于GNU Make的,没有另外使用其他的编译工具(比如SconsCMake等)。但Linux Kernel实现了Kconfig和Kbuild,用于辅助内核的配置和编译。
 
Kconfig,顾名思义,用于辅助2.6以后版本Linux内核的配置(Kernel config);Kbuild,也物如其名,用于辅助2.6以后版本Linux内核的编译(Kernel build)。这里索性将Kconfig和Kbuild称作辅助工具(不单纯叫脚本或配置文件),因为它们自身既有逻辑概念,又有物理存在。如果你曾在Linux Kernel的源码目录中徜徉过,你就会知道Kconfig文件散布在核心源码的各个角落;Kbuild文件还好,只在顶层目录、include目录下子目录、drivers下子目录以及各个arch/$ARCH/include的子目录中分布。
 
如果Linux Kernel没有引入Kconfig和Kbuild,那么本篇文章就没有了存在的必要性 – Makefile纵使数量众多,也是可以慢慢消化的,毕竟Make的规则就那么多。但Kconfig和Kbuild的引入好似为Linux Kernel配置和编译引入了一层抽象,对外(Linux核心开发者)确实是简单了,但对于我这样的要Hack编译体系的人来说,这层抽象本身就具有一定复杂性,势必需要耗时耗力地去理解。下面我们就来结合不同阶段的使用场景来深入理解一下Kconfig和Kbuild。
 
一、make *config阶段
Linux Kernel的配置项都存储于散布在Kernel源码各处的Kconfig文件中。Kconfig文件之于Linux Kernel就好比configure.ac或Makefile.am之于那些使用autotools作为构建工具的用户层应用,至少设计思路是类似的 – 先config,再make;make阶段会利用config阶段生成的一些文件。
 
Kconfig既是配置文件,也是一种配置语言,可以理解为是一种针对Linux Kernel配置的领域特定语言(DSL)。Documentation/kbuild/kconfig-language.txt文件对该种配置语言做了详尽的使用说明,这里就不做赘述了。我们主要关注的是Kconfig在配置过程中所扮演的角色以及对输出的结果的影响。
 
Linux Kernel的配置过程也是在make的驱动下开始的,我们以"make menuconfig"的执行过程为例。
 
1、首先,顶层Makefile通过分析$(MAKECMDGOALS)判定"menuconfig"为config-targets,而非build-target或mixed-target(诸如make defconfig all这样的命令);
 
2、"menuconfig"与Makefile中预置target进行匹配
menuconfig会匹配到Makefile中的"config %config",下面是有关这个target的源码节选:
 
include $(srctree)/scripts/Kbuild.include
… …
include $(srctree)/arch/$(SRCARCH)/Makefile
export KBUILD_DEFCONFIG KBUILD_KCONFIG
 
config %config: scripts_basic outputmakefile FORCE
    $(Q)mkdir -p include/linux include/config
    $(Q)$(MAKE) $(build)=scripts/kconfig $@
 
"config %config"有三个依赖项:scripts_basic、outputmakefile和FORCE。其中outputmakefile、FORCE是两个空目标,而有关scripts_basic target的源码如下:
 
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
    $(Q)$(MAKE) $(build)=scripts/basic
 
scripts_basic target的构建实际上就是编译scripts/basic下的源文件。build变量在scritps/Kbuild.include中定义,其值为"-f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj",所以上述命令展开后就是"make -f scripts/Makefile.build obj=scripts/basic"。scripts/Makefile.build这个文件在整个Linux Kernel编译过程中占据极其重要的位置,其定义了核心编译的主要target(如.o、.s等)的构建命令规则。
 
3、menuconfig目标的构建
在依赖项scripts_basic构建完毕后,Make驱动执行menuconfig目标的构建:
首先,创建两个目录include/linux和include/config,然后执行"make -f scripts/Makefile.build obj=scripts/kconfig menuconfig"。target依旧是"menuconfig",但scripts/Makefile.build中似乎并没有这个target可供匹配啊。别急,我们看看scripts/Makefile.build中的这段代码:
 
src := $(obj)
… …
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
 
这是一段关键代码,kbuild-dir的求值结果为scripts/kconfig,由于scripts/kconfig下没有Kbuild文件,进而kbuild-file求值结果为scripts/kconfig/Makefile,并且该文件被Makefile.build包含了进来。从kbuild-file这个变量的命名可以看出为何各个子目录下的Makefile被称为Kbuild Makefile了。
 
scripts/kconfig/Makefile中包含了许多*config targets,其中就有"menuconfig"这个target:
 
ifdef KBUILD_KCONFIG
Kconfig := $(KBUILD_KCONFIG)
else
Kconfig := arch/$(SRCARCH)/Kconfig
endif
… …
menuconfig: $(obj)/mconf
    $< $(Kconfig)
 
该目标依赖scripts/kconfig/mconf,这是一个小工具程序,Make会首先执行该程序的编译链接;然后执行"scripts/kconfig/mconf arch/x86/Kconfig"($<是一个自动变量,指代该target的第一个依赖项,这里是scripts/kconfig/mconf)。
 
4、"scripts/kconfig/mconf arch/x86/Kconfig"的执行过程
到这里,Kconfig配置文件终于登场了!scripts/kconfig/mconf读取arch/x86/Kconfig,后者是一个针对x86这一体系的顶层Kconfig文件,打开arch/x86/Kconfig后,你会发现它内部source了许多其他Kconfig文件,诸如:
 
….
source "init/Kconfig"
source "kernel/time/Kconfig"
source "mm/Kconfig"
 
mconf会依次读入这些子Kconfig文件,并将配置项的符号表建立起来。如果你是第一次进行Linux Kernel配置,尚没有.config文件,那么这些配置项的初始值从哪里得到呢(不是所有配置项都有默认值)?在init/Kconfig文件中,你会看到这样的配置项:
 
config DEFCONFIG_LIST
    string
    depends on !UML
    option defconfig_list
    default "/lib/modules/$UNAME_RELEASE/.config"
    default "/etc/kernel-config"
    default "/boot/config-$UNAME_RELEASE"
    default "$ARCH_DEFCONFIG"
    default "arch/$ARCH/defconfig"
 
mconf应该就是通过这个DEFCONFIG_LIST配置项找到一份默认config文件的,mconf自上而下依次尝试,直到对应的文件存在,就将存在的文件作为默认.config加载,为各个配置项赋值。在我的RHEL 5.5上$ARCH-DEFCONFIG被作为默认.config加载了;而在我的Ubuntu 10.04上,mconf找到的是/boot/config-2.6.32-30-generic。下面是在RHEL 5.5上执行make menuconfig后控制台的输出结果:
 
$ make menuconfig
scripts/kconfig/mconf arch/x86/Kconfig
#
# using defaults found in arch/x86/configs/x86_64_defconfig
#
#
# configuration written to .config
#
 
*** End of Linux kernel configuration.
 
5、生成.config文件
手工完成配置后,如果选择了save配置,那么在源码顶层目录下会生成一个.config文件,这个就是整个Kernel配置过程的最重要的输出,这类似用户层应用在configure之后生成的Makefile,.config文件实际上也是一个Makefile文件,只是其内容格式比较单一罢了(都是CONFIG_XXX=y形式的变量定义)。
 
至此,Linux Kernel的配置过程Hack结束了,其他*config target执行过程也是大同小异的,再总结一下make menuconfig配置的执行过程:
 
* 首先建立include/config目录,并编译scripts/basic下的一些工具;
* 然后编译scripts/kconfig下的工具,比如mconf等
* 执行scripts/kconfig/mconf arch/x86/Kconfig,该程序会生成.config
 
二、make all阶段
Make *config后,Kconfig文件的使命就算是结束了。剩下的Makefile和Kbuild文件将会在make all阶段扮演重要角色。前面说过Kbuild本身就是Makefile,分布在各个子目录中Makefile也被称为Kbuild Makefile,其实还是Makefile,总而言之,Make all阶段其实就是关于Makefile的事情了。只不过Linux Kernel的整个Makefile组织体系设计的很精巧,特别是与配置阶段输出的结果配合的天衣无缝,下面我们就来具体看看吧。
 
在顶层的Makefile中,我们可以直接定位到Make all的target,不过有两个,依次是:
 
all: vmlinux
all: modules
 
Make会自动合并all的依赖项,并依次对依赖项进行构建,就类似这样:
 
all: foo1
all: foo2
 
foo1:
        @echo "foo1"
foo2:
        @echo "foo2"
 
$> make
foo1
foo2
 
而vmlinux这个target是什么情况呢?见下面代码摘要:
 
# vmlinux image – including updated kernel symbols
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
… …
    $(call vmlinux-modpost)
    $(call if_changed_rule,vmlinux__)
    $(Q)rm -f .old_version
 
… … 
# The actual objects are generated when descending, 
# make sure no implicit rule kicks in
$(sort $(vmlinux-init) $(vmlinux-main)) $(vmlinux-lds): $(vmlinux-dirs) ;
 
从代码中可以看到vmlinux有若干个依赖项,我们沿着这些依赖项继续"深度"搜索,你会发现这是一个"不算浅"的依赖项树型结构,树的根节点就是vmlinux。由于这棵树太过"枝繁叶茂",所以这里只想针对重要且关键的依赖项进行分析。
 
1、prepare
这个phony target是$(vmlinux-dirs)的依赖项,顾名思义,在真正地编译之前做些准备工作。目前所有的准备工作被划分到从prepare0到prepare3的多个phony targets中了,形成一个链式依赖关系,这么做也便于日后扩展:再增加一个prepare-n非常容易。
 
在prepare的过程中有若干的重要的文件被生成了:
 
include/linux/version.h: $(srctree)/Makefile FORCE
    $(call filechk,version.h)
 
include/linux/utsrelease.h: include/config/kernel.release FORCE
    $(call filechk,utsrelease.h)
 
include/config/kernel.release: include/config/auto.conf FORCE
    $(Q)rm -f $@
    $(Q)echo $(kernelrelease) > $@
 
include/config/auto.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
    $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
 
其中include/config/auto.conf依赖$(KCONFIG_CONFIG)和include/config/auto.conf.cmd,顶层Makefile中设置了auto.conf.cmd这个target的空规则:
 
# To avoid any implicit rule to kick in, define an empty command
$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;
 
这样实际上include/config/auto.conf在auto.conf.cmd尚未被创建的情况下只是依赖配置阶段输出的.config文件(KCONFIG_CONFIG  ?= .config)。而Kernel配置后,auto.conf也未被创建,因此在这里Make执行创建auto.conf的命令:make -f Makefile silentoldconfig,该命令的执行结果是auto.conf、auto.conf.cmd、include/linux/autoconf.h以及include/config下的诸多空头文件被创建了出来。
 
2、$(vmlinux-dirs)
$(vmlinux-dirs)是一个非常重要的target,$(vmlinux-init)、$(vmlinux-main)、$(vmlinux-lds)都依赖$(vmlinux-dirs)。
 
init-y      := init/
drivers-y   := drivers/ sound/ firmware/
net-y       := net/
libs-y      := lib/
core-y      := usr/
… …
 
core-y      += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
 
vmlinux-dirs    := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
             $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
             $(net-y) $(net-m) $(libs-y) $(libs-m)))
 
PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=$@
 
此时vmlinux-dirs的值是一组目录集合,诸如init usr kernel mm fs ipc security crypto block drivers sound firmware net lib等。Makefile将这些目录名视为phony target。这样$(vmlinux-dirs): prepare scripts这个规则实际上就是一个multiple targets规则,会被多次执行的,即会对每个target执行一次"$(Q)$(MAKE) $(build)=$@"。我们以init这个phony target为例展开命令:make -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build
obj=init,该命令将根据Makefile.build中定义的规则对init目录进行编译。Makefile.build规则中的默认phony target为__build:
 
PHONY := __build
 
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
     $(subdir-ym) $(always)
    @:
 
__build"复杂"的依赖项会将必要的目标包含进来,其中较关键的是builtin-target这个目标:
 
ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif
 
#
# Rule to compile a set of .o files into one .o file
#
ifdef builtin-target
quiet_cmd_link_o_target = LD      $@
# If the list of objects to link is empty, just create an empty built-in.o
cmd_link_o_target = $(if $(strip $(obj-y)),\
              $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
              $(cmd_secanalysis),\
              rm -f $@; $(AR) rcs $@)
 
$(builtin-target): $(obj-y) FORCE
    $(call if_changed,link_o_target)
 
targets += $(builtin-target)
endif # builtin-target
 
这样每个子目录(诸如mm、init等)下的主要目标就是built-in.o,其依赖的是$(obj-y),展开后其实是一个.o文件列表。
 
这里还要提一点,那就是配置阶段的输出是如何与Build阶段结合的,我们还是看一下init/Makefile,这里节选一些代码:
 
obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
mounts-$(CONFIG_BLK_DEV_RAM)    += do_mounts_rd.o
mounts-$(CONFIG_BLK_DEV_INITRD) += do_mounts_initrd.o
mounts-$(CONFIG_BLK_DEV_MD) += do_mounts_md.o
 
可以看到.config中的配置项的值将各个.o文件分为几个类别,如果配置项值为y,则对应的.o文件归为obj-y列表;如果为m,则对应的.o文件归为obj-m列表,诸如此类(包括lib-y、lib-m、subdir-y、subdir-m等)。这样我们就可以通过调整配置项的值来选择是否将某功能编译到Linux Kernel中,还是以Kernel module形式存在,让人叹为观止!
 
3、$(vmlinux-lds)
在顶层Makefile源码中vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds,vmlinux.lds是vmlinux的linker script。vmlinux.lds也是在构建$(vmlinux-dirs)目标时被构建出来的。
 
$(vmlinux-dirs)中包含arch/x86,而在arch/x86/Makefile中,我们发现了这行代码:
 
core-y += arch/x86/kernel/
 
这样arch/x86/kernel被纳入编译,而vmlinux.lds就是arch/x86/kernel/Makefile中变量extra-y的一个值:
 
extra-y                := head_$(BITS).o head$(BITS).o head.o init_task.o vmlinux.lds
 
vmlinux.lds会被构建,而.lds目标构建规则在scripts/Makefile.build中:
 
# Linker scripts preprocessor (.lds.S -> .lds)
# —————————————————————————
quiet_cmd_cpp_lds_S = LDS     $@
      cmd_cpp_lds_S = $(CPP) $(cpp_flags) -D__ASSEMBLY__ -o $@ $<
 
$(obj)/%.lds: $(src)/%.lds.S FORCE
    $(call if_changed_dep,cpp_lds_S)
 
OK,vmlinux.lds的来龙去脉也算是搞清楚了。
 
4、其他
vmlinux的依赖还包括vmlinux-init、vmlinux-main以及vmlinux.o,见下面顶层Makefile的节选代码:
 
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
 
modpost-init := $(filter-out init/built-in.o, $(vmlinux-init))
vmlinux.o: $(modpost-init) $(vmlinux-main) FORCE
    $(call if_changed_rule,vmlinux-modpost)
 
有了vmlinux-dirs那节的说明,这些依赖项的构建也是大同小异的。
 
当vmlinux的所有依赖项都构建完毕后,vmlinux的创建也就水到渠成了,这里就不多说了。
 
总而言之,Linux Kernel简单配置和编译的背后其实还是蛮复杂的,如果要挖掘细节,即使有了上面的Hack,也还是会耗费你一定时间的^_^。

 

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats