上篇聊了EtherCAT工业以太网。这篇聊嵌入式Linux——当MCU的性能不够用,需要跑复杂的算法(视觉、SLAM、运动规划)时,就得用嵌入式Linux。面试中嵌入式Linux的题目覆盖面很广:内核裁剪、设备树、驱动框架、根文件系统,每个方向都能深挖。
为什么要用嵌入式Linux
MCU(比如STM32)跑RTOS,适合实时控制;嵌入式Linux跑在MPU(比如ARM Cortex-A系列)上,适合复杂计算。两者的分工很明确:
- 实时控制层:MCU+RTOS,负责电机控制、传感器采样、安全保护,响应时间微秒级
- 计算决策层:MPU+Linux,负责视觉处理、路径规划、人机交互,需要MMU和大内存
在机器人中,典型的架构是:工控机(x86或ARM64)跑Ubuntu+ROS2做高层决策,通过EtherCAT/CAN/UART连接下层的MCU做实时控制。
嵌入式Linux的优势:完整的TCP/IP网络栈、丰富的开发工具链、支持复杂文件系统、可以跑Python/C++/Rust等高级语言、有成熟的图形界面框架。代价是启动时间较长(秒级)、实时性不如RTOS(毫秒级抖动)、系统复杂度更高。
启动流程
嵌入式Linux的启动流程比桌面Linux复杂得多,因为嵌入式没有BIOS/UEFI帮你做硬件初始化。
完整的启动链:
- BootROM(芯片内置,不可修改)→ 从预设介质(SPI Flash/eMMC/SD卡)加载第一级引导
- Bootloader(U-Boot最常见)→ 初始化DDR、时钟、外设,加载内核和设备树到内存
- Linux Kernel → 解压、初始化内核子系统、挂载根文件系统
- init进程(systemd或BusyBox init)→ 启动用户空间服务和应用
U-Boot是嵌入式中最常用的bootloader。它支持多种启动方式(TFTP网络启动、SPI Flash、eMMC、SD卡),有命令行可以调试,支持环境变量和启动脚本。在产品开发中,U-Boot的环境变量用来存储MAC地址、序列号、启动参数等信息。
U-Boot的启动流程分两个阶段:SPL(Secondary Program Loader)和U-Boot本体。SPL很小(几十KB),负责初始化DDR和基本时钟,然后加载U-Boot本体到DDR中运行。这种两阶段设计是因为BootROM只能加载很小的数据到片内SRAM,而U-Boot本体太大放不下。
内核启动时的命令行参数(bootargs)也很关键。常见的参数包括:console=ttyS0,115200(串口控制台)、root=/dev/mmcblk0p2(根分区位置)、rootwait(等待块设备就绪)、rw(读写挂载)。配置错误的bootargs是启动失败最常见的原因之一。
设备树(Device Tree)是嵌入式Linux的重要概念。它用DTS(Device Tree Source)文件描述硬件拓扑——哪些外设存在、挂在哪个总线上、中断号是多少、GPIO复用配置是什么。内核通过设备树了解硬件,不需要为每个板子写专用的板级文件。
内核裁剪
嵌入式Linux的内核需要根据硬件平台裁剪。完整内核有几百万行代码,但你的板子可能只需要其中一小部分。
裁剪的方法:
- make menuconfig:图形化配置内核选项。根据板子的外设选择对应的驱动模块,去掉不需要的文件系统和网络协议。
- 设备树裁剪:只保留板子上实际使用的外设节点,禁用不用的外设。
- 内核模块 vs 内置:常用的驱动编译为内置(built-in),不常用的编译为模块(.ko),需要时动态加载。
裁剪的目标是减小内核体积(Flash有限)和加快启动时间。一个裁剪好的嵌入式Linux内核可以压缩到2-3MB,启动时间控制在1-2秒内。
根文件系统
根文件系统(rootfs)是嵌入式Linux启动后挂载的第一个文件系统,包含所有用户空间程序和库。
常见的构建方式:
- Buildroot:轻量级,适合简单的嵌入式系统。配置文件简单,构建速度快,生成的rootfs很小。
- Yocto/OpenEmbedded:功能强大但学习曲线陡峭。适合复杂的产品级系统,支持多层叠加、软件包管理、OTA更新。
- Debian/Ubuntu base:直接用现成的arm64根文件系统,apt安装软件包。开发方便但体积大。
rootfs的关键目录:/bin(基础命令)、/lib(共享库)、/etc(配置文件)、/proc和/sys(内核虚拟文件系统)、/dev(设备节点)。在嵌入式中/dev通常用devtmpfs自动创建设备节点,不需要手动用mknod。
在机器人中,通常用Ubuntu或Debian base——因为要跑ROS2,而ROS2对Ubuntu有官方支持。Buildroot更适合没有复杂应用需求的简单设备。
驱动开发框架
嵌入式Linux驱动有几种标准框架:
字符设备驱动:最基础的驱动类型。通过file_operations注册open/read/write/ioctl等操作。适合简单的寄存器读写设备。
平台设备驱动(platform driver):跟设备树配合的驱动模型。设备树中定义platform_device,内核自动匹配对应的platform_driver。这是现代嵌入式Linux驱动的标准写法。
platform driver的核心是probe函数——当设备树中的compatible属性与驱动声明的of_match_table匹配时,内核调用probe完成设备初始化。probe中要做的事情:ioremap映射物理地址到虚拟地址、申请中断(request_irq)、注册字符设备或input设备、初始化硬件寄存器。remove函数做相反的清理操作。
IIO(Industrial I/O)框架:专门用于ADC、DAC、加速度计、陀螺仪等传感器。提供统一的sysfs接口和字符设备接口。写传感器驱动首选IIO框架。
V4L2(Video for Linux 2):摄像头和视频采集设备的驱动框架。如果你的机器人有视觉传感器,V4L2是必须了解的。
面试要点
设备树的语法和结构。DTS文件是树形结构,用节点和属性描述硬件。每个节点有compatible属性(用于匹配驱动)、reg属性(寄存器地址和大小)、interrupts(中断配置)。面试中可能给你一个原理图,让你写出对应的设备树节点。
内核模块的加载过程。insmod加载.ko文件到内核→调用module_init注册的初始化函数→注册设备或驱动→创建/dev节点或/sys属性。rmmod卸载时调用module_exit清理资源。modprobe比insmod智能——它会自动加载依赖的模块。
启动时间的优化。嵌入式Linux启动时间从十几秒优化到1-2秒,关键手段:并行初始化(异步probe)、延迟加载非关键驱动、使用initramfs避免等待块设备就绪、关闭不必要的内核日志和自检。
用户空间和内核空间的通信方式。面试常考:ioctl(最常用,自定义命令码)、sysfs(暴露属性到/sys目录,适合简单配置)、mmap(共享内存,适合大数据量传输如图像帧)、netlink(内核和用户空间的Socket通信,适合网络子系统事件通知)。每种方式有适用场景,选型要看数据量和实时性要求。
设备树中的中断描述。interrupts属性用三元组描述:中断类型(GIC_SPI/GIC_PPI)、中断号、触发方式(IRQ_TYPE_LEVEL_HIGH/IRQ_TYPE_EDGE_RISING)。理解这些描述方式,才能正确编写设备树和对应的中断处理驱动。
给你的建议
嵌入式Linux的学习路径:先在一块开发板(推荐RK3568或IMX8MM)上跑通完整的构建-烧写-启动流程,然后逐步深入。Buildroot适合快速入门,Yocto适合产品化。
驱动开发从字符设备开始——写一个简单的GPIO控制驱动,理解file_operations、设备号分配、用户空间交互。然后学platform driver模型,理解设备树和驱动的匹配机制。最后学具体的子系统框架(IIO、V4L2、input等)。
调试工具必须掌握:dmesg看内核日志、devmem2读写物理地址、strace跟踪系统调用、perf分析性能热点。这些工具在面试中也是常考内容。
上一篇:第323篇 EtherCAT工业以太网协议
下一篇预告:第325篇 FPGA在机器人中的应用