news 2026/10/20 16:57:32

OpenHarmony系统镜像解剖:从稀疏镜像到目录结构与构建配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenHarmony系统镜像解剖:从稀疏镜像到目录结构与构建配置

编译完OpenHarmony的某一天,我看着out目录里静静躺着的几个镜像文件,突然产生了一个念头:这几个GB的产物里到底装了什么?平时构建脚本跑得昏天黑地,一长串BUILD.gn、config文件填进去,最后出来的东西实际长什么样?于是花了一个周末的时间,把镜像从头到尾翻了个底朝天,从分区划分、文件系统格式,到内部目录、配置和服务进程,算是把这些镜像摸了一遍。这篇文章就是一份面向OpenHarmony镜像内容的拆解笔记,重点记录解剖镜像常用的工具、内部目录的职能划分、镜像内容与构建配置的对应关系,以及我在实际操作中踩过的几个坑。适合刚接触这套开源系统、想弄明白产物从哪里来、从哪里下手的开发者参考。

1. 编译落盘之后,产物里都有哪些镜像文件

1.1 镜像文件的常见构成与用途

先看编译完成后out目录下到底有什么。不同版本、不同产品配置下路径会有差异,但一般来说,编译完一套OpenHarmony标准系统,会在构建输出目录里看到这几个主要镜像:

镜像文件主要用途常见文件系统
system.img系统主体镜像,包含系统服务、应用框架、内置应用、系统配置文件ext4 或 erofs
vendor.img厂商定制分区,存放硬件相关配置、厂商特有服务和驱动库ext4 或 erofs
userdata.img用户数据分区,首次启动时格式化,通常不预置关键数据f2fs 或 ext4
updater.img升级镜像,用于设备OTA升级流程ext4
boot.img内核启动镜像,包含内核和内存盘(或引导参数)内核格式

我只把system.img解开过,vendor.img的结构和它类似,只是内容更偏硬件。如果是研究系统启动流程,system.img优先;如果是排查外设、驱动、传感器这类问题,就要去vendor.img里找。

1.2 镜像的“真身”:稀疏镜像与文件系统格式

在解剖镜像之前,有个概念必须先弄清楚:某些镜像文件本身并不是直接可挂载的文件系统块设备,而是稀疏镜像(sparse image)。稀疏镜像的作用是省空间——文件系统里大片空白区域在镜像里不连续存储,用描述信息代替,这样镜像文件体积会比实际文件系统容量小很多。用file命令看一眼就能分辨:

$ file system.img system.img: Android sparse image, version 1.0, Total of 262144 4096-byte output blocks in 64 input chunks

输出里出现“Android sparse image”字样,就说明这是稀疏格式。有些版本的工具链会直接产出非稀疏的raw镜像,但出于体积考虑,大部分官方产物都是稀疏的。所以,解包前第一步往往是先判断:能不能直接mount,还是需要先转成raw格式。

判断方法很简单,除了file命令,还可以用十六进制查看工具看一眼文件头。如果前几个字节出现了3a ff 26 ed这一串,就是sparse格式的魔数;如果是正常的ext4文件系统,magic number会是0xEF53(小端写入时显示为53 ef)。

1.3 镜像体积哪里来的:一次直观的估量

我习惯在解包前先用ls -lh看一眼镜像体积,再用du和df盘一下编译产物,大致推测系统大小分布。以我编译的一套标准系统为例,system.img在2GB左右,vendor.img在500MB左右,userdata.img虽然文件本身很小,但实际分区容量可能很大。这个比例能给你一个先验认知:系统大部分体积来自动态库、内置应用、字体和配置文件。后面解包后如果看到bin目录、lib目录特别大,就不会意外了。

搞清楚镜像的基本形态之后,接下来就是真正动手拆开它。

2. 动手解剖镜像:从挂载到逐个目录翻看

2.1 解开稀疏镜像,先把它变成可挂载的raw格式

拿到稀疏镜像后,第一步是转成raw。这个操作要用的工具叫simg2img,在Ubuntu等Linux发行版里一般包含在android-tools-fsutils包里,也可以直接搜索安装:

sudo apt install android-tools-fsutils

转换命令非常简单:

simg2img system.img system_raw.img

转换完成后再用file确认,如果输出变成了Linux rev 1.0 ext4 filesystem data这类信息,就说明已经是raw格式,可以继续挂载。顺带一提,反向工具img2simg可以把raw镜像重新打成稀疏镜像,后面如果要改文件再打包,会用到它。

