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