4. Linux内核裁剪与设备树定制

工业网关与通用开发板最大的区别在于:它长期运行在无人值守的现场,硬件接口固定、功能边界清晰,却对启动时间、内存占用和稳定性有更苛刻的要求。i.MX 系列 SoC 的 BSP 默认配置往往把 HDMI、摄像头、音频、蓝牙、Wi-Fi 等消费级外设全部编入内核,这些模块在工业网关里既用不到,又会占用内存、延长启动时间、增加攻击面。因此,内核裁剪与设备树定制不是"可选的优化",而是产品化过程中的必要环节。

内核配置:从 defconfig 到最小可用集

i.MX 的 Linux BSP 通常提供 imx_v7_defconfig(Cortex-A7/A9)或 imx_defconfig(ARM64)作为起点。裁剪的第一步不是直接 make menuconfig 逐项关闭,而是先明确网关需要保留的功能清单:网络(双以太网、可选 4G/5G)、串口(RS232/RS485)、CAN、GPIO、看门狗、RTC、SPI/I2C 外设、文件系统(ext4、squashfs、UBIFS)、以及必要的网络协议栈(Netfilter、VLAN、桥接)。

实际操作中,推荐用 make savedefconfig 生成精简后的 defconfig 并纳入版本管理,而不是在 .config 上反复手工修改。裁剪时重点关注几类配置:

  • CONFIG_DRM_*、CONFIG_SND_*、CONFIG_VIDEO_*:无显示与音频需求的网关可整体关闭,能显著减小内核体积。
  • CONFIG_MODULES:若采用全静态编译,可关闭模块支持,简化根文件系统;若需现场加载驱动,则保留并配合 modules_install。
  • CONFIG_DEBUG_*、CONFIG_FTRACE、CONFIG_KGDB:量产固件应关闭,调试固件可单独保留一份配置。
  • CONFIG_PREEMPT 与 CONFIG_HZ:工业控制对实时性有要求时,可评估 PREEMPT_RT 补丁或至少使用 CONFIG_PREEMPT,HZ 取 250 或 1000 需结合 CPU 负载权衡。

裁剪后应记录内核镜像大小与启动耗时作为基线。例如某 i.MX6UL 网关在裁剪前 zImage 约 6.8 MB、启动到用户态约 9 s;关闭 DRM、音频、摄像头并精简网络选项后,镜像可降至约 3.5 MB、启动时间缩短到 5 s 左右(具体数值随配置与硬件而异,此处为示例量级)。

设备树语法:节点、属性与引用

设备树(Device Tree)用文本描述硬件,编译为 .dtb 后由 bootloader 传给内核。其基本结构是树状节点,每个节点由 node-name@unit-address 标识,节点内包含属性(property)和子节点。属性值可以是字符串、32 位整数数组、字节序列或 phandle 引用。

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

/ {
    model = "Industrial Gateway G1";
    compatible = "fsl,imx6ul-gw", "fsl,imx6ul";

    chosen {
        stdout-path = &uart1;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>; /* 256MB */
    };
};

&uart1 {
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_uart1;
    status = "okay";
};

这里 &uart1 是对 imx6ul.dtsi 中已定义节点的引用,通过 status = "okay" 使能。这种"引用 + 覆盖"的写法是设备树定制的核心:SoC 级 .dtsi 描述片上资源,板级 .dts 只描述本板实际使用与差异部分。

引脚复用:pinctrl 的配置逻辑

i.MX 的每个引脚可复用为多种功能(UART、I2C、GPIO、CAN 等),由 IOMUXC 控制器管理。设备树中通过 pinctrl 子节点描述引脚组,再由外设节点引用。以 UART1 为例:

&iomuxc {
    pinctrl_uart1: uart1grp {
        fsl,pins = <
            MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
            MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
        >;
    };
};

宏 MX6UL_PAD_...__UART1_DCE_TX 由 imx6ul-pinfunc.h 提供,编码了引脚寄存器偏移、复用模式(MUX_MODE)和输入选择等;后面的 0x1b0b1 是电气属性配置字,包含上下拉、驱动强度、转换速率和开漏等位域。工业现场中 RS485 常需配置为开漏或特定驱动强度,这些都要在此处调整,而不是在驱动代码里硬编码。

外设节点添加:以 RS485 与 CAN 为例

添加一个外设节点通常包含三步:使能控制器、配置引脚、补充板级属性。以 CAN1 为例:

&flexcan1 {
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_flexcan1;
    xceiver-supply = ®_can;
    status = "okay";
};

&iomuxc {
    pinctrl_flexcan1: flexcan1grp {
        fsl,pins = <
            MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b0b0
            MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b0b0
        >;
    };
};

对于 RS485,除了 UART 节点本身,还需描述收发方向控制引脚(DE/RE)。常见做法是在 UART 节点下增加 rs485 子节点或使用 linux,rs485-enabled-at-boot-time 等属性,并配合 GPIO 控制方向。若使用带自动方向控制的收发器,则只需正确配置引脚复用与电气属性。

编译与更新:从源码到板端生效

内核与设备树的编译通常由 BSP 的构建脚本驱动,也可手工执行:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs -j8
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j8

生成的 arch/arm/boot/zImage 与 arch/arm/boot/dts/imx6ul-gw.dtb 需放入 boot 分区。更新方式取决于 bootloader:U-Boot 下可通过 TFTP、USB Mass Storage 或 fastboot 写入;量产设备更常用 A/B 双分区或 recovery 机制,避免升级失败导致设备变砖。更新后应检查 /proc/device-tree/ 下节点是否存在、dmesg 中驱动是否成功 probe、以及对应外设(如 can0、ttymxc0)是否出现在 /dev 或 ip link 中。

裁剪与定制应保留一份"全功能调试配置"和一份"量产最小配置",两者共用同一套设备树源文件,仅通过不同的 defconfig 和 .dts 覆盖层区分,便于问题定位与版本追溯。
i.MX 内核裁剪与设备树定制流程 内核配置裁剪 imx_v7_defconfig 基线 关闭 DRM / SND / VIDEO 保留 NET / CAN / UART make savedefconfig 设备树定制 板级 .dts 引用 SoC .dtsi pinctrl 引脚复用配置 外设节点 status = "okay" RS485 / CAN 节点添加 编译与更新 make zImage dtbs modules 写入 boot 分区 A/B 分区或 recovery dmesg / /proc/device-tree 板端验证与结果判读 dmesg 驱动 probe /dev 节点出现 ip link / can0 状态 验证不通过则回退修改 裁剪目标:减小镜像体积、缩短启动时间、降低攻击面,同时保证工业接口完整可用 内核配置 设备树 编译更新 验证反馈

整个流程的关键在于"配置—设备树—验证"三者形成闭环:内核配置决定哪些驱动被编入,设备树决定这些驱动能否在具体硬件上 probe 成功,而板端日志与设备节点则是判断裁剪是否正确的最终依据。工业网关的裁剪策略应始终围绕"接口完整、资源精简、升级可靠"三个目标展开,任何关闭的选项都要有明确的理由,任何新增的节点都要有对应的验证手段。