干 OpenHarmony 开发的,第一次拉完源码基本都会懵一下:目录怎么这么多?明明我只想跑一块 rk3568 板子,结果拉下来几百个仓库、几十个顶层目录,什么 base、foundation、device、vendor、drivers,看名字大概知道是干嘛的,可真让我说清楚每个目录管什么、哪些是当前产品必需的、改代码该进哪个仓,很多人就答不上来了。
这个系列是讲开源鸿蒙 OpenHarmony 系统实战开发的,第一课我就想把源码树给你解剖明白——因为后面所有编译、裁剪、驱动开发、应用移植,全都要在这棵树上动刀。我会从源码树的设计逻辑讲起,逐步拆解每个核心目录,再用 RK3568 这个最常见的开源鸿蒙主控平台,把设备树选择、源码编译流程完整走一遍。内容主要面向刚接触 OpenHarmony 的系统开发者、驱动工程师,以及准备做设备适配的团队。看完你至少能回答两个问题:源码树里到底有什么?我手头的板子该选哪个设备树、怎么编出固件。
1. 先搞清楚:OpenHarmony 源码树是什么样的一张地图
1.1 一个系统,几百个仓库
OpenHarmony 不像 Linux kernel 那样一个 tar 包就是全部。它采用的是多仓模式:每个子系统、甚至子系统里的每个关键组件,都是一个独立的 git 仓库,发布时由一个 manifest 仓库里的 xml 文件把几百个仓库的版本号统一锁定。你用 repo 工具拉下来的所谓“源码树”,本质上是这些仓库在本地按约定路径检出后的集合。
为什么要这么干?核心原因是 OpenHarmony 覆盖的设备跨度太大了:从几百 KB 内存的传感器设备,到几个 GB 内存的开发板、电视、平板,再到未来的 PC 形态。不同硬件需要的组件完全不一样,多仓 + 按需组装,才能做到“同一个系统底座,按产品裁剪”。如果像传统系统那样给一个全量源码包,光是解压和编译就能劝退一大批人。
所以看 OpenHarmony 源码树,第一件事是别把它当成一棵“整树”,而要当成一张“地图”:顶层目录划分的是子系统边界,每个目录又是一块可独立替换的积木。用乐高来类比最贴切——每一袋积木是独立分包的,按说明书(manifest)组装成你要的模型,用不到的袋子可以先扔一边。
1.2 顶层目录的分层逻辑
OpenHarmony 顶层目录并不是乱来的,它暗合了一套从硬件到应用的分层逻辑:
| 层级 | 顶层目录 | 职责 | 典型内容 |
|---|---|---|---|
| 应用层 | applications/ | 系统自带的标准应用 | Launcher、Settings |
| 框架层 | foundation/、base/ | 系统服务、分布式能力、UI 框架 | 软总线、分布式调度、ArkUI |
| 接口层 | interface/ | SDK 接口定义 | 对外 API 声明 |
| 内核层 | kernel/ | 内核源码 | liteos-a / liteos-m / linux |
| 驱动层 | drivers/ | HDF 驱动框架 | hdf_core、框架适配层 |
| 硬件适配层 | device/、vendor/、soc/ | 芯片、单板、产品适配 | rk3568 板级配置 |
| 构建与工具 | build/、productdefine/、prebuilts/ | 构建系统、产品定义、工具链 | hb、GN、预编译工具 |
| 三方库 | third_party/ | 引用的开源软件 | zlib、mbedtls 等 |
这个分层不是死的,但理解它很重要。拿到一块新板子,第一件事是在 device/、vendor/、kernel/ 里找适配;做应用开发,主要关注 applications/ 和 foundation/arkui/;接外设传感器,往 drivers/ 和对应板级 hcs 配置里加东西。我建议新手花半天时间用tree -L 2把源码树结构打出来,对着这张表过一遍,比闷头看代码效率高得多。
提示:不同版本(比如 3.2 Release 和 master)目录划分会有差异,早期版本 base/ 和 foundation/ 的组件归属不完全一样。看源码树时先确认你拉的分支,避免拿旧地图导航新城市。
2. 核心目录逐个解剖:每个目录里到底有什么
2.1 kernel/:一套系统,三种内核
kernel/ 下面是三个候选内核:liteos-a(面向中大型 IoT 和带屏设备)、liteos-m(面向 MCU 级小型设备)、linux(标准系统的主力)。标准系统——也就是跑得了 rk3568 这种带 GPU、能跑富 UI 的系统——用的是 linux 内核,OpenHarmony 在主线 linux 基础上加了针对分布式场景的增强补丁,以及 HDF 驱动框架在内核侧的对接。
选择逻辑其实很清楚:先看产品形态,再看内存和实时性需求。做智能摄像头、带屏中控,通常选 linux;做温湿度传感器、智能开关这类 MCU 设备,走 liteos-m;介于中间的复杂 IoT 设备可以考虑 liteos-a。kernel 目录下的 README 也会写清楚每个内核的适用场景,别一上来就纠结,跟着产品需求走就行。
2.2 device/、vendor/、soc/:硬件适配的“三兄弟”
这是最容易混的三个目录,我刚入坑时也绕了很久。一句话总结:soc 目录放的是芯片厂商(比如 rockchip)提供的平台公共代码,board 目录放的是具体单板(比如某厂商的 rk3568 开发板)的板级代码,vendor 目录放的是“产品”定义——也就是这块板最终做成什么、带哪些应用和配置。
以 rk3568 为例,你会看到这样的代码分布:
- device/soc/rockchip/rk3568/:芯片平台公共适配,比如 GPU、多媒体编解码这类 SoC 级能力;
- device/board/xxx/rk3568/:具体开发板特有的引脚、屏幕、触摸、电源配置;
- vendor/xxx/rk3568/:产品配置,比如打包镜像、预置应用、config.json。
三者层层依赖:soc 提供基础,board 决定板级细节,vendor 决定交付物。做适配时,芯片原厂没动过的东西不要自己乱改,优先改 board 层和 vendor 层,这样以后芯片平台代码升级时冲突最小。我见过有人直接去改 soc 层的公共 dtsi,结果一升级平台代码,全部冲突,血泪教训。
2.3 foundation/ 与 base/:分布式能力的“大本营”
OpenHarmony 区别于其他系统的核心卖点是分布式。这部分代码绝大多数在 foundation/ 和 base/ 两个目录里。foundation/ 下面是子系统,比如 communication(分布式软总线)、distributedschedule(分布式调度)、arkui(ArkUI 声明式 UI 框架)、multimedia(多媒体框架)等;base/ 下面是更底层的公共能力,比如 hiviewdfx(日志/故障管理)、security(安全)、startup(启动恢复)等。
为什么拆成两个顶层目录?我理解是按“复用层级”来区分:base/ 更底层、更通用,上层框架和服务都依赖它;foundation/ 相对上层,可以直接支撑应用开发。做普通应用开发,打交道最多的是 foundation/arkui 和 foundation/ability(元能力框架);做系统定制,base/ 里的 DFX、启动恢复几乎必动。搞清楚这个从属关系后,定位问题的范围能缩小一大半。
2.4 drivers/:HDF 驱动框架,设备接入的标准姿势
OpenHarmony 的设备驱动不推崇直接写一堆内核模块草草了事,而是有一套统一的硬件驱动框架 HDF(Hardware Driver Foundation),代码集中在 drivers/。它分两部分:一部分是框架本身(hdf_core、framework),另一部分是与具体内核/硬件适配的 adapter。
HDF 的典型工作方式是“配置驱动 + 代码实现”:驱动能力用 HCS 配置文件描述,比如一个 I2C 触摸屏驱动,你要在 device_info.hcs 里注册它的 device node,在对应 .hcs 里配置总线、地址、中断引脚,然后驱动代码按 HDF 规范实现接口。这样做的好处是换芯片、换板子时,驱动代码可以不改,只改配置就能适配。这也是 OpenHarmony 设备适配速度能快起来的底层原因。
2.5 build/ 与 productdefine/:构建指挥中心
编译 OpenHarmony 不是直接敲 make,而是用 hb 工具。hb 本身就在 build/ 里。构建的入口逻辑是:先有产品定义(productdefine/products 里的 json),产品定义聚合需要的子系统、部件,构建系统再去对应仓库里找组件的 BUILD.gn 来编译。
productdefine/ 里主要看两类文件:products/ 下的产品 JSON(比如 rk3568.json),声明了产品名、平台、依赖子系统列表;common/ 下是公共部件定义,把 OpenHarmony 的所有子系统按部件粒度登记。你要裁剪系统,就是改产品 JSON,把不需要的子系统从列表里拿掉。这个设计把“系统有什么”和“产品要什么”解耦了,非常实用。我优化开机速度时,第一步就是打开产品 JSON 看哪些重型子系统是被误拉进来的。
2.6 容易被忽略但迟早要用的目录
除了上面几个大头,还有几个目录不常被提起,但实战中迟早会碰上:
- ark/:ArkTS 的编译器和运行时,做应用性能优化或研究方舟编译器时来这里;
- third_party/:几百个第三方开源组件,编译报缺头文件、缺库时来这里找;
- prebuilts/:预编译工具链、python 环境等,编译环境出问题先怀疑这里;
- interface/:SDK 头文件、IDL 定义,应用开发者查接口签名的地方;
- test/:各子系统测试用例,既有 xdevice 自动化测试平台,也有模块单测。
3. 实战走一遍:RK3568 的源码树选型、设备树选择与编译
3.1 为什么 rk3568 在源码树里有一堆设备树
打开内核源码里 arch/arm64/boot/dts/rockchip/ 目录,你会看到一堆 rk3568 开头的设备树文件:rk3568-evb1-ddr4-v10-linux.dtb、rk3568-evb2-lp3-v10-linux.dtb、rk3568-dayu200.dtb……新手看到就懵了,到底该选哪个。
原因是 RK3568 这颗 SoC 被大量板卡复用,但每块板子的硬件细节不一样:DDR 是 lp3 还是 lp4/ddr4,屏幕是哪家的 panel,触摸是 I2C 还是 USB,网口有几个,全都反映在设备树里。设备树本质是“硬件的描述清单”,内核靠它知道怎么初始化板子。选错设备树,轻则某个外设不工作,重则直接起不来。
所以问题不是“哪个设备树通用”,而是“你的板子到底是什么硬件”。开发板厂商出厂的固件,用的设备树一定和板子硬件一致,你只要找到对应名字即可。以常见的 rk3568 开发板(比如 dayu200 这类产品)为例,产品配置走的是 dayu200 路径,最终用到的就是 rk3568-dayu200.dtb 对应的那套 dts。
3.2 三步锁定你的产品该用哪个设备树
第一步,看产品定义。在源码根目录执行hb set,会列出当前源码树支持的产品;或者在 productdefine/products/ 下找对应 json,比如 rk3568.json 或 dayu200.json,确认你当前是哪个产品。
第二步,顺着产品定义找 board。在 vendor/ 对应厂商目录下的 config.json 里,会写明 board 是哪个,类似"board": "rk3568";再到 device/board/ 对应厂商目录下,找到这块板的 kernel 构建脚本。
第三步,看构建脚本里的设备树指向。rk3568 的 kernel 构建脚本(常见路径是 device/board/xxx/rk3568/kernel/build_kernel.sh)会调用内核编译命令并指定 dtb 参数。你实际烧到板子上的 boot 镜像里打包了哪个 dtb,就是由它决定的。
注意:不同厂商、不同版本路径名有差异,但“产品 → 板子 → 内核构建脚本 → dtb”这条链路是通用的。找不到就沿着这个链路的每一步去 grep,一定能定位到。
3.3 修改设备树与重新编译的标准动作
实战中经常要改设备树,比如点亮一块新屏幕、打开一个串口、配置一个 GPIO。标准动作是这样的:
- 找到对应板子的 dts/dtsi 文件,通常在 kernel 源码 arch/arm64/boot/dts/rockchip/ 下,注意优先继承公共的 rk3568.dtsi,不要什么都堆在主 dts 里;
- 按设备树语法添加或修改节点,比如增加 I2C4 下的触摸屏节点,配置好 reg、中断脚、复位脚;
- 重新编译内核和 boot 镜像(
hb build -f或单独编 kernel 目标); - 烧录后通过串口确认设备树加载是否正常,进入系统后可在 /sys/firmware/devicetree/base 下检查节点是否存在。
一个容易踩的坑:HDF 驱动环境下,很多外设参数应该走 HCS 而不是 dts。有人把设备树改了又改,外设还是没反应,其实那个设备根本没走 Linux 原生驱动,而是被 HDF 接管了,改 dts 自然没用。判断方法很简单:看这个外设在源码树里有没有对应的 HDF 驱动目录。
3.4 从源码树到固件:完整编译流程复盘
以 Ubuntu 20.04 + rk3568 标准系统为例,我把完整流程走一遍。先准备环境:装好 python3.8+、git、gcc 等基础工具,磁盘至少留 100GB(源码加编译产物),内存建议 16GB 以上。
获取源码:
repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify repo sync -crepo sync 很容易被网络波动打断,中断后重新执行即可,增量同步不会重复下载已完成的部分。
安装 hb 构建工具:
cd build && ./build_scripts/hb_install.sh source ~/.bashrc验证是否可用:hb -h。然后选择产品并编译:
hb set # 在弹出的列表里选你的板子对应产品 hb build -f第一次编译时间比较长,rk3568 全量构建在主流配置机器上动辄一两个小时起,耐心等。编译产物在 out/ 目录下,标准系统镜像一般在 out/{产品名}/packages/phone/images/ 下,包括 boot.img、system.img、vendor.img、userdata.img 等。刷机用开发板厂商提供的烧录工具即可。如果你以后尝试 x86 形态的 OpenHarmony,流程也是一样的,换一个 x86 产品定义再 hb build,硬件适配层的关注点从 dts 变成对应的启动协议而已。
4. 源码树实战问题排查:我把踩过的坑都列给你
4.1 仓库同步与版本不配套问题
最常见的三个坑,逐个说。
第一,manifest 分支和源码分支不一致。比如 manifest 用的 master,但你手动把某个仓切到了其他分支,编译时很容易出现接口对不上。建议除了自己要改的仓,其他仓保持 manifest 锁定的版本不动,不要手痒去切分支。
第二,repo sync 中断后部分仓不完整。补一次repo sync -c多数能解决;个别仓反复失败,可以先 repo start 一个新分支再 sync,让本地指针归位。
第三,少拉子仓。官方 manifest 已经锁定了整套源码树,不建议随便加第三方裁剪脚本或本地清单,否则有的组件缺失,编译失败时排查成本极高。
4.2 设备树选错或改错后的典型症状
把常见症状和排查方向整理成一张表,遇到问题直接对号入座:
| 症状 | 可能原因 | 排查思路 |
|---|---|---|
| 开机卡在内核早期,串口无输出 | dtb 与硬件不匹配,或 dts 内存配置不对 | 确认 DDR 类型,核对对应 dtsi |
| 屏幕黑屏但系统起来了 | dts 里 panel 时序或使能脚不对 | 用 hdc 看系统日志,查 display 服务状态 |
| 触摸无反应 | I2C 地址/中断脚不对,或驱动没加载 | 核对触摸节点与硬件原理图,dmesg 查 I2C |
| 网口/串口不通 | pinctrl 复用冲突 | 检查 dts 里 pinctrl 配置,排查外设冲突 |
排查工具无外乎三样:串口日志(内核阶段)、hdc shell(系统阶段)、dmesg/hilog(运行日志)。建议新板子到手第一件事就是把串口接好,串口日志能省半条命。我调试新板子时必开串口,比反复猜原因高效太多。
4.3 在源码树里快速定位代码的方法
源码树几百个仓,直接在顶层 find 或 grep 会慢到怀疑人生。我的习惯是分四步。
- 先判断功能属于哪个子系统。比如“开机画面”大概率在 foundation/graphic 或应用的 bootanimation 里,参考前面分层表缩小范围;
- 在对应子系统目录里 grep 关键符号。例如找开机动画:
grep -r "BootAnimation" foundation/ --include="*.cpp"; - 用编译日志反查。编译时看 out 目录下的 ninja log,能看到具体编译了哪些源文件,顺藤摸瓜比猜路径快;
- 借助 IDE。VSCode 打开源码树后让索引跑完再用全局搜索;如果机器性能一般,只把你研究的几个仓加进工作区,体验会好很多。
4.4 源码级调试的三板斧
最后说调试。OpenHarmony 标准系统源码级调试,我日常用得最多的是三招。
第一招加日志:应用层用 OH_LOG,内核和驱动用 printk 或 dev_info,先确认代码到底有没有走到、关键变量值是多少。别一上来就开调试器,日志永远是最快的。第二招 hdc 连接:hdc shell 进系统,ps -ef 看进程,hilog 看系统日志,需要的话还能拉文件出来分析。第三招抓崩溃:native 崩溃会生成 tombstone,一般在 /data/log/faultlog/temp 下,里面有调用栈,排查崩溃基本靠它。
补一句,如果你改的是内核,最省事的验证方式是单独编 kernel 并快速替换 boot 分区,不用每次全量hb build -f。我第一次全量编了三次,后来才发现单独编内核加打包的时间只有全量的三分之一,做驱动调试能省大量等待时间。