Kernel Build System中文手册

文章目录

Kernel Build System

想要使用一些工具时,需要开启内核中的某些设置,所以配置,编译内核就必不可少了,本文是linux kernel官方文档中的Kernel Build System章节的中文版本(基于5.10),翻译水平有限,仅供参考
另外部分章节直接贴了内核文档的translate项目的网址

Kconfig 语言

介绍

配置数据库是一系列的一树形组织的配置选项的集合:

+- Code maturity level options
|  +- Prompt for development and/or incomplete code/drivers
+- General setup
|  +- Networking support
|  +- System V IPC
|  +- BSD Process Accounting
|  +- Sysctl support
+- Loadable module support
|  +- Enable loadable module support
|     +- Set version information on all module symbols
|     +- Kernel module loader
+- ...

每个条目都拥有自己的依赖。这些依赖用来决定一个条目的可见性。任何子条目只有在他的父条目可见时才可见。

菜单条目

许多条目定义了一个配置选项。所有其他的条目用来组织他。一个单独的配置选项一般这样定义:

config MODVERSIONS
      bool "Set version information on all module symbols"
      depends on MODULES
      help
        Usually, modules have to be recompiled whenever you switch to a new
        kernel.  ...

每一行都由一个关键字开始并可以跟随多个参数。config开始一个新的配置条目。后面的行定义了这个配置项的属性。属性可以是配置选项,输入提示,帮助文本和默认值等类型。一个配置选项可以使用同一个名字定义多次,但是每个定义必须仅能有一个输入提示,并且类型不能冲突。

菜单属性

一个菜单条目可以有多个属性。这些属性可能在有些地方不适用。

  • 类型定义: “bool”/”tristate”/”string”/”hex”/”int”
    每个配置选项都必须有一个类型。有且仅有两个基础类型:三态(tristate)和字符串。其他所有的类型都是基于这两个基本类型。类型定义后面可以跟输入提示,以下两个例子的作用是相同的:
bool "Networking support"

bool
prompt "Networking support"
  • 输入提示:“prompt” [“if” ]
    每个菜单项最多只能有一个提示符,用于显示给用户。只针对此提示的可选依赖项可以使用“if”添加。
  • 默认值: “default” [“if” ]
    配置选项可以有任意数量的默认值。如果有多个默认值可见,则只有第一个定义的默认值是激活的。默认值不局限于菜单项是在哪定义的。这意味着默认值可以在其他地方定义,也可以被早期的定义覆盖。只有在用户没有设置其他值(通过上面的输入提示)时,默认值才会分配给配置符号。如果可以看到输入提示,则默认值将显示给用户,用户可以覆盖该值。可以选择使用" if "添加仅与此默认值相关的依赖项。
    默认值故意默认为’ n ',以避免过度构建。除了少数例外,新的配置选项不应该改变这一点。目的是为了“make oldconfig”在一个版本到另一个版本的配置中添加尽可能少的内容。
    注意:
    需要配置为“default y/m"的项包括:
    a. 之前总是会编译的项目的Kconfig选项应为”default y“
    b. 一个新的可以显示/隐藏其他Kconfig项的Kconfig项(但是他自己不产生任何代码)应为”default y“,以便人们可以看到其他的选项
    c. 为“default n”的驱动程序的子驱动程序行为或类似选项。这允许您提供理智的默认值。
    d. 每个人都期待的硬件或基础设施,如CONFIG_NET或CONFIG_BLOCK。这些是罕见的例外。
  • 类型定义+默认值:
"def_bool"/"def_tristate" <expr> ["if" <expr>]

这是类型定义加上值的速记符号。可选地,可以使用“if”添加此默认值的依赖项。

  • 依赖性:“depends on”<expr>

这定义了此菜单项的依赖项。如果定义了多个依赖项,它们将使用“&&”连接。依赖项应用于此菜单项中的所有其他选项(也接受“if”表达式),因此这两个例子是等价的:

bool "foo" if BAR
default y if BAR

和:

depends on BAR
bool "foo"
default y
  • 反向依赖项:“select” <symbol> [“if” <expr>]

虽然正常依赖项会降低符号的上限(见下文),但反向依赖项可用于强制另一个符号的下限。当前菜单符号的值用作可以设置的最小值<symbol>。如果多次选择<symbol>,则将限制设置为最大选择。反向依赖只能与布尔或三态符号一起使用。

注意:
select应谨慎使用。select将强制符号为值,而无需访问依赖项。通过滥用select,即使FOO依赖于未设置的BAR,您也可以选择符号FOO。一般使用仅select非可见符号(任何地方都没有提示)和没有依赖项的符号。这将限制可用性,但另一方面,避免了各个地方的非法配置。

  • 弱反向依赖性:“imply” <symbol> [“if” <expr>]
    这类似于“选择”,因为它对另一个符号强制执行下限,除非“暗示”符号的值仍然可以从直接依赖或可见提示中设置为n。

给出以下示例:

config FOO
    tristate "foo"
    imply BAZ

config BAZ
    tristate "baz"
    depends on BAR

以下值是可能的:
imply

这很有用,例如,对于想要指示其连接到辅助子系统的能力的多个驱动程序,同时允许用户配置该子系统,而无需取消这些驱动程序。

注意:如果FOO=y和BAR=m的组合导致链接错误,您可以使用IS_REACHABLE()保护函数调用:

foo_init()
{
        if (IS_REACHABLE(CONFIG_BAZ))
                baz_register(&foo);
        ...
}

注意:如果BAZ提供的功能对FOO非常理想,FOO不仅应该暗示BAZ,还应该暗示其依赖性BAR:

config FOO
    tristate "foo"
    imply BAR
    imply BAZ
  • 限制菜单显示: “visible if” <expr>

此属性仅适用于菜单块,如果条件为假,菜单块不会显示给用户(不过,其中包含的符号仍然可以由其他符号选择)。它类似于单个菜单项的条件“prompt”属性。“visible”的默认值为真。

  • 数值范围:“range” <symbol> <symbol> [“if” <expr>]
    这允许限制int和hex符号的可能输入值的范围。用户只能输入大于或等于第一个符号且小于或等于第二个符号的值。

  • 帮助文本:“help”

这定义了一个帮助文本。帮助文本的结尾由缩进级别决定,这意味着它以第一行缩进比帮助文本的第一行小的行结尾。

  • 各种选项:“option” <symbol>[=<value>]
    可以通过此选项语法定义各种不太常见的选项,它可以修改菜单项及其配置符号的行为。这些选项目前是可能的:

“defconfig_list”这声明了一个默认条目列表,可以在查找默认配置时使用(当主.config尚未存在时使用)。
“modules”这声明了将符号用作模块符号,从而为所有配置符号启用了第三个模块状态。最多一个符号可能有“模块”选项集。
“allnoconfig_y”这声明了在使用“allnoconfig”时应具有y值的符号。隐藏其他符号的符号来使用。

菜单依赖项

依赖关系定义了菜单项的可见性,也可以减少三态符号的输入范围。表达式中使用的三态逻辑比正常布尔逻辑多使用一个状态来表达模块状态。依赖性表达式具有以下语法:

<expr> ::= <symbol>                           (1)
         <symbol> '=' <symbol>                (2)
         <symbol> '!=' <symbol>               (3)
         <symbol1> '<' <symbol2>              (4)
         <symbol1> '>' <symbol2>              (4)
         <symbol1> '<=' <symbol2>             (4)
         <symbol1> '>=' <symbol2>             (4)
         '(' <expr> ')'                       (5)
         '!' <expr>                           (6)
         <expr> '&&' <expr>                   (7)
         <expr> '||' <expr>                   (8)

表达式按优先顺序降序列出。

将符号转换为表达式。布尔和三态符号被简单地转换为各自的表达值。所有其他符号类型都会导致“n”。
如果两个符号的值相等,则返回“y”,否则返回“n”。
如果两个符号的值相等,则返回“n”,否则返回“y”。
如果的值分别低于、大于、低于或等于的值,则返回“y”,否则返回“n”。
返回表达式的值。用于覆盖优先序。
返回(2-/expr/)的结果。
返回min(/expr/, /expr/)的结果。
返回max(/expr/, /expr/)的结果。
表达式可以具有“n”、“m”或“y”的值(或计算时分别为0、1、2)。当菜单项的表达式评估为“m”或“y”时,菜单条目变得可见。

有两种类型的符号:常数符号和非常数符号。非常量符号是最常见的,用“配置”语句定义。非恒定符号完全由字母数字字符或下划线组成。常量符号只是表达式的一部分。常数符号总是被单引号或双引号包围。在引号中,允许任何其他字符,并且可以使用“”转义引号。

菜单结构

菜单项在树中的位置通过两种方式确定。首先,它可以明确指定:

menu "Network device support"
      depends on NET

config NETDEVICES
      ...

endmenu

“menu”…“endmenu”块中的所有条目都成为“网络设备支持”的子菜单。所有子条目都从菜单条目继承依赖项,例如,这意味着依赖项“NET”被添加到配置选项NETDEVICES的依赖项列表中。

生成菜单结构的另一种方法是分析依赖关系。如果菜单项以某种方式依赖于上一个条目,它可以成为它的子菜单。首先,前一个(父)符号必须是依赖列表的一部分,然后这两个条件之一必须为真:

  • 如果父项设置为“n”,则子条目必须变得不可见

  • 如果父项可见时,子条目只能可见的:

config MODULES
    bool "Enable loadable module support"

config MODVERSIONS
    bool "Set version information on all module symbols"
    depends on MODULES

comment "module support disabled"
    depends on !MODULES

MODVERSIONS直接取决于MODULES,这意味着只有当MODULES与“n”不同时,它才可见。另一方面,只有当MODULES设置为“n”时,comment才可见。

Kconfig语法

配置文件描述了一系列菜单项,其中每行都以关键字开头(帮助文本除外)。以下关键词结束菜单条目:

  • config
  • menuconfig
  • choice/endchoice
  • comment
  • menu/endmenu
  • if/endif
  • source

前五个也开始定义菜单项。
config:

"config" <symbol>
<config options>

这定义了一个配置符号<symbol>,并接受上述任何属性作为选项。

menuconfig:

"menuconfig" <symbol>
<config options>

这与上面的简单config条目相似,但它也给前端一个提示,即所有子选项都应显示为单独的选项列表。为了确保所有子选项真正出现在菜单配置条目下方,而不是外部,<config options>列表中的每个项目都必须依赖于menuconfig符号。在实践中,这是通过使用以下两种结构之一来实现的:

(1):
menuconfig M
if M
    config C1
    config C2
