Binder(三)| Binder 驱动与协议

这里接着上期的小岛居民买水果的故事来说,我们可以把 Binder 驱动当做地下市场。 A 岛的居民找到地下市场的特殊组织,告诉他我想吃 B 岛的水果,地下市场的特殊组织就去 B 岛上进货, 回来之后再给 A 岛居民。 用户空间就是 不同的岛民,内核空间就是地下市场,而 Binder 驱动就是地下市场中的一个特殊的组织。

1. 概述

地基 ------ Binder 驱动 ------ 标准的 Linux 驱动

Binder Driver 会将自己注册成一个 misc device, 并向上提供一个 /dev/binder 节点

但这个节点并不对应真实的硬件设备,Binder 驱动运行于内核态,可以提供 open(), ioctl(), mmap() 等常用的文件操作。

Binder 驱动需要填写 file_operations 结构体,特殊组织 也有自己的架构,不同的部门有着不同的职能。

ini 复制代码
/drivers/staging/android/Binder.c

static const struct file_operations binder_fops = {
	.owner = THIS_MODULE,
    .poll = binder_poll,
    .unlocked_ioctl = binder_ioctl,
    .mmap = binder_mmap,
    .open = binder_open,
    .flush = binder_flush,
    .release = binder_release,
};

这里可以看出, Binder 驱动总共为上层应用提供了 6 个接口,其中最多的是 binder_ioctl, binder_mmap, binder_open 。

这里一般文件操作需要用到的 read() 和 write() 则没有出现,因为它们的功能完全可以用 ioctl() 和 mmap() 来代替。

打开 Binder 驱动 ------ binder_open

binder_mmap

mmap() 可以把设备指定的内存块直接映射到应用程序的内存空间中。(这块我觉得很神奇,也很好奇)

  1. 对于应用程序而言,它通过 mmap() 返回值得到一个内存地址(虚拟地址),这个地址通过虚拟内存转换(分段、分页)后最终将指向物理内存的某个位置。
  2. 对于 Binder 驱动而言,它也有一个指针(binder_proc->buffer), 指向某个虚拟地址,经过虚拟地址的转换后,它和应用程序中指向的物理内存处于同一个位置。

这里的 binder_proc->buffer 是 Binder 驱动为应用程序分配的一个数据结构,用于存储和该进程有关的所有信息,如内存分配、线程管理等。

binder_ioctl

实现了应用进程与Binder 驱动之间的命令交互,承载了 Binder 驱动中的大部分业务。

DNS 服务器 ------ ServiceManager(Binder Server)

SM 是一个标准的 Binder Server

ServiceManager 的构建

arduino 复制代码
// framework/native/cmds/servicemanager.Service_manager.c
int main (int argc, char **argv) {
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;
    // 1. 打开Binder设备,做好初始化
    bs = binder_open(128*1024);
    if (binder_become_context_manager(bs)) {
        // 将自己设置成 Binder 大管家,整个android系统只允许一个 ServiceManager 存在
        // 因而后面还有人调用这个函数就会失败
    	return -1;
    }
	svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler); // 进入循环,等待客户的请求
    return 0;
}
  • 由binder驱动决定被映射到进程空间中的内存起始地址
  • 映射区块大小为 128KB
  • 映射区只读
  • 映射区的改变是私有的,不需要保存文件
  • 从文件的起始地址开始映射

在 Binder Server 进入循环之前,它要先告知 Binder 驱动这一状态变化:

ini 复制代码
bwr.write_size = 0;
bwr.write_cinsumed = 0;
bwr.write_buffer = 0;
redbuffer[0] = BC_ENTER_LOOPER;
binder_write (bs, readbuf, sizeof(unsigned));

之后,SM 进入了循环,循环和典型的基于事件驱动的程序循环框架类似。

  • 从消息队列中读取消息
  • 如果消息是 退出, 则马上结束循环,如果消息是空,则继续读取或者等待一段时间内后再读取,如果消息不为空且不是退出命令,则根据具体情况处理。
  • 如此循环往复直到退出

BR_TRANSACTION

