0
0
0

鸿蒙造物者之路 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 找不到、串口没权限、烧录失败……每一个都配上症状和解法。

助理:那下一章会比较长。

导演:长就对了。这一章救的人最多。

========================================

未完待续。。。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论

欢迎来到我的博客!