探索Linux内核:Kconfig / kbuild的秘密

自从Linux内核代码迁移到Git以来,Linux内核config / build系统(也称为Kconfig / kbuild)已经存在了很长时间。 但是,作为基础设施的支持很少受到关注。 即使是在日常工作中使用它的内核开发人员也从未真正考虑过它。

为了探索如何编译Linux内核,本文将深入探讨Kconfig / kbuild内部过程,解释如何生成.config文件和vmlinux / bzImage文件,并介绍一种用于依赖性跟踪的精巧技巧。

设定档

构建内核的第一步始终是配置。 Kconfig帮助使Linux内核高度模块化和可定制。 Kconfig为用户提供了许多配置目标:

config 使用面向行的程序更新当前配置
nconfig 使用基于ncurses菜单的程序更新当前配置
menuconfig 使用基于菜单的程序更新当前配置
xconfig 利用基于Qt的前端更新当前配置
gconfig 利用基于GTK +的前端更新当前配置
oldconfig 使用提供的.config作为基础更新当前配置
localmodconfig 更新未加载的当前配置禁用模块
localyesconfig 更新当前配置,将本地mod转换为核心
defconfig Arch提供的defconfig中默认的新配置
savedefconfig 将当前配置另存为./defconfig(最小配置)
allnoconfig 新配置,其中所有选项均以“否”回答
allyesconfig 新配置,其中所有选项均接受“是”
allmodconfig 可能时新的配置选择模块
alldefconfig 所有符号均设置为默认的新配置
randconfig 新配置,所有选项均随机回答
listnewconfig 列出新选项
olddefconfig 与oldconfig相同,但在不提示的情况下将新符号设置为其默认值
kvmconfig 为KVM guest虚拟机内核支持启用其他选项
xenconfig 启用xen dom0和来宾内核支持的其他选项
tinyconfig 配置可能的最小内核

我认为menuconfig是这些目标中最受欢迎的。 目标由内核提供并在内核构建期间构建的不同主机程序处理。 有些目标具有GUI(为了方便用户),而大多数则没有。 与Kconfig相关的工具和源代码主要位于内核源代码中的scripts / kconfig /下。 从scripts / kconfig / Makefile中可以看到,有几个宿主程序,包括confmconfnconf 。 除了conf之外 ,它们每个都负责一个基于GUI的配置目标,因此conf处理其中的大多数。

从逻辑上讲,Kconfig的基础结构包括两个部分:一个实现一种新的语言来定义配置项(请参阅内核源代码下的Kconfig文件),另一个则解析Kconfig语言并处理配置操作。

大多数配置目标具有大致相同的内部过程(如下所示):

Kconfig process

第一步,在源根目录下读取Kconfig文件,以构建初始配置数据库。 然后根据此优先级通过读取现有配置文件来更新初始数据库:

.config
/ lib / modules / $(shell,uname -r)/。config
/ etc / kernel-config
/ boot / config-$(shell,uname -r)
ARCH_DEFCONFIG
arch / $(ARCH)/ defconfig

如果您通过menuconfig进行基于GUI的配置,或者通过oldconfig进行基于命令行的配置,则根据您的自定义更新数据库。 最后,配置数据库将转储到.config文件中。

但是.config文件不是构建内核的最终工具。 这就是为什么存在syncconfig目标的原因。 syncconfig曾经是一个称为silentoldconfig的配置目标,但是它不执行旧名称所说的内容,因此被重命名。 另外,由于它仅供内部使用(不适用于用户),因此已从列表中删除了它。

这是syncconfig的说明:

Syncconfig

syncconfig将.config作为输入并输出许多其他文件,这些文件分为三类:

  • auto.conf和tristate.conf用于makefile文本处理。 例如,您可能会在组件的makefile中看到类似以下的语句:
    obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
  • autoconf.h用于C语言源文件。
  • include / config /下的空头文件用于在kbuild期间进行配置依赖性跟踪,这将在下面进行说明。

配置完成后,我们将知道哪些文件和代码段未编译。

编译

称为递归make的基于组件的构建是GNU make管理大型项目的常用方法。 Kbuild是递归生成的一个很好的例子。 通过将源文件划分为不同的模块/组件,每个组件都由其自己的makefile管理。 当您开始构建时,顶层makefile将按正确的顺序调用每个组件的makefile,构建组件,然后将它们收集到最终执行程序中。

Kbuild引用了不同种类的makefile:

  • Makefile是位于源根目录中的顶级Makefile。
  • .config是内核配置文件。
  • arch / $(ARCH)/ Makefile是arch makefile,它是对顶级makefile的补充。
  • scripts / Makefile。*描述了所有kbuild makefile的通用规则。
  • 最后,大约有500个kbuild makefiles