有一点要注意:并不是所有OpenHarmony镜像都是sparse格式。有的版本或产品配置会直接生成system_ext4.img这类raw镜像,这时候就不需要simg2img这一步。我建议一律先用file命令确认格式,不要在“以为是raw”的情况下强行挂载,否则mount命令会报错或者行为异常,我在一开始就吃过这个亏。

2.2 挂载system镜像:两种路径都可以

转成raw之后,挂载就有两种方式:

一种是传统mount方式,需要root权限,适合在主机上快速翻看:

mkdir -p ~/ohos_mnt/system sudo mount -o loop system_raw.img ~/ohos_mnt/system

对ext4来说,这样挂载基本就能直接ls了。但如果镜像文件系统是erofs,就要确认内核是否支持,实测下来Linux 5.4以上通常可以直接挂载,但更早的内核就未必了。

另一种是相对“温柔”的方式,用只读挂载,避免不小心改到镜像里的文件。我在调试过程中一般执行sudo mount -o loop,ro system_raw.img,加一个ro,防止操作失误污染镜像。不管用哪种方式,查看完务必umount,不然文件锁和缓存可能影响后续操作。

2.3 解不出镜像时,用binwalk来辅助判断

如果遇到不太常见的文件系统(比如某些产品用f2fs做userdata),主机上未必有现成的工具支持挂载。这时候我会先用binwalk扫一遍文件头,看能识别出什么:

binwalk system.img

binwalk输出里如果识别到ext4、erofs等相关信息,就说明文件系统类型基本明确。如果没有识别结果,可以直接用od -A x -t x1z -N 64 system.img看前64字节的魔数,然后对照已知文件系统magic判断。这个方法虽然笨,但最可靠。毕竟不同OpenHarmony版本的工具链行为不完全一致,不能拿一套经验套所有镜像。

挂载成功后,真正的“内容探秘”才算开始。

3. 镜像内部目录与文件盘点:整个系统是这样的骨架

3.1 系统目录的“职能分工”

把system.img挂载后,用ls -lR或者tree会看到一长串目录。第一感觉是“这结构怎么有点眼熟又有点陌生”。OpenHarmony的系统目录确实吸收了不少成熟操作系统的设计思路,但落地细节完全不同。按我的观察,几个主要目录的职责可以总结成一张表:

目录职责典型内容
/system/etc系统级配置启动参数、服务配置、权限策略、网络配置
/system/bin用户态可执行文件命令行工具、Init进程入口
/system/lib动态库与模块系统服务依赖的.so、驱动框架模块
/system/lib6464位动态库与lib对应,64位版本
/system/app内置应用目录存放.hap或解包后的应用资源
/system/hap部分版本的应用存放路径各路系统应用的hap安装包
/system/usr资源与共享文件字体、音频、图标等
/vendor/etc厂商配置init配置文件、驱动配置文件、硬件相关信息
/vendor/lib、/vendor/bin厂商库与二进制传感器、音视频等硬件相关组件

我第一次看到/system/app里面全是.hap文件时挺意外的,原来系统里大部分“应用”都是以hap形式随镜像预置的,并没有安装到userdata。这也解释了为什么system镜像会这么大:一个内置应用往往就包含了若干动态库和资源文件。

3.2 配置文件里藏着的系统启动链路

翻到/system/etc/init/目录,会看到若干.cfg文件,比如init.cfg、以及各种子系统相关的cfg。这些就是系统启动过程中的核心配置。OpenHarmony的init进程启动时会读取这类配置文件,然后按序启动一系列服务。我在里面看到过很多服务名,基本能对应到代码仓库里的各个子系统模块,比如分布式软总线、应用管理、窗口管理等等。

如果你想快速理解一个系统启动时做了什么,最直接的办法就是读init目录下的cfg文件。它们一般会写明服务名称、可执行文件路径、启动参数、重启策略等内容。举个例子,某个服务如果配置了restart策略,那它崩溃后init会自动拉起;如果不想它在调试时反复重启,临时把这个服务的配置注释掉再打包镜像是一种常用手段。当然,这样改出来的镜像只能用于开发调试,不能当作正式产物。

cfg文件的语法并不复杂,看起来像JSON和脚本的混合体,我建议初次接触时先用一个简单的搜索命令把所有cfg文件列表搜出来,逐一打开看注释,比对着代码猜要快得多。

3.3 核心系统服务:一个大致的进程视图

