鸿蒙PC真编译移植实战:把ImDisk devio块设备服务搬到HarmonyOS桌面
2026/9/12 17:09:12 网站建设 项目流程

鸿蒙PC真编译移植实战:把ImDisk devio块设备服务搬到HarmonyOS桌面

欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/

欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

适配开源地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_imdisk

ImDisk是 一款经典的开源虚拟磁盘工具(Olof Lagerkvist 开发,MIT 许可),它的核心能力用一句话概括就是:让"文件"和"磁盘"可以互相扮演

镜像变成"网络磁盘"——这要靠 ImDisk 里的devio组件:它把一个镜像文件以**块设备(扇区级)**的形式对外提供,客户端通过 TCP 端口或共享内存连上来,就能按照协议(imdproxy)按扇区读写这个镜像,相当于把一块"硬盘"通过网络共享出去。

本次移植的正是 devio。它是 ImDisk 上游唯一自带 POSIX 构建路径的用户态组件(其余如内核驱动imdisk.sys、控制台imdisk.exe都重度依赖 Win32 / NTDDK,无法移植)。移植到鸿蒙 PC 后,它带来的能力包括:磁盘镜像分发(把系统镜像提供给虚拟机或容器)、跨设备块设备共享(设备 A 起服务,设备 B 按扇区直连读写)、数据恢复工具链桥接(ddrescue、photorec 这类需要裸块设备的工具),以及用shm:共享内存模式做进程间的高性能块设备 IPC。

需要说明的边界:它不能像 Windows 那样挂载成盘符直接用——因为那是内核驱动的活儿,鸿蒙没有对应模型。devio 提供的是"块设备服务",要真正读出文件,还需要客户端侧再实现一层文件系统解析(比如 FAT16)。

引子:把一张图片塞进镜像,再用 devio 读回来

先看最终成果——桌面上这张out.jpg,它原来在鸿蒙 PC 的~/Desktop/demo.jpg,我们把它写进了一个 8MB 镜像,然后用鸿蒙 PC 上跑的 ImDiskdevio(用户态块设备服务)按 TCP 暴露出来,最后在 Mac 上按上游 imdproxy 协议一块块读回来拼成的:


这不是截图,是端到端的真实数据流——鸿蒙 PC 上跑的是 ImDisk 上游官方 C 源码(仅 1 处平台补丁),Mac 上的客户端是按inc/imdproxy.h协议自己写的 130 行 Python,链路全靠鸿蒙hdc的 TCP 端口转发打通。

这篇文章就把这次"真编译移植"的完整过程拆给你看:为什么选 devio、移植架构长什么样、实战怎么一步一步跑通、以及途中踩到的最隐蔽的坑。全程只有一处平台补丁、130 行自写客户端代码,其余全部是 ImDisk 上游原样源码——这是判断"真移植"还是"重写"最直接的标准。

一、为什么选 devio——ImDisk 的"用户态前端"

鸿蒙 PC 上"块设备"是个稀罕物——文件系统读写人人都懂,扇区、对齐、VHD 头这类老存储工程的话,桌面开发很少碰到。但要做 NAS 镜像、磁盘克隆、容器 volume 之类的事情,就绕不开。

开源世界这块最经典的工具是 ImDisk(Olof Lagerkvist 维护、Windows 平台),分两部分:

  • 内核驱动imdisk.sys:把块设备暴露成 Windows 盘符——这条不能移植,驱动必须匹配内核版本
  • 用户态服务devio:把镜像文件按扇区级对外提供,客户端通过 TCP 或共享内存读写——这条可以原样搬

devio 是纯 C,全部代码就两个源文件:devio.c(主程序,约 2300 行)和safeio.c(POSIX 文件 I/O 抽象),协议定义在inc/imdproxy.h,全部 ULONGLONG 字段,小端字节序。TCP 模式就四种请求:INFO(查询镜像大小)、READ(按偏移读扇区)、WRITE(按偏移写扇区)、CLOSE(断开)。

