【问题标题】:Kernel Build Caching/Nondeterminism内核构建缓存/不确定性
【发布时间】:2019-01-23 17:24:56
【问题描述】:

我运行一个 CI 服务器,用于构建自定义 linux 内核。 CI 服务器并不强大,每次构建的时间限制为 3 小时。为了在这个限制内工作,我想到了使用 ccache 缓存内核构建。我希望我可以在每个次要版本发布时创建一个缓存,并将其重用于补丁版本,例如我有一个为 4.18 制作的缓存,我想将其用于所有 4.18.x 内核。

删除构建时间戳后,这对于我正在构建的确切内核版本非常有用。对于上面提到的 4.18 内核,在 CI 上构建它会给出以下统计信息:

$ ccache -s
cache directory                     
primary config                      
secondary config      (readonly)    /etc/ccache.conf
stats zero time                     Thu Aug 16 14:36:22 2018
cache hit (direct)                 17812
cache hit (preprocessed)              38
cache miss                             0
cache hit rate                    100.00 %
called for link                        3
called for preprocessing           29039
unsupported code directive             4
no input file                       2207
cleanups performed                     0
files in cache                     53652
cache size                           1.4 GB
max cache size                       5.0 GB

100% 的缓存命中率和一个小时完成构建,出色的统计数据和预期的一样。

不幸的是,当我尝试构建 4.18.1 时,我得到了

cache directory                     
primary config                      
secondary config      (readonly)    /etc/ccache.conf
stats zero time                     Thu Aug 16 10:36:22 2018
cache hit (direct)                     0
cache hit (preprocessed)             233
cache miss                         17658
cache hit rate                      1.30 %
called for link                        3
called for preprocessing           29039
unsupported code directive             4
no input file                       2207
cleanups performed                     0
files in cache                     90418
cache size                           2.4 GB
max cache size                       5.0 GB

这是 1.30% 的命中率,而构建时间反映了这种糟糕的性能。即仅从单个补丁版本更改。

我预计缓存性能会随着时间的推移而下降,但不会降到这个程度,所以我唯一的想法是,除了时间戳之外,还有更多的不确定性。例如,大多数/所有源文件是否包含完整的内核版本字符串?我的理解是,这样的事情会完全破坏缓存。有没有办法让缓存按我的意愿工作,还是不可能?

【问题讨论】:

  • 是的,许多文件中都包含带有版本的标题。例如,对于模块,有 CONFIG_MODVERSIONS skynet.ie/~mark/home/kernel/symbols.html。您的 233 次点击率很高 - 已报告单次点击:unix.stackexchange.com/questions/226622/…(以及来自重新生成的标题 lists.samba.org/archive/ccache/2014q1/001171.html 的零次点击)。检查某些内核对象编译的gcc -H 输出以获取标头列表,并将它们用于内核版本。
  • 这基本上就是我的想法,有什么好的方法可以不为次要版本号这样做,否则会彻底崩溃
  • @osgx 你认为你可以把你的评论放在一个基本上说不可能的答案中,我会奖励你吗?
  • 我认为,这个问题的好答案应该显示 version.h 的确切生成位置并包含在每个编译文件中。 Version.h 是 include/generated/uapi/linux/version.h#define LINUX_VERSION_CODE 0x041012(对于 4.16.18),并且 $KERNELVERSION 是从顶部 Makefile 导出的 elixir.bootlin.com/linux/v4.16.18/source/Makefile

标签: linux linux-kernel ccache


【解决方案1】:

include/generated/uapi/linux/version.h 标头(在Makefile 顶部生成https://elixir.bootlin.com/linux/v4.16.18/source/Makefile

其中包括确切的内核版本作为宏:

version_h := include/generated/uapi/linux/version.h
old_version_h := include/linux/version.h

define filechk_version.h
    (echo \#define LINUX_VERSION_CODE $(shell                         \
    expr $(VERSION) \* 65536 + 0$(PATCHLEVEL) \* 256 + 0$(SUBLEVEL)); \
    echo '#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))';)
endef

$(version_h): $(srctree)/Makefile FORCE
    $(call filechk,version.h)
    $(Q)rm -f $(old_version_h)

因此,linux 4.16.18 的 version.h 将生成为 (266258 is (4

#define LINUX_VERSION_CODE 266258
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))

稍后,例如在模块构建中,应该可以读取 LINUX_VERSION_CODE 宏值https://www.tldp.org/LDP/lkmpg/2.4/html/lkmpg.html(4.1.6. 为多个内核版本编写模块)

比较宏LINUX_VERSION_CODE 和宏KERNEL_VERSION 的方法。在内核的a.b.c 版本中,此宏的值将是2^{16}a+2^{8}b+c。请注意,此宏不是为内核 2.0.35 及更早版本定义的,因此如果您想编写支持真正旧内核的模块

version.h 是如何包含的?示例模块包括&lt;linux/kernel.h&gt;&lt;linux/module.h&gt;&lt;linux/modversions.h&gt;,其中一个文件可能间接包括全局version.h。大多数甚至所有内核源代码都将包含 version.h。

比较您的构建时间戳时,可能会重新生成 version.h 并禁用 ccache。当忽略时间戳时,LINUX_VERSION_CODE 仅在完全相同的 linux 内核版本中相同,并且在下一个补丁级别更改。

更新:检查一些内核对象编译的gcc -H输出,会有另一个带有完整内核版本宏定义的头文件。例如:include/generated/utsrelease.hUTS_RELEASE 宏)、include/generated/autoconf.hCONFIG_VERSION_SIGNATURE)。

或者甚至对两个补丁级别之间的相同内核对象编译进行gcc -E 预处理并比较生成的文本。使用最简单的 linux 模块,我在 gcc 命令行中直接有 -include ./include/linux/kconfig.h,它包括 include/generated/autoconf.h(但这在 -H 输出中不可见,是 gcc 的错误还是功能?)。

https://patchwork.kernel.org/patch/9326051/

...因为顶级 Makefile 强制将其包含在:

-include $(srctree)/include/linux/kconfig.h

确实如此:https://elixir.bootlin.com/linux/v4.16.18/source/Makefile

# Use USERINCLUDE when you must reference the UAPI directories only.
USERINCLUDE    := \
        -I$(srctree)/arch/$(SRCARCH)/include/uapi \
        -I$(objtree)/arch/$(SRCARCH)/include/generated/uapi \
        -I$(srctree)/include/uapi \
        -I$(objtree)/include/generated/uapi \
                -include $(srctree)/include/linux/kconfig.h

# Use LINUXINCLUDE when you must reference the include/ directory.
# Needed to be compatible with the O= option
LINUXINCLUDE    := \
        -I$(srctree)/arch/$(SRCARCH)/include \
        -I$(objtree)/arch/$(SRCARCH)/include/generated \
        $(if $(KBUILD_SRC), -I$(srctree)/include) \
        -I$(objtree)/include \
        $(USERINCLUDE)

LINUXINCLUDE 导出到 env 并在 source/scripts/Makefile.lib 中用于定义编译器标志 https://elixir.bootlin.com/linux/v4.16.18/source/scripts/Makefile.lib

  c_flags        = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)    

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多