系统服务方面,解包镜像后可以在/bin下找到很多可执行文件,同时也能从cfg文件里看到它们的启动入口。以标准系统为例,比较有代表性的几个服务包括:

  • appspawn:应用进程孵化服务,负责创建新的应用进程,属于比较底层的进程管理服务。
  • foundation:基础服务框架进程,承载大量系统能力和分布式软总线相关能力。
  • samgr:系统能力管理服务,其他子系统通过它注册和查询系统能力。

看到这些名字,你基本能拼出一张“系统服务图”:init进程拉起samgr完成能力注册,再由foundation承载上层系统能力,应用进程由appspawn孵化。虽然实际链路更复杂,但镜像里这些文件给了你一条很好的线索去对照源码追踪。我在排查一个启动失败问题时,就是靠查镜像里有没有对应服务的可执行文件,快速判断出某个子系统是否真的编进了产物。

3.4 动态库和三方组件:镜像体积的“大头”

如果统计一下镜像里各类文件的体积占比,动态库一定排在前列。/system/lib、/system/lib64以及/vendor/lib下面躺着大量.so文件。这里有个很实际的经验:如果某个功能在运行时提示找不到符号或者加载失败,先到对应镜像的lib目录里看一眼有没有对应的库文件,再判断是编译没打进去,还是运行时加载路径不对。很多启动崩溃问题,根源就是二进制存在但依赖的库没进镜像。

从镜像内容反推构建产物,往往能快速定位一个问题是出在代码逻辑还是出在打包环节。

4. 镜像里的文件从哪来:构建配置反推

4.1 组件化打包与构建文件的关系

OpenHarmony的构建系统是高度组件化的,最终镜像里的每一样东西,基本都有对应的构建来源——要么来自某个子系统的构建目标,要么来自产品配置文件的定制项。一个典型的关系链是:

子系统代码→BUILD.gn中定义的group/component→系统镜像打包阶段→镜像里的具体文件路径

在源码仓库里,每个子系统下都有BUILD.gn,其中有ohos_component或者group之类的声明,用来定义这个组件编译后输出哪些产物、安装到哪个目录。镜像里的bin目录、lib目录下能看到的名字,绝大多数都能在对应子系统的BUILD.gn里找到出处。

如果你想知道某个镜像里的文件是谁打进去的,最快的办法是在源码根目录搜索文件名:

grep -r "my_service" --include="*.gn" --include="*.gni" .

这样能定位到构建文件,再往上追溯到组件的依赖关系。这个“镜像文件溯源”方法,在排查“为什么我明明把代码编进去了,镜像里却没有”这类问题时非常有用。

4.2 产品配置如何决定镜像内容差异

同一个版本源码,编译不同产品配置,得到的镜像内容可能差很多。核心原因是产品定义文件决定了要包含哪些子系统、哪些组件。如果打开产品配置文件(一般在productdefine/products目录下),可以看到类似subsystems、deps信息的配置。产品配置里包含的组件越多,镜像体积越大,镜像里的服务和文件也越多。

我遇到过一个典型的场景:同一套代码,编译A产品时/vendor/etc目录里多了某个传感器的配置文件,编译B产品时就没有。查了半天发现A产品配置里支持了对应的传感器组件,B产品没加。镜像内容差异并不神秘,大部分情况都能在productdefine和vendor配置里找到答案。

4.3 镜像修改的三个边界认知

在动手改镜像之前,心里最好先有个边界,避免改了无效或者改出更严重的问题:

第一,编译期修改才是可持续的。直接修改镜像文件并重新打包,在当时可能有效,但下次编译产物会覆盖手工改动,改动不会保留。真正想把文件持久地加进镜像,还是要从构建配置入手。

第二,文件读取权限和上下文标签。OpenHarmony镜像里的文件带着权限位、owner和SELinux相关标签。手工改镜像时如果不小心破坏了文件的context信息,可能导致设备启动后某服务无法访问这个文件,表现成“权限拒绝”但有的时候不是真的文件权限问题。

第三,镜像签名和校验。正式设备会校验镜像签名,手工改动镜像后通常无法过校验。但在开发调试阶段用的是userdebug/eng版本,校验比较宽松,手工改镜像仍能启动,这也是国内不少开发者能愉快地“改镜像做实验”的背景。一旦要刷回量产设备,这种做法就行不通了,这点一定要分清楚。

理解了镜像内容怎么来,接下来一个很自然的实操需求就是:我想往镜像里加东西——这可能是调试一个库、放一个脚本、或者替换一个配置文件。

5. 实战:往镜像里加入自定义内容