为什么选它?因为它协议完整、POSIX 路径现成、可独立验证——只要客户端能按协议读出原始字节流,就证明整个栈通了。而且它足够小:两个 C 文件、一个协议头文件,交叉编译一遍只要几秒,出问题时从头读一遍源码也只要一个下午——这种"小到能完全理解"的规模,恰好适合做鸿蒙 PC 用户态移植的练手样板,比动辄几万行的工具链友好得多。

二、移植架构与策略

整体思路和之前几个工程的"鸿蒙化"一致:HNP 包承载原生 CLI,HAP 壳做 UI 和应用集成

构建脚本与上游devio/Makefile的 POSIX 规则严格等价,只换了编译器和 sysroot:

# 上游 Makefile 等价规则:# cc -Wall -Os -D_XBS5_ILP32_OFFBIG -o devio devio.c safeio.c"$CLANG"-Wall-Os-D_XBS5_ILP32_OFFBIG\-I. -I../inc\--sysroot="$SYSROOT"\-static\-odevio devio.c safeio.c

未定义_WIN32时,devio.c自动走 POSIX 分支(unistd / syslog / socket),这套分支在上游就是给 Linux 用的,鸿蒙的 musl libc 完全兼容。产物是静态链接的 ELF aarch64(约 1.1 MB),不依赖设备上任何动态库,装到哪都能跑。

HNP 包的配置极简——一个hnp.json声明"装什么、链到哪":

{"type":"hnp-config","name":"imdisk_devio","version":"1.0","install":{"links":[{"source":"bin/devio","target":"bin/devio"}]}}

links把包内的bin/devio链接到 hnppublic 的bin/目录,HiShell 的PATH天然包含它,所以装完直接敲devio就能跑,不需要改任何环境变量。这是鸿蒙 PC 上 CLI 工具的标准交付形态——比"下载二进制 + 手动 chmod + 改 PATH"的传统 Linux 玩法优雅得多。

只改一处平台补丁——上游devio.c:1398#ifndef O_DIRECT兜底,在鸿蒙上失效(下面第四节展开讲)。剩下的源码不动、协议不变、HNP 打包流程不变,这就是"极小平台补丁的真实源码级移植"。

HAP 壳用 ArkTS 写一个 7 步案例引导页(第六节专门讲),用户点"复制"按钮把命令粘到 HiShell 执行即可。

三、实战演练:从造镜像到读回图片

3.1 准备:造 8MB 镜像 + 把 demo.jpg 写进去

桌面放一张图片demo.jpg(77 434 字节,约 76 KB)。我们在 HiShell 里做一个 8 MB 空白镜像,再把图片写进镜像开头:

cd~/Desktopls-ldemo.jpg# -rw-rw---- 1 ... 77434 ... demo.jpgddif=/dev/zeroof=disk.imgbs=512count=16384# 8388608 bytes (8.0 M) copied, 145 M/sddif=demo.jpgof=disk.imgconv=notrunc# 77434 bytes (76 K) copied, 74 M/s

conv=notrunc保证不截断镜像、只覆盖开头。此刻镜像的前 77 434 字节就是一张完整的 JPEG,后面全是 0。

3.2 启动 devio 服务(修复 O_DIRECT 后)

devio9000disk.img16384&

这一行最关键的证据是这两行输出:

Successfully opened 'disk.img'. Waiting for connection on port 9000. Press Ctrl+C to cancel.

没修 O_DIRECT 之前,你只会看到进程立刻exit 1,连"打开失败"的提示都没有——因为上游用syslog()输出错误,而鸿蒙没有/dev/log、HiShell 也看不到,所以表现成"静默闪退"。这个坑下面第四节单独拆。

3.3 客户端按 imdproxy 协议连接——INFO 验证

在 Mac 上建端口转发,然后用 130 行 Python 客户端连上去发 INFO 请求:

hdc fport tcp:9000 tcp:9000 python3 tools/devio_demo_client.py info

输出:

file_size = 8388608 bytes req_alignment = 1 flags = 0x0 (RO=0x1)

INFO 响应是 24 字节、三个 ULONGLONG 字段:file_size(镜像总字节数)、req_alignment(读写对齐要求,devio 报 1 表示任意偏移可读写)、flags(能力位图,0x1为只读)。客户端发的是 8 字节请求码0x0000000000000001,小端——这 32 个字节的往返,就是 imdproxy 协议的最小握手。

