上一篇笔记记录了ServiceManager是如何启动,以及如何在客户端进程中获取ServiceManager的远程代理。这一篇将记录如何使用ServiceManager注册服务以及获取服务,同时了解binder驱动是如何传递数据、调用方法的。
0 前情提要
看过前面几篇笔记我们可以知道,创建ServiceManager进程时,binder驱动会为其创建一个binder_proc
对象,调用becomeContextManager时binder驱动会为该进程创建一个binder_node
对象,并且将该binder_node
作为binder驱动的上下文(servicemanager)。
想要使用ServiceManager来注册服务,那么大概有以下几个步骤:
- 获取到ServiceManager的远程代理,这在前面的笔记中已经记录了,handle为0即为ServiceManager;
- 将传入参数组织成Parcel;
- 将数据发送给binder驱动;
- binder驱动找到ServiceManager服务所在进程,向该进程写入数据;
- ServiceManager收到数据并处理。
接下来就一起看看1、2、3、4分别是如何实现的。
1 组织数据
我们先来看看MediaServer服务是如何注册的:
void MediaPlayerService::instantiate() {defaultServiceManager()->addService(String16("media.player"), new MediaPlayerService());
}
这里要注意是,我们调用的是BpServiceManager
的addService方法,我们只需要填入参数,接着由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_objectflat_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对象大致有以下步骤:
- 调用Binder对象的localBinder方法,判断当前对象是远程代理还是本地服务实体;
- 创建一个
flat_binder_object
对象,接下来的操作都是对该对象的设定; - 如果传入Binder对象是远程代理,则type设定为BINDER_TYPE_HANDLE,Handle设定为Binder对象的Handle;
- 如果传入Binder对象是服务实体,则type设定为BINDER_TYPE_BINDER,binder设定为Binder对象的引用计数,cookie设定为Binder对象;
- 写入
flat_binder_object
对象到Parcel中。
对不同的Binder对象有不同的打包方法,如果是远程代理则传handle,如果是服务实体则传入对象的引用以及实体指针。
打包结束之后就要将数据发送给binder驱动了。
2 发送数据
从BpServiceManager::addService中我们可以看到,发送数据调用的是BpBinder
的transact方法:
// 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_dataerr = 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;// 设置目标进程的handletr.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_procstruct binder_proc *proc = filp->private_data;// 根据cmd获取输入数据的size,这里是binder_write_read的sizeunsigned 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_writeret = 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_contextstruct 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中的cmdif (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_dataif (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做了以下事情:
- 读取到
binder_write_read
中存储的用户空间数据指针 - 从用户空间数据指针中读取cmd,这里为BC_TRANSACTION
- 从用户空间读取
binder_transaction_data
的数据,这里面存储有目标进行的handle,需要目标执行的方法,以及数据指针、数据大小等。 - 调用binder_transaction向目标进程传输数据