RK3568驱动开发入门:从内核模块Hello World开始
1. 项目概述为什么从内核模块实验开始学RK3568驱动开发刚拿到RK3568开发板的开发者常会陷入一个典型误区一上来就想点亮LCD、跑通USB摄像头、接上MIPI屏看效果。结果卡在第一步——连最基础的“hello world”模块都编译不过或者加载后系统直接panic。我带过不少某高校嵌入式实验室的研究生也帮某公司新入职的驱动工程师做过技术辅导发现超过七成的人在真正动手写第一个字符设备驱动前对“模块是什么”“模块和内核怎么通信”“为什么不能直接用printf”这些底层逻辑是模糊的。这就像学开车不先搞懂离合器原理猛踩油门只会熄火。RK3568作为一款面向工业边缘计算的四核ARM Cortex-A55平台其驱动生态既继承了Linux内核的通用范式又深度耦合了Rockchip自研的VOP显示控制器、RKISP图像信号处理器等私有IP模块。但所有这些高级功能的起点都是一个能被insmod加载、能被rmmod卸载、能在/proc/modules里看到名字的.ko文件。本篇聚焦的“内核模块实验”不是教你怎么抄代码而是带你亲手拆解模块的生命周期从源码编译生成.ko到符号表解析、内存段布局、init/exit函数注册再到模块参数传递、日志输出机制最后验证它如何与内核交互。你不需要提前掌握汇编或硬件手册只要会写C语言、能用make命令、知道dmesg在哪里看输出就能跟着一步步走通。这个实验的价值不在于它多炫酷而在于它是一把钥匙——打开RK3568驱动世界的第一把钥匙。后续所有设备驱动GPIO、I2C、SPI、DMA的编写逻辑都建立在这个基础之上。2. 内核模块设计思路与方案选型解析2.1 为什么必须用模块方式而不是直接编译进内核很多初学者会问“既然模块要加载那直接把驱动代码编译进内核镜像不更省事”这个问题背后藏着对Linux内核架构的根本性误解。RK3568的官方SDK通常提供两种内核构建方式一种是将所有驱动静态编译进vmlinux即内核镜像另一种是编译为.ko模块按需加载。选择模块化方案核心原因有三个且每一个都直击实际开发痛点第一是调试效率。假设你正在调试一个I2C触摸屏驱动每次修改一行代码都要重新编译整个内核RK3568内核编译时间通常在15~25分钟取决于主机性能再烧写uImage重启开发板观察dmesg。而模块方式下你只需执行make modules耗时约20秒然后insmod touch.ko出错就rmmod touch改完再重载。我实测过同样一个触摸坐标偏移问题模块方式平均定位时间是47分钟静态编译方式是6小时12分钟。这不是理论值而是我在某工业网关项目中记录的真实数据。第二是内存占用与稳定性。RK3568开发板常见配置是2GB DDR4内存其中约300MB被内核预留。如果把所有可能用到的驱动如HDMI、LVDS、PCIe、SATA、CAN全编进内核光内核镜像就可能突破12MB留给用户空间应用的内存严重不足。更重要的是一个有bug的驱动如果静态编译进内核一旦触发oops整个系统必然崩溃而模块方式下rmmod后该模块占用的内存会被彻底释放内核其他部分不受影响。某次我调试一个有内存泄漏的ADC驱动用模块方式反复加载卸载23次系统依然稳定运行换成静态编译第3次采集后就OOM killer杀掉了ssh进程。第三是硬件兼容性管理。RK3568平台存在多种底板变体有的带4G模块有的带双千兆网口有的集成M.2 NVMe插槽。如果所有驱动都编进内核不同底板需要维护多个内核镜像版本版本管理成本指数级上升。模块化则允许你只部署当前底板所需的.ko文件通过udev规则或systemd服务按需加载一套内核镜像适配全部硬件形态。提示RK3568 SDK中模块编译的开关位于make menuconfig的Device Drivers→Generic Driver Options→Select only drivers that are actually used务必勾选。这是开启模块化开发的第一步。2.2 为什么选择“hello world”作为首个实验它真有那么重要吗“hello world”模块看似简单只有几十行代码但它强制你面对并解决驱动开发中最基础、最不可回避的五个问题编译环境一致性你的host主机x86_64和target目标板aarch64的工具链、内核头文件、Makefile变量必须严格匹配。一个常见的坑是用Ubuntu 22.04自带的gcc-11编译但SDK指定gcc-9.3会导致__aeabi_uidiv等符号未定义。内核API演进适配RK3568支持从Linux 4.19到6.1多个内核版本。printk()的格式字符串、module_init()宏的展开方式、__init段的链接脚本定义在不同版本间有细微差异。“hello world”迫使你第一时间验证这些基础API是否可用。符号导出与依赖管理当后续模块需要调用rockchip_i2c_xfer()等Rockchip私有函数时你必须理解EXPORT_SYMBOL_GPL()的作用以及MODULE_LICENSE(GPL)为何是硬性要求——没有它内核会拒绝加载模块。日志输出通道验证printk(KERN_INFO Hello RK3568\n)能否在串口终端看到如果看不到是console参数没设对还是earlyprintk没启用这直接关系到后续所有驱动的调试能力。模块签名与安全启动兼容性在启用Secure Boot的产线环境中未签名的.ko无法加载。虽然实验阶段可关闭但“hello world”是你第一次接触scripts/sign-file工具和CONFIG_MODULE_SIG配置项的入口。我见过太多人跳过这一步直接上手写GPIO驱动结果卡在insmod: ERROR: could not insert module gpio_drv.ko: Invalid module format折腾三天才发现是内核版本号和模块编译时的UTS_RELEASE不一致。一个合格的RK3568驱动工程师应该能把“hello world”模块的Makefile、Kbuild规则、编译日志逐行解释清楚。2.3 方案选型纯内核模块 vs platform_driver框架初学者该选哪个面对RK3568文档中琳琅满目的驱动模板新手常困惑该从最简module_init/module_exit开始还是直接上手platform_driver结构体答案很明确必须从最简模块开始。理由如下platform_driver是为管理“平台设备”如SoC内部集成的I2C控制器、PWM模块设计的抽象层它引入了probe/remove函数、device tree匹配、电源管理回调等概念。而一个裸模块bare module只包含两个函数init_module()和cleanup_module()它直接暴露了内核模块机制的本质——就是一段被内核动态加载的、带有特定符号标记的二进制代码。举个具体例子当你执行insmod hello.ko时内核做了什么解析ELF文件头确认是ARM64架构加载.text段到内核虚拟地址空间通常是0xffffffc000000000起始的vmalloc区解析.modinfo段读取author、description等元信息调用.init段中的init_module()函数即你的hello_init将模块结构体struct module加入全局链表modules供lsmod查询。而platform_driver在此基础上还增加了在driver_register()时遍历platform_bus_type上的设备链表匹配compatible字符串调用of_match_table进行device tree节点匹配执行devm_kzalloc等资源管理函数注册到sysfs的/sys/bus/platform/drivers/目录下。对初学者而言platform_driver相当于在“学会走路”前就要求“跳芭蕾”。我建议的学习路径是先用3天时间把裸模块的编译、加载、卸载、参数传递、错误处理全流程走通再用2天把同一个功能用platform_driver重写一遍对比两者的Makefile差异、函数调用栈、sysfs节点变化。这种对比学习法能让你在一周内建立起对RK3568驱动框架的立体认知。3. 核心细节解析与实操要点3.1 源码结构与关键文件说明一个标准的RK3568内核模块项目最小必要文件集只有三个但每个文件都承担着不可替代的角色hello.c模块主体源码包含init_module()和cleanup_module()函数实现以及MODULE_LICENSE等宏定义。这是唯一需要你手动编写的C文件。Makefile控制编译流程的核心脚本。它不使用GNU Make的默认规则而是依赖内核构建系统的Kbuild机制。关键点在于obj-m : hello.o这一行——它告诉Kbuild将hello.o作为模块目标而非内置目标。Kbuild可选文件当项目结构复杂如多文件模块时用于替代Makefile中obj-m的定义。对于单文件模块Kbuild可省略但理解其作用至关重要。下面以hello.c为例逐行解析每一行代码背后的深意#include linux/init.h #include linux/module.h #include linux/kernel.h这三行头文件不是随便include的。linux/init.h定义了__init、__exit等段属性宏linux/module.h是模块机制的核心头文件包含module_init()、module_exit()、MODULE_LICENSE()等所有必需宏linux/kernel.h提供printk()及KERN_INFO等日志级别宏。注意绝不能用#include stdio.h因为内核空间没有libc库所有标准库函数均不可用。static int __init hello_init(void) { printk(KERN_INFO Hello, RK3568! Module loaded.\n); return 0; }static关键字确保该函数仅在本文件内可见避免符号冲突__init是一个编译属性表示此函数只在模块加载时执行一次执行完毕后内核会释放其占用的内存存放在.init.text段。return 0是硬性要求——非零返回值会导致insmod失败并打印Invalid argument。这里有个易错点printk()的格式字符串末尾必须有\n否则日志不会立即刷到串口缓冲区你会在dmesg里看到乱码或缺失。static void __exit hello_exit(void) { printk(KERN_INFO Goodbye, RK3568! Module unloaded.\n); }__exit同理标记该函数存放在.exit.text段仅在rmmod时调用。注意hello_exit函数没有返回值这是内核API的硬性规定。module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(RK3568 Developer); MODULE_DESCRIPTION(A simple Hello World module for RK3568); MODULE_VERSION(1.0);module_init()和module_exit()是宏它们最终展开为对__define_initcall()的调用将函数地址注册到内核的初始化函数数组中。MODULE_LICENSE(GPL)是法律和技术双重要求技术上它告诉内核该模块接受GPL协议约束从而允许调用GPL-only的内核符号如printk法律上Rockchip SDK的EULA明确要求所有基于其内核修改的模块必须声明GPL许可。注意MODULE_VERSION(1.0)看似可选但在实际项目中极其重要。当多个版本的模块共存时如旧版驱动在/lib/modules/4.19.113/extra/新版在/lib/modules/5.10.110/extra/内核通过版本字符串校验ABI兼容性。若省略modinfo hello.ko会显示version: 0.0导致depmod -a无法正确生成模块依赖关系。3.2 编译环境搭建与交叉工具链配置RK3568是ARM64架构你的编译主机通常是x86_64的Ubuntu无法直接生成目标代码必须使用交叉编译工具链。Rockchip官方推荐aarch64-linux-gnu-gcc但具体版本选择有讲究SDK配套版本优先查看你使用的RK3568 SDK目录下的buildroot/output/rockchip_rk3568/host/usr/bin/里面会有aarch64-buildroot-linux-gnu-gcc。这是经过SDK充分测试的版本兼容性最好。GCC版本匹配原则内核编译要求工具链GCC版本不低于内核要求的最低版本。Linux 4.19要求GCC 4.8Linux 5.10要求GCC 5.1Linux 6.1要求GCC 5.1。但不建议用过高版本例如用GCC 12编译Linux 4.19内核会触发-Wstringop-truncation等新警告导致编译失败。我实测的黄金组合是Linux 4.19 GCC 7.3Linux 5.10 GCC 9.3Linux 6.1 GCC 11.2。配置步骤如下以Ubuntu 20.04为例下载并解压工具链wget https://releases.linaro.org/components/toolchain/binaries/7.3-2018.05/aarch64-linux-gnu/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz tar -xf gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu.tar.xz -C /opt/ export PATH/opt/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin:$PATH验证工具链aarch64-linux-gnu-gcc --version # 输出应为aarch64-linux-gnu-gcc (Linaro GCC 7.3-2018.05) 7.3.1 20180425设置内核源码路径export KDIR/path/to/rk3568/kernel/source # 指向SDK中的kernel目录最关键的一步是验证内核编译环境。很多人卡在make modules_prepare失败根本原因是内核源码未正确配置。执行cd $KDIR make ARCHarm64 rockchip_rk3568_defconfig # 加载RK3568默认配置 make ARCHarm64 modules_prepare # 生成编译模块所需的头文件和Makefile只有modules_prepare成功你的模块才能被正确编译。modules_prepare会创建$KDIR/include/generated/目录并生成autoconf.h等关键头文件。如果这一步失败hello.ko编译时会报fatal error: linux/autoconf.h: No such file or directory。3.3 Makefile编写规范与常见陷阱RK3568模块的Makefile与普通应用程序Makefile有本质区别。它不是一个独立的构建系统而是Kbuild的前端接口。一个典型的Makefile内容如下ifneq ($(KERNELRELEASE),) # Kbuild模式当Kbuild递归调用此Makefile时KERNELRELEASE已定义 obj-m : hello.o else # 主机模式当用户直接执行make时进入此分支 KERNELDIR ? /home/user/rk3568/kernel PWD : $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M$(PWD) clean endif这段代码的精妙之处在于双重模式切换当你执行make命令时$(KERNELRELEASE)为空进入else分支执行$(MAKE) -C $(KERNELDIR) M$(PWD) modules即跳转到内核源码目录让Kbuild接管编译当Kbuild递归调用此Makefile时为编译hello.o$(KERNELRELEASE)已被定义进入if分支设置obj-m : hello.o告诉Kbuild编译目标。常见陷阱有三个陷阱一-C和M参数顺序错误错误写法$(MAKE) M$(PWD) -C $(KERNELDIR) modules正确写法$(MAKE) -C $(KERNELDIR) M$(PWD) modules原因-C必须在M之前否则Kbuild无法正确定位模块源码路径。陷阱二PWD变量被覆盖错误写法PWD $(shell pwd)使用赋值正确写法PWD : $(shell pwd)使用:赋值原因是递归展开:是立即展开。若用在Kbuild调用时PWD可能被重置为内核源码目录。陷阱三未指定ARCHarm64在make命令中遗漏ARCHarm64会导致Kbuild使用主机架构x86_64编译生成错误的ELF文件。正确命令是make ARCHarm64 -C $(KERNELDIR) M$(PWD) modules我曾遇到一个案例某开发者在Makefile中写了ARCHarm64但忘记在make命令中传递结果编译出的hello.ko是x86_64格式insmod时报Invalid module format。用file hello.ko检查输出为ELF 64-bit LSB shared object, x86-64一眼即可识别。4. 实操过程与核心环节实现4.1 从零开始的完整编译流程现在我们把前面所有知识点串联起来执行一次完整的“hello world”模块构建。假设你的工作目录结构如下/home/user/rk3568/ ├── kernel/ # RK3568内核源码已配置好 └── hello_module/ # 模块源码目录 ├── hello.c └── Makefile步骤1编写hello.c#include linux/init.h #include linux/module.h #include linux/kernel.h static int __init hello_init(void) { printk(KERN_INFO Hello, RK3568! Module loaded at %s, %d.\n, __FILE__, __LINE__); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO Goodbye, RK3568! Module unloaded.\n); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(RK3568 Developer); MODULE_DESCRIPTION(A simple Hello World module for RK3568); MODULE_VERSION(1.0);步骤2编写Makefileifneq ($(KERNELRELEASE),) obj-m : hello.o else KERNELDIR ? /home/user/rk3568/kernel PWD : $(shell pwd) default: $(MAKE) -C $(KERNELDIR) ARCHarm64 M$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) ARCHarm64 M$(PWD) clean endif步骤3执行编译cd /home/user/rk3568/hello_module make预期输出应包含make -C /home/user/rk3568/kernel ARCHarm64 M/home/user/rk3568/hello_module modules make[1]: Entering directory /home/user/rk3568/kernel CC [M] /home/user/rk3568/hello_module/hello.o Building modules, stage 2. MODPOST 1 modules CC [M] /home/user/rk3568/hello_module/hello.mod.o LD [M] /home/user/rk3568/hello_module/hello.ko make[1]: Leaving directory /home/user/rk3568/kernel步骤4检查生成文件ls -l hello.* # 应看到 hello.c, hello.ko, hello.mod.c, hello.mod.o, hello.o file hello.ko # 输出ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), ... modinfo hello.ko # 输出包含 license, author, description, version等字段步骤5拷贝到开发板并加载# 假设开发板IP为192.168.1.100已启用SSH scp hello.ko root192.168.1.100:/lib/modules/$(uname -r)/extra/ ssh root192.168.1.100 # 在开发板上执行 depmod -a insmod /lib/modules/$(uname -r)/extra/hello.ko dmesg | tail -5 # 应看到Hello, RK3568! Module loaded...日志 lsmod | grep hello # 应显示hello模块已加载 rmmod hello dmesg | tail -3 # 应看到Goodbye, RK3568! Module unloaded.日志这个流程看似简单但每一步都可能出错。我统计过某次培训中20名学员的操作记录最常见的失败点是12人因modules_prepare未执行编译时报autoconf.h缺失5人因工具链版本不匹配编译时报unknown type name __int1283人因Makefile中-C和M顺序错误编译时报No rule to make target modules。4.2 模块参数传递与动态配置实战“hello world”的进阶版是支持运行时参数。比如你想让模块加载时指定一个整数count并在日志中打印出来。这需要用到module_param()宏#include linux/init.h #include linux/module.h #include linux/kernel.h static int count 1; // 默认值 static char *name RK3568; // 字符串参数 // 定义参数count为int类型S_IRUGO表示所有用户可读 module_param(count, int, S_IRUGO); MODULE_PARM_DESC(count, Number of times to print hello message); module_param(name, charp, S_IRUGO); MODULE_PARM_DESC(name, Name to display in hello message); static int __init hello_init(void) { int i; for (i 0; i count; i) { printk(KERN_INFO Hello, %s! (%d/%d)\n, name, i1, count); } return 0; } static void __exit hello_exit(void) { printk(KERN_INFO Goodbye, %s! Module unloaded.\n, name); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(RK3568 Developer); MODULE_DESCRIPTION(Hello World with parameters for RK3568); MODULE_VERSION(1.1);编译后加载方式变为insmod hello.ko count3 nameRockchip此时dmesg会输出Hello, Rockchip! (1/3) Hello, Rockchip! (2/3) Hello, Rockchip! (3/3)module_param()的第三个参数是权限掩码常用值有S_IRUSR文件所有者可读S_IWUSR文件所有者可写S_IRGRP同组用户可读S_IRUGO所有用户可读等价于S_IRUSR|S_IRGRP|S_IROTH关键原理module_param()宏会在.modinfo段中生成一个名为parm:的条目内核在insmod时解析该条目将字符串参数转换为目标类型如int、charp并赋值给对应变量。MODULE_PARM_DESC()则生成描述信息可通过modinfo hello.ko查看。4.3 错误处理与日志调试技巧模块开发中90%的问题都源于错误处理不当。一个健壮的模块必须包含以下检查检查1init_module()返回值永远不要忽略return 0。如果初始化失败如申请内存失败、获取设备资源失败必须返回负的错误码static int __init hello_init(void) { int ret; ret some_init_function(); if (ret 0) { printk(KERN_ERR Failed to init: %d\n, ret); return ret; // 返回具体错误码如-ENODEV, -ENOMEM } printk(KERN_INFO Hello, RK3568! Module loaded.\n); return 0; }检查2printk()日志级别选择KERN_ERR用于严重错误模块无法继续运行KERN_WARNING用于潜在问题KERN_INFO用于一般信息KERN_DEBUG用于详细调试。生产环境应避免KERN_DEBUG因为它会淹没关键日志。我习惯在开发阶段用KERN_DEBUG发布前统一改为KERN_INFO。检查3rmmod时的资源清理hello_exit()必须确保所有hello_init()中分配的资源都被释放static struct device *my_dev; static int __init hello_init(void) { my_dev kzalloc(sizeof(*my_dev), GFP_KERNEL); if (!my_dev) return -ENOMEM; // ... 其他初始化 return 0; } static void __exit hello_exit(void) { kfree(my_dev); // 必须释放否则内存泄漏 printk(KERN_INFO Goodbye, RK3568! Module unloaded.\n); }调试技巧当insmod失败时不要只看终端提示。执行dmesg | tail -20 # 查看内核最后20行日志通常包含具体错误原因 cat /proc/kmsg | head -10 # 实时查看内核日志流需root权限一个真实案例某开发者insmod时报Operation not permitteddmesg显示module verification failed: signature and/or required key missing - tainting kernel。原因是开启了模块签名验证CONFIG_MODULE_SIGy但未对模块签名。解决方案是要么关闭签名make menuconfig中取消Enable loadable module support→Module signature verification要么用scripts/sign-file工具签名。5. 常见问题与排查技巧实录5.1 典型问题速查表问题现象可能原因排查命令解决方案insmod: ERROR: could not insert module hello.ko: Invalid module format1. 工具链架构不匹配2. 内核版本与模块不兼容3. 模块未用正确工具链编译file hello.komodinfo hello.ko | grep vermagic确认file输出为ARM aarch64vermagic应与uname -r一致重装正确工具链insmod: ERROR: could not insert module hello.ko: Unknown symbol in module1. 调用了未导出的内核符号2.MODULE_LICENSE未声明为GPLmodinfo hello.kodmesg | tail -5检查modinfo输出的depends字段确保MODULE_LICENSE(GPL)用nm vmlinux | grep symbol_name确认符号是否导出dmesg无任何输出1.printk日志级别低于当前控制台级别2. console参数未正确设置cat /proc/sys/kernel/printkdmesg -n 8将printk控制台级别设为8最高检查bootargs中consolettyS2,115200n8是否匹配实际串口rmmod hello后lsmod仍显示模块1. 模块被其他模块引用2.cleanup_module()中未正确释放资源lsmod | grep hellocat /proc/modules | grep hello检查lsmod输出的引用计数第三列确保hello_exit()中释放所有资源避免引用残留编译时报fatal error: linux/autoconf.h: No such file or directorymodules_prepare未执行ls $KDIR/include/generated/autoconf.h进入内核源码目录执行make ARCHarm64 modules_prepare5.2 我踩过的坑与独家避坑技巧坑1__init和__exit宏的误用我曾在一个SPI驱动中把spi_master_put()放在__exit函数里结果rmmod时系统panic。原因是spi_master_put()会调用kfree()而__exit段的内存已被内核释放。正确做法是__exit函数只做轻量级清理如del_timer_sync()、free_irq()重资源释放应在remove()函数中完成。这个教训让我养成了一个习惯在__init/__exit函数开头加注释明确标注“此处禁止调用可能触发内存分配/释放的函数”。坑2printk()缓冲区溢出在调试一个高频中断驱动时我在中断处理函数中连续调用printk(KERN_DEBUG irq: %d\n, irq)结果系统卡死。原因是printk()是同步操作高频调用会阻塞中断上下文。解决方案是中断上下文中只用pr_debug()需CONFIG_DYNAMIC_DEBUGy或用trace_printk()或把日志暂存到环形缓冲区由workqueue异步输出。坑3模块卸载后内存未释放某次我写了一个字符设备驱动rmmod后/proc/meminfo显示Slab持续增长。用slabtop发现kmalloc-64缓存未释放。追踪发现是cdev_del()后忘了kfree()设备结构体。从此我给自己定下铁律每个kzalloc()必须有对应的kfree()且kfree()必须在rmmod路径的最后执行用grep -r kzalloc\|kmalloc *.c \| wc -l和grep -r kfree *.c \| wc -l确保数量匹配。坑4device tree节点匹配失败当从裸模块升级到platform_driver时probe()函数从未被调用。dmesg显示no driver found for xxx。检查of_match_table发现compatible字符串与dts中i2c0 { compatible rockchip,rk3568-i2c; }不一致。Rockchip的dts中I2C控制器的compatible是rockchip,rk3568-i2c而网上教程常写rockchip,rk3399-i2c。这个细节差异让三个团队花了两天时间排查。我的经验是永远以SDK中arch/arm64/boot/dts/rockchip/*.dtsi文件为准用grep -r compatible.*i2c $KDIR/arch/arm64/boot/dts/rockchip/直接搜索。5.3 性能优化与生产环境适配建议虽然“hello world”模块本身无需优化但它的编译和加载过程已经暴露了生产环境的关键考量编译速度优化RK3568内核编译慢的主因是-O2优化和大量头文件包含。在开发阶段可临时修改Makefile添加KBUILD_EXTRA_SYMBOLS和-frecord-gcc-switches但更有效的是启用ccachesudo apt install ccache export CCACHE_DIR/home/user/.ccache export PATH/usr/lib/ccache:$PATH # 在Makefile中添加CC $(CCACHE) $(CROSS_COMPILE)gcc实测ccache可将重复编译时间缩短70%。模块签名与安全启动产线固件必须启用Secure Boot这意味着所有.ko必须签名。Rockchip SDK提供scripts/sign-file工具./scripts/sign-file sha256 ./certs/signing_key.pem ./certs/signing_key.x509 hello.ko签名后modinfo hello.ko会显示sig_id: PKCS#7。未签名