顶部的makefile包括拱形makefile,读取.config文件,下降到子目录,借助脚本 /Makefile.*中定义的例程在每个组件的makefile上调用make ,构建每个中间对象,并将所有中间对象链接到vmlinux。 内核文档Documentation / kbuild / makefiles.txt描述了这些makefile的所有方面。

作为示例,让我们看一下如何在x86-64上生成vmlinux:

vmlinux overview

(该插图基于Richard Y. Steven的博客 。它已更新,并在得到作者许可的情况下使用。)

进入vmlinux的所有.o文件首先进入它们自己的内置文件.a ,该文件通过变量KBUILD_VMLINUX_INITKBUILD_VMLINUX_MAINKBUILD_VMLINUX_LIBS指示 ,然后被收集到vmlinux文件中。

借助简化的makefile代码,了解一下如何在Linux内核中实现递归make:

# In top Makefile
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps)
		+$(call if_changed,link-vmlinux)

# Variable assignments
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)

export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
export KBUILD_VMLINUX_LIBS := $(libs-y1)
export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds

init-y          := init/
drivers-y       := drivers/ sound/ firmware/
net-y           := net/
libs-y          := lib/
core-y          := usr/
virt-y          := virt/

# Transform to corresponding built-in.a
init-y          := $(patsubst %/, %/built-in.a, $(init-y))
core-y          := $(patsubst %/, %/built-in.a, $(core-y))
drivers-y       := $(patsubst %/, %/built-in.a, $(drivers-y))
net-y           := $(patsubst %/, %/built-in.a, $(net-y))
libs-y1         := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2         := $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))
virt-y          := $(patsubst %/, %/built-in.a, $(virt-y))

# Setup the dependency. vmlinux-deps are all intermediate objects, vmlinux-dirs
# are phony targets, so every time comes to this rule, the recipe of vmlinux-dirs
# will be executed. Refer "4.6 Phony Targets" of `info make`
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;

# Variable vmlinux-dirs is the directory part of each built-in.a
vmlinux-dirs    := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
                     $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
                     $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))

# The entry of recursive make
$(vmlinux-dirs):
		$(Q)$(MAKE) $(build)=$@ need-builtin=1

递归制作配方被扩展,例如:

make -f scripts/Makefile.build obj=init need-builtin=1

这意味着make将进入scripts / Makefile.build继续构建每个内置in.a的工作 。 借助scripts / link-vmlinux.sh ,vmlinux文件最终位于源根目录下。

了解vmlinux与bzImage

许多Linux内核开发人员可能不清楚vmlinux和bzImage之间的关系。 例如,这是它们在x86-64中的关系:

vmlinux vs. bzImage

将源根vmlinux剥离,压缩,放入piggy.S ,然后与其他对等对象链接到arch / x86 / boot / compressed / vmlinux中 。 同时,在arch / x86 / boot下生成了一个名为setup.bin的文件。 根据CONFIG_X86_NEED_RELOCS的配置,可能有一个具有重定位信息的可选第三个文件。

内核提供的名为build的主机程序将这两个(或三个)部分构建到最终的bzImage文件中。

依赖追踪

Kbuild跟踪三种依赖关系:

  1. 所有必备文件(* .c和* .h
  2. 所有必备文件中使用的CONFIG_选项
  3. 用于编译目标的命令行依赖项

第一个很容易理解,但是第二个和第三个呢? 内核开发人员经常看到这样的代码段:

#ifdef CONFIG_SMP
__boot_cpu_id = cpu;
#endif

CONFIG_SMP更改时,应重新编译这段代码。 编译源文件的命令行也很重要,因为不同的命令行可能导致不同的目标文件。

.c文件通过#include指令使用头文件时,您需要编写如下规则:

main.o: defs.h
	recipe...

在管理大型项目时,您需要很多这类规则。 将它们全部编写起来将是乏味且乏味的。 幸运的是,大多数现代C编译器都可以通过查看源文件中的#include行为您编写这些规则。 对于GNU编译器集合(GCC),只需添加命令行参数即可: -MD depfile

# In scripts/Makefile.lib
c_flags        = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
                 -include $(srctree)/include/linux/compiler_types.h       \
                 $(__c_flags) $(modkern_cflags)                           \
                 $(basename_flags) $(modname_flags)

这将生成一个.d文件,其内容如下:

init_task.o: init/init_task.c include/linux/kconfig.h \
 include/generated/autoconf.h include/linux/init_task.h \
 include/linux/rcupdate.h include/linux/types.h \
 ...

然后,主机程序fixdep通过将depfile和命令行作为输入,然后以makefile语法输出。<target> .cmd文件来处理其他两个依赖关系,该文件记录了命令行和所有先决条件(包括配置)为目标。 看起来像这样:

# The command line used to compile the target
cmd_init/init_task.o := gcc -Wp,-MD,init/.init_task.o.d  -nostdinc ...
...
# The dependency files
deps_init/init_task.o := \
$(wildcard include/config/posix/timers.h) \
$(wildcard include/config/arch/task/struct/on/stack.h) \
$(wildcard include/config/thread/info/in/task.h) \
...
  include/uapi/linux/types.h \
  arch/x86/include/uapi/asm/types.h \
  include/uapi/asm-generic/types.h \
  ...

递归制作过程中将包括一个。<target> .cmd文件,提供所有依赖项信息并有助于决定是否重建目标。

其背后的秘密是, fixdep将解析depfile.d文件),然后解析其中的所有依赖项文件,在文本中搜索所有CONFIG_字符串,将它们转换为相应的空头文件,并将其添加到目标的先决条件中。 。 每次配置更改时,相应的空头文件也将被更新,因此kbuild可以检测到该更改并重建依赖于此的目标。 由于还记录了命令行,因此比较上次编译参数和当前编译参数很容易。