5.1 通过构建配置持久化添加文件

先说正规做法:通过修改产品配置或对应子系统的构建文件,把自定义文件复制到镜像输出目录。以放置一个自定义配置文件到/system/etc为例,比较常见的方式是在所在子系统的BUILD.gn里写一个copy或install条目。下面这个例子是在某个子系统组件里,把一个本地产物复制到系统etc目录下:

# 在BUILD.gn中新增或修改 ohos_copy("copy_my_config") { sources = [ "//vendor/某厂商/产品目录/configs/my_config.cfg" ] dest = "//out/xxx/packages/phone/system/etc/my_config.cfg" install_images = [ "system" ] }

然后把这target挂到对应组件的依赖里,重新编译,镜像重新打包后,这个文件就会出现在system/etc下。这样做的好处是后续重新编译不会丢,改成什么内容都能跟随构建产出。

如果只是想往vendor分区放文件,把install_images改成vendor即可。产品配置文件里也可以按分区调整目标路径,细节随着版本不同略有差异,但核心思路没变。

5.2 直接改镜像的快速实验方案

有时候不适合重新跑完整编译(耗时太长),只是想临时验证一个文件的效果,我通常走“解包-修改-重新打包”的路子。步骤可以压缩成下面这套流程:

  1. 用fsck.ext4 -fn system_raw.img检查镜像的一致性;
  2. 挂载只读副本到临时目录,拷贝需要修改的文件到挂载点;
  3. umount后生成新镜像;
  4. 如果需要稀疏格式再用img2simg打回去。

但这里有个大坑:直接改挂载点里的文件,如果没有处理好,文件类型、属主、权限会不对,特别是动态链接库,修改后如果owner变成了root而原始是其他uid,可能导致进程启动时访问受限。我一般会养成习惯:在挂载前把原始镜像的所有者、文件权限导出保存,修改后逐个比对,把差异修正。

如果只是想快速替换单个配置文件,还有个更简单办法:直接用debugfs在原始raw镜像上操作,不挂载也能写文件。debugfs对ext4特别友好,比如执行下面的命令可以把宿主机上的文件拷入镜像指定路径:

debugfs -w -R "write my_config.cfg /system/etc/my_config.cfg" system_raw.img

注意目录路径要写对,写完后用debugfs -R "ls /system/etc" system_raw.img验证。这个方式对不熟悉的文件系统类型不适用,比如erofs就要用专门工具或者重新挂载。

5.3 验证修改是否生效

验证方式也有讲究,不能光怀一腔信任。如果设备能通过hdc连上,重启后执行:

hdc shell ls -l /system/etc/my_config.cfg hdc shell cat /system/etc/my_config.cfg

确认文件存在且内容正确。如果需要校验文件是否真的参与系统行为,可以看对应服务的日志。在开发调试阶段,我经常在修改一个配置后,直接kill掉对应进程,等init自动拉起,再用日志确认它到底读取了哪个文件。这种方式比反复重启整机快得多,也更不容易误判。

6. 翻镜像时踩过的坑与排查小技巧

6.1 “挂载成功却看不到文件”?多半是overlay或者文件系统机制

我第一次挂载system镜像时,明明mount成功了,ls却只能看到部分文件,甚至有的文件路径就是找不到。排查后发现,OpenHarmony在运行时还会挂overlay文件系统,镜像里的内容在启动后经过overlay叠加和挂载过滤,实际看到的目录结构并不完全等于镜像里的原始布局。而我只在运行中的设备上执行ls,自然看不到某些镜像里确实存在的文件。

所以要有意识地区分“镜像里的视图”和“运行时的视图”。排查问题时,如果设备上找不到某个文件,先回到镜像里确认它是否存在,再考虑是不是启动流程或overlay把它隐藏了。

6.2 erofs镜像的解包注意点

erofs文件系统在较新版本中用得越来越多。它解包方式和ext4不一样,不能用简单的mount解决所有问题,至少我遇到两种情况:

一种是用fsck.erofs或dump.erofs直接读取目录结构。dump.erofs -l system_erofs.img可以列出目录树,虽然没有完整挂载那么直观,但应付查看结构、确认文件是否存在的需求足够。

另一种是让内核直接挂载erofs。前提是内核开启EROFS支持,且镜像不是稀疏格式。如果遇到wrong fs type的报错,先检查一下自己电脑内核版本,别再硬挂。我自己的习惯是优先用dump.erofs,不依赖内核特性,少踩兼容性的坑。

6.3 userdata镜像为什么几乎解不出有用的东西

