嵌入式 Linux 学习路线

这行不好学,往往不是因为难,是因为不知道从哪一层下手。从应用到底层跨度比 MCU 大一个数量级。我把它切成四层,每层给一个能验证的出口。

先说一个顺序问题:不要一上来啃驱动。驱动是「给应用层提供接口」的活,你不知道应用会怎么用这个接口,写出来的驱动一定不符合实际需求。先把应用层写熟,再往下走。
LAYER 1

应用层:先把 Linux 当工具用熟

这一层不涉及内核,但它是后面所有层的地基。跳过它直接学驱动的人,写出来的驱动没人用得顺手。

必须掌握:文件 IO(open / read / write / ioctl / mmap)、进程与线程(fork / pthread)、进程间通信(管道、消息队列、共享内存、信号)、socket 编程(TCP / UDP / unix domain)、信号处理。

工程能力:Makefile 与 CMake、gdb 调试(断点、backtrace、attach 到运行中进程)、valgrind 查内存、strace 看系统调用、top / ps 定位 CPU 占用。

验收:写一个多线程的串口采集程序,支持热插拔、断线重连、日志轮转,并能在 gdb 里定位一次真实的内存泄漏。

LAYER 2

系统编程:从「能跑」到「扛得住」

这一层决定你做的是 demo 还是产品。核心是把「阻塞」和「并发」这两件事彻底想清楚。

必须掌握:select / poll / epoll 的区别与适用场景、非阻塞 IO、信号的安全用法(async-signal-safe 函数)、mmap 共享内存与文件映射、线程同步(互斥量、条件变量、读写锁)、原子操作。

性能相关:零拷贝(sendfile、splice)、CPU 亲和性、实时调度策略(SCHED_FIFO)、优先级与 nice、内存分配器行为。

验收:写一个单进程处理 200 路并发连接的服务,CPU 占用稳定,压测 24 小时不崩、内存不涨。

提示:「内存不涨」比「不崩」难得多。泄漏往往出在错误分支和超时路径上。

LAYER 3

驱动开发:内核的门槛

从这一层开始,你的错误会让整台机器挂掉,而不是一个进程崩掉。心态要从「调试」切换到「预防」。

必须掌握:模块的编译与加载(Kbuild、insmod / rmmod、模块参数)、字符设备(cdev + file_operations + 自动建节点)、proc / sysfs 接口、内核内存分配(kmalloc / vmalloc / GFP 标志)、并发与竞态(spinlock、mutex、原子变量、RCU 的适用场景)。

设备模型:platform 总线与 platform_driver、设备树匹配(compatible / of_match_table)、pinctrl、regulator、clk 框架。

中断与 DMA:request_irq 的触发方式与共享标志、中断上半部 / 下半部(tasklet、工作队列、线程化中断)、DMA 引擎与一致性内存。

验收:给一个真实传感器写 platform 驱动,走设备树配置,用 IIO 或 input 子系统上报数据,能通过 rmmod / insmod 循环 100 次不泄漏。

LAYER 4

BSP 与系统集成:让整块板子活起来

这一层的工作听起来杂,但它是「一个人能独立 bring-up 一块板」的能力,也是薪资差别最大的地方。

Bootloader:U-Boot 的启动流程、SPL / TPL、环境变量与分区、从多种介质启动(eMMC / SD / NFS / USB)、fastboot 与量产烧录。

内核:配置与裁剪(defconfig、menuconfig)、移植(board 目录、dts、驱动使能)、内核命令行、initramfs。

根文件系统:BusyBox 构建、库裁剪、只读根 + overlay、分区布局、掉电保护。

产品化:启动时间优化(从 uboot 到应用起来的每一段都要量)、双分区 OTA、看门狗与复位原因记录、量产测试模式。

验收:拿一块空板,从零把它 bring-up 起来:串口有输出、能进 U-Boot、能起内核、能挂根文件系统、应用自启、断电 200 次能正常恢复。

启动时间都花在哪

做到 LAYER 4 一定会被要求「开机再快两秒」。先按阶段打点,别凭感觉改。下表的耗时取自常见的 ARM 板子,你的板子会不一样。

阶段常见耗时怎么量能省的地方
ROM → SPL 50 – 200 ms 串口打点、GPIO 翻转后示波器看 关调试串口、缩短 DDR 训练、减少完整性校验范围
SPL → U-Boot 200 – 500 ms U-Boot 的 bootstage 关掉用不上的驱动、环境变量读进内存、跳过 USB 枚举
U-Boot → 内核 100 – 400 ms bootstage + 内核 early printk 精简 initcall、关未用子系统、镜像压缩换 lz4
内核 → init 0.5 – 3 s initcall_debug、printk 时间戳 去掉 probe 里的延时、驱动改异步 probe、少等电源稳定
init → 应用起来 0.2 – 2 s systemd-analyze 或自己打点 少跑 shell 脚本、把必须的初始化挪进应用里并行做
试过的板子里,最大的一块时间经常不在你以为的地方:某个驱动 probe 里有 300 ms 的 msleep 等外部芯片上电,或者 I2C 上挂了个不存在的器件在超时重试。先量,再改。

这些坑,我踩过的大概有八成

  1. 工具链和 sysroot 不匹配 —— 用 A 家的工具链编,链 B 家的库。表现是各种诡异的重定位错误。固定用厂商提供的 toolchain + sysroot,写进文档。
  2. 板子上找不到动态库 —— 库编进去了但没打包进 rootfs,或者 LD_LIBRARY_PATH 没设、rpath 没写、ldconfig 没跑。逐个排查,别靠猜。
  3. compatible 写错一个字母 —— probe 永远不执行,日志里啥也没有。先 /proc/device-tree 确认树是对的,再看驱动里的字符串。
  4. 内核版本一换,编译不过 —— API 一直在变:file_operations 的签名、gpio 接口、platform_driver.remove 的返回值、copy_from_user 的检查方式。写驱动前先确认目标内核版本。
  5. 看不到 printk 输出 —— 日志等级被过滤了。调 dmesg -n 8 或改控制台 loglevel,别急着怀疑代码。
  6. 改了设备树没生效 —— dtb 没重编,或者 bootloader 加载的还是旧 dtb,或者 dtb 在另一个分区。三处都要确认。
  7. 模块 insmod 报 vermagic 不符 —— 模块编译用的内核源码,和板上跑的内核不是同一份(甚至只是改了 config)。必须用板上内核的 build 目录。
  8. 根文件系统只读,改了重启就没了 —— 这是设计,不是 bug。要持久化就得走 overlay 或单独的可写分区。
  9. request_irq 失败 —— 触发方式配错(上升沿 vs 高电平)、共享中断没带 IRQF_SHARED、或者引脚被别的驱动先占了。
  10. 不加 MODULE_LICENSE("GPL") —— 部分内核符号不给用,还会污染内核,日志里一直有 taint 标记。

留言 / 提问

写你卡住的地方:什么芯片、什么现象、已经试过什么。频率高的问题我会写进页面。昵称不填也能发。