因为 ServiceManager 是为了完成 "Binder Server Name"(域名) 和 "Server Handle"(IP地址)间的对应关系而存在的。

  • 注册

当一个Binder Server 创建后,他们要将自己的【名称, Binder句柄】对应关系告知 SM 进行备案

  • 查询

应用程序可以向 SM 发起查询请求,已获得某个 Binder Server 所对应的句柄

  • 其他信息查询

比如SM版本号,当前的状态等。

Service Manager 的功能架构比较简洁------内部维护着一个 svlist 列表,用于存储所有 Server 相关信息(以scvinfo为数据结构),查询和注册都是基于这个表展开的。

  • SVC_MGR_GET_SERVICE
  • SVC_MGR_CHECK_SERVICE

根据Server 名称来找到它的 handle 值

  • SVC_MGR_ADD_SERVICE

用于注册一个 Binder Server

首先在 SM 所维护的数据列表中查找是否一已经有对应的节点存在,否则需要创建一个新的节点来记录这个 Server, 然后将这个 Server 中所带的信息写入列表的相应节点中,以备后期查询。

  • SVC_MGR_LIST_SERVICES

获取列表中的对应 Server

函数 svcmgr_handler 处理完成后,binder_parse 会进一步通过 binder_send_reply 来将执行结果回复给底层 binder 驱动,进而传递给客户端。然后 binder_parse 会进入下一轮的 while 循环,直到 ptr < end 为 false , 此时说明 Service Manager 上一次从驱动层读取的消息都已经处理完成,因而它还会继续向 Binder Driver 发送 BINDER_WRITE_READ 以查询有没有新的消息。如果有的话就处理,否则会进入休眠等待。

获取 ServiceManager 服务 ------ 设计思考

问题:

需要访问 SM (Binder Server) 的服务, 流程应该怎么样?

  • 打开 Binder 设备
  • 执行 mmap
  • 通过 Binder 驱动向 SM 发送请求(SM 的 handle 为 0)
  • 获得结果

这里:

  • 向 SM 发起请求的 Binder Client 可能是 Android APK 应用程序,所以 SM 必须要提供 Java 层接口。
  • 如果每个 Binder Client 都要亲历亲为地执行上面几个步骤来获取 SM 服务,会浪费不少时间。所以它提供了更好的封装来使整个 SM 调用过程更加精简实用。
  • 如果应用程序代码中每次使用 SM 服务(或者其他 Binder Server 服务),都需要打开一次 Binder 驱动、执行 mmap, 后果就是消耗的系统资源越来越多,直到崩溃。

每个进程只允许打开一次 Binder 设备,且只做一次内存映射 ------ 所有需要使用 Binder 驱动的线程共享这 一资源

这里我们就要想着如何设计一个满足上面条件的 Binder Client

  1. 要创建一个类专门管理每个应用进程中的 Binder 操作 ------ 执行 Binder 驱动的一系列命令对上层用户必须是"透明的" ------ ProcessState 类
  2. 与 Binder 驱动进行实际命令通信的是 IPCThreadState

Proxy 代理

对 SM 提供的服务进行封装, 交给代理去做 ------ ServiceManagerProxy

  1. ServiceManagerProxy 的接口,这里它提供的服务和服务端的 SM 必须是一致的。将这些方法提取出来,就是 ServiceManagerProxy 的接口 ------ IServiceMananager
csharp 复制代码
public interface IServiceMangaer {
    public IBinder getService(String name) throws RemoteException;
    public IBinder checkService();
    ...
}

显然, ServiceManagerProxy 需要继承 IServiceMananager

  1. 接口实现
  • 与Binder 建立关系

因为进程中已经有了 ProcessState 和 IPCThreadState 这两个专门与 Binder 驱动通信的类,所以 Java 层 代码使用 Binder 驱动实际上是基于它们来完成的,称为 BpBinder.

  • 向 Binder 发送命令,从而获得 SM 提供的服务
  1. 总结
  • Binder 架构

驱动、SM、Binder Client、Binder Server

  • Binder 驱动

驱动是其他元素的基础

  • Service Manager

SM 既是 Binder 框架的支撑者,同时也是一个标准的 Server

ServiceManagerProxy

