4、Linux内核裁剪:内核配置、设备树(DTS)编写、根文件系统构建(BusyBox/Yocto)

好,咱们进入第四章。这一章可以说是嵌入式Linux固件开发的「硬核三件套」——内核裁剪、设备树、根文件系统。我见过不少新手,甚至一些老手,在这三个环节上栽跟头。说白了,这三件事决定了你的相机能不能跑起来、跑得稳、跑得快。

4.1 内核配置:别让「全功能」害了你

很多人拿到Linux源码,第一件事就是 make menuconfig,然后一路默认。嗯,这其实是个坑。默认配置里开了太多你用不到的东西——网络协议栈、文件系统驱动、各种外设支持。你想想看,一个智能相机,需要蓝牙键盘驱动吗?不需要。

核心原则:只保留相机硬件必须的模块,其余统统关掉或编译为模块。

我在项目中遇到过一件事:某款相机启动时间长达12秒,查了半天,发现内核里编译了200多个驱动模块,其中一半根本用不上。裁剪之后,启动时间降到了4秒以内。

具体怎么做?我建议你从这几个方向入手:

  • 处理器架构:只保留你用的ARM或RISC-V架构,其他全关
  • 驱动部分:只保留摄像头传感器、I2C、SPI、MIPI CSI、SD卡、USB(如果需要)
  • 文件系统:只保留你最终要用的那个(比如ext4或squashfs)
  • 网络协议:如果相机不需要联网,把整个网络子系统关掉

举个例子,我常用的裁剪命令是这样的:

# 基于厂商默认配置开始
make ARCH=arm xxx_defconfig

# 进入菜单配置
make ARCH=arm menuconfig

# 常用裁剪项
# General setup -> Kernel compression mode -> LZ4 (更快解压)
# Device Drivers -> Multimedia support -> 只保留V4L2和你的传感器驱动
# File systems -> 只保留ext4和squashfs
# Networking support -> 如果不需要,直接关掉

小技巧:make savedefconfig 生成最小化配置文件,比手动维护 .config 要清爽得多。

4.2 设备树(DTS):硬件信息的「户口本」

设备树,说白了就是告诉内核:「我这块板子上有哪些硬件,它们连在哪个引脚上,用什么协议通信。」没有设备树,内核就是个瞎子。

我记得第一次写DTS时,犯了个低级错误——把I2C总线的时钟频率写错了,结果摄像头死活初始化不了。查了两天才发现,原来是DTS里配了个400kHz,但传感器只支持100kHz。

一个典型的相机DTS结构大概长这样:

/dts-v1/;
#include "soc.dtsi"

/ {
    model = "SmartCamera v1.0";
    compatible = "vendor,camera-board";

    // 内存配置
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  // 512MB
    };

    // I2C总线,挂载摄像头传感器
    &i2c0 {
        status = "okay";
        clock-frequency = <100000>;

        camera_sensor: sensor@10 {
            compatible = "vendor,imx335";
            reg = <0x10>;
            clocks = <&clk 24>;
            reset-gpios = <&gpio 5 0>;
            port {
                sensor_out: endpoint {
                    remote-endpoint = <&csi_in>;
                };
            };
        };
    };

    // MIPI CSI接口
    &csi {
        status = "okay";
        port {
            csi_in: endpoint {
                remote-endpoint = <&sensor_out>;
            };
        };
    };
};

写DTS时,有几个坑你一定要避开:

  • GPIO编号别搞错:芯片手册上的GPIO编号和DTS里的偏移量可能不一样,我吃过这个亏
  • 时钟频率要匹配:传感器和CSI接口的时钟必须对齐,否则画面会花屏
  • 电源域要正确:有些传感器需要先上电再复位,顺序错了就初始化失败

我曾经踩过的坑:有一次DTS里忘了配电源管理IC的regulator,结果摄像头在低功耗模式下唤醒后,电压不够,画面出现条纹。加了regulator配置后问题解决。

4.3 根文件系统构建:BusyBox vs Yocto

根文件系统,就是Linux启动后挂载的那个「/」。它里面放着init进程、shell、各种工具库。对于嵌入式设备,我们通常不会用完整的桌面Linux文件系统,太臃肿了。

这里有两个主流方案:

方案 适用场景 大小 构建难度
BusyBox 资源受限、功能简单的相机 2-8MB
Yocto 功能复杂、需要定制化 50-500MB

BusyBox方案,我个人用得最多。它把几百个Linux命令打包成一个二进制文件,通过符号链接来区分不同命令。构建起来很简单:

# 下载BusyBox源码
wget https://busybox.net/downloads/busybox-1.36.0.tar.bz2
tar -xjf busybox-1.36.0.tar.bz2
cd busybox-1.36.0

# 配置(选静态编译,省去动态库依赖)
make menuconfig
# 选中:Settings -> Build static binary (no shared libs)

# 编译
make -j4

# 安装到目标目录
make install CONFIG_PREFIX=../rootfs

然后你手动创建几个关键目录:

mkdir -p rootfs/{dev,proc,sys,etc,lib,usr,var,tmp,mnt}
# 创建设备节点
sudo mknod rootfs/dev/console c 5 1
sudo mknod rootfs/dev/null c 1 3

最后用 genimagemkfs.ext4 打包成镜像文件。

Yocto方案,适合那种需要大量第三方库的场景——比如相机要做AI识别,需要OpenCV、TensorFlow Lite。Yocto的构建系统会自动处理依赖关系,但学习曲线比较陡。

我建议你:

  • 如果相机功能简单(拍照、录像、存储),用BusyBox就够了
  • 如果相机需要跑AI模型、支持网络协议栈、有复杂的UI,上Yocto

我的经验:刚开始做相机固件时,别一上来就搞Yocto。先用BusyBox快速验证硬件能不能跑通,等所有驱动都调好了,再考虑迁移到Yocto做产品化。这样能省下大量调试时间。

4.4 三者的协同:一个完整的启动流程

内核、设备树、根文件系统,这三者不是孤立的。它们协同工作的流程是这样的:

  1. Bootloader(比如U-Boot)加载内核镜像和设备树二进制文件到内存
  2. 内核启动,解析设备树,初始化硬件
  3. 内核挂载根文件系统(从SD卡、Flash或网络)
  4. 执行 /sbin/init,启动用户空间程序

这里有个关键点:设备树必须和内核版本匹配。我遇到过有人把Linux 5.10的设备树用在5.15内核上,结果GPIO控制完全乱套了。

另外,根文件系统的类型也要在内核里配好。如果你用squashfs做只读根文件系统,但内核没开 CONFIG_SQUASHFS,那启动时就会挂载失败,直接kernel panic。

一句话总结:内核裁剪要「狠」,设备树要「准」,根文件系统要「轻」。这三件事做好了,你的相机固件就成功了一半。

下一章,我们会深入摄像头驱动的具体实现——V4L2框架、MIPI CSI协议、ISP图像处理。到时候我会分享一些调试摄像头驱动时的「血泪史」,敬请期待。