我运行一个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%的命中率,而构建时间反映了这种糟糕的性能。仅从单个补丁版本更改。
我本来希望缓存性能会随着时间的推移而降低,但不会达到这个程度,所以我唯一的想法是,非确定性比简单的时间戳更多。例如,大多数/所有源文件都包括完整的内核版本字符串吗?我的理解是,这样的事情会完全破坏缓存。有没有一种方法可以使缓存按我的意愿工作?还是不可能?
答案 0 :(得分: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是(4 << 16)+(16 << 8)+ 18 = 0x41012)
#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?示例模块包括<linux/kernel.h>
<linux/module.h>
和<linux/modversions.h>
,并且这些文件之一可能间接包含全局version.h
。而且大多数甚至所有内核源代码都将包含version.h。
在比较构建时间戳时,可能会重新生成version.h并禁用ccache。忽略时间戳时,LINUX_VERSION_CODE
仅对于完全相同的Linux内核版本是相同的,并且在下一个补丁程序级别中将被更改。
更新:检查某些内核对象编译的gcc -H
输出,将会有另一个带有完整内核版本宏定义的标头。例如:include/generated/utsrelease.h
(UTS_RELEASE
宏),include/generated/autoconf.h
(CONFIG_VERSION_SIGNATURE
)。
或者甚至在两个补丁程序级别之间gcc -E
进行相同内核对象编译的预处理,并比较生成的文本。使用最简单的linux模块,我直接在gcc命令行中有-include ./include/linux/kconfig.h
,它包括include/generated/autoconf.h
(但是在-H
输出中不可见,这是bug还是gcc功能?)。 / p>
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)