内核构建缓存/非确定性

Max*_*ich 6 linux linux-kernel ccache

我运行一个CI服务器,用于构建自定义Linux内核.CI服务器功能不强,每个构建的时间限制为3h.为了在这个限制内工作,我有了使用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
Run Code Online (Sandbox Code Playgroud)

缓存命中率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
Run Code Online (Sandbox Code Playgroud)

这是1.30%的命中率,而构建时间反映了这种糟糕的表现.从仅一个补丁版本改变.

我原本预计缓存性能会随着时间的推移而降低,但不会达到这种程度,所以我唯一的想法就是有更多的非确定性而不仅仅是时间戳.例如,大多数/所有源文件是否包括完整的内核版本字符串?我的理解是,像这样的东西会彻底打破缓存.有没有办法让缓存按照我的意愿工作或者不可能?

osg*_*sgx 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)
Run Code Online (Sandbox Code Playgroud)

因此,linux 4.16.18 的 version.h 将生成为 (266258 is (4 << 16) + (16 << 8) + 18 = 0x41012)

#define LINUX_VERSION_CODE 266258
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
Run Code Online (Sandbox Code Playgroud)

稍后,例如在模块构建中,应该有办法读取 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是如何包含的?示例模块包含<linux/kernel.h> <linux/module.h><linux/modversions.h>,并且这些文件之一可能间接包含 global version.h。大多数甚至所有内核源代码都将包含 version.h。

比较构建时间戳时,可能会重新生成 version.h 并禁用 ccache。当忽略时间戳时,LINUX_VERSION_CODE仅对于完全相同的 Linux 内核版本是相同的,并且它会针对下一个补丁级别进行更改。

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

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

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

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

-include $(srctree)/include/linux/kconfig.h
Run Code Online (Sandbox Code Playgroud)

它实际上是这样的: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)
Run Code Online (Sandbox Code Playgroud)

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)    
Run Code Online (Sandbox Code Playgroud)