纸翼 · 加载中
2205 words
11 minutes
嵌入式linux驱动(编译模块和内核)

一、Linux驱动模块基础(Hello World示例)#

1. 驱动模块代码框架#

以“Hello World”驱动为例,核心代码结构如下:

#include <linux/init.h> // 初始化/退出函数声明
#include <linux/module.h> // 模块相关宏(module_init/module_exit等)
// 模块初始化函数(insmod时调用)
static int hello_init(void) {
printk("hello world \n"); // 内核打印(非用户空间printf)
return 0; // 成功返回0,失败返回非0
}
// 模块退出函数(rmmod时调用)
static void hello_exit(void) {
printk("byb byb \n");
}
// 注册初始化/退出函数
module_init(hello_init);
module_exit(hello_exit);
// 模块许可证(必须为GPL,否则insmod会报错)
MODULE_LICENSE("GPL");

2. 编译驱动模块(Makefile)#

驱动模块需通过Makefile编译,典型Makefile如下:

obj-m += helloworld.o # 生成helloworld.ko模块(m表示模块,-m编译为模块)
KDIR:=/home/topeet/topeet/imx6ull/linux-tmx-rel_imx_4.1.15_2.0.0_ga # 内核源码路径
PWD:=$(shell pwd) # 当前目录(驱动源码所在路径)
all:
make -C $(KDIR) M=$(PWD) modules # 调用内核make,编译模块

编译依赖前提

  • 内核源码需先编译通过(确保.config、头文件等准备就绪);
  • 编译驱动的内核源码,必须与开发板运行的内核镜像一致(否则insmod会因版本不匹配失败);
  • Ubuntu环境需支持arm交叉编译(若开发板是ARM架构,需安装arm-linux-gnueabihf-*工具链)。

3、驱动加载与卸载#

编译生成.ko后,通过以下命令操作:

  • 加载:insmod helloworld.ko(执行hello_init);
  • 查看:lsmod(列出已加载模块);
  • 卸载:rmmod helloworld(执行hello_exit);
  • 内核打印查看:dmesg(查看printk输出)。

二、内核配置:make menuconfig#

1. 进入menuconfig图形界面#

进入内核源码根目录,执行命令:

Terminal window
make menuconfig

2. menuconfig操作技巧#

  • 搜索功能:按 / 键,输入关键词(如“LED”),快速定位配置项;
  • 配置驱动状态:按 空格 切换三种状态:
    • <*>:编译进内核(开机自动加载);
    • <M>:编译为模块(需insmod手动加载);
    • < >:不编译;
  • 退出方式
    • 保存退出:选 <Yes>(会更新.config);
    • 不保存退出:选 <No>.config不变)。

3. 与menuconfig相关的文件#