展望未来

在新的维护者山田昌宏(Masahiro Yamada)于2017年初加入Kconfig之前,Kconfig / kbuild保持了很长一段时间,现在kbuild再次处于活跃开发中。 如果您很快就会发现与本文有所不同的内容,请不要感到惊讶。

翻译自: https://opensource.com/article/18/10/kbuild-and-kconfig

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
linux2.6.x的配置文件kconfig语法linux在2.6版本以后将配置文件由原来的config.in 改为kconfig,对于kconfig的语法在/Documentation/kbuild/kconfig-language.txt中做了详细的说 明,在这里给出kconfig-language.txt的中文版。 介绍 ---- 在配置数据库的配置选项是以树的形式组织的: +- 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 +- ... 每个选项都有其自己的依赖关系。这些依赖关系决定了选项是否是可见的。父选项可见,子选项才能可见。 菜单选项 -------- 大多数的选项都定义了一个配置选项,其它选项则有助于对它们进行组织。(原文:Most entries define a config option, all other entries help to organize them.)一个配置选项定义可以是下面 的形式: config MODVERSIONS bool "Set version information on all module symbols" depends MODULES help Usually, modules have to be recompiled whenever you switch to a new kernel. ... 每行都是以关键字开始,并可以接多个参数。"config" 为定义了一新的配置选项。下面的几行定义了该配置 选项的属性。属性可以是该配置选项的类型,输入提示(input prompt),依赖关系,帮助信息和默认值。一 配置选项可以用相同的名字定义多次,但每个定义只能有一个输入提示并且类型还不能冲突。 菜单属性 -------- 一菜单选项可以有多个属性。并不要求这些属性可以用在任何地方(见语法)。 - 类型定义:"bool"/"tristate"/"string"/"hex"/"int" 每个配置选项都必须指定类型。有两个基本类型:tristate 和 string,其他类型都是基于这两个基本 类型。类型定义可以用输入提示,所以下面的两个例子是等价的: bool "Networking support" 和 bool prompt "Networking support" - 输入提示: "prompt" ["if" ] 每个菜单选项最多只能有一个显示给用户的输入提示。可以用 "if" 来表示该提示的依赖关系,当然这是 可选的。 - 默认值:"default" ["if" ] 一个配置选项可以有任意多个默认值。如果有多个默认值,那么只有第一个被定义的值是可用的。默认值并 不是只限于应用在定义他们的菜单选项。这就意味着默认值可以定义在任何地方或被更早的定义覆盖。 如果用户没有设置(通过上面的输入提示),配置选项的值就是默认值。如果可以显示输入提示的话,就会把 默认值显示给用户,并可以让用户进行修改。 默认值的依赖关系可以用 "if" 添加。(可选项) - 依赖关系:"depends on"/"requires" 为一菜单选项定义依赖关系。如果定义了多个依赖关系,它们之间用 '&&' 间隔。依赖关系也可以应用到 该菜单中所有的其它选项(同样接受一if表达式),所以下面的两个例子是等价的: bool "foo" if BAR default y if BAR and depends on BAR bool "foo" default y - 反向依赖关系:"select" ["if" ] 尽管普通的依赖关系可以降低一选项的上限,反向依赖能将这一限制降的更低。当前菜单选项的值是symbol 的最小值。如果symbol被选择了多次,上限就是其中的最大值。 反向依赖只能用在 boolean 或 tristate 选项上。 - 数据范围:"range" ["if" ] 为int和hex类型的选项设置可以接受输入值范围。用户只能输入大于等于第一个symbol,小于等于第二个 symbol的值。 - 帮助信息: "help" or "---help---" 定义一帮助信息。帮助信息的结束就由缩进的水平决定的,这也就意味着信息是在第一个比帮助信息开始行 的缩进小的行结束。 "---help---" 和 "help" 在实现的作用上没有区别,"---help---" 有助于将文件中的配置逻辑与 给开发人员的提示分开。 菜单依赖关系 ------------ 依赖关系决定了菜单选项是否可见,也可以减少tristate的输入范围。tristate逻辑比boolean逻辑在表 达式中用更多的状态(state)来表示模块的状态。依赖关系表达式的语法如下: ::= (1) '=' (2) '!=' (3) '(' ')' (4) '!' (5) '&&' (6) '||' (7) 表达式是以优先级的降序列出的。 (1) 将symbol赋给表达式。boolean和tristate类型的symbol直接赋给表达式。所有其它类型的symbol 都赋 'n'。 (2) 如果两个symbol相等,返回'y',否则为'n'。 (3) 如果两个symbol相等,返回'n',否则为'y'。 (4) 返回表达式的值。用于改变优先级。 (5) 返回 (2-/expr/) 的结果。 (6) 返回 min(/expr/,/expr/) 的结果。 (7) 返回 max(/expr/,/expr/) 的结果。 一个表达式的值可以是'n','m'或'y'(或者是计算的结果 0,1,2)。当表达式的值为'm'或'y'的时候,菜 单项才是可见的。 symbol有两种类型:不可变的和可变的。不可变的symbol是最普通的,由'config'语句定义,完全由数字 、字母和下划线组成(alphanumeric characters or underscores)。 不可变的symbol只是表达式的一部分。经常用单引号或双引号括起来。在引号中,可以使用任何字符,使用引 号要用转义字符'\'。 菜单结构 -------- 菜单在树中的位置可由两种方法决定。第一种可以是这样: menu "Network device support" depends NET config NETDEVICES ... endmenu 所有的在"menu" ... "endmenu" 之间都是"Network device support"的子菜单。所有的子菜单选项 都继承了父菜单的依赖关系,比如,"NET"的依赖关系就被加到了配置选项NETDEVICES的依赖列表中。 还有就是通过分析依赖关系生成菜单的结构。如果菜单选项在一定程度上依赖于前面的选项,它就能成为该选 项的子菜单。首先,前面的(父)选项必须是依赖列表中的一部分并且它们中必须有满足下面两个条件的选项: - 如果父选项为'n',子选项必须不可见。 - 如果父选项可见,子选项才能可见。 config MODULES bool "Enable loadable module support" config MODVERSIONS bool "Set version information on all module symbols" depends MODULES comment "module support disabled" depends !MODULES MODVERSIONS 直接依赖 MODULES,这就意味着如果MODULES不为'n',该选项才可见。换句话说,当 MODULES可见时,选项才可见(MODULES的(空)依赖关系也是选项依赖关系的一部分)。 Kconfig 语法 ------------ 配置文件描述了菜单选项,每行都是以一关键字开头(除了帮助信息)。下面的关键字结束一菜单选项: - config - menuconfig - choice/endchoice - comment - menu/endmenu - if/endif - source 前5个同样可以用在菜单选项定义的开始。 config: "config" 定义了一配置选项 并且可以接受任何前面介绍的属性。 menuconfig: "menuconfig" 此关键字和前面的关键字很相似,但它在前面的基础上要求所有的子选项作为独立的行显示。(This is similar to the simple config entry above, but it also gives a hint to front ends, that all suboptions should be displayed as a separate list of options.) choices: "choice" "endchoice" 该关键字定义了一组选择项,并且选项可以是前面描述的任何属性。尽管boolean只允许选择一个配置选项, tristate可以抒多个配置选项设为'm',但选项只能是boolean或tristate类型。这可以在一个硬件有多 个驱动的情况下使用,最终只有一个驱动被编译进/加载到内核,,但所有的驱动都可以编译成模块。 选项可以接受的另一个选项是"optional",这样选项就被设置为'n',没有被选中的。 comment: "comment" 这里定义了在配置过程中显示给用户的注释,该注释还将写进输出文件中。唯一可用的可选项是依赖关系。 menu: "menu" "endmenu" 这里定义了一个菜单,详细信息请看前面的"菜单结构"。唯一可用的可选项是依赖关系。 if: "if" "endif" 这里定义了if结构。依赖关系被加到所有在if ... endif 中的菜单选项中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值