为什么你学 Linux 驱动这么痛苦?因为你缺的不是代码,而是架构路线
立芯嵌入式|架构学习路线与意义
写给所有已经会写驱动、会跑 RTOS Demo,但一看 Linux / Zephyr / AUTOSAR 就懵的嵌入式工程师。
很多同学学嵌入式,前期其实并不慢。
点灯会了,串口会了,I2C、SPI、ADC、DMA 也能调出来。 FreeRTOS 也能跑,任务、队列、信号量、定时器也都知道怎么用。
但一旦进入复杂工程,问题就来了:
为什么项目一大,代码就开始乱?
为什么 BSP、业务、OS、Middleware 越写越耦合?
为什么换一个传感器,改动能一路传到 App?
为什么自己写 RTOS 项目还行,一看 Linux 驱动就像看天书?
为什么 device、driver、bus、probe、file_operations这些东西明明都认识,但组合起来就看不懂?
很多人以为自己卡在 Linux 语法、内核 API、驱动模板。
但我觉得不是。
你真正缺的,是一条从 MCU / RTOS 工程自然过渡到 Linux 驱动架构的学习路线。
更直白一点说:
你不是代码不会写,而是没有理解高手为什么要那样组织代码。
https://www.bilibili.com/video/BV1iBVS6qEbW/?spm_id_from=333.1387.search.video_card.click
我这几年带学员、做项目、拆企业代码,有一个非常强烈的感受:
高手在架构设计上,最后总是趋同的。
不是因为他们互相抄。
而是因为工程复杂度到了一定程度以后,背后的约束是一样的:
业务会变;
硬件会变;
芯片会变;
OS 会变;
Middleware 会变;
团队会变;
测试、量产、维护、二次开发都会进来。
所以最后大家都会走向同一类设计思想:
分层,是为了隔离变化。抽象,是为了依赖倒置。 对象模型,是为了沉淀语义。 管理模型,是为了让系统不靠人脑硬记。
嵌入式不是不能谈架构。
恰恰相反,嵌入式越往后走,越需要架构。
因为你面对的不是一个函数,也不是一个模块,而是一整个产品系统。
大多数人刚开始做项目,都会自然形成一个最简单的分层:
App
Middleware
OS
BSP / Handler
Core / Driver / HAL
这个阶段的核心目标很简单:
先把功能做出来。
比如你做一个智能手表、工业采集终端、温湿度采集 Demo。
App 负责业务逻辑,Middleware 放 LVGL、协议栈、日志库,OS 负责任务调度,BSP 封装外设驱动,底层再调用 STM32 HAL 或芯片原厂 SDK。
这个阶段有没有价值?
当然有。
它适合:
Demo 原型验证;
课程入门阶段;
设备数量少、业务简单的项目;
1~2 人快速迭代的小项目。
这个阶段解决的问题是:
代码别写乱。
也就是说,至少你知道 UI 别直接写寄存器,业务别直接碰 HAL,BSP 要把板级外设封一层。
但它的问题也很明显:
只要产品继续变复杂,这套结构很快就会顶不住。
为什么?
因为 App 还是容易碰到底层。 业务逻辑还是容易知道太多硬件细节。 换一个 OS、换一个 BSP、换一个中间件,变化还是会往上层传导。
所以第一阶段够你做 Demo,但不够你做长期产品。
当项目开始进入产品化,架构就必须往前走一步。
这时候你不能再假设底层是稳定的。
你要反过来思考:
作为 App 的设计者,我应该认为 OS、Middleware、BSP、MCU,甚至芯片平台,未来都有可能被替换。
于是第二阶段就出来了:
App
Service
Platform
├─ platform_mcu
├─ platform_os
├─ platform_bsp
└─ platform_middleware
Impl
Vendor / HAL / FreeRTOS / LVGL / SDK
这一阶段最关键的变化,不是多建几个文件夹。
而是你开始用 Platform Wrapper 去隔离变化。
比如:
App 不直接调用 FreeRTOS,而是调用 platform_os;
Service 不直接操作 HAL I2C,而是调用 platform_mcu_i2c;
业务不直接知道某个屏幕复位脚,而是调用 platform_bsp_display_reset();
LVGL、日志库、安全库,也通过 port / adapter 接入。
这背后其实就是一个工程思想:
依赖倒置:上层不要依赖具体实现,上层只依赖抽象接口。
到了这一阶段,你会明显感觉工程能力变强了。因为你可以:
替换 OS;
替换传感器;
替换板型;
替换中间件;
做 Mock 测试;
做 CI / 单元测试;
做 FCT 制造测试;
做局部插桩;
做日志、诊断、安全密钥等切面能力。
这时候系统已经不只是“能跑”。
它开始具备产品工程的味道。
第一阶段解决的是:
代码别写乱。
第二阶段解决的是:
变化别传导到上层。
很多人学分层架构的时候,会有一个疑问:
Service 层到底是什么?
是不是为了显得高级,硬加出来的一层?
不是。
Service 层不是凭空设计出来的,它往往是从 App 中沉淀出来的。
比如一开始你在 App 里面写:
电池采集;
低功耗管理;
背光控制;
OTA 升级;
存储管理;
诊断日志;
BLE 通信;
传感器融合。
刚开始没问题。
但写着写着你会发现,这些逻辑不属于某一个具体业务页面。
它们更像是系统能力。
比如 OTA,不是某一个页面的能力,而是整个设备的升级能力。 比如 battery,不是某一个任务的能力,而是整个系统都需要知道的电源状态。 比如 diagnosis,不是某一个模块的能力,而是产品长期维护必须具备的诊断能力。
所以这些能力就会逐渐从 App 中沉淀出来,形成 Service。
Service 的本质,是把 App 中可复用、可迁移、可长期沉淀的系统能力抽出来。
这一步非常重要。
因为从这个时候开始,你的项目就不再只是“写业务代码”。
你开始建设自己的平台能力。
第二阶段已经能隔离底层变化了。
但它还不够。
因为系统继续复杂以后,你会遇到另一个问题:
代码层面的隔离够了,但语义层面的管理不够。
举个简单例子。
如果你有 1 个 LED,随便写都行。 如果你有 3 个 LED,封几个函数也能凑合。 但如果你有 100 个设备呢?
有显示屏、触摸、IMU、温湿度、电池、Flash、RTC、蜂鸣器、马达、蓝牙、4G、定位模块……
你不可能靠人脑去记:
哪些设备需要初始化?
哪些设备需要启动?
哪些设备支持读取?
哪些设备需要注入到 Service?
哪些设备依赖 I2C?
哪些设备依赖 SPI?
哪些设备有自己的上下文?
哪些设备有自己的操作接口?
这时候就必须进入第三阶段:
plf_object
├─ plf_device
└─ plf_service
每个对象统一具备:
cfg - 静态配置
ctx - 运行时上下文
data - 当前数据
ops - 行为接口
也就是说,你开始把系统中的设备和服务统一建模。
Display 是一个 device。 Touch 是一个 device。 IMU 是一个 device。 Battery Service 是一个 service。 OTA Service 是一个 service。 Diagnosis Service 也是一个 service。
它们都来自统一的对象基座。
这样做的好处是什么?
系统终于可以被统一管理了。
你可以设计:
device_manager
service_manager
board_manager
然后统一处理生命周期:
register
init
start
process
stop
deinit
这时候工程能力又上了一个台阶。
以前你是“人肉管理模块”。 现在是“框架管理对象”。
以前你是“每个模块自己玩自己的”。 现在是“所有设备和服务都有统一协议”。
所以第三阶段解决的问题是:
系统别靠人脑硬记。
这也是很多嵌入式同学真正缺的能力。
你不只是会写一个驱动,而是能设计一套让很多驱动、很多服务、很多业务长期演进的系统结构。
https://www.bilibili.com/video/BV1y4yJBrEok/?spm_id_from=333.1387.search.video_card.click
现在重点来了。
很多同学一看 Linux 驱动,觉得非常抽象:
device
driver
bus
probe
remove
device tree
file_operations
i2c_client
i2c_driver
i2c_adapter
但如果你已经理解了前面的对象模型和管理模型,你会发现:
Linux 并不是突然冒出来的一套天书。
它只是把这些思想做得更成熟、更彻底、更通用。
我们可以做一个对应:
你看,本质是不是一样的?
都是在解决几个问题:
设备怎么描述?
驱动怎么注册?
设备和驱动怎么匹配?
生命周期由谁调用?
硬件资源怎么和驱动解耦?
上层怎么用统一接口访问不同设备?
这就是为什么我一直强调:
你在 MCU / RTOS 阶段把平台化、对象模型、生命周期理解透了,再去看 Linux 驱动,就不是硬背 API,而是在看一套更成熟的工业级答案。
以 I2C 为例,Linux 的桥模式其实并不神秘
我们拿 I2C 举例。
在 MCU / RTOS 平台里,你可能是这样设计的:
Service(使用者)
↓
IMU Device(从设备对象,比如 MPU6050)
↓
MPU6050 Driver(BSP 驱动)
↓
I2C Impl(MCU I2C 控制器实现)
↓
I2C Master 硬件控制器
到了 Linux 里面,本质上变成:
User Space APP
↓
i2c_client(从设备对象)
↓
i2c_driver(具体设备驱动,比如 MPU6050)
↓
i2c_adapter(I2C 控制器)
↓
I2C 硬件控制器
再加上 Device Tree 来描述硬件资源。
你会发现,这里面的思想其实是一样的:
这就是桥模式思想在 Linux 中的体现。
很多人看 Linux 看不懂,不是因为 i2c_driver 太难,而是因为他没有在 MCU 阶段建立过这种“设备对象 - 驱动实现 - 总线控制器”的抽象关系。
所以他只能背模板。
模板一变,就懵了。
很多人学嵌入式的路线是这样的:
STM32 HAL
FreeRTOS API
LVGL API
Linux Driver API
Zephyr API
AUTOSAR API
这条路不是不能学。
但如果只沿着 API 学,会很痛苦。
因为每换一个平台,你就像重新开始。
更好的路线应该是:
功能分层
↓
平台抽象
↓
对象模型
↓
生命周期管理
↓
总线模型 / 驱动模型
↓
Linux / Zephyr / AUTOSAR
也就是说,不是从一个 API 跳到另一个 API。
而是从一个架构思想,迁移到另一个更成熟的平台。
你会发现,Linux 有 device model、driver model、bus model。 Zephyr 也有 device model、driver model、devicetree、subsystem、Kconfig、CMake。 AUTOSAR 也有 SWC、RTE、BSW、MCAL、CDD、Complex Driver。
名字不一样,但背后都在解决类似的问题:
如何描述资源?如何隔离变化?如何统一接口?如何管理生命周期?如何让团队协作?如何让系统长期演进?
这才是架构学习真正有价值的地方。
我一直不太喜欢把架构讲成一种很虚的东西。
架构不是 PPT 上画几个框。 架构也不是把目录拆得很漂亮。 架构更不是为了显得自己很高级。
架构真正解决的是工程现实问题。
当你只有一个人、一个板子、一个传感器的时候,架构可以很简单。
但当你面对的是:
你就会发现,没有架构,系统根本长不大。
小项目靠经验,大项目靠结构。 Demo 靠手速,产品靠架构。
真正好的架构,不是一步到位设计出来的。
它一定是随着需求,一层一层长出来的。
第一阶段,不让代码乱。 第二阶段,不让变化往上传。 第三阶段,不让系统靠人脑记。 再往后,才能自然进入 Linux、Zephyr、AUTOSAR 这些成熟平台。
因为工程设计到最后,不只是写代码。
它是在复杂约束下做取舍。
分几层? 抽象到什么程度? 哪些能力沉淀成 Service? 哪些设备抽象成 Device? 生命周期由谁管? 配置放在哪里? 上层能不能直接碰底层? 底层变化要不要影响业务?
这些问题没有唯一答案。
但高手的设计,往往有一种共同特征:
该隔离的地方隔离,该收敛的地方收敛,该开放的地方开放,该约束的地方约束。
它不是为了复杂而复杂。
它是恰到好处。
这就是为什么我说:
高手在架构设计上总是趋同的。
因为背后是哲学。
同时它也是艺术。
因为每一次分层、每一次抽象、每一次边界收敛,都要刚刚好。
如果你现在还处在“会写驱动,但看不懂复杂工程”的阶段,不要急。
这不是你不够努力。
很可能只是你的学习路线还停留在 API 层面。
从今天开始,你可以换一个视角:
不要只问“这个函数怎么用”。 要问“这一层为什么存在”。
不要只问“这个驱动怎么写”。 要问“这个设备在系统对象模型里是什么角色”。
不要只问“Linux 的 probe 什么时候调用”。 要问“为什么生命周期要由框架来驱动”。
当你能从 分层、抽象、对象、生命周期、总线模型 这条路线一路看过去,Linux 驱动就不再是天书。
它只是一个更大的、更成熟的、更工业化的嵌入式系统。
而这,才是嵌入式工程师真正应该补上的架构课。
加我V,发你架构循序渐进学习手册。
https://www.bilibili.com/video/BV16ENzz3Ea6/?spm_id_from=333.1387.search.video_card.click
立芯嵌入式 专注嵌入式工程化、平台化架构与企业级项目实战。 让普通工程师不只会写代码,更能看懂系统、设计系统、驾驭系统。