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 的实现


相关推荐
帅得不敢出门10 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡
我又来搬代码了11 小时前
【Android】使用productFlavors构建多个变体
android
德育处主任13 小时前
Mac和安卓手机互传文件(ADB)
android·macos
芦半山13 小时前
Android“引用们”的底层原理
android·java
迃-幵14 小时前
力扣:225 用队列实现栈
android·javascript·leetcode
大风起兮云飞扬丶14 小时前
Android——从相机/相册获取图片
android
Rverdoser14 小时前
Android Studio 多工程公用module引用
android·ide·android studio
aaajj14 小时前
[Android]从FLAG_SECURE禁止截屏看surface
android
@OuYang14 小时前
android10 蓝牙(二)配对源码解析
android
Liknana14 小时前
Android 网易游戏面经
android·面试