第一次翻镜像时,我把userdata.img也解了一遍,结果翻完就后悔了。userdata分区通常是空壳,首次启动时系统会在设备上对它格式化,里面不会有任何预置的系统文件。如果在userdata.img里找不到东西,别怀疑自己“拆错了”,这是完全正常的情况。

要调试用户数据分区相关问题,最好是设备启动后直接通过hdc进入shell查看/data目录,而不是解包镜像。

6.4 镜像解包前的“先确认快照”习惯

在解包修改镜像之前,我建议先把原始镜像的SHA256值记下来。理由是:你修改后的镜像一旦刷到设备上出了问题,至少能迅速判断问题是不是自己改动导致的。如果原始镜像能正常启动,修改后启动失败,优先回头看改动内容——很多问题其实就出在改文件后忘了处理权限和属主,而不是代码本身。

sha256sum system.img > sha256_before.txt

刷机后如果发现问题,再hash一次当前镜像,对比就知道刷进去的到底是不是自己改过的版本。这个习惯在反复实验时特别有用,能省掉很多自我怀疑的时间。

6.5 把镜像当“线索库”使用

最后分享一个我觉得很有价值的思路:镜像不只是拿来刷机的,它还是一个巨大的“线索库”。遇到一些诡异问题时,我会先把镜像解包,在镜像里搜索关键词,查找相关配置、库和服务。比如有一次想确认某个功能是否默认开启,直接在/system/etc下grep相关配置项,很快找到了开关位置。比起直接去翻代码、跑编译,先看镜像里的实际内容往往更快也更精确。

对于OpenHarmony这种体量的系统,镜像里的内容其实是源码和构建配置的“快照”,它忠实地记录了那次编译选进了哪些代码、配置了哪些文件、打好了哪些依赖。读懂镜像,等于掌握了一种从产物反向理解系统构建与运行逻辑的能力。这套思路对于任何需要接触二进制、镜像、构建产物的开发场景都通用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 16:57:12

智能体协作力:2026年AI应用的关键能力与训练方法

最近这段时间,我陆续翻了不少模拟项目、课程设计和开放式的面试题目,发现信号已经非常明显:关于AI的讨论重点,正在从"哪家模型更强""提示词怎么写",快速转向"怎么让多个智能体各司其职、互相…

作者头像 李华
网站建设 2026/10/20 16:55:33

C#控制安捷伦34401A万用表串口通信实战指南

简介:本资源是一套基于C#开发的Agilent 34401A数字万用表上位机控制程序,面向电子测量、自动化测试领域的初/中级工程师及高校实验开发者,解决传统手动读数效率低、难以集成到自动测试系统中的实际问题。压缩包共31个文件,含7个核…

作者头像 李华
网站建设 2026/10/20 16:55:09

Claude与GPT工作流适配:从响应节奏到结构化输出的工程化选择

1. 这不是“选边站队”,而是工作流适配的底层逻辑最近在几个技术协作群里,频繁看到类似这样的提问:“刚买了Claude Pro,结果发现写周报不如GPT-4o顺手”“用GPT调API跑通了数据清洗,但换Claude后提示词要重写三遍”“团…

作者头像 李华
网站建设 2026/10/20 16:54:54

STM32定时器从时钟配置到PWM与输入捕获实践

1. 定时器时钟源头:先搞清楚定时器到底跑多快1.1 从RCC开始:定时器时钟不是随便能用的拿到STM32先别急着初始化TIMx,第一步应该回答一个问题:定时器挂在哪条总线上,它的输入时钟到底是多少。很多人调定时器时间不对&am…

作者头像 李华
网站建设 2026/10/20 16:54:07

筋斗云视频云平台接入实践:从推流到播放的全链路选型与踩坑记录

第一次听到“筋斗云”这个名字,是在一个技术交流群里。当时有位同行在问有没有适合中小团队接入的视频云平台,要求别太复杂、开箱即用,有人回了四个字:“试试筋斗云。”坦白说,我第一反应是这名字取得挺占便宜&#xf…

作者头像 李华
网站建设 2026/10/20 16:53:23

COMSOL光子晶体谷霍尔效应仿真:从单胞到单向传输全流程

做光子晶体的同学,今年基本绕不开一个词:光子晶体谷霍尔效应。简单说,就是利用晶格对称性破缺,在动量空间的K和K谷处打开带隙,然后在两个拓扑性质不同的光子晶体边界上,激发出一种由谷自由度锁定传播方向的…

作者头像 李华