我正在尝试使用 CLion 进行一些 Linux 内核编程。我观察到一些标题,如
<linux/kernel.h>
和 <linux/module.h>
被正确识别,但其他一些标题,如 <linux/init.h>
则未被正确识别。
为了更清楚,这里有一个示例程序:
1 #include <linux/init.h>
2 #include <linux/kernel.h>
3
4 int init_module(void) {
5 printk("initializing");
6 return 0;
7 }
这里,我的第 1 行和第 5 行以红色突出显示,并带有典型的“未找到文件”/“调用未声明的函数”错误,但第 2 行正确地未突出显示,并且如果我尝试编写链接到该库的内容完全没有问题。
我尝试为我的内核安装最新的标头,即
sudo apt install linux-headers-$(uname -r)
,但它似乎不起作用。我能做什么?
编辑:
输出
locate init.h | grep linux/init.h
(some thimeshift things...)
/usr/src/linux-headers-5.4.0-187/include/linux/init.h
/usr/src/linux-headers-5.4.0-189/include/linux/init.h
/usr/src/linux-headers-5.4.0-74/include/linux/init.h
...这很好奇,因为我的内核版本是5.4.0-190,并且
sudo apt-get install linux-headers-$(uname -r)
的输出如下(手动翻译成英文):
...
linux-headers-5.4.0-190-generic is already at the most recent version (5.4.0-190.210).
...
编辑:
添加 CMake 文件有点有效
include_directories(/usr/src/linux-headers-5.4.0-187/include/)
,但这只是一个“补丁”,而不是解决方案,所以我将问题悬而未决,希望得到一些更“明确”的答案
事情没那么简单,有两个主要原因:
CLion 想要使用 CMake,因此您必须编写一个自定义
CMakeLists.txt
来查找内核标头并调用 make
来调用内核 Makefile 并使用自定义命令构建模块。这并不容易,因为内核 Makefile 相当复杂,需要正确定义许多变量。
正如您似乎已经发现的那样,通常当前内核的内核头位于
/usr/src/linux-headers-$(uname -r)/
。但是,.h
C 头文件位于不同的目录中。在 headers 目录中,我知道至少有 4 个不同的包含目录:include
、include/generated
、arch/XXX/include
和 arch/XXX/include/generated
。这些都需要包括在内。
鉴于上述情况,构建内核模块的
CMakeLists.txt
应该执行以下操作:
uname -r
命令)。/usr/src/linux-headers-$(uname -r)
找到内核头文件。我们将此路径保存在 KDIR
变量中。ARCH
变量。没有简单的方法可以自动执行此操作,因为像 uname -m
这样的命令行工具会输出错误的名称(例如 x86_64
而不是 x86
)。include_directories()
包含适当的目录。Kbuild
(或 Makefile
)文件来构建模块。make
运行自定义 custom_command()
命令来构建模块。dummy_target
目标用于依赖性检查。最终结果如下:
cmake_minimum_required(VERSION 3.28)
project(mymod C)
set(CMAKE_C_STANDARD 99)
add_definitions(-D__KERNEL__ -DMODULE)
# Find running kernel release
execute_process(
COMMAND uname -r
OUTPUT_VARIABLE KERNEL_RELEASE
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(KDIR "/usr/src/linux-headers-${KERNEL_RELEASE}")
set(ARCH "x86")
# Check that the headers are installed
if (NOT EXISTS "${KDIR}/include/linux/module.h")
message(FATAL_ERROR "Kernel headers dir not found: ${KDIR}")
endif()
# Check that the headers are for the specified arch
if (NOT EXISTS "${KDIR}/arch/${ARCH}/include")
message(FATAL_ERROR "Arch-specific include dir not found: ${KDIR}/arch/${ARCH}/include")
endif()
message(NOTICE "Building for arch: ${ARCH}")
message(NOTICE "Kernel release: ${KERNEL_RELEASE}")
message(NOTICE "Kernel headers: ${KDIR}")
# Include generic kernel headers
include_directories("${KDIR}/include")
include_directories("${KDIR}/include/generated")
# Include arch-specific kernel headers
include_directories("${KDIR}/arch/${ARCH}/include")
include_directories("${KDIR}/arch/${ARCH}/include/generated")
# Set some useful variables
set(MODULE_NAME mymod)
set(SRC_FILE "${MODULE_NAME}.c")
set(OBJ_FILE "${MODULE_NAME}.o")
set(KO_FILE "${MODULE_NAME}.ko")
# Generate Kbuild file in the source directory
FILE(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/Kbuild "obj-m := ${OBJ_FILE}")
# Custom `make` command used to build the module
add_custom_command(
OUTPUT ${KO_FILE}
COMMAND $(MAKE) -C ${KDIR} modules ARCH=${ARCH} M=${CMAKE_CURRENT_BINARY_DIR} src=${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${SRC_FILE}
VERBATIM
)
add_custom_target(mymod ALL DEPENDS ${KO_FILE})
add_library(dummy_target "${SRC_FILE}")
上面的
CMakeLists.txt
可以很好地构建以下mymod.c
内核模块:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
int __init myinit(void) {
pr_info("Hello World!\n");
return 0;
}
void __exit myexit(void) {
pr_info("Goodbye World!\n");
}
module_init(myinit);
module_exit(myexit);
MODULE_VERSION("0.1");
MODULE_AUTHOR("Marco Bonelli");
MODULE_DESCRIPTION("Test module");
MODULE_LICENSE("GPL 2.0");
注意,
MODULE_LICENSE()
是强制性的,如果没有指定许可证,内核Makefile将拒绝构建模块!
最终项目在 CLion 中应如下所示:
注意:
Kbuild
文件是由CMakeLists.txt
自动生成的。您需要写入的唯一两个文件是 CMakeLists.txt
和 mymod.c
。
唯一的问题(我不确定如何解决)是,正如您从上面的屏幕截图中看到的那样,CLion 似乎认为它还应该考虑用户空间包含诸如
/usr/include
、/usr/x86_64-linux-gnu/include
等目录。这些绝对应该不被考虑用于内核模块,但我不确定如何使用CMakeLists.txt
禁用/删除它们。