endif

(2):
menuconfig M
config C1
    depends on M
config C2
    depends on M

在以下示例(3)和(4)中,C1和C2仍然具有M依赖性,但不会再出现在menuconfig M下,因为C0不依赖于M:

(3):
menuconfig M
    config C0
if M
    config C1
    config C2
endif

(4):
menuconfig M
config C0
config C1
    depends on M
config C2
    depends on M

choices:

"choice" [symbol]
<choice options>
<choice block>
"endchoice"

这定义了一个选择组,并接受上述任何属性作为选项。一种选择只能是bool或tristate类型。如果没有为选择指定类型,其类型将由组中第一个选择元素的类型决定,或者如果没有选择元素指定类型,则保持未知状态。

虽然布尔选择只允许选择一个配置条目,但三态选择也允许将任意数量的配置条目设置为“m”。如果单个硬件存在多个驱动程序,并且只能将单个驱动程序编译/加载到内核中,但所有驱动程序都可以编译为模块,则可以使用。

choice接受另一个选项“optional”,该选项允许将choice设置为“n”,并且无需选择条目。如果没有[symbol]与一个选择相关联,那么您就不能对该choice有多个定义。如果[symbol]与choice相关联,那么您可以在另一个地方定义相同的choice(即具有相同的条目)。

comment:

"comment" <prompt>
<comment options>

这定义了一个注释,该注释在配置过程中显示给用户,并回显到输出文件中。唯一可能的选择是依赖项。

menu:

"menu" <prompt>
<menu options>
<menu block>
"endmenu"

这定义了一个菜单块,有关更多信息,请参阅上面的“菜单结构”。唯一可能的选项是依赖项和“可见”属性。

if:

"if" <expr>
<if block>
"endif"

这定义了一个if块。依赖性表达式<expr>附加到所有封闭的菜单项中。

source:

"source" <prompt>

这读取指定的配置文件。此文件总是被解析的。

mainmenu:

"mainmenu" <prompt>

如果配置程序选择使用它,这会设置配置程序的标题栏。它应该放在配置的顶部,在任何其他语句之前。

“#”Kconfig源文件注释:

源文件行中任何地方的未引号“#”字符表示源文件注释的开头。那行的其余部分是评论。

Kconfig提示

这是Kconfig提示的集合,其中大部分乍一看并不明显,其中大部分已经成为几个Kconfig文件中的成语。

添加常见功能并使使用可配置

这是一个常见的成语,用于实现与某些架构相关的特性/功能,但不是所有架构。这样做的推荐方法是使用名为HAVE_*的配置变量,该变量在通用Kconfig文件中定义,并由相关架构选择。通用的IOMAP功能就是一个例子。

我们将在lib/Kconfig中看到:

# Generic IOMAP is used to ...
config HAVE_GENERIC_IOMAP

config GENERIC_IOMAP
      depends on HAVE_GENERIC_IOMAP && FOO

在lib/Makefile中,我们将看到:

obj-$(CONFIG_GENERIC_IOMAP) += iomap.o

对于每个使用通用IOMAP功能的架构,我们将看到:

config X86
      select ...
      select HAVE_GENERIC_IOMAP
      select ...

注意:我们使用现有的配置选项,避免创建新的配置变量来选择HAVE_GENERIC_IOMAP。

注意:使用内部配置变量HAVE_GENERIC_IOMAP,是为了克服选择的限制,无论依赖项如何,select都会强制配置选项为“y”。依赖项被移动到符号GENERIC_IOMAP,我们避免了选择强制符号等于“y”的情况。

添加需要编译器支持的功能

有几个功能需要编译器支持。描述编译器功能依赖性的推荐方法是使用“依赖”,然后是测试宏:

config STACKPROTECTOR
      bool "Stack Protector buffer overflow detection"
      depends on $(cc-option,-fstack-protector)
      ...

如果您需要将编译器功能暴露给makefiles和/或C源文件,CC_HAS_是配置选项的推荐前缀:

config CC_HAS_ASM_GOTO
      def_bool $(success,$(srctree)/scripts/gcc-goto.sh $(CC))
仅构建为模块

要将组件构建限制为仅模块,请用“depends on m来限定其配置符号。例如:

config FOO
      depends on BAR && m

将FOO限制为模块(=m)或禁用(=n)。

Kconfig递归依赖限制

Kconfig 宏语言

概念

基本想法的灵感来自Make。当我们看Make时,我们注意到两种语言合二为一。一种语言描述了由目标和先决条件组成的依赖性图。另一个是执行文本替换的宏语言。

两个语言阶段之间有明显的区别。例如,您可以编写如下的makefile:

APP := foo
SRC := foo.c
CC := gcc

$(APP): $(SRC)
        $(CC) -o $(APP) $(SRC)

宏语言用其扩展形式替换变量引用,并处理源文件,就像输入了如下:

foo: foo.c
        gcc -o foo foo.c

然后,Make分析依赖关系图,并确定要更新的目标。

这个想法在Kconfig中非常相似——可以像这样描述Kconfig文件:

CC := gcc

config CC_HAS_FOO
        def_bool $(shell, $(srctree)/scripts/gcc-check-foo.sh $(CC))

Kconfig中的宏语言将源文件处理成以下中间体:

config CC_HAS_FOO
        def_bool y

然后,Kconfig进入评估阶段,以解决kconfig-language.rst中解释的符号间依赖关系。

变量

就像在Make中一样,Kconfig中的变量作为宏变量工作。宏变量被“就地”扩展,以产生一个文本字符串,然后可以进一步扩展。要获取变量的值,请将变量名称用 ( ) 括起来。即使是单字母变量名也需要括号; ()括起来。即使是单字母变量名也需要括号; ()括起来。即使是单字母变量名也需要括号;X是一个语法错误。也不支持${CC}中的花括号形式。

有两种类型的变量:简单扩展变量和递归扩展变量。

使用:=赋值运算符定义一个简单扩展的变量。从Kconfig文件中读取行后,其右侧立即展开。

使用=赋值运算符定义递归扩展变量。它的右侧只是存储为变量的值,而不以任何方式扩展它。相反,在使用变量时执行扩展。

还有另一种类型的赋值运算符;+=用于将文本附加到变量上。如果左侧最初被定义为简单变量,则+=的右侧会立即展开。否则,其评估将被推迟。

变量引用可以采取以下形式的参数:

$(name,arg1,arg2,arg3)

您可以将参数化引用视为一个函数。(更准确地说,“用户定义函数”与下面列出的“内置函数”相反)。

有用的函数在使用时必须展开,因为如果传递不同的参数,同一函数的展开会有所不同。因此,使用=赋值运算符定义用户定义的函数。参数在正文定义中引用 ( 1 )、 (1)、 1)、(2)等。

事实上,递归扩展变量和用户定义的函数在内部是相同的。(换句话说,“变量”是“零参数函数”。)当我们从广义上说“变量”时,它包括“用户定义的功能”。

内置功能

和Make一样,Kconfig提供了几个内置功能。每个函数都需要特定数量的参数。

在Make中,每个内置函数至少需要一个参数。Kconfig允许内置函数的零参数,例如$(fileno)、$(lineno)。你可以把这些视为“内置变量”,但这毕竟只是我们如何称呼它的问题。让我们在这里说“内置功能”来指代原生支持的功能。

Kconfig目前支持以下内置功能。

  • $(shell,command)
    “shell”函数接受单个参数,该参数被扩展并传递给子shell以执行。然后,命令的标准输出被读取并返回为函数的值。输出中的每个换行都替换为空格。任何尾随的换行都会被删除。不会返回标准错误,也不会返回任何程序退出状态。
  • $(info,text)
    “信息”函数接受单个参数并将其打印到stdout。它评估为一个空字符串。
  • $(warning-if,condition,text)
    “警告-如果”函数需要两个参数。如果条件部分是“y”,则文本部分将发送到stderr。文本的前缀是当前Kconfig文件的名称和当前行号。
  • $(error-if,condition,text)
    “error-if”函数类似于“warning-if”,但如果条件部分是“y”,它会立即终止解析。
  • $(filename)
    “文件名”不需要参数,$(文件名)被扩展到正在解析的文件名。
  • $(lineno)
    “lineno”不需要参数,$(lineno)扩展到正在解析的行号。

Make vs Kconfig

Kconfig采用类似Make的宏语言,但函数调用语法略有不同。

Make中的函数调用如下所示:

$(func-name arg1,arg2,arg3)

函数名称和第一个参数至少用一个空格隔开。然后,从第一个参数中修剪前导空格,而其他参数中的空格则保留。你需要使用一种技巧来用空格开始第一个参数。例如,如果你想让“信息”函数打印“你好”,你可以写如下:

empty :=
space := $(empty) $(empty)
$(info $(space)$(space)hello)

Kconfig仅使用逗号作为分隔符,并在函数调用中保留所有空格。有些人更喜欢在每个逗号分隔符后放一个空格:

$(func-name, arg1, arg2, arg3)

在这种情况下,“func-name”将接收“arg1”、“arg2”、“arg3”。前导空格的存在可能很重要,这取决于功能。这同样适用于Make——例如,$(subst .c,.o,$(sources))是一个典型的错误;它用“.o”替换“.c”。

在Make中,用户定义的函数使用内置函数“call”引用,如下:

$(call my-func,arg1,arg2,arg3)

Kconfig以相同的方式调用用户定义的函数和内置函数。省略“call”使语法变短。

在Make中,一些函数逐字处理逗号,而不是参数分隔符。例如,$(shell echo hello, world)运行命令“echo hello, world”。同样,$(info hello,world)将“hello,world”打印到stdout。你可以说这是_有用的_不一致。

在Kconfig中,为了更简单的实现和语法一致性,出现在$()上下文中的逗号总是分隔符。它的意思是:

$(shell, echo hello, world)

是一个错误,因为它传递了两个参数,而“shell”函数只接受一个。要在参数中传递逗号,您可以使用以下技巧:

comma := ,
$(shell, echo hello$(comma) world)

警告

变量(或函数)不能跨tokens扩展。因此,您不能将变量用作由多个tokens组成的表达式的速记。以下生效:

RANGE_MIN := 1
RANGE_MAX := 3

config FOO
        int "foo"
        range $(RANGE_MIN) $(RANGE_MAX)

但是,以下内容不起作用:

RANGES := 1 3

config FOO
        int "foo"
        range $(RANGES)

变量不能扩展到Kconfig中的任何关键字。以下操作不起作用:

MY_TYPE := tristate

config FOO
        $(MY_TYPE) "foo"
        default y

