Android 13 - binder阅读(4)- 使用ServiceManager注册服务

上一篇笔记记录了ServiceManager是如何启动,以及如何在客户端进程中获取ServiceManager的远程代理。这一篇将记录如何使用ServiceManager注册服务以及获取服务,同时了解binder驱动是如何传递数据、调用方法的。

0 前情提要

看过前面几篇笔记我们可以知道,创建ServiceManager进程时,binder驱动会为其创建一个binder_proc对象,调用becomeContextManager时binder驱动会为该进程创建一个binder_node对象,并且将该binder_node作为binder驱动的上下文(servicemanager)。

想要使用ServiceManager来注册服务,那么大概有以下几个步骤:

  1. 获取到ServiceManager的远程代理,这在前面的笔记中已经记录了,handle为0即为ServiceManager;
  2. 将传入参数组织成Parcel;
  3. 将数据发送给binder驱动;
  4. binder驱动找到ServiceManager服务所在进程,向该进程写入数据;
  5. ServiceManager收到数据并处理。

接下来就一起看看1、2、3、4分别是如何实现的。


1 组织数据

我们先来看看MediaServer服务是如何注册的:

void MediaPlayerService::instantiate() {
    defaultServiceManager()->addService(
            String16("media.player"), new MediaPlayerService());
}

这里要注意是,我们调用的是BpServiceManageraddService方法,我们只需要填入参数,接着由BpServiceManager帮我们打包数据与binder驱动进行通信。这里填入的第一个参数类型为String16,第二个参数类型为BnMediaPlayerService(服务实体)。

我们无法直接在源码中找到BpServiceManager或者是BnServiceManager的声明和实现,这是因为IServiceManager接口是在aidl中定义的,aidl文件路径位于:frameworks/native/libs/binder/aidl/android/os/IServiceManager.aidl

编译器会根据aidl文件生成binder通信所需的文件(Bp、Bn文件),编译后生成文件位于out目录下:
android/out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm_armv7-a-neon_shared/gen/aidl/android/os

BpServiceManager实现在IServiceManager.cpp,我们来看看addService是如何打包数据并且发送给binder驱动的:

::android::binder::Status BpServiceManager::addService(const ::std::string& name, const ::android::sp<::android::IBinder>& service, bool allowIsolated, int32_t dumpPriority) {
  ::android::Parcel _aidl_data;
  // 默认不是rpchandle
  _aidl_data.markForBinder(remoteStrong());
  ::android::Parcel _aidl_reply;
  ::android::status_t _aidl_ret_status = ::android::OK;
  ::android::binder::Status _aidl_status;

	// 1. 写入interface token
  _aidl_ret_status = _aidl_data.writeInterfaceToken(getInterfaceDescriptor());
	// 2. 写入服务名称
  _aidl_ret_status = _aidl_data.writeUtf8AsUtf16(name);
	// 3. 写入服务实体
  _aidl_ret_status = _aidl_data.writeStrongBinder(service);
	// 以下可以不看
  _aidl_ret_status = _aidl_data.writeBool(allowIsolated);
  _aidl_ret_status = _aidl_data.writeInt32(dumpPriority);

	// 4. 与binder驱动进行通信
  _aidl_ret_status = remote()->transact(BnServiceManager::TRANSACTION_addService, _aidl_data, &_aidl_reply, 0);

	// 5. 读取返回值
  _aidl_ret_status = _aidl_status.readFromParcel(_aidl_reply);

  return _aidl_status;
}

首先看接口参数类型,name是std::string,我们自己手写bp文件常用的是String8或者String16,但是从aidl生成结果来看,谷歌似乎逐步开始移除这些内部定义的类转向stl,由于没有接触过早期aidl生成文件,这里仅为我自己的猜测。

首先要写入InterfaceDescriptor字符串,由IInterface提供的宏来声明,它的作用类似于一个令牌,服务端接收消息时会检查发送来的数据中的Descriptor和本身的是否一致,判断消息是否发错人了。

namespace android {
namespace os {
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager")
}  // namespace os
}  // namespace android

接下来我们要重点了解writeStrongBinder是如何打包一个服务本体对象的:

status_t Parcel::flattenBinder(const sp<IBinder>& binder) {
    BBinder* local = nullptr;
    // 1. 获取BBinder对象
    if (binder) local = binder->localBinder();
    if (local) local->setParceled();
	// 2. 创建一个flat_binder_object
    flat_binder_object obj;

    if (binder != nullptr) {
    	// 3. 如果传递的Binder对象是一个远程代理
        if (!local) {
            BpBinder *proxy = binder->remoteBinder();
            const int32_t handle = proxy ? proxy->getPrivateAccessor().binderHandle() : 0;
            obj.hdr.type = BINDER_TYPE_HANDLE;
            obj.binder = 0;
            obj.flags = 0;
            obj.handle = handle;
            obj.cookie = 0;
        } else {
        	// 4. 如果传递的Binder对象是一个本地服务实体
            obj.flags = FLAT_BINDER_FLAG_ACCEPTS_FDS;
            if (local->isRequestingSid()) {
                obj.flags |= FLAT_BINDER_FLAG_TXN_SECURITY_CTX;
            }
            if (local->isInheritRt()) {
                obj.flags |= FLAT_BINDER_FLAG_INHERIT_RT;
            }
            obj.hdr.type = BINDER_TYPE_BINDER;
            obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
            obj.cookie = reinterpret_cast<uintptr_t>(local);
        }
    }
	// 5. 将flat_binder_object写入到Parcel中
    status_t status = writeObject(obj, false);
    return finishFlattenBinder(binder);
}

对代码做了一些精简,打包binder对象大致有以下步骤:

  1. 调用Binder对象的localBinder方法,判断当前对象是远程代理还是本地服务实体;
  2. 创建一个flat_binder_object对象,接下来的操作都是对该对象的设定;
  3. 如果传入Binder对象是远程代理,则type设定为BINDER_TYPE_HANDLE,Handle设定为Binder对象的Handle;
  4. 如果传入Binder对象是服务实体,则type设定为BINDER_TYPE_BINDER,binder设定为Binder对象的引用计数,cookie设定为Binder对象;
  5. 写入flat_binder_object对象到Parcel中。

对不同的Binder对象有不同的打包方法,如果是远程代理则传handle,如果是服务实体则传入对象的引用以及实体指针。

打包结束之后就要将数据发送给binder驱动了。


2 发送数据

BpServiceManager::addService中我们可以看到,发送数据调用的是BpBindertransact方法:

// 4. 与binder驱动进行通信
_aidl_ret_status = remote()->transact(BnServiceManager::TRANSACTION_addService, _aidl_data, &_aidl_reply, 0);

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
	status = IPCThreadState::self()->transact(binderHandle(), code, data, reply, flags);
}

BpBinder::transact调用的又是IPCThreadState::transact,之前也有提过,与binder驱动完成通信都是在IPCThreadState中完成。

status_t IPCThreadState::transact(int32_t handle,
                                 uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err;
    flags |= TF_ACCEPT_FDS;
    // 1. 将传入参数组织成为binder_transaction_data
    err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);
    if ((flags & TF_ONE_WAY) == 0) {
        if (reply) {
        	// 2 与binder通信,等待消息返回
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }

    }
    return err;
}

2.1 数据的再封装

IPCThreadState::transact的第一步是对数据再封装:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
	// 1. 组织数据头
    binder_transaction_data tr;
    tr.target.ptr = 0;
    // 设置目标进程的handle
    tr.target.handle = handle;
    // 设置本次binder通信的处理事务
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;
    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
    	// 设置数据的指针,大小,偏移量等
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    } 
    // 2. 将数据写入到Parcel中
    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));
    return NO_ERROR;
}

之前是将传入参数进行封装,这里又添加了目标进程的handle 和 本次通信需要处理的事务,把这些信息封装到binder_transaction_data中,并且设定之前封装的数据的指针,最后再将binder_transaction_data 写入到Parcel中完成封装。

这里说一点不太重要的,对于当前进程有两个数据端口,数据输入端口mIn 和 数据输出端口mOut,这里与binder通信属于输出,所以数据要写入到mOut中。

2.2 与binder驱动通信

完成数据的再封装后就要发送数据了,一起看看waitForResponse做了什么:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    uint32_t cmd;
    int32_t err;

    while (1) {
    	// 1. 调用talkWithDriver与binder驱动通信,并且等待数据返回
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck();

        if (mIn.dataAvail() == 0) continue;
        cmd = (uint32_t)mIn.readInt32();

        switch (cmd) {
        case BR_REPLY:
            {
                binder_transaction_data tr;
                err = mIn.read(&tr, sizeof(tr));

                if (reply) {
                    if ((tr.flags & TF_STATUS_CODE) == 0) {
                    	// 2. 拷贝返回数据
                        reply->ipcSetDataReference(
                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(binder_size_t),
                            freeBuffer);
                    } 
                }
            }
        }
    }
    return err;
}

waitForResponse的核心是调用talkWithDriver与binder驱动进行谈话:

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
	// 1. 创建binder_write_read 
    binder_write_read bwr;
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
	// 写入输入数据大小与数据指针
    bwr.write_size = outAvail;
    bwr.write_buffer = (uintptr_t)mOut.data();

    // This is what we'll read.
    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (uintptr_t)mIn.data();
    } else {
    	// 写入输出数据大小与数据指针
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }
    // 写入数据消耗量
    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
    	// 2. 与binder驱动通信,返回结果存储在bwr中
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
    } while (err == -EINTR);
    return err;
}

