Кеширование сборки ядра / недетерминизм

Я запускаю 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% попаданий, и время сборки отражает эту низкую производительность. Это из-за изменения только одной версии патча.

Я ожидал, что производительность кэширования со временем ухудшится, но не до такой степени, поэтому я думаю только о том, что существует больше недетерминированности, чем просто временная метка. Например, все ли исходные файлы включают строку с полной версией ядра? Насколько я понимаю, что-то подобное полностью нарушит кеширование. Есть ли способ заставить кеширование работать так, как хотелось бы, или это невозможно?


person Max Ehrlich    schedule 16.08.2018    source источник
comment
Да, во многих файлах есть заголовки с версией. Например, для модулей есть 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 вывод компиляции некоторого объекта ядра, чтобы получить список заголовков, найдите их версию ядра с помощью команды grep.   -  person osgx    schedule 18.08.2018
comment
Это в основном то, о чем я думал, есть ли какой-нибудь хороший способ не делать этого для младшего номера версии, иначе все черт возьми вырвется наружу   -  person Max Ehrlich    schedule 20.08.2018
comment
@osgx Как вы думаете, вы могли бы поместить свой комментарий в ответ, который в основном говорит: «Нет, это невозможно, и я награжу вас наградой»?   -  person Max Ehrlich    schedule 25.08.2018
comment
Думаю, хороший ответ на вопрос должен показать, где именно создается 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   -  person osgx    schedule 25.08.2018


Ответы (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)

Таким образом, версия version.h для linux 4.16.18 будет сгенерирована как (266258 is (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 у меня -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

На самом деле это так: 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)    
person osgx    schedule 25.08.2018
comment
version.h не включен глобально, проверьте комментарии для scripts/checkversion.pl elixir.bootlin. com / linux / v4.16.18 / source / scripts / checkversion находит варианты использования LINUX_VERSION_CODE или KERNEL_VERSION без включения ‹linux / version.h› или случаев включения ‹linux / version.h›, которые в нем не нужны . - person osgx; 26.08.2018
comment
Другая проблема, о которой сообщалось при нулевом совпадении для локальных перестроек (тот же уровень исправлений, но разные заголовки git): helenfornazier.blogspot.com/2015/06/ Проверьте, установлен ли в вашем menuconfig флаг CONFIG_LOCALVERSION_AUTO, отключите его и попробуйте снова. Этот флаг, похоже, изменяет основной файл заголовка, поскольку он автоматически добавляет версию git в строку версии, заставляя CCache перекомпилировать почти все.. Другой пост: nickdesaulniers. github.io/blog/2018/06/02/ со ссылками на lwn.net/Articles/ 437864 - person osgx; 26.08.2018