显然,从设计中,$(shell command)在文本替换阶段进行了扩展。您不能将符号传递给“shell”函数。

以下操作无法正常工作:

config ENDIAN_FLAG
        string
        default "-mbig-endian" if CPU_BIG_ENDIAN
        default "-mlittle-endian" if CPU_LITTLE_ENDIAN

config CC_HAS_ENDIAN_FLAG
        def_bool $(shell $(srctree)/scripts/gcc-check-flag ENDIAN_FLAG)

相反,您可以按如下方式进行操作,以便任何函数调用都静态扩展:

config CC_HAS_ENDIAN_FLAG
        bool
        default $(shell $(srctree)/scripts/gcc-check-flag -mbig-endian) if CPU_BIG_ENDIAN
        default $(shell $(srctree)/scripts/gcc-check-flag -mlittle-endian) if CPU_LITTLE_ENDIAN

Kbuild

参见 Kbuild

包含输出文件和环境变量介绍

Kconfig make config

参见配置目标和编辑器
介绍了关于.config及各种make *config相关的知识

Linux Kernel Makefiles

本文档描述 Linux 内核的 Makefiles。

1 概述

Makefiles 包含五个部分:

Makefile                the top Makefile.
.config                 the kernel configuration file.
arch/$(ARCH)/Makefile   the arch Makefile.
scripts/Makefile.*      common rules etc. for all kbuild Makefiles.
kbuild Makefiles        there are about 500 of these.

顶层 Makefile 读取来自内核配置过程的.config 文件。

顶层 Makefile 负责构建两个主要产物:vmlinux(常驻内核镜像)和 modules(任何模块文件)。它通过递归进入内核源代码树的子目录来构建这些目标。访问的子目录列表取决于内核配置。顶层 Makefile 以文本方式包含一个名为 arch/$(ARCH)/Makefile 的体系结构 Makefile。该 arch Makefile 向顶层 Makefile 提供体系结构特定的信息。

每个子目录都包含一个 kbuild Makefile,用于执行从上至下传递的命令。该 Makefile 利用.config 文件中的信息来构建各种文件列表,供 kbuild 用于编译内置或模块化目标。

scripts/Makefile.* 包含了所有基于 kbuild makefiles 构建内核时所需的定义和规则等。

2 各司其职

人们与内核 Makefiles 有四种不同的关联方式。

用户是指那些编译内核的人。他们通常输入诸如“make menuconfig”或“make”之类的命令,一般不会阅读或编辑任何内核 Makefile(或其他源文件)。

普通开发者是指从事设备驱动、文件系统和网络协议等功能开发的人员。他们需要维护所负责子系统的 kbuild Makefiles。为此,他们需对内核 Makefiles 有整体了解,并掌握 kbuild 公共接口的详细知识。

架构开发者是指从事整个架构(如 sparc 或 ia64)开发的人员。架构开发者需要了解 arch Makefile 以及 kbuild Makefiles。

Kbuild 开发者是指那些从事内核构建系统工作的人员。这些人需要了解内核 Makefile 的所有方面。

本文档主要面向普通开发者和架构开发者。

3 kbuild 文件

内核中的大多数 Makefile 都是使用 kbuild 基础设施的 kbuild Makefile。本章介绍 kbuild makefile 中使用的语法。kbuild 文件的首选名称是’Makefile’,但也可以使用’Kbuild’,如果同时存在’Makefile’和’Kbuild’文件,则会使用’Kbuild’文件。

第3.1节“目标定义”是一个快速介绍;后续章节将提供更多细节,并附有实际示例。

3.1 目标定义

目标定义是 kbuild Makefile 的主要部分(核心)。这些行定义了要构建的文件、任何特殊的编译选项以及要递归进入的子目录。

最简单的 kbuild makefile 只包含一行:

示例:

obj-y += foo.o

这告诉 kbuild 在该目录中有一个名为 foo.o 的对象。foo.o 将从 foo.c 或 foo.S 构建。

如果 foo.o 要构建为模块,则使用变量 obj-m。因此,经常使用以下模式:

示例:

obj-$(CONFIG_FOO) += foo.o

$(CONFIG_FOO) 的值为 y(表示内置)或 m(表示模块)。如果 CONFIG_FOO 既不是 y 也不是 m,则该文件不会被编译或链接。

3.2 内置对象目标 - obj-y

kbuild 的 Makefile 在 $(obj-y) 列表中指定了 vmlinux 的目标文件。这些列表依赖于内核配置。

Kbuild 会编译所有 $(obj-y) 文件,然后调用“$(AR) rcSTP”将这些文件合并为一个 built-in.a 文件。这是一个没有符号表的精简归档文件,稍后会被 scripts/link-vmlinux.sh 链接到 vmlinux 中。

$(obj-y) 中文件的顺序很重要。列表中的重复项是允许的:第一个实例会被链接到 built-in.a 中,后续实例将被忽略。

链接顺序至关重要,因为某些函数(module_init() / __initcall)会在启动时按照它们出现的顺序被调用。因此请注意,更改链接顺序可能会影响例如 SCSI 控制器的检测顺序,从而导致磁盘重新编号。

示例:

#drivers/isdn/i4l/Makefile
# Makefile for the kernel ISDN subsystem and device drivers.
# Each configuration option enables a list of files.
obj-$(CONFIG_ISDN_I4L)         += isdn.o
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
3.3 可加载模块目标 - obj-m

$(obj-m) 指定了构建为可加载内核模块的目标文件。

一个模块可以由一个源文件或多个源文件构建。在只有一个源文件的情况下,kbuild 的 makefile 只需将该文件添加到$(obj-m)中。

示例:

#drivers/isdn/i4l/Makefile
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

注意:在此示例中,$(CONFIG_ISDN_PPP_BSDCOMP) 的值为 ‘m’

如果内核模块由多个源文件构建而成,您需按照上述相同方式指定构建模块;然而,kbuild 需要知道您希望从哪些目标文件构建模块,因此您必须通过设置$(-y)变量来告知它。

示例:

#drivers/isdn/i4l/Makefile
obj-$(CONFIG_ISDN_I4L) += isdn.o
isdn-y := isdn_net_lib.o isdn_v110.o isdn_common.o

在此示例中,模块名称将为 isdn.o。Kbuild 将编译$(isdn-y)中列出的目标文件,然后对这些文件列表执行“$(LD) -r”以生成 isdn.o。

由于 kbuild 识别$(< 模块名称>-y)用于复合对象,您可以使用 CONFIG_ 符号的值选择性地将目标文件作为复合对象的一部分包含进来。

示例:

#fs/ext2/Makefile
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o dir.o file.o ialloc.o inode.o ioctl.o \
          namei.o super.o symlink.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o xattr_user.o \
                                xattr_trusted.o

在此示例中,只有当$(CONFIG_EXT2_FS_XATTR)的值为‘y’时,xattr.o、xattr_user.o 和 xattr_trusted.o 才会成为复合对象 ext2.o 的组成部分。

注意:当然,当您将对象构建到内核中时,上述语法同样适用。因此,如果设置了 CONFIG_EXT2_FS=y,kbuild 会为您将各个部分构建成 ext2.o 文件,并如预期那样将其链接到 built-in.a 中。

3.4 导出符号的对象

在 Makefiles 中无需为导出符号的模块使用特殊标记。

3.5 库文件目标 - lib-y

使用 obj-*列出的对象用于模块,或合并到该目录下的 built-in.a 中。此外,还可以列出将包含在库 lib.a 中的对象。所有以 lib-y 列出的对象会被合并为该目录下的单一库。同时在 obj-y 和 lib-y 中列出的对象不会包含在库中,因为它们已可访问。为保持一致性,lib-m 中列出的对象将包含在 lib.a 中。

注意,同一个 kbuild makefile 可能既列出要构建到内置中的文件,也列出作为库一部分的文件。因此同一目录可能同时包含 built-in.a 和 lib.a 文件。

示例:

#arch/x86/lib/Makefile
lib-y    := delay.o

这将基于 delay.o 创建一个库 lib.a。为了让 kbuild 实际识别正在构建的 lib.a,该目录应被列在 libs-y 中。

另见“6.4 列出下降时要访问的目录”。