file_size = 8388608正好是我们造的镜像大小——协议字节序、字段长度、对端行为全对上,说明:

  • devio进程真的在设备上跑着
  • TCP 端口转发(hdc fport)通畅
  • 上游inc/imdproxy.h协议完全兼容

3.4 掉坑:devio 是"一次性"服务

但接下来read命令就ConnectionResetError了。看devio.c主循环才发现真相:

sd=accept(...);closesocket(ssd);// ← accept 一次就关监听...if(!comm_read(...)||req==IMDPROXY_REQ_CLOSE){puts("Connection closed.");return0;// ← 客户端一断,进程就退出}

devio 是单连接设计——listen(ssd, 1)accept()一次,客户端断开它就return 0退出。这个设计在 upstream 场景里是合理的:devio 通常和 ImDisk 客户端驱动(或 ImDisk Tool 一类管理程序)配对使用,一个 devio 实例服务一个客户端、进程生命周期绑定连接生命周期,简单可靠。但用来做演示就掉坑里:我那个客户端"每条命令新建连接"——info用完,服务就死了。这个行为保持了 upstream 原样,正确的修法是改客户端(一个连接里做完所有事),而不是给 devio 加 accept 循环——那是"改行为",不是"移植"。

3.5 单连接all命令完成"查询+读取+落盘"

改造客户端,让它在一个连接里完成所有操作:先 INFO 拿大小,再分块 READ 全部数据(单次不超过 64 MB,与 devio 的buffer_size对齐),最后落盘并识别文件类型。

defcmd_all(out_path):s=connect()size=do_info(s)# 一次连接里发 INFOdata=b""foroffsetinrange(0,size,CHUNK):# 分块 READ(4MB/次)errno,chunk=do_read(s,offset,min(CHUNK,size-offset))data+=chunkwithopen(out_path,"wb")asf:f.write(data)print(sniff(data[:8]))# 识别文件头

跑:

python3 tools/devio_demo_client.py all ~/Desktop/out.jpg

上半部分是info失败的ConnectionResetError(3.4 节描述的"服务被自己耗死");下半部分是新的all命令成功:

[INFO] file_size=8388608 alignment=1 flags=0x0 [READ] offset=0 +4194304 bytes [READ] offset=4194304 +4194304 bytes [READ] 合计读取 8388608 / 8388608 字节 [HEXDUMP] 前 256 字节: 00000000 ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01 ......JFIF...... 00000010 00 01 00 00 ff db 00 43 00 03 02 02 02 02 02 03 .........C...... ... [CHECK] 识别出:JPEG 图片(文件头 ff d8 ff) [SAVE] 已保存 -> /Users/zhubo/Desktop/out.jpg

ff d8 ff——这就是 JPEG 文件头。鸿蒙 PC 上跑的 devio 把 8 MB 镜像按块设备协议、TCP 路径、4 MB 分块完完整整地传回了 Mac,前 77 434 字节就是当初塞进去的那张demo.jpg

桌面右侧(鸿蒙任务栏下)能看到out.jpg文件。打开它——和原始demo.jpg像素级相同。这就是端到端的铁证。

四、关键移植坑:O_DIRECT 静默失败

这个坑单独拎出来讲,因为它太典型、太隐蔽——99% 的鸿蒙用户态程序调试都会撞上类似问题。

现象devio启动后立刻exit 1,没有错误信息。

诊断四步法(所有"静默失败"的鸿蒙程序都适用):

  1. 看返回值:echo $?—— 拿到exit 1
  2. return 1的位置:grep -n "return 1" devio.c
  3. 看对应上下文:syslog(LOG_ERR, "Failed to open '%s': %m\n", argv[2]);
  4. 关键:鸿蒙没有/dev/logsyslog()输出直接被吞——即使打开失败,你也看不到任何东西

所以"静默闪退"的本质是错误信息不可见,不是没有错误。

调试技巧:在 devio.c 顶部加一个补丁,让syslog重定向到stderr

