linux 模块安装与卸载

模块实现

新建 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.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

这是因为前面编译内核的时候 ARCHCROSS_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/lib/modules/6.5.7+/

# 切换到根文件系统目录
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 <module_dir>  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/lib/modules/6.5.7+/

# 切换到根文件系统目录
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 /lib/modules/6.5.7+/my_module.ko

模块安装

linux 设备驱动的动态加载可以使用 insmodmodprobe 两种方式,insmod 一次只能加载一个特定的驱动,且需要驱动的绝对路径,而 modprobe 则可以一次性将有依赖关系的驱动全部加载到内核,不需要驱动的具体地址。但需要将驱动拷贝到/lib/modules/$(uname -r)/目录下。

insmod

insmod /lib/modules/6.5.7+/my_module.ko

在这里插入图片描述
base module initmodule_base_init 打印的信息一致

modprobe

当然也可以使用

modprobe my_module

注意不要带 .ko 扩展名

如果提示找不到 modprobe depmod等命令,需要在 busybox 中使能该功能,并重新打包根文件系统

在这里插入图片描述

模块卸载

rmmod my_module

在这里插入图片描述
base module exitmodule_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 目录

猜你喜欢

转载自blog.csdn.net/tyustli/article/details/134100824
今日推荐