1. 这不是教科书,是我在RK3568产线踩出来的驱动开发路径
“Linux设备驱动开发:从内核模块到设备树、I2C/CAN的系统路径”——这个标题听起来像一本厚得能砸核桃的技术手册封面,但实际在工厂产线、车载终端、工业网关这些真实场景里,它是一条用烧坏三块开发板、重刷七次固件、改烂二十版设备树文件才趟出来的实操路径。我干这行十年,前五年写驱动像写散文,靠printk打点+反复insmod/rmmod硬扛;后五年才真正明白:驱动不是孤立的代码,而是内核、硬件、Bootloader、用户空间四层之间精密咬合的齿轮。你写的模块再漂亮,设备树里少配一个compatible字符串,或者pinctrl-0漏掉一个上拉配置,整块板子就卡在kernel panic: unable to mount root fs——连串口都吐不出有效日志。
标题里五个关键词,每个都对应一个现实战场:内核模块是你的第一把刀,但刀刃朝哪砍、砍多深,全由设备树定调;而I2C/CAN这类总线驱动,根本不是写个probe()函数就完事,它背后牵着时序精度、中断嵌套、DMA缓冲区对齐、甚至PCB走线长度这些硬件级变量。最近帮一家做智能电表的企业调RK3568上的CAN FD通信,发现他们用的can-utils工具发帧失败,查了三天才发现问题不在驱动,而在设备树里clock-frequency设成了24MHz,而实际晶振是25MHz——时钟偏差0.04%,却让CAN控制器无法完成位定时同步。这种细节,任何PDF文档都不会标红加粗告诉你。
所以这篇不是讲“怎么写hello world模块”,而是还原一条从芯片手册第17页的寄存器定义开始,到最终cat /sys/class/can/can0/device/name能稳定输出设备名的完整链路。你会看到:如何用dtc -I dts -O dtb反编译设备树确认引脚复用是否冲突;为什么i2c-tools里的i2cdetect扫不到设备,大概率是i2c-gpio驱动没加载或scl/sda上拉电阻虚焊;怎样在rk3568-evb.dts里给SSD1306 OLED屏配disp节点时,避开瑞芯微SDK里那个坑人的rockchip,display-timing参数陷阱。所有内容,都来自我笔记本里贴着胶布的调试记录本——那上面还留着2023年11月在东莞某工厂凌晨三点用示波器抓I2C波形时画的时序草图。
2. 整体设计逻辑:为什么必须按“模块→设备树→总线”顺序推进
2.1 驱动开发的本质是“分层解耦”,不是堆砌代码
很多人一上来就猛敲module_init(),结果编译通过、加载成功,但/dev下死活不出现设备节点。根源在于没理解Linux驱动模型的分层契约:内核模块只负责“怎么做”,设备树负责“在哪里做”,总线驱动负责“和谁一起做”。这三者缺一不可,且存在严格的依赖时序。
以I2C设备为例,典型错误流程是:先写好ssd1306_probe()函数,直接insmod ssd1306.ko,然后发现dmesg里只有ssd1306: loading out-of-tree module taints kernel,再无后续。此时你该做的不是改probe函数,而是立刻检查:
ls /sys/bus/i2c/devices/是否有i2c-0目录(证明I2C总线驱动已加载)cat /sys/bus/i2c/devices/i2c-0/name输出是否为rk3568-i2c(确认总线控制器识别正确)ls /sys/firmware/devicetree/base/i2c@fdd90000/下是否有ssd1306@3c节点(设备树是否挂载到该总线)
这三个检查项,分别对应总线层、控制器层、设备层。如果第一项失败,说明CONFIG_I2C_ROCKCHIP=y没在内核配置里打开,或者rockchip-i2c.ko没加载;第二项失败,可能是设备树里i2c@fdd90000节点的status = "okay"写成了"ok"(内核只认okay);第三项失败,则是ssd1306@3c节点没放在正确的I2C总线下,或者compatible = "solomon,ssd1306"拼写错误(注意逗号是英文半角,且solomon必须小写)。
提示:
/sys/firmware/devicetree/base/是设备树在内存中的二进制镜像映射,所有节点路径都以此为根。用find /sys/firmware/devicetree/base -name "ssd1306"能快速定位设备树节点是否生效,比翻dts源文件快十倍。
2.2 设备树不是配置文件,是硬件描述的“宪法”
新手常把设备树当.ini文件用,以为改几个参数就能让设备工作。实际上,设备树是内核启动时解析的硬件拓扑描述语言,它定义了内存映射、中断号、时钟源、电源域等底层资源分配。一个错误的reg属性,可能让驱动读取到错误的寄存器地址;一个缺失的interrupts属性,会导致中断永远无法触发。
以RK3568的CAN控制器为例,其设备树节点必须包含:
&can0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&can0_tx &can0_rx>; clocks = <&cru SCLK_CAN0>, <&cru ACLK_CAN0>; clock-names = "can", "apb_pclk"; #address-cells = <1>; #size-cells = <0>; can@ff3e0000 { compatible = "rockchip,rk3568-can"; reg = <0xff3e0000 0x1000>; // 必须与芯片手册完全一致 interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>; // 中断号必须查RK3568 TRM第5章 clocks = <&cru SCLK_CAN0>; clock-names = "can"; phy-mode = "can"; rockchip,phy-type = <0>; // 0=internal, 1=external }; };这里reg = <0xff3e0000 0x1000>中的0xff3e0000,必须严格对照《RK3568 Technical Reference Manual》第12章“Peripheral Memory Map”中CAN0控制器的基地址。若错写成0xff3d0000,驱动初始化时ioremap()会映射到错误内存区域,后续所有寄存器操作都是空中楼阁。而interrupts = <GIC_SPI 123 ...>中的123,必须查TRM第5章“Interrupt Controller”表格,确认CAN0_RX对应的SPI编号——这个编号在不同SoC上完全不同,绝不能凭经验猜测。
2.3 I2C/CAN驱动开发的核心矛盾:协议栈抽象与硬件特性的撕扯
I2C和CAN看似都是标准协议,但Linux内核提供的i2c-core和can-dev框架,本质是在软件抽象层强行统一硬件差异。这就导致开发者必须在“遵循框架规范”和“适配硬件特性”之间走钢丝。
I2C的典型撕扯点在于时序控制:
- 内核
i2c-rockchip驱动默认使用i2c-rk3399兼容模式,但RK3568的I2C控制器时钟分频算法与RK3399不同。若直接沿用旧驱动,i2c-gpio模拟I2C时clock-frequency设为100kHz,实际波形可能只有70kHz,导致某些传感器(如BME280)拒绝响应。 - 解决方案不是改驱动源码,而是在设备树里显式指定
#clock-cells = <0>并添加clock-frequency = <100000>,强制驱动使用精确时钟配置。
CAN的撕扯更隐蔽:CAN FD(Flexible Data-rate)需要同时配置经典CAN和FD模式的位定时参数。内核can-dev框架要求驱动实现struct can_bittiming_const,但RK3568的CAN控制器硬件只支持一套寄存器配置FD位定时。这意味着驱动必须在set_bittiming()回调里,根据priv->can.ctrlmode & CAN_CTRLMODE_FD标志,动态切换寄存器写入逻辑——这部分代码在官方SDK里被注释掉了,需手动解封。
注意:
can-utils工具集(如candump)依赖AF_CAN协议族,若内核未启用CONFIG_CAN_RAW=y和CONFIG_CAN_BCM=y,即使驱动加载成功,用户空间也无法创建CAN socket。这是90%新手卡住的第一道墙。
3. 核心细节拆解:从模块编译到设备树落地的实操要点
3.1 内核模块开发:绕过“Hello World”的真实起点
写一个能打印"Hello, world!"的模块毫无意义。真实驱动开发始于寄存器映射与中断注册。以RK3568 GPIO按键驱动为例,关键步骤如下:
第一步:获取设备资源
static int button_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct resource *res; int irq; // 从设备树获取寄存器地址 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "no memory resource\n"); return -ENODEV; } priv->base = devm_ioremap_resource(&pdev->dev, res); // 自动释放内存 if (IS_ERR(priv->base)) return PTR_ERR(priv->base); // 获取中断号(设备树里interrupts属性) irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "no irq resource\n"); return irq; } // 注册中断处理函数 ret = devm_request_irq(&pdev->dev, irq, button_irq_handler, IRQF_TRIGGER_FALLING | IRQF_SHARED, "button", priv); if (ret) { dev_err(&pdev->dev, "failed to request irq %d\n", irq); return ret; } }这里platform_get_resource()和platform_get_irq()是核心,它们依赖设备树中reg和interrupts属性的正确性。若设备树里reg写错,ioremap_resource()返回NULL,后续所有操作崩溃;若interrupts缺失,platform_get_irq()返回负值,驱动直接退出。
第二步:设备树节点编写
&gpio0 { button@0 { compatible = "mycompany,gpio-button"; reg = <0x0 0xfdc00000 0x0 0x1000>; // GPIO0控制器基地址 interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>; // GPIO0_25对应SPI 25 gpio-key,gpio = <&gpio0 25 GPIO_ACTIVE_LOW>; // 使用GPIO0_25 linux,code = <KEY_ENTER>; // 按键映射为回车键 debounce-interval = <20>; // 消抖20ms status = "okay"; }; };注意reg属性必须与RK3568 TRM中GPIO0控制器地址0xfdc00000完全一致;interrupts中的25必须查TRM确认GPIO0_25的SPI编号;gpio-key,gpio里的25是GPIO编号,而非引脚号(引脚号需查《RK3568 Pinmux.xlsx》中GPIO0_25对应的物理引脚)。
3.2 设备树配置:那些文档里不会写的致命细节
设备树编译和加载是驱动开发中最易出错的环节。以下是我整理的RK3568平台高频陷阱:
陷阱1:pinctrl节点引用错误
&i2c0 { pinctrl-names = "default"; pinctrl-0 = <&i2c0_xfer>; // 错误!应为<&i2c0_xfer &i2c0_clk> status = "okay"; ssd1306@3c { compatible = "solomon,ssd1306"; reg = <0x3c>; ... }; };RK3568的I2C0需要SCL和SDA两组引脚,i2c0_xfer只定义了SDA,缺少SCL引脚组。正确写法是pinctrl-0 = <&i2c0_xfer &i2c0_clk>,其中i2c0_clk在rk3568-pinctrl.dtsi中定义。若遗漏,i2cdetect -y 0会显示Error: Could not open file/dev/i2c-0or/dev/i2c/0: No such file or directory,因为引脚复用失败导致I2C控制器无法初始化。
陷阱2:clock-frequency单位混淆
&i2c1 { clock-frequency = <400000>; // 单位是Hz,不是kHz! status = "okay"; };很多文档写成<400>,这是错误的。clock-frequency属性值单位为赫兹(Hz),400kHz必须写<400000>。若写错,i2c-rockchip驱动计算分频系数时会溢出,导致I2C时钟远超规格,传感器直接锁死。
陷阱3:disp设备树节点的时序陷阱为SSD1306配置disp节点时,常见错误是直接复制LCD节点:
&disp { status = "okay"; oled@0 { compatible = "solomon,ssd1306"; reg = <0>; rockchip,screen-width = <128>; rockchip,screen-height = <64>; rockchip,display-timing = <&timing0>; // 错误!OLED不需要display-timing ... }; };rockchip,display-timing是LCD专用参数,OLED无需像素时序。若强行添加,内核解析时会因timing0节点不存在而报错,整个disp子系统初始化失败。正确做法是删除该属性,改用rockchip,panel-width和rockchip,panel-height。
3.3 I2C驱动实操:从i2cdetect失败到i2cget成功的全流程
以调试SSD1306 OLED屏为例,完整排错路径如下:
Step 1:确认I2C总线存在
# 查看I2C总线列表 ls /sys/bus/i2c/devices/ # 正常输出:i2c-0 i2c-1 i2c-2 ... # 检查I2C0控制器状态 cat /sys/bus/i2c/devices/i2c-0/name # 应输出 rk3568-i2c dmesg | grep i2c # 查看内核日志是否有"i2c rk3568-i2c ff3d0000.i2c: registered"Step 2:验证设备树节点加载
# 进入设备树节点目录 ls /sys/firmware/devicetree/base/i2c@fdd90000/ # 应看到 ssd1306@3c 目录 # 检查compatible属性 cat /sys/firmware/devicetree/base/i2c@fdd90000/ssd1306@3c/compatible # 应输出 solomon,ssd1306\0Step 3:执行I2C扫描
# 扫描I2C0总线 i2cdetect -y 0 # 若输出全U,说明无设备响应;若显示3c,说明设备存在但可能未初始化 # 强制探测(绕过ACPI检查) i2cdetect -y -r 0Step 4:读取设备ID
# SSD1306的设备ID寄存器地址为0x00,读取1字节 i2cget -y 0 0x3c 0x00 # 正常返回 0x3c 或 0x00(取决于芯片版本) # 若返回0xff,说明SCL/SDA上拉电阻失效或线路断开 # 用万用表测SCL/SDA对地电压,正常应为3.3V(上拉至VCC)Step 5:加载驱动模块
# 编译驱动后加载 insmod ssd1306.ko dmesg | tail -20 # 查看probe函数是否执行 # 检查设备节点 ls /dev/i2c-* # 应有 /dev/i2c-0 ls /sys/class/i2c-dev/ # 应有 i2c-03.4 CAN驱动实操:从硬件接线到candump收包
CAN调试比I2C更依赖硬件环境。以下是RK3568 CAN0的标准化流程:
硬件准备
- 使用共模扼流圈(如Bourns SRN6045)隔离CAN_H/CAN_L
- 终端电阻必须为120Ω(两端各一个),否则信号反射导致误码
- CAN收发器(如TJA1050)的VIO引脚接3.3V,VCC接5V,GND单点接地
设备树配置
&can0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&can0_tx &can0_rx>; clocks = <&cru SCLK_CAN0>, <&cru ACLK_CAN0>; clock-names = "can", "apb_pclk"; can@ff3e0000 { compatible = "rockchip,rk3568-can"; reg = <0xff3e0000 0x1000>; interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru SCLK_CAN0>; clock-names = "can"; phy-mode = "can"; rockchip,phy-type = <0>; // 关键:添加bit-timing参数 can-transceiver = <&can_transceiver>; }; }; &can_transceiver { compatible = "nxp,tja1050"; status = "okay"; };内核配置确保.config中启用:
CONFIG_CAN=y CONFIG_CAN_RAW=y CONFIG_CAN_BCM=y CONFIG_CAN_DEV=y CONFIG_CAN_RK3568=y # RK3568专用驱动用户空间配置
# 加载CAN模块 modprobe can modprobe can_raw modprobe can_dev # 配置CAN接口(经典CAN) ip link set can0 type can bitrate 500000 sample-point 0.75 ip link set up can0 # 测试收发 cansend can0 123#abcd1234 # 发送ID=0x123, 数据=abcd1234 candump can0 # 接收数据若candump无输出,检查:
ip -details link show can0中state DOWN还是UPcat /proc/net/can_stats查看tx_frames和rx_frames计数- 用示波器抓CAN_H波形,确认差分电压是否在±1.5V~±2.5V范围内
4. 实操过程详解:RK3568平台SSD1306与CAN FD双驱动联调
4.1 项目背景:智能网关的双外设集成需求
客户要求在RK3568核心板上同时集成:
- SSD1306 OLED屏(I2C接口,用于本地状态显示)
- CAN FD总线(连接车载ECU,传输诊断数据)
挑战在于:两个外设共用同一块PCB,I2C走线与CAN差分对距离过近,导致CAN通信时OLED出现闪屏。这暴露了驱动开发中常被忽视的硬件协同设计问题。
4.2 硬件层协同设计要点
PCB布局约束
- I2C走线(SCL/SDA)必须远离CAN_H/CAN_L差分对,最小间距≥20mil(0.5mm)
- I2C上拉电阻(4.7kΩ)靠近SSD1306芯片,而非主控端
- CAN终端电阻(120Ω)必须放在总线最远端,而非RK3568引脚处
电源噪声抑制
- SSD1306的VDD和VCC分开供电:VDD接3.3V LDO,VCC接5V DC-DC
- 在CAN收发器VCC引脚旁加10μF钽电容 + 100nF陶瓷电容
- I2C总线上增加π型滤波(两个100Ω电阻 + 100nF电容)
4.3 软件层协同优化策略
I2C时序调整
&i2c0 { clock-frequency = <100000>; // 降频至100kHz,降低EMI i2c-scl-rising-time-ns = <300>; // 显式设置上升时间 i2c-scl-falling-time-ns = <10>; // 下降时间 status = "okay"; };降低I2C频率可减少高频谐波干扰CAN总线。i2c-scl-rising-time-ns参数强制驱动使用更平缓的边沿,进一步抑制辐射。
CAN FD位定时精调
# 设置经典CAN段(用于兼容旧ECU) ip link set can0 type can bitrate 500000 sample-point 0.75 # 设置FD数据段(用于高速传输) ip link set can0 type can fd on bitrate 500000 dbitrate 2000000 dsample-point 0.7 # 启用CAN FD ip link set up can0关键参数dsample-point 0.7确保FD数据段采样点落在眼图中心,避免因PCB阻抗不匹配导致的采样误差。
4.4 双驱动联调实录:从冲突到稳定的全过程
Day 1:现象记录
- 单独运行OLED驱动:屏幕稳定显示
- 单独运行CAN驱动:
candump can0接收正常 - 同时运行:OLED每3秒闪一次,CAN丢包率15%
Day 2:信号分析用示波器抓取I2C SCL波形,发现CAN发送瞬间SCL出现尖峰干扰(幅度达1.2V)。结论:CAN共模噪声通过地平面耦合到I2C信号线。
Day 3:硬件修正
- 在I2C走线下方铺铜,并用过孔连接到数字地
- 为OLED模块单独添加磁珠(FBMH3225HM102NT),隔离电源噪声
- 将CAN收发器GND引脚直接连接到板边接地焊盘,缩短回流路径
Day 4:软件优化
- 修改I2C驱动,在
ssd1306_write_cmd()函数中添加udelay(1)延时,错开CAN中断服务程序执行窗口 - 在CAN中断处理函数顶部添加
local_irq_disable(),防止I2C中断嵌套
Day 5:验证结果
- OLED连续运行24小时无闪屏
- CAN FD持续收发10GB数据,误码率为0
dmesg | grep -i "i2c\|can"无警告日志
实操心得:驱动稳定性70%取决于硬件设计,30%才是软件优化。与其花三天调驱动,不如花半天检查PCB layout。我见过太多工程师在代码里疯狂加
mdelay(),却忽略了一个没打的地孔。
5. 常见问题与排查技巧实录:产线工程师的故障速查表
5.1 内核模块加载失败的十大原因及对策
| 现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
insmod: ERROR: could not insert module xxx.ko: Invalid module format | 内核版本不匹配 | uname -rvsmodinfo xxx.ko | grep vermagic | 用当前运行内核的make modules重新编译 |
dmesg显示Unknown symbol in module | 依赖模块未加载 | dmesg | tail | modprobe xxx_dependent,再insmod xxx.ko |
ls /dev/无设备节点 | class_create()失败 | dmesg | grep "class_create" | 检查MKDEV()主次设备号是否冲突,用cat /proc/devices查看已占用号 |
cat /sys/module/xxx/parameters/为空 | module_param()未声明 | grep module_param xxx.c | 确保MODULE_PARM_DESC()与module_param()配对 |
rmmod提示Device or resource busy | 设备被用户进程占用 | lsof /dev/xxx | kill -9相关进程,或echo 1 > /sys/module/xxx/parameters/force_unload |
独家技巧:用strace insmod xxx.ko跟踪系统调用,可精准定位open("/lib/modules/...")失败的具体路径。
5.2 设备树相关故障的黄金三步法
Step 1:编译验证
# 检查dts语法错误 dtc -I dts -O dtb -o /tmp/test.dtb myboard.dts # 检查节点覆盖冲突 scripts/dtc/dtc -I dts -O dtb -@ -o /tmp/overlay.dtb overlay.dtsStep 2:运行时验证
# 查看设备树是否加载成功 cat /proc/cmdline \| grep dtb # 确认bootargs中指定了dtb文件 # 检查节点是否解析 ls /sys/firmware/devicetree/base/ \| grep "your-node-name" # 查看属性值 hexdump -C /sys/firmware/devicetree/base/your-node/compatibleStep 3:内核日志溯源
# 过滤设备树相关日志 dmesg \| grep -i "of_" # of_parse_phandle, of_iomap等 dmesg \| grep -i "unmatched node" # 节点未匹配驱动5.3 I2C/CAN专项故障排查表
| 问题现象 | 根本原因 | 快速验证 | 修复动作 |
|---|---|---|---|
i2cdetect -y 0显示UU | 设备已绑定驱动,但probe失败 | ls /sys/bus/i2c/devices/0-003c/是否存在 | 检查compatible字符串是否匹配驱动of_match_table |
candump can0无输出,但ip link show can0显示UP | CAN收发器未供电 | 用万用表测TJA1050的VCC引脚 | 检查电源电路,确认DC-DC输出正常 |
i2cget -y 0 0x3c 0x00返回0xff | SDA线被拉低 | 断开SSD1306,测SDA对地电阻 | 若电阻<1kΩ,说明SSD1306芯片损坏或焊接短路 |
| CAN通信误码率高 | 终端电阻缺失或阻值错误 | 用万用表测CAN_H与CAN_L间电阻 | 正常应为60Ω(两端120Ω并联),若为∞则电阻缺失 |
避坑指南:RK3568的I2C控制器在CONFIG_I2C_DESIGNWARE_CORE=n时,i2c-rockchip驱动无法加载。务必在内核配置中启用CONFIG_I2C_DESIGNWARE_CORE=y,否则所有I2C设备都会失效。
5.4 性能调优实战:让I2C吞吐量提升300%
默认I2C驱动使用轮询模式,CPU占用率高。通过DMA优化可显著提升性能:
Step 1:启用DMA支持
&i2c0 { dma-names = "tx", "rx"; dmas = <&dmac 0 12>, <&dmac 0 13>; // 查RK3568 TRM获取DMA通道号 status = "okay"; };Step 2:修改驱动启用DMA在drivers/i2c/busses/i2c-rockchip.c中:
// 找到rk3x_i2c_xfer_msg()函数 if (i2c->dma && msg->len > DMA_THRESHOLD) { // DMA_THRESHOLD设为32 rk3x_i2c_dma_send(i2c, msg); } else { rk3x_i2c_fifo_send(i2c, msg); }Step 3:验证效果
# 对比DMA开启前后 time i2cget -y 0 0x3c 0x00 # 单字节读取 time dd if=/dev/zero of=/dev/i2c-0 bs=1024 count=100 # 大块写入实测RK3568上I2C批量传输速度从120KB/s提升至480KB/s,CPU占用率下降65%。
6. 系统裁剪与部署:从开发板到量产固件的最后一步
6.1 内核精简:砍掉90%无用代码
量产固件必须极致精简。以RK3568为例,标准内核镜像约12MB,裁剪后可压至3.2MB:
必删模块
CONFIG_SOUND=m→ 删除所有ALSA音频驱动CONFIG_USB_GADGET=m→ 若无需USB Device模式CONFIG_DRM=m→ 仅保留CONFIG_DRM_ROCKCHIP=y,删除其他GPU驱动CONFIG_INPUT_JOYSTICK=m→ 删除游戏手柄驱动
关键保留项
CONFIG_I2C_ROCKCHIP=y(I2C控制器)CONFIG_CAN_RK3568=y(CAN控制器)CONFIG_GPIO_RK3399=y(GPIO驱动,RK3568复用此配置)CONFIG_MMC_SDHCI_OF_ROCKCHIP=y(eMMC启动必需)
裁剪验证
# 编译后检查模块依赖 modinfo drivers/i2c/busses/i2c-rockchip.ko \| grep "depends:" # 确保无`i2c-core`以外的依赖 # 检查符号表大小 nm vmlinux \| wc -l # 裁剪前约12万符号,裁剪后应≤4万6.2 根文件系统构建:BusyBox最小化实践
放弃Yocto/Poky,用BusyBox手工构建根文件系统:
核心组件
busybox(静态链接,含sh,ls,cat,i2c-tools,can-utils)udev(动态设备节点管理)dropbear(轻量SSH服务)syslogd(日志服务)
精简技巧
make menuconfig中禁用CONFIG_FEATURE_WTMP(不记录登录历史)- 删除
CONFIG_FIND、CONFIG_GREP等非必需工具 i2c-tools只编译i2cdetect,i2cget,i2cset
部署脚本
#!/bin/sh # mkrootfs.sh mkdir -p rootfs/{dev,proc,sys,etc,usr/bin} cp -a /path/to/busybox rootfs/usr/bin/ ln -s busybox rootfs/usr/bin/sh ln -s busybox rootfs/usr/bin/ls # ... 创建必要符号链接 mknod rootfs/dev/console c 5 1 mknod rootfs/dev/null c 1 3 # 打包为cpio find rootfs \| cpio -o -H newc > rootfs.cpio6.3 固件烧录与OTA升级设计
分区规划(eMMC)
| 分区 | 大小 | 用途 |
|---|---|---|
boot | 32MB | u-boot + kernel + dtb |
rootfs | 256MB | 只读根文件系统 |
userdata | 剩余空间 | 用户数据存储 |
OTA升级机制
- 使用
mtd-utils的nandwrite工具写入新固件 - 双分区A/B切换:
boot分区包含