文章目录
- 模块实现
- 编译模块的 makefile
- 编译报错解决
- 模块编译日志
- 自动化
- 模块安装
- 模块卸载
- 配置头文件路径
- C/C++ 插件
- clangd 插件
模块实现
新建 my_module.c
文件
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>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
新建 Makefile
文件
# 指定内核路径
KERNELDIR := /home/tyustli/code/open_source/kernel/linux-6.5.7
# 指定当前路径
CURRENT_PATH := $(shell pwd)
# 指定编译的模块名
obj-m := my_module.obuild: kernel_moduleskernel_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 kernelThe 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 20210621You are using: gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0CC [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.oMODPOST /home/tyustli/code/qemu_code/linux_driver/0001_module_init/Module.symversCC [M] /home/tyustli/code/qemu_code/linux_driver/0001_module_init/my_module.mod.oLD [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 ]; thenecho "Incorrect number of arguments for command
Usage: my_module_build.sh <module_dir> build your own module"exit
fi# 切换到指定的目录
cd $1# 如果是清除工程,就执行 make clean 命令
if [ "$2" == "clean" ]; thenmake cleanexit
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
插件
C/C++ 插件
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"}
}
clangd 插件
使用下面命令生成 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
目录