这里 Android 系统在 ServiceManagerProxy 上面加了一层封装 ServiceManager.java

这样应用程序使用 SM 就更加方便了,连 ServiceManagerProxy 都不用创建了。

ServiceManager.getService(name);

SM 中的所有服务接口都设计成了 static 的,这样用户不需要额外的创建对象就能使用其功能了。

typescript 复制代码
// framework/base/core/java/android/os/ServiceManager.java

public static IBinder getService(String name) {
	try {
        // 1.sCache 中记录的是 getService 的历史查询结果
        IBinder service = sCashe.get(name); // 查看缓存
    	if (service != null) {
            return service; //从缓存中找到结果,直接返回
        } else {
            //2. sCache中没有查询到就会发起查询请求
            return getIServiceManager().getService(name); // 向 SM 发起查询
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
}	
	return null;
}

public static IServiceManager getIServiceManager() {
	if (sServiceManager != null) {
        return sServiceManager; // 返回一个 IServiceManager 对象
    }
    sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
    return sServiceManager;
}
scss 复制代码
// framework/base/core/java/android/os/ServiceManagerNative.java
static public IServiceManager asInterface(IBinder obj) {
	if (obj == null) {
        return null;
    }	
    // 1. 先查询本地是否已经有 IServiceManager 存在
	IServiceManager in = (IServiceManager)obj.queryLoaclInterface(descripor);
    if (in != null) {
        return in
    }
    // 这里终于出现了,如果没有查询到,就新建一个 ServiceManagerProxy
    return new ServiceManagerProxy(obj);
}

// 作为 SM 的代理,ServiceManagerProxy 必定要参与 Binder 通信的
// 它的构造函数中传入了 IBinder 的对象
public ServiceManagerProxy(IBinder remote) {
	mRemote = remote; // 只是简单的记录下了这个 IBinder 对象,类似于电话号码
}

// 用的时候再去拨打这个号码
public IBinder getService(String name) throws RemoteException {
	Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IServiceManager.descriptor);
    data.writeString(name);
    // 利用 IBinder 对象执行命令
    mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);

    IBinder binder = reply.readStrongBinder();
    reply.recycle();
    data.recycle();
    return binder;
}  

这里详细说一下这个流程:

  1. 准备数据

通过 Parcel 打包数据

  1. IBinder.transact

利用 IBinder 的 transact 将请求发送出去,而不用理会 Binder 驱动的 open, mmap 等具体的 binder 协议中的命令。所以这个 IBinder 一定会在 ProcessState 和 IPCThreadState 来与 Binder 驱动进行通信。

  1. 获取结果

上面 transact 之后,我们就可以直接获取到结果了。

因为涉及到进程间通信,结果并不是马上就能获取到,Binder 驱动一定要现将调用者线程挂起,直到有了结果才能将它唤醒。这样做的好处是调用者可以像进程内函数调用一样去编写程序,而不用考虑很多的 IPC 的细节。

注意: 客户端和服务端的代码要保持一致。通常,使用 AIDL 产生的 Binder Server 会自动生成这些业务码,而不需要手工编写。

IBinder 和 BpBinder

Binder 提供的功能可以统一在 IBinder 中表示,至少有如下接口方法:

java 复制代码
// framework/base/core/java/android/os/IBinder.java
public interface IBinder {
    public IInterface queryLocalInterface (String descriotor);
    public boolean transact(int code, Parcel data, Parcel reply, inte flags)
        throws Remote Exception;
    .....
}

此外,还应该有获取 IBinder 对象的一个类,即 BinderInternal

arduino 复制代码
// framework/base/core/java/android/os/IBinder.java
public class BinderInternal {
    public static final native IBinder getContextObject();
    ...
}

// framework/base/core/jni/android_util_Binder.cpp
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz) {
    // 这里返回的就是一个 BpBinder对象
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}

getContextObject 是通过 ProcessState 来实现的,然后 把 ProcessState 中创建的对象转化成 Java层的 Binder 对象。

IBinder 只是一个接口类,显示还会有具体的实现类继承于它。

Native 层: BpBinder.cpp ------> 由 ProcessState 创建的。

