鸿蒙造物者之路 02 · 三种身法
续写章节|沿用「造物者」系列的武侠讲解体,面向电子信息 / 嵌入式方向的新手
=================前情提要===============
实训楼三楼,老陈甩给我一块 Hi3861,让我清出 100 G 硬盘,然后留下一句"点灯不只是点灯"。
临走前他说,在搭环境之前,我得先弄明白一件事——我要编译的那个系统,最底下踩着的到底是什么。
========================================
这一章你会得到什么
🎯 学完这一章,你应该能:
- ☐ 理解 KAL 内核抽象层为什么必须存在,它藏起了什么
- ☐ 说出 LiteOS-M / LiteOS-A / Linux 三个内核的差异和各自的战场
- ☐ 理解 HDF 驱动框架要解决什么痛点,为什么它能「一次开发多内核部署」
- ☐ 看懂为什么 LiteOS-A 在实际项目里用得不多(这是实话,书里会讲)
**🧰 需要准备:**第 01 章的基础概念。仍然不需要动手。
**⏱ 预计用时:**约 40 分钟
📊 难度:★★☆☆☆ 概念偏多,但都是后面要用到的
一、100 G 的硬盘,和 352 KB 的内存
第二天晚上八点,我准时推开实验室的门。
老陈已经在了,桌上摊着三块开发板:一块是我昨天拿走的 Hi3861,一块明显大一圈、带着摄像头排线,还有一块更夸张,巴掌大,接口密密麻麻,接了一块 MIPI 屏。
"来了?坐。"他指了指那三块板子,“认得出吗?”
我摇摇头。
“从左往右:Hi3861、Hi3516DV300、RK3568。”
他在每块板子前面写了三个数:
Hi3861 SRAM 352 KB → 轻量系统 → LiteOS-M
Hi3516DV300 DDR 512 MB → 小型系统 → LiteOS-A / Linux
RK3568 DDR 2 GB / 8 GB → 标准系统 → Linux
"内存差了一万倍,但跑的都是 OpenHarmony。"老陈说,“你现在告诉我,为什么同一个系统能在 352 KB 和 8 GB 上都能活?”
我想了想:“……因为能裁剪?”
"对,但不够准确。"老陈说,“能裁剪是结果,能换内核才是原因。”
他把中间那块板的屏幕点亮,调出一张图:
┌────────────────────────────────┐
│ 系统服务 / 框架 / 应用 │ ← 这三层基本不变
├────────────────────────────────┤
│ 内核抽象层 KAL │ ← 关键的"转接头"
├────────────────────────────────┤
│ LiteOS-M │ LiteOS-A │ Linux │ ← 三选一,可替换
└────────────────────────────────┘
“看见中间那层了吗?“老陈敲了敲"KAL"三个字母,”**KAL,Kernel Abstract Layer,内核抽象层。**整个鸿蒙能做’弹性部署’,全靠它。”
二、KAL:让上层不知道底下是谁
"打个比方。"老陈拿起一块万用表,“你用万用表量电压,你关心里面是碳膜电阻还是金属膜电阻吗?”
“不关心。我只关心表笔插进去、读数出来。”
"KAL 就是这个’表笔接口’。"老陈说,“它把三种内核的差异磨平,向上暴露统一的一套 API——主要是 POSIX 标准接口,以及面向 MCU 的 CMSIS 接口。”
他在白板上写:
/* 你写的代码 —— 三种内核上全都长这样 */
pthread_t tid;
pthread_create(&tid, NULL, my_task, NULL); /* 创建线程 */
sem_wait(&my_sem); /* 等信号量 */
mq_send(mq, buf, len, 0); /* 发消息队列 */
“这些是 POSIX 接口。在 Linux 上,KAL 直接转调 Linux 的 pthread;在 LiteOS-A 上,转成 LiteOS-A 自己的实现;在 LiteOS-M 上——”
“LiteOS-M 不是没有进程吗?”
"所以 KAL 会做映射。"老陈说,“LiteOS-M 上只有任务(Task)概念,没有进程。KAL 就把’进程’映射成一个特殊的任务组,把 POSIX 的线程接口映射到 LiteOS-M 的任务接口。你写的 pthread_create,在 LiteOS-M 上最终会变成一个 LOS_TaskCreate。”
我倒吸一口气:“所以同一份代码,底下干的完全不是一件事?”
"完全不是。但你不用管。"老陈说,“这就是抽象层的价值——把’怎么做’藏起来,只留’做什么’给你。”
记牢这句:鸿蒙能"一套系统打天下",技术上的第一功臣是 KAL,第二功臣是 HDF(下下章讲)。
三、第一种身法:LiteOS-M
"先看最小的那个。"老陈拿起 Hi3861。
3.1 它是个 RTOS
"LiteOS-M 是一个实时操作系统内核(RTOS)。"老陈说,“你玩过 FreeRTOS 吧?”
“玩过一点。”
"那你上手会很快,因为概念几乎是通用的。"他在白板上列了两列:
| 概念 | FreeRTOS | LiteOS-M |
|---|---|---|
| 执行单元 | Task(任务) | Task(任务) |
| 调度方式 | 抢占式 + 时间片 | 抢占式 + 时间片 |
| 任务间同步 | Semaphore / Mutex / Queue | Semaphore / Mutex / Event / Queue |
| 通信 | Queue / 通知 | Queue / Event |
| 定时器 | Software Timer | Software Timer |
| 内存管理 | heap_1~5 | 多种内存池算法 |
| 中断管理 | 有 | 有 |
| 虚拟内存 | 无 | 无 |
| MMU 支持 | 无 | 无(部分支持 MPU) |
"看见没——左边你会的,右边全能用。"老陈说,“差别在细节,不在思维。”
“那我以前写的 FreeRTOS 代码能直接搬过来吗?”
"不能直接搬,但能一行行对译。"老陈说,“API 名字不一样,但套路一模一样。给你看个对照:”
/* ---------- FreeRTOS 写法 ---------- */
xTaskCreate(task_func, "demo", 512, NULL, 5, &handle);
xSemaphoreGive(sem);
xQueueSend(queue, &data, portMAX_DELAY);
/* ---------- LiteOS-M 写法 ---------- */
TSK_INIT_PARAM_S task = {0};
task.pfnTaskEntry = (TSK_ENTRY_FUNC)task_func;
task.uwStackSize = 512;
task.pcName = "demo";
task.usTaskPrio = 5;
LOS_TaskCreate(&taskId, &task);
LOS_SemPost(semId);
LOS_QueueWrite(queueId, &data, sizeof(data), LOS_WAIT_FOREVER);
"结构一一对应,只是换了套名字。"老陈总结。
3.2 它有多小
“LiteOS-M 的内核本身有多小?”
"裁剪到最小,十几 KB。"老陈说,“Hi3861 有 352 KB SRAM,其中内核和基础系统服务占掉一部分,剩下的才是你的应用和协议栈。”
他把白板擦出一块,写了个内存示意:
Hi3861 SRAM 352 KB 的粗略分配
┌───────────────────────────────┐
│ LiteOS-M 内核 + 基础服务 │ ~ 几十 KB
├───────────────────────────────┤
│ Wi-Fi / BLE 协议栈 │ ~ 一百多 KB ← 大头在这
├───────────────────────────────┤
│ Drivers / 文件系统 / 网络 │
├───────────────────────────────┤
│ 你的应用 │ ← 剩给你的
└───────────────────────────────┘
"看见了吗?在这种设备上,协议栈比你想象中占地方得多。"老陈说,“这就是为什么要裁剪——你不联网的话,把 Wi-Fi 协议栈砍掉,能省出一大半内存。”
3.3 它不能干什么
"LiteOS-M 最重要的事,是知道它做不到什么。"老陈的语气严肃了一点:
- 没有虚拟内存。所有任务共享同一片物理地址空间,一个任务数组越界,可能把另一个任务的栈踩烂。
- 没有进程隔离。不存在"进程 A 崩了不影响进程 B"这回事。
- 没有完整的文件系统(可以有轻量的,但不是必须)。
- 没有动态加载。所有代码一次性编译进镜像,不能运行时装新程序。
- 没有多用户 / 权限体系。
"但它有确定性。"老陈话锋一转,“中断响应是微秒级、可预测的。这一点 Linux 给不了你。”
“什么时候非得用它?”
"工业控制、电机驱动、电池供电的传感器、任何’必须在 X 微秒内响应’的场合。"老陈说,“你让 Linux 去控制一个每秒转一万次的电机,调度延迟一抖,电机就废了。LiteOS-M 不会抖。”
四、第二种身法:LiteOS-A
"往上走一层。"老陈拿起中间那块 Hi3516DV300。
4.1 从"任务"到"进程"
“LiteOS-A 和 LiteOS-M 的最大区别,就一个字——进程。”
他在白板上画了两条线:
LiteOS-M LiteOS-A
───────────── ─────────────
任务 A ┐ 进程 A ── 线程 A1, A2 ← 独立地址空间
任务 B ├─ 共享同一片地址空间 进程 B ── 线程 B1 ← 独立地址空间
任务 C ┘ 进程 C ── 线程 C1, C2 ← 独立地址空间
↑ ↑
一个越界全遭殃 A 崩了 B 没事
"LiteOS-A 支持 MMU,有虚拟内存,有用户态和内核态之分。"老陈说,“这意味着——”
- 进程之间有独立地址空间,一个崩了不会拖垮别人
- 用户程序不能直接碰硬件,必须走系统调用
- 可以支持动态加载、多用户权限
“听起来很像 Linux 了?”
"思路是,但它比 Linux 轻。"老陈说,“LiteOS-A 的内核代码量比 Linux 小两个数量级,启动快,实时性比 Linux 好。它的定位是——给那些需要进程隔离、但又跑不动完整 Linux 的设备。”
“比如?”
“智能摄像头的 SOC、车机里的 MCU、带屏的工控终端、智能音箱。内存 1 MB 到 128 MB 这一档,就是它的地盘。”
4.2 它的尴尬位置
"不过我得跟你说实话。"老陈的语气变了变,“LiteOS-A 现在的位置有点尴尬。”
“为什么?”
"因为内存这几年越来越便宜。"老陈说,“原本需要 LiteOS-A 的那档设备,现在厂商发现——干脆直接上 Linux 省心,反正 128 MB 内存也没多少钱,而且 Linux 的驱动生态、社区支持比 LiteOS-A 强太多了。”
他顿了顿:
"所以现在业内主流是两头走:
极小的(< 1MB) → LiteOS-M ← 没有替代品,必须用
中等的(1-128MB) → Linux ← 越来越多人直接选它
大的(> 128MB) → Linux ← 唯一选择
“LiteOS-M 和 Linux 是主力,LiteOS-A 你要知道它存在、知道它干嘛的,但不用花太多时间。”
我把这句记了下来——这是那种只有干过的人才会告诉你的实话。
五、第三种身法:Linux
"最后是 Linux。"老陈指着那块 RK3568。
“标准系统的 OpenHarmony,内核就是 Linux LTS(长期支持版本)。你熟悉的那套 Linux,驱动、文件系统、网络协议栈、进程调度,全都在。”
“那 OpenHarmony 在 Linux 上做了什么?”
"在它上面搭了一整套’鸿蒙的东西’。"老陈说,“Linux 只负责最底下的进程管理、内存管理、驱动;上面那些——分布式软总线、ArkUI、Ability 框架、方舟运行时——全是 OpenHarmony 自己的。”
┌─────────────────────────────────────┐
│ OpenHarmony 应用层(ArkTS App) │
├─────────────────────────────────────┤
│ Ability 框架 / ArkUI / 方舟运行时 │
├─────────────────────────────────────┤
│ 分布式软总线 / 分布式数据 / 图形 / AI │
├─────────────────────────────────────┤
│ HDF 驱动框架 │
├─────────────────────────────────────┤
│ Linux Kernel(LTS) │ ← 这一层是你熟悉的
└─────────────────────────────────────┘
"所以你要是 Linux 基础好,学标准系统的鸿蒙会非常快。"老陈说,“你已经有底子了,只是往上加料。”
“那驱动呢?Linux 的驱动能直接用吗?”
"能,但鸿蒙推荐走 HDF。"老陈说,“这就是我接下来要讲的东西。”
六、HDF:把驱动统一起来
老陈在白板上写下四个字母:HDF。
“**HDF,Hardware Driver Foundation,硬件驱动框架。**这是 OpenHarmony 南向最核心的设计,没有之一。”
6.1 传统驱动开发的痛
“先说没有 HDF 的世界长什么样。”
他画了三个框:
FreeRTOS 上写一个 I2C 驱动 → 用 FreeRTOS 的 API
Linux 上写一个 I2C 驱动 → 用 Linux 的 i2c_driver 模型
某个私有 RTOS 上写 → 用厂商自己发明的 API
结论:三种系统,三份完全不同的代码,三个不同的人维护
"一个芯片厂商,卖同一颗传感器,要给五种系统写五套驱动。"老陈说,“这就是传统嵌入式最疼的地方——重复造轮子,而且每造一个都有 bug。”
6.2 HDF 怎么解决
“HDF 的做法是:把驱动的模型统一,把差异用配置描述。”
他画了一张简图:
┌──────────────────────────────────────────┐
│ 应用程序 / 系统服务 │
│ ↑ 通过 HDI 接口调用 │
├──────────────────────────────────────────┤
│ HDI(硬件设备接口,标准化) │
├──────────────────────────────────────────┤
│ HDF 核心框架 │
│ · DeviceManager(设备管理) │
│ · DeviceHost(驱动宿主进程) │
│ · DriverLoader(驱动加载) │
│ · 服务管理 / 消息机制 / 事件通知 │
├──────────────────────────────────────────┤
│ 平台驱动(GPIO/I2C/SPI/UART...)│ 器件驱动 │
├──────────────────────────────────────────┤
│ 操作系统内核(LiteOS-M / LiteOS-A / Linux)│
└──────────────────────────────────────────┘
“几个关键概念,你先记住名字:”
| 概念 | 是什么 | 一句话理解 |
|---|---|---|
| HDI | Hardware Device Interface | 驱动对外暴露的标准接口,上层只认它 |
| Device Host | 驱动宿主 | 装驱动的"容器",可以放内核态也可以放用户态 |
| Device Node | 设备节点 | 一个具体的设备实例,在配置文件里声明 |
| Driver Entry | 驱动入口 | 驱动的三个回调:Bind / Init / Release |
| HCS | HDF Configuration Source | 用文本描述硬件配置的文件,编译成二进制 |
| Dispatch | 消息分发 | 上层调驱动时,请求从这里进来 |
6.3 最狠的一点:一次开发,多内核部署
"HDF 最大的价值是这个——"老陈加重语气:
同一个驱动源码,编译进 LiteOS-M 能用,编译进 Linux 也能用。
“怎么做到的?”
"靠 HCS 配置文件 + 内核适配层。"老陈说,“驱动代码里只调用 HDF 提供的统一接口(比如 GpioWrite()、I2cTransfer()),这些接口在不同内核上有不同实现,但对你的驱动来说长得一模一样。你只需要在编译的时候,通过 HCS 说清楚:这个设备挂在哪个总线上、地址是多少、中断号是几。”
/* 驱动代码:三种内核上完全一样 */
static int32_t MySensorReadData(struct MySensorDev *dev, uint8_t *buf, uint32_t len)
{
/* 用 HDF 统一的 I2C 接口,不关心底下是 Linux 还是 LiteOS */
struct I2cMsg msg = {
.addr = dev->i2cAddr,
.flags = I2C_FLAG_READ,
.len = len,
.buf = buf,
};
return I2cTransfer(dev->i2cHandle, &msg, 1);
}
/* HCS 配置:只描述"它接在哪儿",不写死代码 */
device_sensor :: device {
match_attr = "my_sensor";
busNumber = 1; /* I2C 总线 1 */
devAddr = 0x44; /* 器件地址 */
irqNumber = 25;
}
"换一块板子,改配置;换一个内核,不用改代码。"老陈说,“这句话你记一辈子都不亏。”
6.4 两种驱动:内核态和用户态
“还有个设计很聪明——驱动可以跑在用户态。”
┌─────────────────┐ ┌─────────────────┐
│ 用户态驱动 │ │ 内核态驱动 │
│ (User Space) │ │ (Kernel Space) │
├─────────────────┤ ├─────────────────┤
│ 崩了:宿主进程重启 │ │ 崩了:整个系统挂 │
│ 调试:gdb 随便挂 │ │ 调试:只能打日志 │
│ 性能:多一次 IPC │ │ 性能:最快 │
│ 适用:大多数外设 │ │ 适用:性能敏感/核心 │
└─────────────────┘ └─────────────────┘
"用户态驱动崩了不会让系统死机——这对量产设备太重要了。"老陈说,“你想想,一个智能门锁因为某个传感器驱动的一个空指针,整个门锁变砖,用户被锁在门外——这锅谁背?”
“所以一般驱动都放用户态?”
"大部分是,但不是全部。"老陈说,“对时序要求极严的(比如屏幕刷新的 MIPI-DSI)、或者必须在中断上下文里干的,还是得放内核态。HDF 允许你自由选择,这是它灵活的地方。”
七、裁剪:100 G 硬盘的真正原因
"最后一个问题。"我举起手,“你说让我清 100 G 硬盘。我现在懂了——因为要编译三种内核?”
"不全是。"老陈摇头,“因为你要下载的是整套 OpenHarmony 源码,而不是某一个内核。”
他在白板上算:
OpenHarmony 完整源码树(含历史与全部子系统) :数十 GB
+ 编译工具链(LLVM / GCC / hb / gn / ninja) :数 GB
+ 预编译的二进制(prebuilts) :数 GB
+ 编译中间产物(out 目录) :数十 GB ← 真正的吞盘兽
────────────────────────────────────────────
合计峰值:100 GB 起,建议留 200 GB
"最占地方的不是源码,是编译出来的 out 目录。"老陈说,“一套标准系统的完整编译,中间产物能有几十个 G。”
他看我表情不太好看,笑了:
“不过你运气好——第一站是轻量系统。”
“轻量系统编译小?”
"小得多。"老陈说,“Hi3861 那套,源码加编译产物,十几二十个 G 就够了。等你以后去编标准系统,那才是真正的硬盘杀手。”
“那为什么还要我先清 100 G?”
"因为我不想你明天跑来跟我说’老师我硬盘满了’。"老陈把 Hi3861 推给我,“拿回去,今晚先把 Ubuntu 装好。”
“要装 Linux?”
"必须。"老陈说,“OpenHarmony 的编译环境,官方支持的就是 Ubuntu。你在 Windows 上能写北向的 ArkTS,但南向编译,必须 Linux。”
他用记号笔在白板上写下一行大字:
开发环境:Windows + Ubuntu(虚拟机或双系统)
├─ Windows → DevEco Studio(写北向应用)
└─ Ubuntu → 南向编译、烧录、串口调试
“虚拟机可以吗?”
“可以,但配置给足。“老陈竖起手指,”**4 核 CPU、8 GB 内存、100 GB 磁盘,这是底线。**低于这个,编译会卡到你怀疑人生。”
=================小剧场模式=================
我:老师,我还是有点绕。三种内核、三种系统形态、KAL、HDF……它们到底怎么对上的?
老陈:我给你捋一遍。
我:(掏出笔)
老陈:系统形态是"这个设备有多胖"——轻量 / 小型 / 标准。这是从产品角度看。
我:嗯。
老陈:内核是"底下踩的是谁"——LiteOS-M / LiteOS-A / Linux。这是从技术角度看。
我:对应关系是?
老陈:轻量系统配 LiteOS-M;小型系统配 LiteOS-A 或 Linux;标准系统配 Linux。基本是一一对应,但小型系统有得选。
我:那 KAL 和 HDF 在哪儿?
老陈:KAL 在内核和上层之间,让上层不知道底下是谁。 HDF 在内核和驱动之间,让驱动不知道底下是谁。
我:(愣住)……所以它们俩干的是同一类事?
老陈:(点头)**都是加一层抽象,把差异藏起来。**区别是 KAL 抽象的是"系统能力",HDF 抽象的是"硬件能力"。
我:明白了。上面往下看,看不见内核;驱动往下看,也看不见内核。
老陈:对。这就是 OpenHarmony 能一套代码打天下的全部秘密。
========================================
八、一张表收尾
下课前,老陈在白板上留了最后一张表,说让我抄回去贴在床头:
| 维度 | 轻量系统 mini | 小型系统 small | 标准系统 standard |
|---|---|---|---|
| 典型内存 | 128 KB ~ 1 MB | 1 MB ~ 128 MB | 128 MB 以上 |
| 处理器 | MCU(Cortex-M / RISC-V 32) | 应用处理器(Cortex-A) | 应用处理器(Cortex-A) |
| 内核 | LiteOS-M | LiteOS-A 或 Linux | Linux |
| 进程模型 | 无进程,只有任务 | 有进程 / 线程 | 完整进程 / 线程 |
| 虚拟内存 | 无 | 有(MMU) | 有(MMU) |
| 实时性 | 强(微秒级) | 较强 | 一般 |
| 文件系统 | 轻量可选 | 支持 | 完整 |
| 动态加载 | 不支持 | 支持 | 支持 |
| 典型设备 | 门锁、传感器、开关 | 摄像头、音箱、车机 T-Box | 手机、平板、智慧屏 |
| 入门开发板 | Hi3861 | Hi3516DV300 | RK3568 |
| 业内真实使用度 | ★★★★★ | ★★☆☆☆ | ★★★★★ |
"最后一行什么意思?"我问。
"LiteOS-A 那一列,知道就行。"老陈说,“你的精力应该放在** LiteOS-M 和 Linux 两头**,中间那个了解概念足够。”
他收拾东西准备走,走到门口又回头:
“对了,明天带上你的电脑,我们装系统、拉源码、编第一个固件。”
“编出来能干什么?”
“让那颗蓝灯,按你的意志闪烁。”
本章自检
过一遍下面的问题。答不上来的,回去翻对应小节——那些就是你这个章的欠账。
- ☐ 用自己的话解释:为什么同一套系统能跑在 128 KB 和 8 GB 的设备上?
- ☐ KAL 抽象的是什么?HDF 抽象的是什么?
- ☐ 三种内核各自有什么、没有什么?
- ☐ 为什么轻量系统没有「进程」这个概念?
- ☐ HDF 的 policy 和 preload 是干嘛的?(知道有这回事就行,第 04 章细讲)
**➡️ 下一站:**第 03 章 · 铸剑 —— 开始动手,也是全书劝退率最高的一章。
=================剧情片段=================
导演:卡。这一章概念密度挺高的,观众能消化吗?
助理:内核、KAL、HDF、系统形态……确实多。但都落在"为什么"上了——为什么要有 KAL、为什么要有 HDF、为什么要分三种形态。
导演:讲技术最忌讳只讲"是什么",不讲"为什么要有它"。 观众记住"它解决了什么痛",自然就记住它了。
助理:下一章呢?
导演:终于要动手了。
助理:《铸剑》。
导演:对。环境搭建——Ubuntu、DevEco、源码拉取、hb 编译、烧录、串口。这一步是新手劝退率最高的地方。
助理:要不要多写点踩坑?
导演:必须。 别人的踩坑记录比官方文档有用。权限问题、Python 版本、pip 源、hb 找不到、串口没权限、烧录失败……每一个都配上症状和解法。
助理:那下一章会比较长。
导演:长就对了。这一章救的人最多。
========================================
未完待续。。。


