嵌入式与Linux软件工程素养:学习资料与实践路线
嵌入式与Linux软件工程素养:学习资料与实践路线
写出一段能够工作的代码并不难,困难的是让代码在资源不足、并发竞争、硬件异常、需求变更和多年维护之后,依然安全、清晰并且容易验证。
对于C、C++、Linux Kernel和RTOS开发者,所谓“工程素养”并不是背诵更多语法,而是逐渐形成一套稳定的判断方式:
1 | |
本文整理一套长期学习路线:
1 | |
它的目标不是把所有规范从头背到尾,而是提高代码品味、工程判断力和Review能力。
1. 资料应该怎样使用
不同资料解决的问题不同:
| 资料类型 | 主要回答的问题 |
|---|---|
| 安全编码标准 | 哪些写法可能产生未定义行为、越界、溢出或资源错误? |
| 嵌入式编码规范 | 怎样让固件代码更清楚、更可移植、更不容易误用? |
| C++设计指南 | 怎样表达所有权、生命周期、接口约束和资源管理? |
| Linux开发文档 | 一个可维护、可Review、可合入的Kernel Patch应该是什么样? |
| Git历史和邮件列表 | 当前代码为什么这样设计?哪些方案被拒绝过? |
| Bug复盘 | 怎样把一次故障转化为可复用的设计原则? |
不要试图一次性读完所有资料。更有效的方式是:先建立索引,遇到真实问题时查规则,再回到成熟代码与历史讨论中验证理解。
2. SEI CERT C Coding Standard
2.1 资料入口
- SEI CERT Secure Coding Standards在线索引
- SEI CERT C Coding Standard 2016 PDF
- SEI CERT C Coding Standard介绍页
- SEI CERT Coding Standards Wiki介绍
在线索引适合按规则搜索,PDF适合建立整体框架。链接和在线内容可能随站点调整,引用具体规则时还应记录所用版本和访问日期。
2.2 重点目录
CERT C按问题类别组织规则,常见目录包括:
1 | |
对嵌入式、Linux和RTOS开发,建议优先阅读:
MEM:分配、释放、对象生命周期和非法访问;ERR:失败路径、返回值和错误传播;INT:整数转换、截断、溢出和有符号性;EXP:求值顺序、副作用和未定义行为;ARR:边界、长度计算和指针关系;CON:数据竞争、同步和可见性。
2.3 怎样用于Review
例如:
1 | |
Review时不要只说“这里最好判断一下”。应该结构化分析:
1 | |
CERT C帮助你把个人偏好变成可解释的安全理由。
3. Barr Group Embedded C Coding Standard
Barr Group的资料更贴近MCU、RTOS、BSP和Driver开发,强调一致性、可读性、可移植性以及降低固件缺陷。
可以把两类资料这样搭配:
1 | |
规范不是越多越好。团队应该挑选适用规则,说明例外条件,并通过静态检查、Review和测试让规则真正落地。
4. C++ Core Guidelines
即使主要工作仍是C或Kernel,C++ Core Guidelines对理解接口、所有权和生命周期也很有价值。建议重点关注:
1 | |
阅读时不要停在“什么时候使用class”。更重要的问题是:
1 | |
RAII的核心不是“使用智能指针”这么简单,而是让资源生命周期与对象作用域形成可验证的关系。
5. Linux Kernel官方开发文档
5.1 Kernel Development Process
它帮助理解Maintainer、邮件列表、Patch提交、Review和版本迭代。Kernel开发不是“代码能编译就提交”,而是一套公开论证与共同维护过程。
5.2 Coding Style
不要只把它当缩进手册。更值得关注的是:
- 函数应保持清晰并只承担可理解的职责;
- 错误处理怎样使用
goto集中释放资源; typedef何时会隐藏重要类型信息;- 注释为什么应解释“为什么”,而不是复述代码;
- API返回值怎样表达成功、失败和部分结果。
代码风格的价值,是降低团队阅读和Review时的认知切换成本。
5.3 Submitting Patches
重点学习:
1 | |
5.4 Submit Checklist
它适合作为提交前检查框架。团队还可以在此基础上增加自己的硬件矩阵、压力测试、静态分析和回归要求。
6. 不要只读当前源码:学习Git历史
源码告诉你“现在是什么”,历史经常告诉你“为什么变成这样”。
常用命令:
1 | |
它们回答的问题不同:
| 命令 | 适用问题 |
|---|---|
git blame |
当前某行主要来自哪个提交? |
git show |
该提交改了什么,Commit Message怎样解释? |
git log -p |
文件怎样逐步演化? |
git log -L |
一个函数的历史怎样变化? |
git log -S |
哪个提交增加或删除了某段精确文本? |
git log -G |
哪些diff匹配某个正则模式? |
推荐调查流程:
1 | |
需要注意:git blame只显示最后修改者,不等于最初设计者,也不等于责任归属。
7. Linux Patch与邮件列表
7.1 lore.kernel.org
lore可以看到Patch原始邮件、Reviewer意见、Maintainer取舍以及v1到vN的演进。它特别适合回答:
1 | |
例如研究DMAEngine、DW AXI DMAC、direction、cyclic DMA或slave DMA时,应把源码、Git提交和dmaengine邮件列表结合起来。
7.2 Patchwork
Patchwork更适合浏览Patch Series、版本和处理状态。状态只能帮助导航,最终技术理由仍应阅读邮件线程和实际合入提交。
8. Linux Kernel文档总入口
遇到具体子系统时,优先寻找官方文档和代码内Documentation,例如:
1 | |
推荐证据优先级:
1 | |
二手文章适合建立入口,但版本相关的实现结论必须回到目标源码验证。
9. 四阶段学习路线
9.1 第一阶段:代码安全
1 | |
优先建立:NULL与错误返回检查、长度和数组边界、整数溢出、资源生命周期、完整错误路径以及并发安全。
实践方式:每周选择一个真实Bug,用一条CERT规则和一条团队工程规则解释它。
9.2 第二阶段:Linux工程规范
1 | |
练习把一个大修改拆成逻辑清楚的Patch Series;为每个Commit Message写清问题、用户影响、根因、修复理由和测试。
9.3 第三阶段:真实源码设计
1 | |
每次阅读函数先画调用位置、对象关系和状态变化,再追历史。不要只问“怎样实现”,还要问“哪些约束迫使它这样实现”。
9.4 第四阶段:Patch Review
1 | |
重点观察Maintainer真正关心的内容:
- API是否容易误用;
- 状态生命周期是否明确;
- 是否产生stale state;
- 锁粒度和上下文是否正确;
- 所有错误路径是否闭合;
- ABI或用户可见行为是否改变;
- 是否引入不必要复杂度;
- 测试是否真正覆盖失败模式。
10. 建立自己的工程原则库
每解决一个真实Bug,固定记录:
1 | |
10.1 DMA direction stale
1 | |
10.2 malloc未判空
1 | |
10.3 RTOS临时线程快速创建销毁
1 | |
原则库不是“永远正确的格言”。每条原则都应记录适用条件、反例和Trade-off,避免从一个Bug过度推广。
11. 一套固定的代码Review问题
以后阅读或Review代码,可以依次问:
11.1 需求与约束
1 | |
11.2 所有权与生命周期
1 | |
11.3 错误与恢复
1 | |
11.4 并发与状态
1 | |
11.5 接口与可维护性
1 | |
12. 怎样把资料转化为能力
只收藏链接不会自动产生工程判断力。建议为每个主题执行一个小闭环:
1 | |
例如研究“错误路径漏释放”:
- 阅读CERT C资源管理和错误处理规则;
- 在Kernel源码中找使用
goto清理的驱动probe函数; - 用Git历史查一次泄漏修复;
- 阅读Reviewer为什么要求集中清理;
- 为自己的模块注入第N步失败;
- 检查所有资源是否逆序释放;
- 把结论加入Review Checklist。
13. 推荐的长期节奏
不需要每天阅读大量文档,可以保持稳定节奏:
1 | |
衡量进步的标准不是收藏了多少资料,而是能否:
- 在代码写下之前识别风险;
- 在Review时解释问题的后果与边界;
- 用证据说明设计取舍;
- 为失败路径设计可执行测试;
- 把一次Bug转化为团队以后不会重复犯的机制。
14. 总结
嵌入式与Linux软件工程素养来自多种证据的长期结合:
1 | |
最终目标不是记住“什么时候用inline、UINT32、class或汇编”,而是面对一个具体场景时,能够清楚说明:
当前约束是什么,有哪些方案,每种方案牺牲什么,为什么这个选择最合适,以及失败后系统如何安全恢复。