Java 层: Binder.java 中的 BinderProxy ---> javaObjectForIBinder 通过 JNI 的 NewObject() 创建的。

这里继续看看 mRemote->transact 时的实现

java 复制代码
// framework/base/core/java/android/os/Binder.java
final class BinderProxy implements IBinder {
    public native boolean transact(int code, Parcel data, Parcel reply, int flags)
    throws RemoteException;
    ...
}

BinderProxy 中的 transact 就是一个 native 接口,真正的实现还是在 android_util_Binder.cpp 中

最后还是通过 BpBinder.transact 来处理用户的 Binder 请求:

arduino 复制代码
// framework/native/libs/binder/BpBinder.java
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status = IPCThreadState::slef()->transact(mHandle,code,data,reply,flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}

绕了这么大的圈子,最后还是通过 IPCThreadState 及 ProcessState 来实现的。

IPCThreadState 及 ProcessState

关键点:

  1. 保证同一个进程中只有一个 ProcessState 实例存在,而且只有在 ProcessState 对象创建时才打开 Binder 设备以及做内存映射。
  2. 向上层提供 IPC 服务
  3. 跟 IPCThreadState 分工合作,各司其职。
php 复制代码
// framework/native/libs/binder/ProcessState.cpp
sp<ProcessState> ProcessState::self() {
	Mutex::Autolock _l(gProcessMutex);
    if (gProcess != NULL) {
        return gProcess;
    }
    gProcess = new ProcessState; // 创建对象
	return gProcess;
}

我们接下来看看 ProcessState 的构造函数:

scss 复制代码
ProcessState::ProcessState()
	: mDriverFD(open_driver()) 
	,mVMStart(MAP_FAILED), mManagesContexts(false)
    , mBinderContextCheckFunc(NULL), mBinderContextUserData(NULL)
    , mThreadPoolStarted(false), mThreadPoolSeq(1)
{
    if (mDriverFD >= 0) { // 成功打开 /dev/binder
        #if !defined(HAVE_WIN32_IPC)
        // 开始执行 mmap, 内存块为 BINDER_VM_SIZE ,接近1M 的空间
            mVMStart = mmap(0, BINDER_VM_SIZE, PORT_READ, MAP_PRIVATE 
                            | MAP_NORESERVE, mDriverFD, 0);
        
        

这里调用了 open_driver() 打开了 /dev/binder 节点,然后执行 mmap() ,映射的内存块大为 BINDER_VM_SIZE.

之前我们在获取 IBinder 时使用了 BinderInternal 中的 getContextObject() ,这个方法最后的实现是在 ProcessState 中。

javascript 复制代码
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller)
{
	return getStrongProxyForHandle(0); // 传入0, 代表 SM
}

BpBinder 是 Native 层的代理,最后由 javaObjectForIBinder 转化为 Java 层的 BinderProxy

scss 复制代码
// BpBinder.cpp
BpBinder::BpBinder(int32_t handle)
	: mHandle(handle) // 如果是 SM, handle 为 0
	, mAlive(1), mObitsSent(0), mObituaries(NULL)
{
	...
        extendObjectLifetime(OBJECT_LIFETIME_WEAK);
    	IPCThreadState::self()->incWeakHandle(handle);
}

IPCThreadState

有的变量希望只在本线程内是全局的,其他线程无法访问到,

TLS (Thread Local Storage) 机制就是为了解决这个问题提出来的,能够保证某个变量仅在自己线程中内访问有效,而其他线程中得到的是这个变量的独立副本,互不干扰。

我们来看一下 IPCThreadState 的构造函数

scss 复制代码
IPCThreadState::IPCThreadState()
	: mProcess(ProcessState::self()), // ProcessState 整个进程只有一个
      mMyThreadId(androidGetTid()), // 当前线程的 id
      mStrictModePolicy(0), mLastTransactionBinderFlags(0)
{
	pthread_setspecific(gTLS, this);
    clearCaller();
    // mIn 是一个 Parcel ,用于接收 Binder 发过来的数据
    mIn.setDataCapacity(256);
    // mOut 用于存储要发送给 Binder 的命令数据的
    mOut.setDataCapacity(256);

IPCThreadState 负责与 Binder 驱动进行具体的命令交互(ProcessState 只是负责打开了 Binder 节点并做 mmap), 因此它的 transact 函数非常重要。

status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)

java 复制代码
getService@ServiceManagerProxy
  transact@BinderProxy
    transact@BpBinder
      transact@IPCThreadState

Transaction 有 4 种 flag

  • TF_ONE_WAY: 表示当前业务是异步的,不需要等待 (这个经常用到)
  • TF_ROOT_OBJECT: 所包含的内容是根对象
  • TF_SATUS_CODE: 所包含的内容是 32-bit 的状态值
  • TF_ACCEPT_FDS = 0x10: 允许回复中包含文件的表述符

IPCThreadState 中与 Binder 驱动真正通信的地方就在 talkWithDriver 中。

arduino 复制代码
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) { // Binder 设备还没有打开
    	return -EBADF;
    }
	binder_write_read bwr; // 读写都使用这个数据结构
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