文件作用类比
Makefile编译规则,定义“如何编译”(如obj-m += xxx.o菜谱
Kconfig内核配置选项(菜单结构、依赖关系)饭店菜单
.config编译后生成的配置(记录“选了哪些菜”)已点的菜

4. menuconfig读取的Kconfig路径#

默认从 Arch/$ARCH/Kconfig 开始($ARCH为架构,如arm),再递归包含子目录的Kconfig

  • 例:ARM架构下,会从 arch/arm/Kconfig 启动,包含drivers/fs/等子目录的Kconfig
  • 自定义驱动的Kconfig可放在驱动目录(如drivers/redled/Kconfig),通过source "drivers/redled/Kconfig"被主Kconfig包含。

5. Kconfig语法(以LED驱动为例)#

source "drivers/redled/Kconfig" # 包含子目录的Kconfig
config LED__4412 # 配置项名称(最终宏为CONFIG_LED__4412)
tristate "Led Support for GPIO Led" # 三态(*、M、空)
depends on LEDS_CLASS # 依赖:只有选了LEDS_CLASS,才能选LED__4412
help # 帮助信息
This option enable support for led

关键语法说明:#

  • tristate:支持<*>(内核)、<M>(模块)、< >(不编译);
  • bool:仅支持<*>(内核)、< >(不编译);
  • depends on正向依赖(选A的前提是选B);
  • select反向依赖(选A时,自动选B);
  • help:配置项的说明文字(编译后可在include/generated/autoconf.h中看到宏定义)。

6. 配置的“落地”:autoconf.h#

make menuconfig保存退出后,内核会生成 include/generated/autoconf.h,将配置项以宏定义形式记录(如#define CONFIG_LED__4412_M 1)。
驱动代码中可通过 #ifdef CONFIG_XXX 判断是否编译某段代码。

三、Linux内核添加新驱动全流程#

目标:添加 drivers/hello/ 驱动,并将最终配置固化到编译脚本中。

第一步:创建驱动源文件#

动作:编写具体的驱动逻辑。

drivers/hello/hello.c
#include <linux/module.h>
#include <linux/init.h>
static int __init hello_init(void)
{
printk(KERN_INFO "hello driver init\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "hello driver exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

第二步:编写子目录 Makefile#

动作:告诉编译系统如何编译这个文件。 关键点:使用 $(CONFIG_HELLO) 进行条件编译。

# File: drivers/hello/Makefile
# 如果 .config 中 CONFIG_HELLO=y 或 =m,则编译 hello.o
obj-$(CONFIG_HELLO) += hello.o

第三步:编写子目录 Kconfig#

动作:告诉编译系统有哪些配置选项(供 menuconfig 显示)。

# File: drivers/hello/Kconfig
config HELLO
tristate "Hello Driver support" # tristate 表示支持 Y / M / N
help
This is a basic hello world driver for testing.

第四步:修改上级 Makefile#

动作:告诉编译系统去哪里找子目录的代码。 关键点:使用 obj-y(无条件进入目录),具体编不编由子目录 Makefile 决定。

# File: drivers/Makefile (在末尾添加)
# 强制进入 hello 目录
obj-y += hello/

第五步:修改上级 Kconfig#

动作:将子菜单挂载到内核配置树中,使其能在 menuconfig 中被看到。

# File: drivers/Kconfig (在末尾添加)
source "drivers/hello/Kconfig"

第六步:生成基础配置(打地基)#

动作:加载板级默认配置,确保内核能跑起来。 原因imx_v7_defconfig 包含了 SoC、串口、内存等必需配置,比从零开始 menuconfig 高效且不易出错。

Terminal window
# 在内核根目录执行
make imx_v7_defconfig

第七步:开启新驱动(装修)#

动作:在基础配置上,勾选刚才添加的 HELLO 驱动。

Terminal window
make menuconfig

操作路径

Device Drivers --->
[*] Hello Driver support (选择 M 编译成模块,或 * 编译进内核)

保存退出,此时当前目录生成了包含 CONFIG_HELLO=m.config 文件。


第八步:导出最终配置#

动作:备份经过验证的 .config,作为后续编译的蓝本。

Terminal window
cp .config config_hello_ok

第九步:编写/修改内核编译脚本#

动作:将新配置固化到自动化编译流程中。 关键点:必须先 cp 替换配置,再执行 make oldconfig 同步依赖关系。

build_kernel.sh
#!/bin/bash
KERNEL_DIR="/home/user/linux-imx"
CONFIG_FILE="config_hello_ok"
# 1. 将新配置放入内核源码目录
cp ${CONFIG_FILE} ${KERNEL_DIR}/.config
# 2. 同步依赖(非常重要!处理新增的 Kconfig 选项)
make -C ${KERNEL_DIR} ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- oldconfig
# 3. 开始编译
make -C ${KERNEL_DIR} ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4 modules
# 4. 验证结果
ls ${KERNEL_DIR}/drivers/hello/hello.ko

四、扩展:驱动编译的“坑”与解决思路#

  1. 内核版本不匹配

    • 现象:insmod时报version mismatch
    • 解决:确保编译驱动的内核源码,与开发板运行的zImage/uImage同一套(内核版本、配置完全一致)。
  2. 交叉编译环境

    • 若开发板是ARM(如IMX6ULL),Ubuntu需安装arm-linux-gnueabihf-工具链,并在Makefile中指定CROSS_COMPILE=arm-linux-gnueabihf-
    • 例:make CROSS_COMPILE=arm-linux-gnueabihf- -C $(KDIR) M=$(PWD) modules
  3. 直接修改.config的风险

    • 图中提到“想去掉LED相关,直接改.config可以吗?可以,但不推荐”;
    • 原因:.config依赖Kconfig的联动逻辑,直接修改可能破坏依赖关系;
    • 推荐:通过make menuconfig交互式修改。

五、总结流程#

  1. 准备内核源码(编译通过,与开发板内核一致);
  2. 编写驱动代码(如helloworld.c);
  3. 编写Makefile(指定内核源码路径、编译模块);
  4. (可选)通过make menuconfig配置内核(决定驱动是否编译/如何编译);
  5. 编译驱动:make(生成.ko);
  6. 加载/测试:insmod/rmmod + dmesg验证。

六、在正点原子RK3588上的实践#

  • 为什么 SDK 和教程不一样

教程是直接操作内核源码树的通用 Linux 流程。你的 RK3588 SDK 在它之上加了一层 Rockchip 构建封装,通过 ./build.sh kernel 统一管理。

但两者底层完全打通——内核源码的 Kconfig、Makefile、.config 机制是一模一样的。区别仅在于配置文件的存放位置和流程

对比项通用教程你的 RK3588 SDK
编译命令make ARCH=arm ..../build.sh kernel
基础配置xxx_defconfigrockchip_linux_defconfig
覆盖配置手动 cp .configconfig fragmentrk3588_linux.config
启用新驱动手动 cp .config 备份追加到 fragment 文件 中固化

从你之前编译的输出可以看到:

Using .config as base
Merging ./arch/arm64/configs/rk3588_linux.config

SDK 的流程是:rockchip_linux_defconfig(基础)+ rk3588_linux.config(增量覆盖)→ 合并出最终 .config


在你的 SDK 中添加 hello 驱动的完整流程#

第1~5步:创建驱动文件(和教程完全一样)#

这些步骤直接在内核源码树 kernel/ 目录下操作,不涉及 SDK 构建系统:

Terminal window
# 1. 创建驱动源码
mkdir -p kernel/drivers/hello
# 写入 kernel/drivers/hello/hello.c 和 Makefile
# 2. 写入 kernel/drivers/hello/Kconfig
# 3. 修改 kernel/drivers/Makefile:末尾加一行
echo 'obj-y += hello/' >> kernel/drivers/Makefile
# 4. 修改 kernel/drivers/Kconfig:末尾加一行
echo 'source "drivers/hello/Kconfig"' >> kernel/drivers/Kconfig

第6~8步:配置和固化(和你 SDK 的做法不同!)#

这是最关键的区别。你不需要手动 cp .config,而是用 fragment 机制:

Terminal window
# 步骤A:生成基础配置
cd kernel/
make ARCH=arm64 rockchip_linux_defconfig
# 步骤B:合并现有 fragment
scripts/kconfig/merge_config.sh .config arch/arm64/configs/rk3588_linux.config
# 步骤C:用 menuconfig 开启 HELLO 驱动
make ARCH=arm64 menuconfig
# 路径:Device Drivers → Hello Driver support → 选 M 或 *
# 步骤D:将新增的 HELLO 配置导出到 fragment 文件
# 只提取 .config 中比 defconfig 多出来的 HELLO 相关项
grep "CONFIG_HELLO" .config >> arch/arm64/configs/rk3588_linux.config

第9步:编译#

之后每次 ./build.sh kernel 都会自动 merge fragment,包含 CONFIG_HELLO

Terminal window
cd /home/huangzy/RK3588
./build.sh kernel

总结:你 SDK 的核心口诀#

源码修改(Kconfig、Makefile、.c)→ 在 kernel/ 下操作,和通用教程一样
配置固化 → 追加到 kernel/arch/arm64/configs/rk3588_linux.config,而不是备份 .config
编译 → 回到 SDK 根目录,./build.sh kernel

嵌入式linux驱动(编译模块和内核)
https://blog.huangzy.xyz/posts/嵌入式linux驱动编译模块和内核/
Author
纸翼
Published at
2026-07-08
License
CC BY-NC-SA 4.0

Some information may be outdated