OpenHarmony 6.0 Binder通信接口设计全解析
每个进程有独立的虚拟地址空间,无法直接访问对方的物理内存。这是操作系统隔离保护的基础。传统IPC方式,数据至少需要两次拷贝(用户→内核→用户),效率低,延迟高。物理内存 (Physical RAM)│ ││ [数据...] ││ ││ │ 内核空间 (Kernel) │ ││ │ (映射到所有进程) │ ││ │▲ ▲│ MMU翻译 │ MMU翻译│ ││ 进程A虚拟空间 │ │ 进程B虚拟空间
·
友情提示:以下分析均基于开源OpenHarmony代码,可能和HarmonyOS有区别
OpenHarmony 6.0 Binder通信接口设计全解析
前言:为什么需要Binder
进程隔离
每个进程有独立的虚拟地址空间,无法直接访问对方的物理内存。这是操作系统隔离保护的基础。
传统IPC方式,数据至少需要两次拷贝(用户→内核→用户),效率低,延迟高。
物理内存 (Physical RAM)
┌─────────────────────────────────────────┐
│ │
│ [数据...] │
│ │
│ ┌───────────────────────────────┐ │
│ │ 内核空间 (Kernel) │ │
│ │ (映射到所有进程) │ │
│ └───────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘
▲ ▲
│ MMU翻译 │ MMU翻译
│ │
┌───────┴────────┐ ┌─────┴────────┐
│ 进程A虚拟空间 │ │ 进程B虚拟空间 │
│ │ │ │
│ 0-3G: 用户空间 │ │ 0-3G: 用户空间 │
│ 3-4G: 内核空间 │ │ 3-4G: 内核空间 │
│ │ │ │
│ app code │ │ service code │
│ heap │ │ heap │
│ stack │ │ stack │
└────────────────┘ └────────────────┘
如果采用共享内存的方式,进程生命周期耦合、同步复杂、安全性差(可随意篡改数据)。
进程A (发送方) 进程B (接收方)
┌─────────────────┐ ┌─────────────────┐
│ 虚拟地址: │ │ 虚拟地址: │
│ 0x7000: data │ │ 0x8000: buf │
└────────┬────────┘ └────────┬────────┘
│ 拷贝1 │
▼ ▼
┌───────────────────────────────────────────────┐
│ 内核缓冲区 (临时存储) │
│ 物理地址: 0xFFFF1000 │
└──────────┬────────────────────────────────────┘
│ 拷贝2
┌──────────┴────────┐ ┌──────────────┐
│ 用户→内核→用户 │ │ 效率低 │
└───────────────────┘ └──────────────┘
物理内存页面: 0x5000
┌─────────────────────────┐
│ 共享数据区 │
└────┬────────────────────┘
│
├──────► 映射到进程A
│ 虚拟地址: 0x6000
│ 问题: 生命周期耦合
│ 问题: 需手动同步
│
└──────► 映射到进程B
虚拟地址: 0x9000
问题: 安全性差
问题: 易死锁
Binder核心思想是,在内核层面实现对象映射(也可以简单理解为对象映射)。
物理内存 (内核驱动管理)
┌─────────────────────────────────────────┐
│ Binder节点对象池 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 对象A (物理页) │ │ 对象B (物理页) │ │
│ │ 引用计数=2 │ │ 引用计数=1 │ │
│ └──────┬──────┘ └──────┬──────┘ │
└────────┼────────────────────┼──────────┘
│ │
┌────────▼────────┐ ┌──────▼──────────┐
│ 进程A │ │ 进程B │
│ 虚拟地址: 0x1000 │ │ 虚拟地址: 0x2000 │
│ (只映射引用) │ │ (只映射引用) │
└──────────────────┘ └──────────────────┘
通信时内核直接操作对象:
→ 零拷贝数据传递
→ 引用计数自动管理
→ UID/PID权限验证
底层驱动代码在:
drivers/staging/android/binder.c
一、接口设计
1. IRemoteBroker抽象接口
- 核心方法:
class IRemoteBroker { public: virtual int32_t OnRemoteRequest(uint32_t code, MessageParcel& data, MessageParcel& reply, MessageOption& option) = 0; virtual sptr<IRemoteObject> AsObject() = 0; }; - 设计原则:定义统一通信契约,实现接口与实现分离
- 版本控制:通过
INTERFACE_TOKEN实现版本兼容const char* IMyService::GetDescriptor() { return "ohos.my.service.IMyService@1.0"; }
2. 接口生命周期管理
- 引用计数:
sptr/wptr智能指针自动管理对象生命周期 - 死亡通知:
DeathRecipient回调处理服务异常终止class MyDeathRecipient : public IRemoteObject::DeathRecipient { public: void OnRemoteDied(const wptr<IRemoteObject>& obj) override { // 服务异常终止处理逻辑 } };
二、代理-桩模式实现
1. 服务端桩类(Stub)
// MyServiceStub.h
#include "iremote_stub.h" // 引入 IRemoteStub 模板
#include "i_my_service.h" // 业务接口头文件
// MyServiceStub 继承 IRemoteStub 模板(而非直接继承 IRemoteBroker)
class MyServiceStub : public IRemoteStub<IMyServiceInterface> {
public:
// 实现业务接口的纯虚函数
int Add(int a, int b) override {
// 实际业务逻辑
return a + b;
}
std::string GetInfo() override {
return "Service is alive";
}
protected:
// 重写 OnRemoteRequest,处理客户端调用分发
int OnRemoteRequest(uint32_t code, MessageParcel &data,
MessageParcel &reply, MessageOption &option) override {
switch (code) {
case CMD_ADD: { // 命令码:Add方法
int a = data.ReadInt32();
int b = data.ReadInt32();
int result = Add(a, b);
reply.WriteInt32(result);
return ERR_NONE;
}
case CMD_GET_INFO: { // 命令码:GetInfo方法
std::string info = GetInfo();
reply.WriteString(info);
return ERR_NONE;
}
default:
return IPCObjectStub::OnRemoteRequest(code, data, reply, option);
}
}
private:
// 定义命令码(通常由IDL工具生成)
enum {
CMD_ADD = 1,
CMD_GET_INFO = 2,
};
};
2. 客户端代理类(Proxy)
// MyServiceProxy.h
#include "iremote_proxy.h" // 引入 IRemoteProxy 模板
// Proxy 不继承 IRemoteObject,而是持有它的指针
class MyServiceProxy : public IRemoteProxy<IMyServiceInterface> {
public:
// 构造函数接收 IRemoteObject(从服务管理器获取)
explicit MyServiceProxy(const sptr<IRemoteObject> &impl)
: IRemoteProxy<IMyServiceInterface>(impl) {
// impl 指向服务端的 Binder 对象
}
// 实现业务接口(通过 IPC 调用转发到服务端)
int Add(int a, int b) override {
MessageParcel data; // 输入参数
MessageParcel reply; // 返回结果
MessageOption option; // 同步/异步选项
// 写入参数
data.WriteInt32(a);
data.WriteInt32(b);
// 通过持有的 IRemoteObject 发起跨进程调用
// Remote() 返回保存的 IRemoteObject 指针
int error = Remote()->SendRequest(CMD_ADD, data, reply, option);
if (error != ERR_NONE) {
return -1;
}
// 读取返回结果
return reply.ReadInt32();
}
std::string GetInfo() override {
MessageParcel data, reply;
MessageOption option;
Remote()->SendRequest(CMD_GET_INFO, data, reply, option);
return reply.ReadString();
}
private:
// 与服务端 Stub 使用相同的命令码定义
enum {
CMD_ADD = 1,
CMD_GET_INFO = 2,
};
};
3.通信能力的封装:IRemoteObject
IRemoteObject 是 OpenHarmony/HarmonyOS(鸿蒙操作系统)IPC 机制中的核心抽象接口,与 Android 的 IBinder 概念类似。这个其实才是对Binder驱动的封装。
class IRemoteObject : public virtual Parcelable, public virtual RefBase {
// 提供跨进程通信的基础能力
}
- 基础抽象:所有可跨进程传递的对象都必须继承自 IRemoteObject
- 不能直接实例化:它是抽象类,定义接口规范,由 IRemoteStub(服务端)和 IRemoteProxy(客户端)具体实
注意:IRemoteObject和IRemoteBroker不是一个东西,IRemoteBroker主要是定义业务接口抽象,IRemoteObject是定义底层通信能力抽象。
关键点:IRemoteObject 是连接 IRemoteProxy 和 IRemoteStub 的桥梁,客户端和服务端都通过它发送/接收 IPC 消息。如下图:
客户端进程 服务端进程
┌─────────────────┐ ┌─────────────────┐
│ IRemoteProxy │ │ IRemoteStub │
│ (继承并调用IRemoteObject)│ │ (继承IRemoteObject)│
└───────┬─────────┘ └────────┬────────┘
│ SendRequest() │
│───────────────────────────────────>│
│ 通过Binder驱动传递 │
│ │
│<───────────────────────────────────│
│ 返回结果 │
│ │
▼ ▼
IRemoteObject 是通信的"信道"而非"数据"
关键类之间的关系:
| 类名 | 与 IRemoteObject 的关系 | 职责 |
|---|---|---|
IRemoteBroker |
无关(接口契约) | 定义服务接口方法 |
IRemoteStub |
继承 IRemoteObject | 服务端桩,接收并处理请求 |
IRemoteProxy |
持有 IRemoteObject | 客户端代理,封装 SendRequest 调用 |
IPCObjectProxy |
实现类 | Binder 框架实例化的代理对象 |
IPCObjectStub |
实现类 | Binder 框架实例化的桩对象 |
| 与android的区别: | ||
| 特性 | OpenHarmony IRemoteObject | Android IBinder |
| -------- | ------------------------------ | ------------------- |
| 系统 | 鸿蒙 OS | Android |
| 驱动 | Binder 驱动(类似) | Binder 驱动 |
| 服务管理 | System Ability Manager (SAMgr) | ServiceManager |
| 语言 | C++ 为主 | Java/C++ 混合 |
4.类设计
┌─────────────────┐
│ DeathRecipient │
│ (死亡通知接口) │
└────────┬────────┘
│ 注册/回调
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ 鸿蒙 IPC 框架核心类关系 │
│ │
│ IRemoteBroker RefBase Parcelable MessageParcel │
│ ▲ ▲ ▲ ▲ │
│ │ │ │ │ │
│ └───────┬────────┴────────┬─────────┴────────────────┼────────────────┘
│ │ │ │
│ ▼ ▼ ▼
│ IMyServiceInterface IRemoteObject ──────── MessageOption │
│ ▲ ▲ ▲ ▲ │
│ │ │ │ │ │
│ ┌─────────┴─────────┐ │ │ │ │
│ │ │ │ │ │ │
│ ▼ ▼ │ │ │ │
│ IRemoteStub<T> IRemoteProxy<T> │ │ │ │
│ ▲ ▲ │ │ │ │
│ │ │ │ │ 持有 │ 使用 │
│ ▼ ▼ │ │ │ │
│ MyServiceStub MyServiceProxy─┘ │ │ │
│ │ │ │
└──────────────────────────────────────┼─────────────────────┼──────────────────┘
│ │
│ 通过 Binder 驱动通信 │
▼ ▼
┌──────────────────────────────────┐
│ Binder Driver (Kernel) │
│ • 进程间对象引用管理 │
│ • 数据传递(零拷贝) │
│ • 权限验证 (UID/PID) │
└──────────────────────────────────┘
三、数据契约设计
1. Parcelable接口规范
class Parcelable {
public:
virtual bool Marshalling(MessageParcel& parcel) const = 0;
virtual bool Unmarshalling(MessageParcel& parcel) = 0;
};
2. 复杂对象序列化示例
class MyData : public Parcelable {
public:
bool Marshalling(MessageParcel& parcel) const override {
parcel.WriteString(name_);
parcel.WriteInt32(age_);
return true;
}
// ...Unmarshalling实现...
private:
std::string name_;
int32_t age_;
};
3.共享内存类对象打包
DMA共享内存对象通常包含:
- 文件描述符(fd):指向dma_buf的句柄
- 物理地址/虚拟地址:内存区域地址
- 大小(size):内存区域长度
- 缓存同步信息:CPU/GPU缓存一致性状态
不能直接打包的原因: - fd是进程内有效的整数句柄,跨进程后无效
- 虚拟地址在不同进程地址空间中映射不同
- 需要让内核重新映射共享内存到目标进程
数据发送的时候,需要通过发送句柄的方式,让接收方线程重新mmap:
class DmaBuffer : public Parcelable {
public:
int fd; // dma_buf 文件描述符
size_t size;
void* virt_addr;
// 序列化:打包fd而非内存数据
bool Marshalling(MessageParcel& parcel) const override {
// 关键:将fd传递给内核,让内核在目标进程重建fd
bool ok = parcel.WriteFileDescriptor(fd);
ok &= parcel.WriteUint64(size);
// 不打包virt_addr,因为目标进程会重新mmap
return ok;
}
// 反序列化:接收fd并重新映射
bool Unmarshalling(MessageParcel& parcel) override {
// 从parcel读取fd(内核已在目标进程创建新fd)
fd = parcel.ReadFileDescriptor();
size = parcel.ReadUint64();
// 重要:在目标进程重新mmap内存映射
virt_addr = mmap(nullptr, size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
return fd >= 0 && virt_addr != MAP_FAILED;
}
~DmaBuffer() {
if (virt_addr) munmap(virt_addr, size);
if (fd >= 0) close(fd);
}
};
4.指针类对象打包
同dma buffer,指针也不能直接打包发送到对端。也是需要继承Parcelable,在Marshalling、Unmarshalling中完成对象的打包和解析。
// 步骤1:让对象继承 RefBase 和 Parcelable
class MyData : public RefBase, public Parcelable {
public:
int value;
std::string name;
// 实现序列化
bool Marshalling(MessageParcel& parcel) const override {
parcel.WriteInt32(value);
parcel.WriteString(name);
return true;
}
// 实现反序列化
bool Unmarshalling(MessageParcel& parcel) override {
value = parcel.ReadInt32();
name = parcel.ReadString();
return true;
}
};
// 步骤2:使用 sptr 替代 shared_ptr
void SendData(sptr<MyData> data) { // sptr 管理 RefBase 对象
MessageParcel parcel;
parcel.WriteParcelable(data.GetRefPtr()); // ✅ 正确
}
// 步骤3:在接收方重建
sptr<MyData> ReceiveData(MessageParcel& parcel) {
sptr<MyData> data = new MyData(); // 创建对象
parcel.ReadParcelable(data.GetRefPtr()); // 填充数据
return data;
}
四、接口调用的一般流程
服务注册流程
- 实现
IRemoteBroker接口 - 创建
Binder对象并设置死亡通知 - 调用
SAManagerClient::AddService()注册 - Binder驱动创建
binder_node结构 - 服务信息写入ServiceManager并广播
客户端调用流程
- 调用
SAManagerClient::GetService()获取代理 - 生成
binder_ref并建立引用计数 - 构造
MessageParcel并验证接口Token - 通过Binder驱动发送
BC_TRANSACTION - 服务端接收
BR_TRANSACTION并触发OnRemoteRequest
更多推荐



所有评论(0)