新建 my_module.c
文件
#include
#include
#include
#include
static int __init module_base_init(void)
{
printk("base module init!\r\n");
return 0;
}
static void __exit module_base_exit(void)
{
printk("base module exit!\r\n");
}
module_init(module_base_init);
module_exit(module_base_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("tyustli");
新建 Makefile
文件
# 指定内核路径
KERNELDIR := /home/tyustli/code/open_source/kernel/linux-6.5.7
# 指定当前路径
CURRENT_PATH := $(shell pwd)
# 指定编译的模块名
obj-m := my_module.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
此时如果直接在模块路径执行 make
会有 如下警告和报错,cc1: error: cannot load plugin ./scripts/gcc-plugins/arm_ssp_per_task_plugin.so: ./scripts/gcc-plugins/arm_ssp_per_task_plugin.so: undefined symbol: _Z16gen_load_tp_hardP7rtx_def
完整日志如下
make -C /home/tyustli/code/open_source/kernel/linux-6.5.7 M=/home/tyustli/code/qemu_code/linux_driver/0001_module_init modules
make[1]: Entering directory '/home/tyustli/code/open_source/kernel/linux-6.5.7'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: arm-none-linux-gnueabihf-gcc (GNU Toolchain for the A-profile Architecture 10.3-2021.07 (arm-10.29)) 10.3.1 20210621
You are using: gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
CC [M] /home/tyustli/code/qemu_code/linux_driver/0001_module_init/my_module.o
cc1: error: cannot load plugin ./scripts/gcc-plugins/arm_ssp_per_task_plugin.so: ./scripts/gcc-plugins/arm_ssp_per_task_plugin.so: undefined symbol: _Z16gen_load_tp_hardP7rtx_def
make[3]: *** [scripts/Makefile.build:243: /home/tyustli/code/qemu_code/linux_driver/0001_module_init/my_module.o] Error 1
make[2]: *** [/home/tyustli/code/open_source/kernel/linux-6.5.7/Makefile:2037: /home/tyustli/code/qemu_code/linux_driver/0001_module_init] Error 2
make[1]: *** [Makefile:237: __sub-make] Error 2
make[1]: Leaving directory '/home/tyustli/code/open_source/kernel/linux-6.5.7'
make: *** [Makefile:11: kernel_modules] Error 2
这是因为前面编译内核的时候 ARCH
和 CROSS_COMPILE
是通过 shell
脚本传进去的,编译模块的时候又没有指定这些。
解决方法就是在内核顶层的 Makefile
直接定义这两个变量(有点粗暴)
ARCH = arm
CROSS_COMPILE = arm-none-linux-gnueabihf-
make -C /home/tyustli/code/open_source/kernel/linux-6.5.7 M=/home/tyustli/code/qemu_code/linux_driver/0001_module_init modules
make[1]: Entering directory '/home/tyustli/code/open_source/kernel/linux-6.5.7'
CC [M] /home/tyustli/code/qemu_code/linux_driver/0001_module_init/my_module.o
MODPOST /home/tyustli/code/qemu_code/linux_driver/0001_module_init/Module.symvers
CC [M] /home/tyustli/code/qemu_code/linux_driver/0001_module_init/my_module.mod.o
LD [M] /home/tyustli/code/qemu_code/linux_driver/0001_module_init/my_module.ko
make[1]: Leaving directory '/home/tyustli/code/open_source/kernel/linux-6.5.7'
模块编译好之后,最好的方法就是将 rootfs 设置为 nfs
,这样直接将编译的 ko 放到网络文件系统中,直接启动内核即可。
由于目前使用的是 ubuntu + wifi
的形式,如果想让 qemu 联网,需要建立网桥,但是 wifi
网卡没有处于 AP 模式,处于 Managed 模式的无线网卡没有足够多的信息做网桥,只能转换成 master
模式
iwconfig wlp2s0 mode master
结果网卡不支持
Error for wireless request "Set Mode" (8B06) :
SET failed on device wlp2s0 ; Operation not permitted.
只能通过软件 hostapd
来实现。。。。。。
这个有点复杂,超出了研究 linux driver 的目的。
解决方法就是,每次生成 xxx.ko
之后,将生成的 ko 文件拷贝到根文件目录下,然后重新打包 rootfs
根文件系统。这样 linux 启动之后模块就在 rootfs 根文件系统中
# 将生成的 .ko 文件拷贝到根文件系统的 roorfs 中
cp ./my_module.ko /home/tyustli/code/open_source/busybox/rootfs/dev
# 切换到根文件系统目录
cd /home/tyustli/code/open_source/busybox
# 生成虚拟 SD 卡系统镜像
sudo dd if=/dev/zero of=rootfs.ext3 bs=1M count=32
# 格式化镜像
sudo mkfs.ext3 rootfs.ext3
#将文件复制到镜像中
sudo mkdir tmpfs_rootfs
sudo mount -t ext3 rootfs.ext3 tmpfs_rootfs/ -o loop
sudo cp -r rootfs/* tmpfs_rootfs/
sudo umount tmpfs_rootfs
rmdir tmpfs_rootfs
这里 rootfs 就制作好了,重新启动 linux
sudo qemu-system-arm -M vexpress-a9 -m 512M \
-kernel /home/tyustli/code/open_source/kernel/linux-6.5.7/arch/arm/boot/zImage \
-dtb /home/tyustli/code/open_source/kernel/linux-6.5.7/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb -nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-sd /home/tyustli/code/open_source/busybox/rootfs.ext3
脚本源码
# 参数解析
# ./my_module_build.sh para1 para2(可选)
# 脚本名称 指定模块路径 是否执行 make clean 命令
# 判断 shell 脚本有几个参数,如果没有指定 module 目录, shell 脚本就报错退出
if [ $# -eq 0 ]; then
echo "Incorrect number of arguments for command
Usage: my_module_build.sh build your own module"
exit
fi
# 切换到指定的目录
cd $1
# 如果是清除工程,就执行 make clean 命令
if [ "$2" == "clean" ]; then
make clean
exit
fi
# 编译指定目录的模块
make
# 将生成的 .ko 文件拷贝到根文件系统的 roorfs 中
cp ./my_module.ko /home/tyustli/code/open_source/busybox/rootfs/dev
# 切换到根文件系统目录
cd /home/tyustli/code/open_source/busybox
# 生成虚拟 SD 卡系统镜像
sudo dd if=/dev/zero of=rootfs.ext3 bs=1M count=32
# 格式化镜像
sudo mkfs.ext3 rootfs.ext3
#将文件复制到镜像中
sudo mkdir tmpfs_rootfs
sudo mount -t ext3 rootfs.ext3 tmpfs_rootfs/ -o loop
sudo cp -r rootfs/* tmpfs_rootfs/
sudo umount tmpfs_rootfs
rmdir tmpfs_rootfs
# 切换回指定的目录
cd $1
# 启动 kernel
sudo qemu-system-arm -M vexpress-a9 -m 512M \
-kernel /home/tyustli/code/open_source/kernel/linux-6.5.7/arch/arm/boot/zImage \
-dtb /home/tyustli/code/open_source/kernel/linux-6.5.7/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb -nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-sd /home/tyustli/code/open_source/busybox/rootfs.ext3
用法
./my_module_build.sh 0001_module_init/
查看模块文件是否存在
ls /dev/my_module.ko
模块安装
/dev/my_module.ko
base module init
和 module_base_init
打印的信息一致
rmmod my_module
base module exit
和 module_base_exit
打印的信息一致
在 my_module.c
文件中使用 #include
包含了一些内核的头文件,那么这些头文件如何跳转可以借助 C/C++
插件,或者 clangd
插件
Ctrl + Shift + p
输入 C/C++:Edit Configurations(JSON)
,在当前路径下会自动新建一个 .vscode
文件,并生成 c_cpp_properties.json
,在文件中输入
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/tyustli/code/open_source/kernel/linux-6.5.7/include",
"/home/tyustli/code/open_source/kernel/linux-6.5.7/include/uapi",
"/home/tyustli/code/open_source/kernel/linux-6.5.7/arch/arm/include",
"/home/tyustli/code/open_source/kernel/linux-6.5.7/arch/arm/include/generated",
"/home/tyustli/code/open_source/kernel/linux-6.5.7/arch/arm/include/generated/uapi"
],
"defines": [
],
"compilerPath": "/home/tyustli/cross_tool/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-gcc",
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-arm"
}
],
"version": 4
}
这样配置之后头文件可以正常跳转
同样可以在 .vscode
生成 settings.json
文件,在其中输入
{
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/*.su":true,
"Documentation":true,
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/*.su":true,
"Documentation":true,
},
"files.associations": {
"map.h": "c",
"module.h": "c",
"init.h": "c",
"types.h": "c",
"kernel.h": "c",
"kobject.h": "c",
"sysfs.h": "c",
"kernfs.h": "c",
"idr.h": "c",
"radix-tree.h": "c",
"xarray.h": "c",
"mm.h": "c",
"sched.h": "c",
"seccomp.h": "c",
"*.tcc": "c",
"compiler_attributes.h": "c"
}
}
使用下面命令生成 compile_commands.json
文件
bear -- make
配置 clangd
,由于 clangd 和 c/c++ 会存在冲突,在 settings.json
中
"C_Cpp.intelliSenseEngine": "disabled",
配置 c_cpp_properties.json
文件
"compileCommands": "${workspaceFolder}/linux_driver/0001_module_init/compile_commands.json"
最终会生成一个 .cache
目录