Linux 内核移植与裁剪
一、移植流程
板级资料
├── 原理图
├── Reference Manual
└── Boot 模式说明
↓
选择 SoC 系列(arm/mach-imx 或 arch/arm64/boot/dts/...)
↓
复制参考板级 .dts → 改名为 myboard.dts
├── 修改 memory reg
├── 改 chosen 节点(console、bootargs)
├── 调整 pinctrl-0
├── 改 /soc/<ip> 的 reg / interrupt / status
└── 加自创设备节点
↓
复制参考 board/ 目录(如 board/freescale/mx6ull_myboard/)
├── Kconfig
├── MAINTAINERS
├── Makefile
└── board.c(init_early、init_machine)
↓
复制参考 defconfig → myboard_defconfig
└── 配置 CONFIG_* 选项
↓
make myboard_defconfig
make menuconfig
make -j$(nproc) LOADADDR=0x80008000 uImage dtbs modules
二、设备树定制(重点)
1. 参考板设备树
ls arch/arm/boot/dts/
imx6ull.dtsi # SoC 通用
imx6ull-14x14-evk.dts # 评估板
2. 自定义设备树
/dts-v1/;
#include "imx6ull.dtsi"
/ {
model = "MY Board";
compatible = "myvendor,myboard", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB
};
leds {
compatible = "gpio-leds";
led-status {
label = "status";
gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
default-state = "off";
linux,default-trigger = "heartbeat";
};
};
};
&uart1 {
pinctrl-0 = <&pinctrl_uart1>;
pinctrl-names = "default";
status = "okay";
};
&i2c1 {
clock-frequency = <100000>;
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
&usdhc1 {
pinctrl-0 = <&pinctrl_usdhc1>;
cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
status = "okay";
};
三、裁剪配置
1. 减少内核大小
# 配置
CONFIG_LOCALVERSION="-myboard"
CONFIG_LOG_BUF_SHIFT=14
CONFIG_PRINTK=n # 减内核体积(保留 printk 函数)
# 关闭不需要的功能
CONFIG_INPUT_EVDEV=n # 不用输入设备
CONFIG_SOUND=n # 不用音频
CONFIG_WLAN=n # 不用 WiFi
CONFIG_USB=n # 不用 USB(需要时再开)
CONFIG_RTC=n # 不用 RTC 驱动
# 用动态加载
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
2. 内核裁剪建议
| 选项 | 大小 | 推荐 |
|---|---|---|
| PREEMPT_NONE | 小 | 不需要实时 |
| PREEMPT_VOLUNTARY | 中 | 一般应用 |
| PREEMPT | 大 | 桌面级 |
| PREEMPT_RT | 大 | 实时 |
3. 用 localmodconfig(自动裁剪)
# 根据当前使用的模块生成最小 config
make localmodconfig
四、添加板级支持(旧方式,mach-xxx)
现代内核(5.x+)已逐步废弃,但 i.MX 等还在用
// arch/arm/mach-imx/mach-myboard.c
static void __init myboard_init(void)
{
// 板级 init
}
static const struct machine_desc myboard_desc __initconst = {
.name = "MY Board",
.atag_offset = 0x100,
.init_machine = myboard_init,
.dt_compat = myboard_dt_compat,
};
DT_MACHINE_START(MYBOARD, "MY Board")
.init_machine = myboard_init,
.dt_compat = myboard_dt_compat,
MACHINE_END
推荐新代码直接用设备树驱动模型,不再写 machine_desc。
五、补丁管理
# 在内核源码中打补丁
git am /path/to/patches/*.patch
# 或手动
patch -p1 < xxx.patch
# 生成补丁
git format-patch -1 HEAD
补丁目录结构:
patches/
├── 0001-myserial-add-driver.patch
├── 0002-mygpio-fix-interrupt-handling.patch
└── series
六、驱动集成
1. 静态编入内核
# drivers/<subsystem>/Kconfig
config MY_DRIVER
tristate "My Driver"
depends on SPI_MASTER
help
Enable this to support My Device.
# drivers/<subsystem>/Makefile
obj-$(CONFIG_MY_DRIVER) += my_driver.o
2. 单独编译为模块
# drivers/<subsystem>/Makefile
obj-m += my_driver.o
七、内核与驱动协调
- 内核 API 经常变(3.x → 6.x)
- 关注
LWN.net、kernelnewbies.org - 使用
git log看 API 演化
八、Buildroot 集成(推荐)
把内核源码放在 board/myvendor/myboard/:
board/myvendor/myboard/
├── kernel-patches/
├── linux.config # defconfig 覆盖
├── readme.txt
└── rootfs_overlay/ # 自定义 rootfs
Buildroot 重新生成内核非常方便。
九、常见问题
| 现象 | 解决 |
|---|---|
| 启动卡在 "Starting kernel" | bootargs / 串口配置错 |
| 内核 panic(VFS) | rootfs 不对、root= 错 |
| No filesystem | 文件系统没编进内核 |
| 时钟不对 | device tree 时钟没配 |