# 将驱动模块编译进内核的完整方法
## 目录
– [方法一:外部模块编译](#方法一外部模块编译最灵活)
– [方法二:内置模块编译](#方法二内置模块编译编译进内核镜像)
– [方法三:通过 menuconfig 图形界面](#方法三通过-menuconfig-图形界面配置)
– [方法四:直接修改 .config 文件](#方法四直接修改-config-文件)
– [方法五:通过 defconfig 预置配置](#方法五通过-defconfig-预置配置)
– [方法六:通过 Kbuild 条件编译](#方法六通过-kbuild-条件编译不修改-kconfig)
– [方法七:通过 modprobe 自动加载](#方法七通过-modprobe-自动加载)
– [方法八:通过内核引导参数加载](#方法八通过内核引导参数加载)
– [总结对比](#总结对比)
– [开发阶段推荐流程](#开发阶段推荐流程)
—
## 方法一:外部模块编译(最灵活)
驱动代码放在内核源码**外部**,单独 `make` 编译,生成 `.ko` 文件。
### 目录结构
“`
/home/eh/linux/xuexi1/my_driver/01char_driver/驱动环境测试/
├── moudle.c
└── Makefile
“`
### Makefile
“`makefile
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
obj-m +=moudle.o
KDIR := /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
“`
### 编译和加载
“`bash
make # 编译 .ko
insmod moudle.ko # 加载
rmmod moudle # 卸载
“`
### 适用场景
驱动开发调试阶段,改代码后快速重新编译测试。
—
## 方法二:内置模块编译(编译进内核镜像)
驱动代码放在内核源码**内部**,编译时链接进 `zImage`。
### 步骤 1:创建内核源码中的驱动目录
“`bash
mkdir -p /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/drivers/char/my_driver
“`
### 步骤 2:复制驱动源码
“`bash
cp /home/eh/linux/xuexi1/my_driver/01char_driver/驱动环境测试/moudle.c \
/home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/drivers/char/my_driver/
“`
### 步骤 3:创建 Kconfig
“`bash
vi /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/drivers/char/my_driver/Kconfig
“`
“`kconfig
config MY_HELLO
tristate “Hello driver for IMX6ULL”
default y
help
A simple hello world character device driver.
“`
### 步骤 4:创建 Makefile
“`bash
vi /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/drivers/char/my_driver/Makefile
“`
“`makefile
obj-$(CONFIG_MY_HELLO) += hello.o
“`
### 步骤 5:在父级 Makefile 中引用
“`bash
vi /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/drivers/char/Makefile
“`
在文件末尾添加:
“`makefile
obj-$(CONFIG_MY_HELLO) += my_driver/
“`
### 步骤 6:在父级 Kconfig 中引用
“`bash
vi /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/drivers/char/Kconfig
“`
找到合适的位置添加:
“`kconfig
source “drivers/char/my_driver/Kconfig”
“`
### 步骤 7:配置并编译
“`bash
cd /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
# 方式A:通过 menuconfig
make menuconfig
# 路径:Device Drivers -> Character devices -> Hello driver for IMX6ULL
# 方式B:直接修改 .config
echo “CONFIG_MY_HELLO=y” >> .config
make -j4
“`
### 验证
“`bash
# 编译后,内核镜像中会包含驱动
ls drivers/char/my_driver/*.o
# 内核镜像变大
ls -lh vmlinux
“`
### 适用场景
驱动开发完成,正式集成到内核中。
—
## 方法三:通过 menuconfig 图形界面配置
### 启动 menuconfig
“`bash
cd /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
make menuconfig
“`
### 操作步骤
1. 按 `/` 键,输入 `MY_HELLO`,搜索配置项位置
2. 记住路径:`Location: -> Device Drivers -> Character devices -> My Driver`
3. 用方向键导航到该位置
4. 按 `Y` 键 — 编译进内核(`[*]`)
5. 或按 `M` 键 — 编译为模块(`<M>`)
6. 保存退出:`Esc Esc` → `Yes`
### 验证配置
“`bash
grep “CONFIG_MY_HELLO” .config
# 输出应为:CONFIG_MY_HELLO=y 或 CONFIG_MY_HELLO=m
“`
—
## 方法四:直接修改 .config 文件
### 添加配置项
“`bash
# 编辑 .config
vi /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/.config
“`
在文件末尾添加:
“`
CONFIG_MY_HELLO=y
“`
### 用 sed 命令行修改
“`bash
# 编译进内核 (=y)
sed -i ‘/# CONFIG_MY_HELLO is not set/s/^# //’ .config
sed -i ‘s/CONFIG_MY_HELLO=m/CONFIG_MY_HELLO=y/’ .config
# 编译为模块 (=m)
sed -i ‘s/CONFIG_MY_HELLO=y/CONFIG_MY_HELLO=m/’ .config
# 禁用 (=n)
sed -i ‘/CONFIG_MY_HELLO/d’ .config
“`
### 追加新配置
“`bash
# 如果 .config 中没有 CONFIG_MY_HELLO
echo “CONFIG_MY_HELLO=y” >> .config
# 更新依赖
make oldconfig
“`
—
## 方法五:通过 defconfig 预置配置
### 从现有 defconfig 修改
“`bash
cd /home/eh/linux/xuexi1/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
# 基于 IMX6ULL 默认配置
make imx_v7_defconfig
# 添加自定义驱动配置
echo “CONFIG_MY_HELLO=y” >> .config
# 更新配置(解析依赖)
make oldconfig
# 编译
make -j4
“`
### 导出当前配置为 defconfig
“`bash
# 把当前 .config 导出为 defconfig(精简格式,只保留非默认选项)
make savedefconfig
# 生成的文件
mv defconfig arch/arm/configs/imx_v7_mydefconfig
“`
—
## 方法六:通过 Kbuild 条件编译(不修改 Kconfig)
有时候只需要根据现有配置决定是否编译,不需要新增配置项。
### 方式 A:依赖已有配置项
“`makefile
# 假设 IMX6ULL 已经有 CONFIG_SOC_IMX6ULL
obj-$(CONFIG_SOC_IMX6ULL) += hello.o
“`
### 方式 B:根据架构编译
“`makefile
obj-$(CONFIG_ARCH_MXC) += hello.o
“`
### 方式 C:使用 ifdef 条件编译
“`makefile
ifdef CONFIG_MY_HELLO
obj-y += hello.o
endif
“`
### 方式 D:根据模块参数
“`makefile
obj-$(CONFIG_HELLO_ENABLE) += hello.o
“`
—
## 方法七:通过 modprobe 自动加载
如果驱动是 `.ko` 模块,可以让内核自动加载。
### 在 /etc/modules 中注册
“`bash
# 开发板上执行
echo “moudle” >> /etc/modules
“`
### 创建模块依赖配置
“`bash
# 更新模块依赖
depmod -a
# 查看依赖关系
cat /lib/modules/4.1.15/modules.dep | grep moudle
“`
### systemd 自动加载
“`bash
# 创建 systemd 服务
vi /etc/systemd/system/hello.service
“`
“`ini
[Unit]
Description=Hello Driver
After=basic.target
[Service]
Type=oneshot
ExecStart=/sbin/insmod /path/to/moudle.ko
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
“`
“`bash
systemctl enable hello
systemctl start hello
“`
—
## 方法八:通过内核引导参数加载
在 U-Boot 或内核启动参数中指定模块路径:
“`bash
# U-Boot 中设置
setenv bootargs “root=/dev/nfs nfsroot=… modules=module_name”
“`
或在开发板启动后:
“`bash
# 自动从指定目录加载所有模块
modprobe -a moudle
“`
—
## 总结对比
| 方法 | 配置方式 | 编译位置 | 是否修改内核源码 | 适用场景 |
|——|———|———|—————|———|
| 外部模块 | 外部 Makefile | 外部目录 | 不修改 | 开发调试 |
| 内置模块 | Kconfig + Makefile | 内核源码内 | 修改 | 正式集成 |
| menuconfig | 图形界面 | – | 不修改源码 | 快速配置 |
| 直接改 .config | 文本编辑器 | – | 不修改源码 | 快速测试 |
| defconfig | 预置配置 | – | 不修改源码 | 批量部署 |
| 条件编译 | 已有配置项 | 内核 | 不修改源码 | 复用配置 |
| modprobe 自动 | /etc/modules | 外部目录 | 不修改 | 自动加载 |
—
## 开发阶段推荐流程
“`
开发阶段:外部模块(方法一)
↓ 代码稳定后
集成阶段:内置模块(方法二)+ menuconfig(方法三)
↓ 配置确定后
发布阶段:defconfig(方法五)固化配置
“`
—
## 补充:驱动 Makefile 中的 CONFIG 判断
在 Makefile 中,`CONFIG_XXX` 会被替换为实际的值(y/m/n):
“`makefile
# 基础写法
obj-$(CONFIG_MY_HELLO) += hello.o
# 多条件判断
obj-$(CONFIG_MY_HELLO) += hello.o
obj-$(CONFIG_MY_HELLO_DEBUG) += hello_debug.o
# 子目录递归
obj-$(CONFIG_MY_HELLO) += subdir/
# 子目录的 Makefile
obj-$(CONFIG_MY_HELLO) += hello_core.o
obj-$(CONFIG_MY_HELLO) += hello_io.o
“`
展开说明:
– `CONFIG_MY_HELLO=y` → `obj-y += hello.o` → 编译进内核
– `CONFIG_MY_HELLO=m` → `obj-m += hello.o` → 编译为模块
– `CONFIG_MY_HELLO=n` → 不生成任何编译指令 → 不编译
—
## 补充:驱动代码中的条件编译
“`c
#include <linux/module.h>
#include <linux/kernel.h>
#ifdef CONFIG_MY_HELLO_VERBOSE
#define DPRINTK(fmt, args…) printk(KERN_DEBUG “[HELLO] ” fmt, ##args)
#else
#define DPRINTK(fmt, args…) do {} while(0)
#endif
#ifdef CONFIG_MY_HELLO
static int __init hello_init(void)
{
printk(KERN_INFO “Hello driver initialized\n”);
DPRINTK(“Verbose: module init\n”);
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO “Hello driver removed\n”);
}
module_init(hello_init);
module_exit(hello_exit);
#endif
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“ErHu”);
MODULE_DESCRIPTION(“Hello driver for IMX6ULL”);
“`