上面这段代码在ServiceManager的启动那一篇笔记中贴过,那边是服务实体从binder驱动读取数据,我们这边是代理向binder驱动发送数据。

发送的数据封装在binder_write_read,存储有输入输出数据的指针,内核态可以从这两个指针拷贝数据或者传输数据。ioctl发送的命令是BINDER_WRITE_READ。接下来就要进入内核态看binder驱动是如何处理的了。


3 binder驱动处理远程调用

我们在这里再贴一次binder_ioctl的代码:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	struct binder_thread *thread;
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;

	thread = binder_get_thread(proc);
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	switch (cmd) {
	case BINDER_WRITE_READ:
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		if (ret)
			goto err;
		break;
	}
}

进入binder_ioctl后一开始就获取了当前进程的binder_proc对象,我们再回顾一下:当前是在MediaServer进程中,注册服务前会打开binder驱动,这时候就为MediaServer进程创建了一个binder_proc对象并存储在fd中,并且将其context指向了ServiceManger的binder_node。记住这些,接下来一起看binder_ioctl_write_read

static int binder_ioctl_write_read(struct file *filp,
				unsigned int cmd, unsigned long arg,
				struct binder_thread *thread)
{
	int ret = 0;
	// 获取当前进程的binder_proc
	struct binder_proc *proc = filp->private_data;
	// 根据cmd获取输入数据的size,这里是binder_write_read的size
	unsigned int size = _IOC_SIZE(cmd);
	// 用户空间binder_write_read的指针
	void __user *ubuf = (void __user *)arg;
	// 创建内核态中的binder_write_read对象
	struct binder_write_read bwr;
	// 1. 从用户态拷贝binder_write_read的内容
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}

	if (bwr.write_size > 0) {
		// 2. 写入数据大于0,调用binder_thread_write
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
		if (ret < 0) {
			bwr.read_consumed = 0;
			if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
				ret = -EFAULT;
			goto out;
		}
	}
	// 3. 将内核态bwr的内容重新拷贝到用户态
	if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}
out:
	return ret;
}

binder_ioctl_write_read首先要将用户态的binder_write_read拷贝到内核空间中,接着调用binder_thread_write向目标进程做数据写入,最后再将binder_write_read拷贝到用户空间。这里的拷贝只是拷贝了binder_write_read,里面仅存储了数据大小和数据指针,性能开销不会很大。

binder_thread_write代码非常长,我同样做了一些精简:

static int binder_thread_write(struct binder_proc *proc,
			struct binder_thread *thread,
			binder_uintptr_t binder_buffer, size_t size,
			binder_size_t *consumed)
{
	uint32_t cmd;
	// 获取到binder_context
	struct binder_context *context = proc->context;
	// 1. 用户空间的数据指针
	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
	void __user *ptr = buffer + *consumed;
	void __user *end = buffer + size;

	while (ptr < end && thread->return_error.cmd == BR_OK) {
		int ret;
		// 2. 读取用户空间binder_transaction_data中的cmd
		if (get_user(cmd, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);

		switch (cmd) {
		case BC_TRANSACTION:
		case BC_REPLY: {
			struct binder_transaction_data tr;
			// 3. 从用户空间读取binder_transaction_data
			if (copy_from_user(&tr, ptr, sizeof(tr)))
				return -EFAULT;
			ptr += sizeof(tr);
			// 4. 调用binder_transaction传输数据
			binder_transaction(proc, thread, &tr,
					   cmd == BC_REPLY, 0);
			break;
		}
		*consumed = ptr - buffer;
	}
	return 0;
}

精简后看到binder_thread_write做了以下事情:

  1. 读取到binder_write_read中存储的用户空间数据指针
  2. 从用户空间数据指针中读取cmd,这里为BC_TRANSACTION
  3. 从用户空间读取binder_transaction_data的数据,这里面存储有目标进行的handle,需要目标执行的方法,以及数据指针、数据大小等。
  4. 调用binder_transaction向目标进程传输数据

请添加图片描述
我们用一张图来看binder通信过程中的数据传递(封装与读取)过程~,这样应该会清楚很多了。

binder驱动读取到了binder_transaction_data后就要开始向目标进程写入数据调用相应的方法了,这里是binder驱动比较关键的地方,由于代码比较长,我们放到下一篇笔记中来记录。


如果有小伙伴觉得有帮助,可以一键三连表示一波支持哈哈~如果觉得笔记中有错误的小伙伴也可以在评论区指出。

有想看前面笔记的同学可以参考:
https://blog.csdn.net/qq_41828351/category_11748830.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值