lib-y 的使用通常仅限于 lib/ 和 arch/*/lib。

3.6 向下进入目录

Makefile 仅负责构建其所在目录中的对象文件。子目录中的文件应由这些子目录中的 Makefile 处理。构建系统会自动递归地在子目录中调用 make,前提是你已告知它这些子目录的存在。

为此,使用 obj-y 和 obj-m。ext2 位于单独的目录中,fs/中的 Makefile 通过以下赋值告知 kbuild 下降进入该目录。

示例:

#fs/Makefile
obj-$(CONFIG_EXT2_FS) += ext2/

如果 CONFIG_EXT2_FS 被设置为‘y’(内置)或‘m’(模块化),相应的 obj-变量将被设置,kbuild 将进入 ext2 目录继续处理。

Kbuild 不仅利用此信息决定是否需要访问该目录,还用于判断是否将该目录中的对象链接至 vmlinux。

当 Kbuild 以’y’标志进入目录时,该目录下所有内置对象将合并为 built-in.a,最终被链接到 vmlinux 中。

相反,当 Kbuild 以’m’标志进入目录时,该目录内容不会链接到 vmlinux。若该目录 Makefile 指定了 obj-y,这些对象将成为孤立项,很可能是 Makefile 或 Kconfig 依赖存在错误。

最佳实践是使用 CONFIG_ 变量指定目录名,这样当对应 CONFIG_ 选项既非’y’也非’m’时,kbuild 可完全跳过该目录。

3.7 编译标志

ccflags-y、asflags-y 及 ldflags-y
这三个标志仅适用于它们被分配到的 kbuild makefile 中。它们用于递归构建过程中所有正常的 cc、as 和 ld 调用。注意:具有相同行为的标志以前被命名为 EXTRA_CFLAGS、EXTRA_AFLAGS 和 EXTRA_LDFLAGS。它们仍然被支持,但使用已被弃用。

ccflags-y 指定了使用$(CC)编译时的选项。

示例:

# drivers/acpi/acpica/Makefile
ccflags-y                       := -Os -D_LINUX -DBUILDING_ACPICA
ccflags-$(CONFIG_ACPI_DEBUG)    += -DACPI_DEBUG_OUTPUT

此变量是必要的,因为顶层 Makefile 拥有变量$(KBUILD_CFLAGS)并将其用于整个树的编译标志。

asflags-y 指定了汇编器选项。

示例:

#arch/sparc/kernel/Makefile
asflags-y := -ansi

ldflags-y 指定了与 $(LD) 链接时的选项。

示例:

#arch/cris/boot/compressed/Makefile
ldflags-y += -T $(srctree)/$(src)/decompress_$(arch-y).lds
subdir-ccflags-y, subdir-asflags-y

上述两个标志与 ccflags-y 和 asflags-y 类似。不同之处在于,带有 subdir 前缀的变体对当前 kbuild 文件及其所有子目录生效。使用 subdir-*指定的选项会在非 subdir 变体指定的选项之前被添加到命令行中。

示例:

subdir-ccflags-y := -Werror
ccflags-remove-y, asflags-remove-y

这些标志用于在编译器、汇编器调用中移除特定的标志。

示例:

ccflags-remove-$(CONFIG_MCOUNT) += -pg
CFLAGS_$@, AFLAGS_$@

CFLAGS_$@ 和 AFLAGS_$@ 仅适用于当前 kbuild makefile 中的命令。

$(CFLAGS_$@) 为 $(CC) 指定针对特定文件的选项。$@部分是一个字面值,用于指定所针对的文件。

CFLAGS_$@ 的优先级高于 ccflags-remove-y;CFLAGS_$@ 可以重新添加被 ccflags-remove-y 移除的编译器标志。

示例:

# drivers/scsi/Makefile
CFLAGS_aha152x.o =   -DAHA152X_STAT -DAUTOCONF
CFLAGS_gdth.o    = # -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ \
                     -DGDTH_STATISTICS

这两行代码为 aha152x.o 和 gdth.o 指定了编译标志。

$(AFLAGS_$@) 是针对汇编语言源文件的类似功能。

AFLAGS_$@ 的优先级高于 asflags-remove-y;AFLAGS_$@ 可以重新添加被 asflags-remove-y 移除的汇编器标志。

示例:

# arch/arm/kernel/Makefile
AFLAGS_head.o        := -DTEXT_OFFSET=$(TEXT_OFFSET)
AFLAGS_crunch-bits.o := -Wa,-mcpu=ep9312
AFLAGS_iwmmxt.o      := -Wa,-mcpu=iwmmxt
3.9 依赖跟踪

Kbuild 跟踪以下依赖关系:

  1. 所有先决条件文件(包括 *.c 和*.h)
  2. 所有先决条件文件中使用的 CONFIG_ 配置选项
  3. 编译目标时使用的命令行
    因此,如果您更改了$(CC)的某个选项,所有受影响的文件都将重新编译。
3.10 特殊规则

当 kbuild 基础设施无法提供所需支持时,会使用特殊规则。典型例子包括构建过程中生成的头文件,另一个例子是架构特定的 Makefile,它们需要特殊规则来准备启动镜像等。

特殊规则以普通 Make 规则的形式编写。Kbuild 不在 Makefile 所在目录下执行,因此所有特殊规则必须提供相对于目标文件和依赖文件的路径。

定义特殊规则时使用两个变量:

$(src)
$(src)是一个相对路径,指向 Makefile 所在的目录。引用源码树中的文件时始终使用$(src)。
$(obj)
$(obj)是一个相对路径,指向目标文件保存的目录。引用生成的文件时始终使用$(obj)。

示例:

#drivers/scsi/Makefile
$(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl
        $(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl

这是一条特殊规则,遵循 make 所需的常规语法。

目标文件依赖于两个前提文件。对目标文件的引用前缀为$(obj),对前提文件的引用使用$(src)(因为它们不是生成的文件)。

$(kecho)
在规则中向用户回显信息通常是一种良好实践,但当执行“make -s”时,除了警告/错误外不应显示任何输出。为支持这一点,kbuild 定义了$(kecho),它会将跟随$(kecho)的文本输出到标准输出,除非使用了“make -s”。
示例:

#arch/blackfin/boot/Makefile
$(obj)/vmImage: $(obj)/vmlinux.gz
       $(call if_changed,uimage)
       @$(kecho) 'Kernel: $@ is ready'
3.11 $(CC)支持功能

内核可以使用多个不同版本的$(CC)进行构建,每个版本支持一组独特的特性和选项。kbuild 提供了基本功能来检查$(CC)的有效选项。$(CC)通常是 gcc 编译器,但也有其他替代方案可用。
as-option
as-option 用于检查$(CC)在编译汇编文件(*.S)时是否支持给定的选项。如果第一个选项不被支持,可以指定一个可选的第二个选项。

示例:

#arch/sh/Makefile
cflags-y += $(call as-option,-Wa$(comma)-isa=$(isa-y),)

在上述示例中,如果$(CC)支持选项-Wa$(comma)-isa=$(isa-y),则 cflags-y 将被赋予该选项。第二个参数是可选的,如果提供,将在第一个参数不被支持时使用。

as-instr
as-instr 用于检查汇编器是否支持特定指令,并输出 option1 或 option2 选项。测试指令中支持 C 语言转义字符。注意:as-instr-option 使用 KBUILD_AFLAGS 作为汇编器选项
cc-option
cc-option 用于检查 $(CC) 是否支持给定的选项,如果不支持则使用可选的第二个选项。

示例:

#arch/x86/Makefile
cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)

在上述示例中,如果$(CC)支持,cflags-y 将被赋值为选项-march=pentium-mmx,否则为-march=i586。cc-option 的第二个参数是可选的,如果省略,当第一个选项不被支持时,cflags-y 将不会被赋值。注意:cc-option 使用 KBUILD_CFLAGS 作为$(CC)的选项。

cc-option-yn
cc-option-yn 用于检查 gcc 是否支持给定选项,若支持则返回‘y’,否则返回‘n’。

示例:

#arch/ppc/Makefile
biarch := $(call cc-option-yn, -m32)
aflags-$(biarch) += -a32
cflags-$(biarch) += -m32

上例中,若$(CC)支持-m32 选项,则$(biarch)被设为 y。当$(biarch)等于‘y’时,展开的变量$(aflags-y)和$(cflags-y)将分别被赋值为-a32 和-m32。注意:cc-option-yn 使用 KBUILD_CFLAGS 作为$(CC)的选项。

cc-disable-warning
cc-disable-warning 用于检查 gcc 是否支持特定警告,并返回禁用该警告的命令行开关。此特殊函数的存在是因为 gcc 4.4 及更高版本会接受任何未知的-Wno-*选项,仅当源文件中存在其他警告时才会对此发出警告。

示例:

KBUILD_CFLAGS += $(call cc-disable-warning, unused-but-set-variable)

上例中,仅当 gcc 实际接受时,-Wno-unused-but-set-variable 才会被添加到 KBUILD_CFLAGS 中。

cc-ifversion
cc-ifversion 用于测试$(CC)的版本,若版本表达式为真则等于第四个参数,若为假则等于第五个参数(如提供)。

示例:

#fs/reiserfs/Makefile
ccflags-y := $(call cc-ifversion, -lt, 0402, -O1)

在这个例子中,如果 $(CC) 的版本低于 4.2,ccflags-y 将被赋值为 -O1。cc-ifversion 接受所有 shell 操作符:-eq、-ne、-lt、-le、-gt 和 -ge。第三个参数可以是本例中的文本,也可以是展开的变量或宏。

cc-cross-prefix
cc-cross-prefix 用于检查 PATH 中是否存在带有列出的前缀之一的$(CC)。返回第一个在 PATH 中找到 prefix$(CC)的前缀——如果未找到任何 prefix$(CC),则不返回任何内容。在调用 cc-cross-prefix 时,多个前缀之间用单个空格分隔。此功能对于尝试将 CROSS_COMPILE 设置为已知值但可能有多个值可供选择的架构 Makefile 非常有用。建议仅在交叉编译(主机架构与目标架构不同)时尝试设置 CROSS_COMPILE。如果 CROSS_COMPILE 已设置,则保留其旧值。

示例:

#arch/m68k/Makefile
ifneq ($(SUBARCH),$(ARCH))
        ifeq ($(CROSS_COMPILE),)
               CROSS_COMPILE := $(call cc-cross-prefix, m68k-linux-gnu-)
        endif
endif
3.12 $(LD)支持功能

ld-option
ld-option 用于检查$(LD)是否支持提供的选项。ld-option 接受两个选项作为参数。第二个参数是一个可选选项,如果$(LD)不支持第一个选项,则可以使用该选项。

示例:

#Makefile
LDFLAGS_vmlinux += $(call ld-option, -X)
3.13 脚本调用

构建规则可以调用脚本来编译内核。这些规则应始终提供适当的解释器来执行脚本,不应依赖脚本的可执行权限设置,也不应直接调用脚本。为了方便手动调用脚本(例如执行./scripts/checkpatch.pl),仍建议为脚本设置可执行权限。

Kbuild 提供了变量$(CONFIG_SHELL)、$(AWK)、$(PERL)、$(PYTHON)和$(PYTHON3)来引用相应脚本的解释器。

示例:

#Makefile
cmd_depmod = $(CONFIG_SHELL) $(srctree)/scripts/depmod.sh $(DEPMOD) \
             $(KERNELRELEASE)

4 主机程序支持

Kbuild 支持在编译阶段构建用于主机环境的可执行程序。使用主机程序需要两个步骤。

第一步是告知 kbuild 存在一个主机程序,这通过使用变量“hostprogs”来实现。

第二步是为可执行文件添加显式依赖。可以通过两种方式实现:在规则中添加依赖,或利用变量“always-y”。以下将描述这两种方法。

4.1 简单主机程序

在某些情况下,需要在执行构建的计算机上编译并运行程序。以下这行代码告诉 kbuild,程序 bin2hex 将在构建主机上被编译。

示例:

hostprogs := bin2hex

在上述示例中,kbuild 假设 bin2hex 是由一个名为 bin2hex.c 的单一 C 源文件生成的,该文件与 Makefile 位于同一目录下。

4.2 复合主机程序

主机程序可以基于复合对象构建。用于定义主机程序复合对象的语法与内核对象类似。$(<可执行文件名>-objs)列出了链接最终可执行文件所需的所有对象。

示例:

#scripts/lxdialog/Makefile
hostprogs     := lxdialog
lxdialog-objs := checklist.o lxdialog.o

扩展名为 .o 的对象文件由对应的 .c 文件编译而来。在上述示例中,checklist.c 被编译为 checklist.o,而 lxdialog.c 被编译为 lxdialog.o。

最后,这两个.o 文件被链接成可执行文件 lxdialog。注意:对于主机程序,不允许使用 -y 的语法。

4.3 为主机程序使用 C++语言

kbuild 提供了对用 C++ 编写的宿主程序的支持。这一功能最初仅用于支持 kconfig,不建议在一般情况下使用。

示例:

#scripts/kconfig/Makefile
hostprogs     := qconf
qconf-cxxobjs := qconf.o

在上面的例子中,可执行文件由 C++文件 qconf.cc 构成,通过$(qconf-cxxobjs)标识。

如果 qconf 由.c 和.cc 文件混合组成,则可使用额外的一行来标识这一点。

示例:

#scripts/kconfig/Makefile
hostprogs     := qconf
qconf-cxxobjs := qconf.o
qconf-objs    := check.o
4.4 控制主机程序的编译器选项

编译主机程序时,可以设置特定的编译标志。这些程序总是通过$(HOSTCC)编译,并传递$(KBUILD_HOSTCFLAGS)中指定的选项。若要设置对该 Makefile 中创建的所有主机程序生效的标志,请使用变量 HOST_EXTRACFLAGS。

示例:

#scripts/lxdialog/Makefile
HOST_EXTRACFLAGS += -I/usr/include/ncurses

要为单个文件设置特定的标志,可使用以下结构:

示例:

#arch/ppc64/boot/Makefile
HOSTCFLAGS_piggyback.o := -DKERNELBASE=$(KERNELBASE)

也可以指定额外的链接器选项。

示例:

#scripts/kconfig/Makefile
HOSTLDLIBS_qconf := -L$(QTDIR)/lib

当链接 qconf 时,将会传递额外的选项“-L$(QTDIR)/lib”。

4.5 主机程序的实际构建

Kbuild 仅在主机程序被作为先决条件引用时才会构建它们。这可以通过两种方式实现:

  1. 在特殊规则中明确列出先决条件。
    示例:
#drivers/pci/Makefile
hostprogs := gen-devlist
$(obj)/devlist.h: $(src)/pci.ids $(obj)/gen-devlist
        ( cd $(obj); ./gen-devlist ) < $<

目标 $(obj)/devlist.h 在 $(obj)/gen-devlist 更新之前不会被构建。请注意,特殊规则中对主机程序的引用必须以 $(obj) 为前缀。

  1. 始终使用 always-y
    当没有合适的特殊规则,且需要在进入 Makefile 时构建主机程序时,应使用 always-y 变量。

示例:

#scripts/lxdialog/Makefile
hostprogs     := lxdialog
always-y      := $(hostprogs)

Kbuild 为此提供了以下简写形式:

hostprogs-always-y := lxdialog

这将告知 kbuild 即使没有任何规则引用,也要构建 lxdialog。

5 用户空间程序支持

与主机程序类似,Kbuild 也支持为目标架构(即与正在构建的内核相同的架构)构建用户空间可执行文件。

语法非常相似,区别在于使用“userprogs”而非“hostprogs”。

5.1 简单用户空间程序

以下行告诉 kbuild 程序 bpf-direct 应针对目标架构进行构建。

示例:

userprogs := bpf-direct

Kbuild 在上述示例中假设 bpf-direct 是由一个名为 bpf-direct.c 的单个 C 源文件生成的,该文件与 Makefile 位于同一目录下。

5.2 组合式用户空间程序

用户空间程序可以基于组合对象构建。定义用户空间程序组合对象的语法与内核对象类似。$(-objs)列出了链接最终可执行文件时使用的所有对象。

示例:

#samples/seccomp/Makefile
userprogs      := bpf-fancy
bpf-fancy-objs := bpf-fancy.o bpf-helper.o

扩展名为.o 的对象由对应的.c 文件编译而来。在上述示例中,bpf-fancy.c 被编译为 bpf-fancy.o,bpf-helper.c 被编译为 bpf-helper.o。

最终,这两个.o 文件被链接成可执行文件 bpf-fancy。注意:用户空间程序不允许使用 -y 语法。

5.3 控制用户空间程序的编译器选项

编译用户空间程序时,可以设置特定标志。程序始终会使用$(CC)并传递$(KBUILD_USERCFLAGS)中指定的选项进行编译。若要设置对该 Makefile 中创建的所有用户空间程序生效的标志,请使用变量 userccflags。

示例:

# samples/seccomp/Makefile
userccflags += -I usr/include

要为单个文件设置特定的标志,可使用以下结构:

示例:

bpf-helper-userccflags += -I user/include

也可以指定额外的链接器选项。

示例:

# net/bpfilter/Makefile
bpfilter_umh-userldflags += -static

在链接 bpfilter_umh 时,会传递额外的静态选项-static。

5.4 实际构建用户空间程序时

Kbuild 仅在被告知时才构建用户空间程序,有两种实现方式。

  1. 将其作为另一个文件的先决条件添加
    示例:
#net/bpfilter/Makefile
userprogs := bpfilter_umh
$(obj)/bpfilter_umh_blob.o: $(obj)/bpfilter_umh

$(obj)/bpfilter_umh 在$(obj)/bpfilter_umh_blob.o 之前构建

  1. 使用 always-y
    示例:
userprogs := binderfs_example
always-y := $(userprogs)

Kbuild 为此提供了以下简写形式:

userprogs-always-y := binderfs_example

这将告知 Kbuild 在访问此 Makefile 时构建 binderfs_example。

6 Kbuild 清理基础设施

“make clean”会删除内核编译所在对象树中的大多数生成文件,包括宿主程序等生成文件。Kbuild 能识别列在$(hostprogs)、$(always-y)、$(always-m)、$(always-)、$(extra-y)、$(extra-)和$(targets)中的目标文件。执行“make clean”时,这些文件都会被删除。当执行“make clean”时,整个内核源码树中匹配模式“.[oas]”、“.ko”的文件以及 kbuild 生成的一些附加文件都会被删除。

可以在 kbuild makefiles 中通过使用$(clean-files)来指定额外的文件或目录。

示例:

#lib/Makefile
clean-files := crc32table.h

执行“make clean”时,文件“crc32table.h”将被删除。Kbuild 会假定文件与 Makefile 位于相同的相对目录下,除非以$(objtree)为前缀。

若要从 make clean 中排除特定文件或目录,请使用$(no-clean-files)变量。

通常 kbuild 会因“obj-* := dir/”而向下进入子目录,但在架构 Makefile 中,当 kbuild 基础设施不足时,有时需要显式指定。

示例:

#arch/x86/boot/Makefile
subdir- := compressed

上述赋值指示 kbuild 在执行“make clean”时进入 compressed/目录。

为了支持构建最终启动镜像的 Makefile 中的清理基础设施,有一个名为 archclean 的可选目标:

示例:

#arch/x86/Makefile
archclean:
        $(Q)$(MAKE) $(clean)=arch/x86/boot

当执行“make clean”时,make 会进入 arch/x86/boot 目录,并像往常一样进行清理。位于 arch/x86/boot/的 Makefile 可能会使用“subdir-”技巧进一步深入子目录。

注意 1:arch/$(ARCH)/Makefile 不能使用“subdir-”,因为该文件被包含在顶层 makefile 中,而此时 kbuild 基础设施尚未运行。

注 2:在“make clean”过程中,将访问所有列在 core-y、libs-y、drivers-y 和 net-y 中的目录。

7 架构 Makefiles

顶层 Makefile 在开始进入各个子目录之前,负责设置环境并进行准备工作。顶层 Makefile 包含通用部分,而 arch/$(ARCH)/Makefile 则包含针对该架构设置 kbuild 所需的内容。为此,arch/$(ARCH)/Makefile 会设置一系列变量并定义若干目标。

当 kbuild 执行时,大致遵循以下步骤:

内核配置 => 生成.config 文件
将内核版本信息存储到 include/linux/version.h 中
更新目标 prepare 的所有其他先决条件:- 额外先决条件在 arch/$(ARCH)/Makefile 中指定
递归进入 init-、core、drivers-、net-、libs-*等列出的所有目录,构建全部目标。- 上述变量的值在 arch/$(ARCH)/Makefile 中展开。
随后所有目标文件被链接,生成的 vmlinux 文件位于对象树的根目录。最先链接的对象由 arch/$(ARCH)/Makefile 中的 head-y 指定。
最后,架构特定的部分会进行任何必要的后处理并构建最终的启动镜像。——这包括构建引导记录、准备 initrd 镜像等操作。

7.1 设置变量以调整针对特定架构的构建

KBUILD_LDFLAGS
通用的$(LD)选项

用于所有链接器调用的标志。通常指定仿真模式即可满足需求。

示例:

#arch/s390/Makefile
KBUILD_LDFLAGS         := -m elf_s390

注意:可通过 ldflags-y 进一步自定义使用的标志。详见第 3.7 节。

LDFLAGS_vmlinux
链接 vmlinux 时传递给$(LD)的选项

LDFLAGS_vmlinux 用于指定在链接最终 vmlinux 镜像时传递给链接器的额外标志。LDFLAGS_vmlinux 利用了 LDFLAGS_$@的支持功能。

示例:

#arch/x86/Makefile
LDFLAGS_vmlinux := -e stext

OBJCOPYFLAGS(目标文件复制标志)
objcopy 标志

当使用 $(call if_changed,objcopy) 转换 .o 文件时,将使用 OBJCOPYFLAGS 中指定的标志。$(call if_changed,objcopy) 通常用于在 vmlinux 上生成原始二进制文件。

示例:

#arch/s390/Makefile
OBJCOPYFLAGS := -O binary

#arch/s390/boot/Makefile
$(obj)/image: vmlinux FORCE
        $(call if_changed,objcopy)

在这个例子中,二进制文件 $(obj)/image 是 vmlinux 的二进制版本。$(call if_changed,xxx) 的用法将在后面描述。

KBUILD_AFLAGS
汇编器标志

默认值 - 参见顶层 Makefile。根据架构需求追加或修改。

示例:

#arch/sparc64/Makefile
KBUILD_AFLAGS += -m64 -mcpu=ultrasparc

KBUILD_CFLAGS
$(CC) 编译器标志

默认值 - 参见顶层 Makefile。根据架构需求追加或修改。

通常,KBUILD_CFLAGS 变量依赖于配置。

示例:

#arch/x86/boot/compressed/Makefile
cflags-$(CONFIG_X86_32) := -march=i386
cflags-$(CONFIG_X86_64) := -mcmodel=small
KBUILD_CFLAGS += $(cflags-y)

许多架构的 Makefile 会动态运行目标 C 编译器以探测支持的选项:

#arch/x86/Makefile

...
cflags-$(CONFIG_MPENTIUMII)     += $(call cc-option,\
                                -march=pentium2,-march=i686)
...
# Disable unit-at-a-time mode ...
KBUILD_CFLAGS += $(call cc-option,-fno-unit-at-a-time)
...

第一个例子利用了配置选项在被选中时会扩展为‘y’的技巧。

KBUILD_AFLAGS_KERNEL
专用于内置模块的汇编器选项

$(KBUILD_AFLAGS_KERNEL) 包含用于编译常驻内核代码的额外 C 编译器标志。

KBUILD_AFLAGS_MODULE
模块专用的汇编器选项

$(KBUILD_AFLAGS_MODULE) 用于添加架构特定的选项,这些选项供汇编器使用。

在命令行中应使用 AFLAGS_MODULE(参见 kbuild.rst)。

KBUILD_CFLAGS_KERNEL
专用于内置代码的$(CC)选项

$(KBUILD_CFLAGS_KERNEL)包含用于编译常驻内核代码的额外 C 编译器标志。

KBUILD_CFLAGS_MODULE
构建模块时使用的$(CC)选项

$(KBUILD_CFLAGS_MODULE)用于添加架构特定的$(CC)编译选项。命令行中应使用 CFLAGS_MODULE(参见 kbuild.rst)。

KBUILD_LDFLAGS_MODULE
链接模块时使用的$(LD)选项

$(KBUILD_LDFLAGS_MODULE)用于添加链接模块时的架构特定选项,通常为链接器脚本。

命令行中应使用 LDFLAGS_MODULE(参见 kbuild.rst)。

KBUILD_LDS

带完整路径的链接器脚本。由顶层 Makefile 指定。
KBUILD_LDS_MODULE

带有完整路径的模块链接器脚本。由顶层 Makefile 以及架构相关的 Makefile 额外指定。
KBUILD_VMLINUX_OBJS(内核构建系统变量,表示 vmlinux 目标文件列表)

构成 vmlinux 的所有目标文件。这些文件按照 KBUILD_VMLINUX_OBJS 中列出的顺序被链接到 vmlinux 中。
KBUILD_VMLINUX_LIBS

vmlinux 的所有 .a “lib” 文件。KBUILD_VMLINUX_OBJS 和 KBUILD_VMLINUX_LIBS 共同指定了用于链接 vmlinux 的所有目标文件。

7.2 为 archheaders 添加先决条件

archheaders 规则用于生成可能通过“make header_install”安装到用户空间的头文件。

当在架构本身上运行时,它会在“make archprepare”之前执行。

7.3 为 archprepare 添加先决条件

archprepare 规则用于列出在开始进入子目录前需要构建的依赖项,通常用于包含汇编器常量的头文件。

示例:

#arch/arm/Makefile
archprepare: maketools

在此示例中,文件目标 maketools 将在进入子目录前被处理。另见描述 kbuild 如何支持生成偏移头文件的 XXX-TODO 章节。

7.4 列出遍历时需要访问的目录

架构相关的 Makefile 与顶层 Makefile 协作,定义变量以指定如何构建 vmlinux 文件。注意模块构建没有对应的架构特定部分,模块构建机制是完全架构无关的。

head-y, init-y, core-y, libs-y, drivers-y, net-y
$(head-y) 列出了在 vmlinux 中首先链接的对象。

$(libs-y) 列出了可以找到 lib.a 归档文件的目录。

其余部分列出了可以找到 built-in.a 目标文件的目录。

$(init-y) 目标文件将位于 $(head-y) 之后。

然后其余部分按以下顺序排列:

$(core-y)、$(libs-y)、$(drivers-y) 和 $(net-y)。
顶层 Makefile 定义了所有通用目录的值,而 arch/$(ARCH)/Makefile 仅添加特定于体系结构的目录。

示例:

#arch/sparc64/Makefile
core-y += arch/sparc64/kernel/
libs-y += arch/sparc64/prom/ arch/sparc64/lib/
drivers-$(CONFIG_OPROFILE)  += arch/sparc64/oprofile/
7.5 架构特定的启动映像

架构相关的 Makefile 定义了处理 vmlinux 文件的目标,包括压缩、封装引导代码以及将生成文件复制到指定位置的操作。这些操作可能包含各种安装命令,但具体目标在不同架构间并无统一标准。

通常会在 arch/$(ARCH)/目录下的 boot/子目录中放置任何额外的处理流程。

Kbuild 并未提供智能方式来支持构建 boot/目录中指定的目标。因此,arch/$(ARCH)/Makefile 需手动调用 make 命令来构建 boot/目录下的目标。

推荐的做法是在 arch/$(ARCH)/Makefile 中包含快捷指令,并在调用 arch/$(ARCH)/boot/Makefile 时使用完整路径。

示例:

#arch/x86/Makefile
boot := arch/x86/boot
bzImage: vmlinux
        $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@

“$(Q)$(MAKE) $(build)=<dir>”是在子目录中调用 make 的推荐方式。

对于特定架构目标的命名没有固定规则,但执行“make help”会列出所有相关目标。为了支持这一点,必须定义$(archhelp)。

示例:

#arch/x86/Makefile
define archhelp
  echo  '* bzImage      - Image (arch/$(ARCH)/boot/bzImage)'
endif

当不带参数执行 make 时,将构建遇到的第一个目标。在顶层 Makefile 中,第一个目标是 all:。架构默认情况下应始终构建可启动镜像。在“make help”中,默认目标会以‘*’标记突出显示。若要选择不同于 vmlinux 的默认目标,需向 all:添加新的先决条件。

示例:

#arch/x86/Makefile
all: bzImage

当不带参数执行“make”时,将构建 bzImage。

7.6 构建非 kbuild 目标

extra-y
extra-y 指定在当前目录下创建的额外目标,这些目标是在 obj-* 所指定目标之外添加的。

在 extra-y 中列出所有目标出于两个目的:

  1. 启用 kbuild 检查命令行中的更改
    当使用$(call if_changed,xxx)时
  2. kbuild 知道在“make clean”期间要删除哪些文件
    示例:
#arch/x86/kernel/Makefile
extra-y := head.o init_task.o

在此示例中,extra-y 用于列出应构建但不作为 built-in.a 一部分链接的目标文件。

7.7 构建启动映像有用的命令

Kbuild 提供了一些在构建启动映像时有用的宏。

if_changed
if_changed 是用于以下命令的基础设施。

用法:

target: source(s) FORCE
        $(call if_changed,ld/objcopy/gzip/...)

当规则被评估时,会检查是否有任何文件需要更新,或者自上次调用以来命令行发生了变化。后者会在可执行文件的任何选项发生变化时强制重新构建。任何使用 if_changed 的目标必须列在$(targets)中,否则命令行检查将失败,目标将总是被构建。对$(targets)的赋值不带$(obj)/前缀。if_changed 可以与 7.8 节“自定义 kbuild 命令”中定义的自定义命令结合使用。

注意:忘记 FORCE 先决条件是一个典型的错误。另一个常见的陷阱是空格有时很重要;例如,下面的代码会失败(注意逗号后的额外空格):

target: source(s) FORCE

错误! $(call if_changed, ld/objcopy/gzip/…)

注意:
if_changed 不应在同一目标上多次使用。它会将执行的命令存储在对应的 .cmd 文件中
多次调用会导致覆盖,当目标已是最新状态时,仅通过变更命令的测试触发命令执行,从而产生非预期结果。

ld
链接目标。通常使用 LDFLAGS_$@来为 ld 设置特定选项。

示例:

#arch/x86/boot/Makefile
LDFLAGS_bootsect := -Ttext 0x0 -s --oformat binary
LDFLAGS_setup    := -Ttext 0x0 -s --oformat binary -e begtext

targets += setup setup.o bootsect bootsect.o
$(obj)/setup $(obj)/bootsect: %: %.o FORCE
        $(call if_changed,ld)

在此示例中,存在两个可能的目标,需要为链接器提供不同的选项。链接器选项通过 LDFLAGS_$@语法指定——每个潜在目标对应一组。$(targets)被赋予所有潜在目标,kbuild 据此知晓目标并会:

  1. 检查命令行变更
  2. 在 make clean 时删除目标
    前置条件中的“: %: %.o”部分是一种简写,免去了我们逐一列出 setup.o 和 bootsect.o 文件的麻烦。

注意:
常见错误是遗漏“targets :=”赋值,导致目标文件在无明显原因的情况下被重新编译。
objcopy
复制二进制文件。通常使用 arch/$(ARCH)/Makefile 中指定的 OBJCOPYFLAGS。OBJCOPYFLAGS_$@可用于设置额外选项。
gzip
压缩目标。使用最大压缩率对目标进行压缩。

示例:

#arch/x86/boot/compressed/Makefile
$(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE
        $(call if_changed,gzip)

dtc
创建适合链接到 vmlinux 的扁平化设备树 blob 对象。链接到 vmlinux 的设备树 blob 会被放置在镜像的初始化段中。平台代码必须在调用 unflatten_device_tree()之前将 blob 复制到非初始化内存中。

要使用此命令,只需将 *.dtb 添加到 obj-y 或 targets 中,或者让其他目标依赖于 %.dtb

存在一个核心规则用于从 $(src)/%.dts 生成 $(obj)/%.dtb;架构 Makefiles 无需显式编写该规则。

示例:

targets += $(dtb-y)
DTC_FLAGS ?= -p 1024
7.8 自定义 kbuild 命令

当 kbuild 在 KBUILD_VERBOSE=0 条件下执行时,通常仅显示命令的简写形式。要使自定义命令也遵循此行为,kbuild 要求设置两个变量:

quiet_cmd_<command>     - what shall be echoed
      cmd_<command>     - the command to execute

示例:

#
quiet_cmd_image = BUILD   $@
      cmd_image = $(obj)/tools/build $(BUILDFLAGS) \
                                     $(obj)/vmlinux.bin > $@

targets += bzImage
$(obj)/bzImage: $(obj)/vmlinux.bin $(obj)/tools/build FORCE
        $(call if_changed,image)
        @echo 'Kernel: $@ is ready'

当使用“make KBUILD_VERBOSE=0”时更新 $(obj)/bzImage 目标时,会出现以下行:

BUILD arch/x86/boot/bzImage

7.9 链接器脚本的预处理

在构建 vmlinux 镜像时,会使用链接器脚本 arch/$(ARCH)/kernel/vmlinux.lds。该脚本是同一目录下 vmlinux.lds.S 文件的预处理版本。kbuild 识别 .lds 文件并包含一条规则 *lds.S -> *lds。

示例:

#arch/x86/kernel/Makefile
extra-y := vmlinux.lds

#Makefile
export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)

对 extra-y 的赋值用于告知 kbuild 构建目标 vmlinux.lds。对 $(CPPFLAGS_vmlinux.lds) 的赋值则指示 kbuild 在构建目标 vmlinux.lds 时使用指定的选项。

当构建 *.lds 目标时,kbuild 使用以下变量:

KBUILD_CPPFLAGS : Set in top-level Makefile
cppflags-y      : May be set in the kbuild makefile
CPPFLAGS_$(@F)  : Target-specific flags.
                Note that the full filename is used in this
                assignment.

kbuild 针对 *lds 文件的基础设施被用于多个架构特定的文件中。

7.10 通用头文件

目录 include/asm-generic 包含可能在各个架构之间共享的头文件。推荐的使用通用头文件的方法是在 Kbuild 文件中列出该文件。有关语法等更多信息,请参阅“8.2 generic-y”。

7.11 后链接处理阶段

如果存在 arch/xxx/Makefile.postlink 文件,该 Makefile 将被调用来对后链接对象(vmlinux 和 modules.ko)进行架构相关的后链接处理。同时必须支持 clean 目标。

此阶段在 kallsyms 生成之后运行。如果架构需要修改符号位置,而非直接操作 kallsyms,更简便的方式可能是为.tmp_vmlinux?目标添加另一个后链接目标,并通过 link-vmlinux.sh 调用。

例如,powerpc 使用此功能来检查链接的 vmlinux 文件的重定位合理性。

8 导出头文件的 Kbuild 语法

内核包含一组导出至用户空间的头文件。许多头文件可直接导出,但其他头文件在准备供用户空间使用前需进行最小化预处理,包括:

  • 移除内核专用注解
  • 移除对 compiler.h 的引用
  • 删除所有内核内部段落(由 ifdef __KERNEL__ 保护的部分)
    所有位于 include/uapi/、include/generated/uapi/、arch/<架构>/include/uapi/及 arch/<架构>/include/generated/uapi/目录下的头文件均会被导出。

可在 arch/<架构>/include/uapi/asm/和 arch/<架构>/include/asm/目录下定义 Kbuild 文件,用于列出来自 asm-generic 的汇编文件。Kbuild 文件语法详见后续章节。

8.1 禁止导出头文件

no-export-headers 主要用于 include/uapi/linux/Kbuild 中,以避免在不支持特定头文件(如 kvm.h)的架构上导出它们。应尽可能避免使用此功能。

8.2 generic-y

如果某个架构直接使用了 include/asm-generic 中的头文件副本,则会在 arch/$(ARCH)/include/asm/Kbuild 文件中如下列出:

示例:

#arch/x86/include/asm/Kbuild
generic-y += termios.h
generic-y += rtc.h

在构建的准备阶段,会在以下目录生成一个包装头文件:

arch/$(ARCH)/include/generated/asm

当导出一个架构使用通用头文件的头文件时,会在导出头文件集合的目录中生成类似的包装文件:

usr/include/asm

在这两种情况下,生成的包装文件都将如下所示:

示例:termios.h:

#include <asm-generic/termios.h>
8.3 generated-y

若某架构在生成通用-y 封装文件的同时还生成了其他头文件,generated-y 用于指定这些文件。

这可以防止它们被视为过时的 asm-generic 包装器而被移除。

示例:

#arch/x86/include/asm/Kbuild
generated-y += syscalls_32.h
8.4 mandatory-y

mandatory-y 主要用于 include/(uapi/)asm-generic/Kbuild 中,定义所有架构必须拥有的最小 ASM 头文件集合。

其工作方式类似于 optional generic-y。如果某个必需的头文件在 arch/$(ARCH)/include/(uapi/)/asm 中缺失,Kbuild 会自动生成一个基于 asm-generic 的包装头文件。

9 Kbuild 变量

顶层 Makefile 导出以下变量:

VERSION(主版本号)、PATCHLEVEL(次版本号)、SUBLEVEL(修订号)、EXTRAVERSION(额外版本信息)
这些变量定义了当前内核版本。少数架构的 Makefile 会直接使用这些值,但更推荐使用$(KERNELRELEASE)。

$(VERSION)、$(PATCHLEVEL)和$(SUBLEVEL)共同构成三部分版本号,例如"2"、“4"和"0”。这三个值始终为数字。

$(EXTRAVERSION)用于定义更细分的预发布版本或补丁版本标识,通常是非数字字符串如"-pre4",且经常留空。

KERNELRELEASE(内核发布版本)
$(KERNELRELEASE) 是一个单独的字符串,例如“2.4.0-pre4”,适用于构建安装目录名称或在版本字符串中显示。某些架构的 Makefile 会为此目的使用它。
ARCH
此变量定义了目标架构,如“i386”、“arm”或“sparc”。一些 kbuild Makefile 会测试$(ARCH)以确定需要编译哪些文件。

默认情况下,顶层 Makefile 将 $(ARCH) 设置为与主机系统架构相同。对于交叉编译,用户可以通过命令行覆盖 $(ARCH) 的值:

make ARCH=m68k …
INSTALL_PATH
此变量为架构相关的 Makefile 定义了安装驻留内核镜像和 System.map 文件的位置。用于架构特定的安装目标。
INSTALL_MOD_PATH, MODLIB
$(INSTALL_MOD_PATH)为模块安装指定了$(MODLIB)的前缀路径。该变量未在 Makefile 中定义,但用户可按需传入。

$(MODLIB)定义了模块安装的目录。顶层 Makefile 将$(MODLIB)定义为$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)。用户可通过命令行按需覆盖此值。

INSTALL_MOD_STRIP
如果指定了这个变量,它会导致模块在安装后被剥离。如果 INSTALL_MOD_STRIP 为‘1’,则将使用默认选项–strip-debug。否则,INSTALL_MOD_STRIP 的值将用作 strip 命令的选项。

10 Makefile 语言

内核 Makefile 设计为与 GNU Make 配合使用。这些 Makefile 仅使用 GNU Make 的文档化功能,但确实采用了诸多 GNU 扩展特性。

GNU Make 支持基础的列表处理函数。内核 Makefiles 采用了一种新颖的列表构建与操作风格,几乎不使用“if”语句。

GNU Make 有两种赋值运算符“:=”和“=”。“:=”会立即对右侧进行求值并将实际字符串存入左侧,而“=”类似于公式定义,它会将右侧以未求值的形式存储,并在每次使用左侧时重新求值。

某些情况下“=”是合适的,但通常“:=”才是正确的选择。

构建外部模块

本文档介绍了如何构建树外内核模块。

1.介绍

“kbuild”是Linux内核使用的构建系统。模块必须使用kbuild来保持与构建基础设施的变化兼容,并选择正确的标志来标记“gcc”。提供了树内和树外构建模块的功能。构建方法相似,所有模块最初都是在树上开发和构建的。

本文档涵盖的针对有兴趣构建树外(或“外部”)模块的开发人员的信息。外部模块的作者应该提供一个隐藏大部分复杂性的makefile,因此只需输入“make”即可构建模块。这很容易完成,第3节将介绍一个完整的示例。

2.如何构建外部模块

要构建外部模块,您必须有一个预构建的内核,该内核包含构建中使用的配置和标头文件。此外,内核必须是在启用模块的情况下构建的。如果您使用的是发行版内核,则您的发行版将为您运行的内核提供一个软件包。

另一种选择是使用“make”目标“modules_prepare”。这将确保内核包含所需的信息。目标仅作为一种为构建外部模块准备内核源树的简单方法而存在。

注意:即使设置了CONFIG_MODVERSIONS,“modules_prepare”也不会构建Module.symvers;因此,需要执行完整的内核构建才能使模块版本管理工作。

2.1 命令语法

构建外部模块的命令是:

$ make -C <path_to_kernel_src> M=$PWD

由于命令中给出的“M=<dir>”选项,kbuild系统知道正在构建外部模块。

要针对正在运行的内核进行构建,请使用:

$ make -C /lib/modules/`uname -r`/build M=$PWD

然后,要安装刚刚构建的模块,请将目标“modules_install”添加到命令中:

$ make -C /lib/modules/`uname -r`/build M=$PWD modules_install
2.2 选项

($KDIR指的是内核源目录的路径。)

make -C $KDIR M=$PWD
-C $KDIR
内核源所在的目录。“make”在执行时实际上会更改为指定的目录,并在完成后更改回。

M=$PWD
通知kbuild正在构建一个外部模块。给“M”的值是外部模块(kbuild文件)所在的目录的绝对路径。

2.3 目标

构建外部模块时,只有“make”目标的子集可用。

make -C $KDIR M=$PWD [target]
默认值将构建位于当前目录中的模块,因此不需要指定目标。所有输出文件也将在此目录中生成。没有尝试更新内核源,并且是为内核成功执行“make”的先决条件。

modules
外部模块的默认目标。它具有与未指定目标相同的功能。请参阅上面的描述。
modules_install
安装外部模块。默认位置是/lib/modules/<kernel_release>/extra/,但可以通过INSTALL_MOD_PATH添加前缀(在第5节中讨论)。
clean
仅删除模块目录中的所有生成文件。
help
列出外部模块的可用目标。

2.4 构建单独的文件

可以构建作为模块一部分的单个文件。这同样适用于内核、模块,甚至适用于外部模块。

示例(模块foo.ko,由bar.o和baz.o组成):

make -C $KDIR M=$PWD bar.lst
make -C $KDIR M=$PWD baz.o
make -C $KDIR M=$PWD foo.ko
make -C $KDIR M=$PWD ./

3.为外部模块创建Kbuild文件

在最后一节中,我们看到了为正在运行的内核构建模块的命令。然而,该模块实际上并没有构建,因为需要构建文件。此文件中将包含正在构建的模块的名称,以及必要的源文件列表。文件可能就像这一行一样简单:

obj-m := <module_name>.o

kbuild系统将从<module_name>.c构建<module_name>.o,并在链接后,将生成内核模块<module_name>.ko。上面一行可以放在“Kbuild”文件或“Makefile”中。当模块由多个来源构建时,需要额外一行列出文件:

<module_name>-y := <src1>.o <src2>.o ...

注意:描述kbuild使用的语法的进一步文档位于Linux内核Makefiles中。

以下示例演示了如何为模块8123.ko创建构建文件,该模块由以下文件构建:

8123_if.c
8123_if.h
8123_pci.c
8123_bin.o_shipped      <= Binary blob
3.1 共享Makefile

外部模块总是包含一个包装器makefile,该文件支持使用“make”构建没有参数的模块。kbuild不使用此目标;它只是为了方便。其他功能,如测试目标,可以包括在内,但由于可能的名称冲突,应从kbuild中过滤掉。

示例1:

--> filename: Makefile
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
obj-m  := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o

else
# normal makefile
KDIR ?= /lib/modules/`uname -r`/build

default:
        $(MAKE) -C $(KDIR) M=$$PWD

# Module specific targets
genbin:
        echo "X" > 8123_bin.o_shipped

endif

KERNELRELEASE的检查用于分离makefile的两个部分。在示例中,kbuild将只看到两个任务,而“make”将看到除这两个任务以外的所有内容。这是因为在文件上进行了两次传递:第一次通过是在命令行上运行的“make”实例;第二次通过是由kbuild系统发起的,该系统由默认目标中的参数化“make”发起。

3.2 分开的Kbuild文件和Makefile

在较新版本的内核中,kbuild将首先查找名为“Kbuild”的文件,只有在找不到时,它才会查找makefile。使用“Kbuild”文件允许我们将示例1中的makefile拆分为两个文件:

示例2:

--> filename: Kbuild
obj-m  := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o

--> filename: Makefile
KDIR ?= /lib/modules/`uname -r`/build

default:
        $(MAKE) -C $(KDIR) M=$$PWD

# Module specific targets
genbin:
        echo "X" > 8123_bin.o_shipped

由于每个文件的简单性,示例2中的拆分是值得怀疑的;然而,一些外部模块使用由几百行组成的makefile,在这里,将kbuild部分与其余部分分开确实是值得的。

下一个示例显示了向后兼容的版本。

示例3:

--> filename: Kbuild
obj-m  := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o

--> filename: Makefile
ifneq ($(KERNELRELEASE),)
# kbuild part of makefile
include Kbuild

else
# normal makefile
KDIR ?= /lib/modules/`uname -r`/build

default:
        $(MAKE) -C $(KDIR) M=$$PWD

# Module specific targets
genbin:
        echo "X" > 8123_bin.o_shipped

endif

这里包含makefile中的“Kbuild”文件。这允许在“make”和kbuild部分拆分为单独的文件时使用只知道makefiles的旧版本的kbuild。

3.3二进制数据块

部分外部模块需要以二进制块形式包含目标文件。kbuild 对此提供支持,但要求数据块文件命名为 <filename>_shipped。当 kbuild 规则生效时,会创建一份去除_shipped 后缀的副本,得到<filename> 。这个缩短后的文件名可用于模块的赋值操作。

在本节中,8123_bin.o_shipped 被用于构建内核模块 8123.ko;它已被包含为 8123_bin.o:

8123-y := 8123_if.o 8123_pci.o 8123_bin.o

尽管普通源文件和二进制文件之间没有区别,但在为模块创建目标文件时,kbuild 会采用不同的规则。

3.4 构建多个模块

kbuild 支持使用单个构建文件构建多个模块。例如,如果你想构建两个模块 foo.ko 和 bar.ko,kbuild 的配置行将是:

obj-m := foo.o bar.o
foo-y := <foo_srcs>
bar-y := <bar_srcs>

就是这么简单!

4. 包含文件

在内核中,头文件根据以下规则保存在标准位置:

如果头文件仅描述模块的内部接口,则该文件与源文件放置在同一目录下。

如果头文件描述的是内核其他部分(位于不同目录中)所使用的接口,则该文件应放置在 include/linux/目录下。

注意:
此规则有两个显著例外:较大的子系统在 include/下有自己独立的目录,例如 include/scsi;而架构特定的头文件位于 arch/$(ARCH)/include/目录下。

4.1 内核包含项

要包含位于 include/linux/下的头文件,只需使用:

#include <linux/module.h>

kbuild 会向“gcc”添加选项,以便搜索相关目录。

4.2 单个子目录

外部模块通常将头文件放在其源代码所在目录的单独 include/文件夹中,尽管这不是内核的常规风格。要告知 kbuild 该目录,可以使用 ccflags-y 或 CFLAGS_<filename>.o。

以第 3 节的示例为例,如果我们将 8123_if.h 移动到一个名为 include 的子目录中,生成的 kbuild 文件将如下所示:

--> filename: Kbuild
obj-m := 8123.o

ccflags-y := -Iinclude
8123-y := 8123_if.o 8123_pci.o 8123_bin.o

请注意,在赋值语句中,-I 和路径之间没有空格。这是 kbuild 的一个限制:此处不能有空格。

4.3 多个子目录

kbuild 可以处理分布在多个目录中的文件。考虑以下示例:

.
|__ src
|   |__ complex_main.c
|   |__ hal
|       |__ hardwareif.c
|       |__ include
|           |__ hardwareif.h
|__ include
|__ complex.h

要构建模块 complex.ko,我们需要以下 kbuild 文件:

--> filename: Kbuild
obj-m := complex.o
complex-y := src/complex_main.o
complex-y += src/hal/hardwareif.o

ccflags-y := -I$(src)/include
ccflags-y += -I$(src)/src/hal/include

如你所见,kbuild 知道如何处理位于其他目录中的目标文件。诀窍在于指定相对于 kbuild 文件所在位置的目录路径。尽管如此,这种做法并不推荐。

对于头文件,必须明确告知 kbuild 查找路径。kbuild 执行时,当前目录始终是内核树的根目录(即“-C”参数指定的路径),因此需要使用绝对路径。$(src)通过指向当前执行的 kbuild 文件所在目录来提供绝对路径。

5. 模块安装

内核中包含的模块会被安装到以下目录:

/lib/modules/$(KERNELRELEASE)/kernel/
而外部模块则安装到:

/lib/modules/$(KERNELRELEASE)/extra/

5.1 INSTALL_MOD_PATH

上述是默认目录,但像往常一样可以进行一定程度的定制。可以使用变量 INSTALL_MOD_PATH 为安装路径添加前缀:

$ make INSTALL_MOD_PATH=/frodo modules_install
=> Install dir: /frodo/lib/modules/$(KERNELRELEASE)/kernel/

INSTALL_MOD_PATH 可以设置为普通的 shell 变量,或者如上所示,在调用“make”时在命令行中指定。这在安装树内和树外模块时都有效。

5.2 INSTALL_MOD_DIR

默认情况下,外部模块会被安装到/lib/modules/$(KERNELRELEASE)/extra/目录下,但您可能希望将特定功能的模块存放在单独的目录中。为此,可以使用 INSTALL_MOD_DIR 来指定一个替代"extra"的名称。

$ make INSTALL_MOD_DIR=gandalf -C $KDIR \
       M=$PWD modules_install
=> Install dir: /lib/modules/$(KERNELRELEASE)/gandalf/

6. 模块版本控制

模块版本控制由 CONFIG_MODVERSIONS 标签启用,用于进行简单的 ABI 一致性检查。系统会为每个导出符号的完整原型生成一个 CRC 值。当模块被加载或使用时,内核中的 CRC 值会与模块中的相应值进行比较;如果不一致,内核将拒绝加载该模块。

Module.symvers 文件包含了内核构建过程中所有导出符号的列表。

6.1 来自内核的符号(vmlinux + 模块)

在内核构建过程中,会生成一个名为 Module.symvers 的文件。Module.symvers 包含了从内核及已编译模块导出的所有符号。每个符号对应的 CRC 值也会被存储。

Module.symvers 文件的语法如下:

<CRC>       <Symbol>         <Module>                         <Export Type>     <Namespace>

0xe1cc2a05  usb_stor_suspend drivers/usb/storage/usb-storage  EXPORT_SYMBOL_GPL USB_STORAGE

各字段之间用制表符分隔,且某些值可能为空(例如,若某个导出符号未定义命名空间)。

对于未启用 CONFIG_MODVERSIONS 的内核构建,CRC 值将显示为 0x00000000。

Module.symvers 具有双重作用:

它列出了来自 vmlinux 及所有模块的所有导出符号。
若启用了 CONFIG_MODVERSIONS,它还会列出 CRC 校验值。

6.2 符号与外部模块

构建外部模块时,编译系统需要访问内核中的符号以检查所有外部符号是否已定义。这一过程在 MODPOST 阶段完成。modpost 通过读取内核源码树中的 Module.symvers 获取符号信息。在 MODPOST 阶段,会生成一个新的 Module.symvers 文件,其中包含该外部模块的所有导出符号。

6.3 来自其他外部模块的符号

有时,一个外部模块会使用另一个外部模块导出的符号。为避免出现未定义符号的警告,Kbuild 需要全面掌握所有符号信息。针对此情况有两种解决方案。

注意:推荐使用顶层 kbuild 文件的方法,但在某些场景下可能不太实际。

使用顶层 kbuild 文件
如果你有两个模块,foo.ko 和 bar.ko,其中 foo.ko 需要从 bar.ko 中获取符号,你可以使用一个共同的顶层 kbuild 文件,以便两个模块在同一构建过程中编译。考虑以下目录布局:

./foo/ <= contains foo.ko
./bar/ <= contains bar.ko

顶层 kbuild 文件将如下所示:

#./Kbuild (or ./Makefile):
        obj-m := foo/ bar/

执行以下命令:

$ make -C $KDIR M=$PWD

随后将按预期编译这两个模块,并完全了解任一模块中的符号。

使用“make”变量 KBUILD_EXTRA_SYMBOLS
若无法添加顶层的 kbuild 文件,可在构建文件中为 KBUILD_EXTRA_SYMBOLS 指定一个以空格分隔的文件列表。这些文件将在 modpost 初始化符号表时被加载。

7. 技巧与窍门

7.1 测试 CONFIG_FOO_BAR

模块常需检查特定的 CONFIG_ 选项以决定是否包含某项功能。在 kbuild 中,这是通过直接引用 CONFIG_ 变量实现的:

#fs/ext2/Makefile
obj-$(CONFIG_EXT2_FS) += ext2.o

ext2-y := balloc.o bitmap.o dir.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o

传统上,外部模块使用“grep”直接在.config 中检查特定 CONFIG_ 设置。这种做法已被弃用。如前所述,外部模块应使用 kbuild 进行构建,因此可沿用与树内模块相同的方法来测试 CONFIG_ 定义。

为用户导出内核头文件(暂时用不到)

参见导出内核头文件供用户空间使用

递归问题(暂时用不到)

可重用构建(暂时用不到)

参见可重现的构建

GCC 插件(暂时用不到)

参见GCC 插件基础设施

使用 Clang/LLVM编译Linux(暂时用不到)

参见使用 Clang/LLVM 构建 Linux

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值