Binder 的执行过程多数是阻塞型的(同步操作),Binder 去调用服务进程提供的接口函数,那么此函数执行结束时结果就已经产生,不涉及回调机制。

常见的机制就是,让调用者进程暂时挂起,直到目标进程返回结果后,Binder 再唤醒等待的进程。

binder_thread_write()

arduino 复制代码
int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
	void _user *buffer, int size, signed long *consumed)

在 getService() 时

  • proc: 调用者进程
  • thread: 调用者线程
  • buffer: 即 bwr.write_buffer , 在 talkWithDriver 中,它被设置成:

bwr.write_buffer = (long unsigned int)mOut.data();

// 先空一下,这块作者说的啰嗦,我看的好晕,先进入总结吧

总结

  1. ServiceManagerProxy

当某个 Binder Server 在启动时,会把自己的名称 name 和对应的 Binder 句柄值保存在 ServiceManager 中。调用者通常只知道 Binder Server 的名称,所以必须先向 Service Manager 发起查询请求,也就是 getService(name)。

而 Service Manager 自身也是一个 Server, 就好像互联网上的 DNS 服务器本身也需要提供IP地址才能访问一样。只不过这个IP地址是预先就设定好的(句柄值为0),因而任何Binder Client 都可以通过 0 这个 Binder 句柄创建一个 BpBinder, 再通过 Binder 驱动去获取 Service Manager 的服务。具体而言,就是调用 BinderInternal.getContextObject()来获得 Service Manager 的 BpBinder。

Android 系统同时支持 Java 与 c/c++ 层的 Binder 机制,因此很多对象都必须持有"双重身份",如BpBinder 在 Java 层以 IBinder 来表示。对于 Service Manager 而言,IBinder 的真正持有者与使用者是 ServiceManagerProxy。

  1. ProcessState 和 IPCThreadState

这个是Android 系统特别为程序进程使用 Binder 机制封装的两个类。

在 getService() 这个场景中,调用者是从 Java 层的 IBinder.transact() 开始,层层往下调用到 IPCThreadState.transact(), 然后通过 waitForResponse 进入主循环 ------ 直到收到 Service Manager 的回复后才跳出循环,并将结果再次层层传到应用层。真正与 Binder 驱动打交道的地方是 talkWithDriver()中的 ioctl()。

  1. Binder 驱动
  1. Service Manager 的实现


相关推荐
追光天使1 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
小雨cc5566ru1 小时前
uniapp+Android智慧居家养老服务平台 0fjae微信小程序
android·微信小程序·uni-app
一切皆是定数2 小时前
Android车载——VehicleHal初始化(Android 11)
android·gitee
一切皆是定数2 小时前
Android车载——VehicleHal运行流程(Android 11)
android
problc2 小时前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java
图王大胜3 小时前
Android SystemUI组件(11)SystemUIVisibility解读
android·framework·systemui·visibility
服装学院的IT男7 小时前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android
Arms2067 小时前
android 全面屏最底部栏沉浸式
android
服装学院的IT男7 小时前
【Android 源码分析】Activity生命周期之onStop-1
android
ChinaDragonDreamer10 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin