将 Linux 内核源代码树中的驱动模块代码挪到独立的工程中进行构建时,有时候需要涉及到对内核头文件的修改。虽然可以通过在 Makefile 中增加 ccflags-y
或者类似的选项将本地工程中头文件所在的文件夹加入编译器的搜索路径,但是,由于该路径是追加在头文件搜索路径的最后,导致修改后的头文件无法得到加载。
如下的信息是设置 ccflags-y := -I$(src)/../../include
后构建过程的输出:
# make -n
...
make -C /lib/modules/3.10.0-693.el7.x86_64/build M=/root/kmod-ceph/kmod-ceph/drivers/block modules
...
make -f scripts/Makefile.build obj=/root/kmod-ceph/kmod-ceph/drivers/block
set -e; echo ' CC /root/kmod-ceph/kmod-ceph/drivers/block/rbd.mod.o'; gcc -Wp,-MD,/root/kmod-ceph/kmod-ceph/drivers/block/.rbd.mod.o.d -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include -I./arch/x86/include -Iarch/x86/include/generated -Iinclude -I./arch/x86/include/uapi -Iarch/x86/include/generated/uapi -I./include/uapi -Iinclude/generated/uapi -include ./include/linux/kconfig.h -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -std=gnu89 -O2 -m64 -mno-mmx -mno-sse -mpreferred-stack-boundary=3 -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -maccumulate-outgoing-args -Wframe-larger-than=2048 -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -DCONFIG_AS_AVX512=1 -DCONFIG_AS_SHA1_NI=1 -DCONFIG_AS_SHA256_NI=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -Wframe-larger-than=2048 -fstack-protector-strong -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -mfentry -DCC_USING_FENTRY -fno-inline-functions-called-once -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO -I/root/kmod-ceph/kmod-ceph/drivers/block/../../include -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(rbd.mod)" -D"KBUILD_MODNAME=KBUILD_STR(rbd)" -DMODULE -c -o /root/kmod-ceph/kmod-ceph/drivers/block/rbd.mod.o /root/kmod-ceph/kmod-ceph/drivers/block/rbd.mod.c;
...
make -f ./scripts/Makefile.modpost
...
注意其中头文件搜索路径最后一项即我们通过 ccflags-y
选项设置的路径:
-I/root/kmod-ceph/kmod-ceph/drivers/block/../../include
显然,此时我们的头文件已经没有任何机会得到优先加载了。
通过分析内核模块构建的 Makefile(/lib/modules/$(uname -r)/build/Makefile,实际上就是 linux 源代码树根目录下的 Makefile),可以得到如下的关系:
linux/Makefile
include scripts/Kbuild.include
build := -f $(srctree)/scripts/Makefile.build obj
include scripts/Makefile.lib
其中与内核模块构建相关的关键代码如下(以 Linux kernel 5.5 为例):
// linux/Makefile
# Use make M=dir or set the environment variable KBUILD_EXTMOD to specify the
# directory of external module to build. Setting M= takes precedence.
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
...
PHONY += $(objtree)/Module.symvers
$(objtree)/Module.symvers:
@test -e $(objtree)/Module.symvers || ( \
echo; \
echo " WARNING: Symbol version dump $(objtree)/Module.symvers"; \
echo " is missing; modules will have no dependencies and modversions."; \
echo )
build-dirs := $(KBUILD_EXTMOD)
PHONY += modules
modules: descend $(objtree)/Module.symvers
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
...
# Handle descending into subdirectories listed in $(build-dirs)
# Preset locale variables to speed up the build process. Limit locale
# tweaks to this spot to avoid wrong language settings when running
# make menuconfig etc.
# Error messages still appears in the original language
PHONY += descend $(build-dirs)
descend: $(build-dirs)
$(build-dirs): prepare
$(Q)$(MAKE) $(build)=$@ \
single-build=$(if $(filter-out $@/, $(filter $@/%, $(single-no-ko))),1) \
need-builtin=1 need-modorder=1
// linux/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)
a_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
$(_a_flags) $(modkern_aflags)
cpp_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
$(_cpp_flags)
显然,只能从 NOSTDINC_FLAGS
或者 LINUXINCLUDE
的定义想办法,在 Linux kernel 4.7 以前,NOSTDINC_FLAGS
被定义成一个未定义的 recursively expanded 变量,因此只需要在内核模块的 Makefile 中将 NOSTDINC_FLAGS
定义成头文件搜索目录即可,如下所示:
ifeq ($(KERNELRELEASE),)
# KVERSION should be set in the environment if this
# build is not for the currently running kernel.
KVERSION ?= $(shell uname -r)
# BUILD_DIR should be set in the environment if a
# subdirectory of /lib/modules/ is not appropriate.
BUILD_DIR ?= /lib/modules/${KVERSION}/build
PWD := $(shell pwd)
export NOSTDINC_FLAGS
NOSTDINC_FLAGS += -I$(PWD)/../../arch/x86/include
NOSTDINC_FLAGS += -I$(PWD)/../../include
modules:
echo $(PWD)
$(MAKE) -C $(BUILD_DIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(BUILD_DIR) M=$(PWD) modules_install
clean:
$(MAKE) -C $(BUILD_DIR) M=$(PWD) clean
.PHONY: modules modules_install clean
else
# Called from kernel build system -- just declare the module(s).
obj-m := rbd.o
endif
从下面构建过程的输出中可以看到内核模块本地头文件路径放在了最高优先级:
# make -n
...
make -C /lib/modules/3.10.0-693.el7.x86_64/build M=/root/kmod-ceph/kmod-ceph/drivers/block modules
...
make -f scripts/Makefile.build obj=/root/kmod-ceph/kmod-ceph/drivers/block
set -e; echo ' CC [M] /root/kmod-ceph/kmod-ceph/drivers/block/rbd.o'; gcc -Wp,-MD,/root/kmod-ceph/kmod-ceph/drivers/block/.rbd.o.d -I/root/kmod-ceph/kmod-ceph/drivers/block/../../arch/x86/include -I/root/kmod-ceph/kmod-ceph/drivers/block/../../include -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include -I./arch/x86/include -Iarch/x86/include/generated -Iinclude -I./arch/x86/include/uapi -Iarch/x86/include/generated/uapi -I./include/uapi -Iinclude/generated/uapi -include ./include/linux/kconfig.h -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -std=gnu89 -O2 -m64 -mno-mmx -mno-sse -mpreferred-stack-boundary=3 -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -maccumulate-outgoing-args -Wframe-larger-than=2048 -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -DCONFIG_AS_AVX512=1 -DCONFIG_AS_SHA1_NI=1 -DCONFIG_AS_SHA256_NI=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -Wframe-larger-than=2048 -fstack-protector-strong -Wno-unused-but-set-variable -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -mfentry -DCC_USING_FENTRY -fno-inline-functions-called-once -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO -DMODULE -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(rbd)" -D"KBUILD_MODNAME=KBUILD_STR(rbd)" -c -o /root/kmod-ceph/kmod-ceph/drivers/block/.tmp_rbd.o /root/kmod-ceph/kmod-ceph/drivers/block/rbd.c;
...
make -f ./scripts/Makefile.modpost
...
但是需要注意的是,在 kbuild: Initialize exported variables 中,NOSTDINC_FLAGS
被初始定义成空,在 kbuild: Make NOSTDINC_FLAGS a simply expanded variable 中更是被定义成了 simply expanded 变量,因此,RHEL/CentOS 8.x 系列(基于 4.18 内核)无法使用上面的方法,当前我能想到的就是构建前手工修改 Linux 内核 Makefile,将 NOSTDINC_FLAGS
恢复成 4.7 以前的行为,当然,为了避免潜在的问题,更好的解决方案是在内核模块 Makefile 中新增一个导出变量,手工修改 Linux 内核 Makefile 并在 NOSTDINC_FLAGS
或者 LINUXINCLUDE
中引用该变量。
参考资料
The Two Flavors of Variables
https://www.gnu.org/software/make/manual/html_node/Flavors.html
Appending More Text to Variables
https://www.gnu.org/software/make/manual/html_node/Appending.html
Communicating Variables to a Sub-make
https://www.gnu.org/software/make/manual/html_node/Variables_002fRecursion.html
Functions for Transforming Text
https://www.gnu.org/software/make/manual/html_node/Functions.html
A Dive Into Kbuild
Linux Kernel Makefiles
https://github.com/torvalds/linux/blob/master/Documentation/kbuild/makefiles.rst
ccflag option in Makefile
https://stackoverflow.com/questions/51453193/ccflag-option-in-makefile
How do I force make/GCC to show me the commands?
https://stackoverflow.com/questions/5820303/how-do-i-force-make-gcc-to-show-me-the-commands
Gcc Options for Directory Search
https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html
Why would one use #include_next in a project?
https://stackoverflow.com/questions/10261382/why-would-one-use-include-next-in-a-project
CPP Wrapper Headers
https://gcc.gnu.org/onlinedocs/cpp/Wrapper-Headers.html#Wrapper-Headers
CPP Common Predefined Macros
https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html#Common-Predefined-Macros
最后修改于 2020-03-09