#ifdefined(__OHOS__)||defined(__linux__)#include<stdio.h>#definesyslog(prio,...)fprintf(stderr,__VA_ARGS__)#endif

重新编译后再跑,立刻看到:

Failed to open 'disk.img': Invalid argument

Invalid argumentEINVAL,指向O_DIRECT

根因:上游devio.c:1398

image_fd=_open(argv[2],O_BINARY|O_DIRECT|O_FSYNC|O_RDWR);

上游用#ifndef O_DIRECT兜底:

#ifndefO_DIRECT#defineO_DIRECT0#endif

陷阱是:鸿蒙的<fcntl.h>已经定义了O_DIRECT(Linux 也是)。#ifndef不成立,宏不会被替换为 0,真实O_DIRECT被传给open()

Linux 上O_DIRECT支持普通文件读写,但鸿蒙的 f2fs / overlayfs 实现要么不支持,要么需要严格的对齐要求,传给普通文件就EINVAL

修复:1 行

#ifdefined(__OHOS__)||defined(__linux__)#undefO_DIRECT#endif

效果等同于把O_DIRECT强制为 0(#ifndef兜底才有意义)。剩下的源码不动、协议不变、HNP 打包流程不变。这就是"极小平台补丁的真实移植"——补丁只动 1 行,但能精准定位到根因的能力是"0 提示闪退"和"一次跑通"的分水岭。

五、UI 设计:7 步案例引导

"打开应用看不到怎么用"是 CLI 工具上鸿蒙 PC 最常被吐槽的点。这次直接做一个 7 步案例引导页,每一步都含:序号徽章、标题、执行位置标签(HiShell绿 /电脑端橙)、说明、命令块(点"复制"按钮粘贴即可)、预期输出。

7 步:

  1. 进入桌面,确认素材——cd ~/Desktop+ls -l demo.jpg
  2. 造 8 MB 空白镜像——dd if=/dev/zero of=disk.img bs=512 count=16384
  3. 把图片写进镜像——dd if=demo.jpg of=disk.img conv=notrunc
  4. 后台启动 devio 服务——devio 9000 disk.img 16384 &
  5. 确认服务在跑——ps -ef | grep devio
  6. 从电脑读回镜像(电脑端)——hdc fport+devio_demo_client.py all
  7. 收尾停掉服务——kill %1

第 6 步特意标电脑端橙色标签,并强调"这一步在电脑上执行,不是在 HiShell 里"——避免用户在 HiShell 里跑hdc/python3然后得到command not found

底部还保留 3 条"其他常用命令"(devio/-r只读 /shm:共享内存),方便日常调试和深入探索shm:模式(这是把 devio 接到鸿蒙应用进程内做 IPC 的关键模式)。

六、这个移植有意思在哪

回顾一下工程层面:

  • 源码级移植,不是重写:6 个上游文件逐字节拷贝,只改 1 行<fcntl.h>兜底
  • 协议零修改:客户端脚本里IMDPROXY_REQ_INFO = 1IMDPROXY_READ_REQ字段顺序都是从上游imdproxy.h直接抄的
  • HNP 标准交付:产物是imdisk_devio.hnp(约 450 KB,含bin/devio),通过 HAP 安装到/data/app/.../hnppublic/bin/devio,HiShellPATH直接能跑
  • UI 与 CLI 分离:HAP 壳只负责把命令展示出来,不碰 devio 的二进制,CLI 的语义完全是 upstream 的语义

这种"上游源码级真实移植"和"自研用户态核心"是两条完全不同的路径。ohos_ImDisk/工程走的是后者——自研 FAT16、自己实现块设备读写、客户端兼容 ImDisk 协议;本文的ohos_ImDisk_native/工程走的是前者——直接跑 ImDisk 上游的 C 源码。两个工程 bundle 不同(org.imdisk.native.ohosvsorg.imdisk.ohos),可共存于同一台设备。

真机验收清单(每一项都有截图佐证):

验收项结果
HAP 可安装,桌面显示 ImDisk devio
HiShell 中devio输出ver 3.10官方版权
devio 9000 disk.img 16384 &打开镜像并监听✅ Successfully opened + Waiting for connection
客户端 INFO 返回file_size=8388608✅ 协议兼容
客户端分块 READ 8 MB 无损读回✅ 2 × 4194304 字节
读回数据识别出 JPEG 头ff d8 ff✅ 与写入的 demo.jpg 一致
落盘out.jpg可打开、与原图像素级相同✅ 端到端闭环

七、结语:技术没有银弹,但有可复用的套路

鸿蒙 PC 上能跑 ImDisk 官方devio这件事本身意义不大——大部分人用不到块设备。但它证明的是:

  • OHOS NDK 的 clang + sysroot 真的能编译、运行上游 200 行级 POSIX C 程序
  • HNP 交付链能把 CLI 工具集成到桌面应用里
  • TCP 端口转发(hdc fport)能让 Mac / PC 客户端连到设备里的服务、按 upstream 协议通信
  • 错误可见性(syslog→stderr)是任何鸿蒙用户态程序调试的必修课

把 devio 搬上鸿蒙 PC 只是开始。往近了说,ImDisk 上游还有imdiskctlarcdr等更多 CLI 组件可以照这个套路搬过来,凑齐一套完整的镜像管理工具链;往远了说,块设备服务打开的是一类新场景:

  • 磁盘镜像分发:把系统镜像放在 HAP 资源里,用 devio 以块设备形式提供给虚拟机或容器
  • 跨设备存储共享:设备 A 起 devio 服务,设备 B 的客户端通过 TCP 直连读写(配合分布式软总线做端口发现)
  • 数据恢复工具链:ddrescue、photorec 这类工具需要直接访问块设备,devio 是它们和鸿蒙文件系统之间的桥
  • shm:模式做进程内 IPC:把鸿蒙应用通过共享内存把镜像块设备暴露给同一台设备上的另一个进程,绕开 TCP 的序列化开销

当你能在 HarmonyOS 桌面上跑通devio这种"传统存储工具"的真实源码,你就真的能用鸿蒙 PC 做点过去只能在 Windows / Linux 上做的事情了。

八、常见问题 FAQ

Q1:devio 启动就退出,什么提示都没有?

echo $?看返回码:1通常是镜像打开失败,2是 socket/bind 失败。若完全无输出,说明用的是未打 syslog 补丁的旧二进制——鸿蒙没有/dev/logsyslog()的输出会被直接丢弃("静默闪退"的元凶)。新版已把syslog重定向到stderr,能看到Failed to open 'disk.img': Invalid argument这类完整信息。

Q2:客户端第一条命令成功,第二条就Connection reset

devio 是单连接服务listen(ssd, 1)且只accept()一次,客户端断开它就打印Connection closed.并退出。这是 upstream 行为(配合 ImDisk 客户端驱动的一对一长连接),不是 bug。用all命令在一个连接里完成"查询 + 读取 + 落盘",或每次演示前重启服务。

Q3:hdc fportTCP Port listen failed at 9000

转发规则已存在,无需重复建立。用hdc fport ls查看,或hdc fport rm tcp:9000 tcp:9000后重建。

Q4:ps auxbad aux

鸿蒙用的是 toybox 的ps,不支持 BSD 风格的aux参数,改用ps -ef

Q5:为什么 devio 不能在hdc shell里跑?

hdc shell运行在sh:s0域,SELinux 会拒绝执行 HNP 二进制(HarmonyOS 的安全设计,非 bug)。必须在设备自带的**「终端」(HiShell)** App 中运行。反过来,hdc/python3是电脑端命令,也不要在 HiShell 里跑。

Q6:镜像块数怎么算?

块数 = 镜像字节数 ÷ 512。8 MB = 8388608 ÷ 512 =16384。非 Windows 平台上 devio 无法自动探测镜像大小(上游限制),必须由命令行显式给出,否则客户端读到的file_size会不对。

Q7:能像 Windows 那样挂载成盘符(Z:)直接用吗?

不能。上游imdisk.sys是 Windows 内核驱动,无法移植到鸿蒙。本工程提供的是块设备服务——需要客户端按 imdproxy 协议读写扇区。想挂文件系统,得在客户端侧再实现一层(例如 FAT16 解析)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询