native层的c++如何通过binder获取sm服务(四)

前几篇Binder相关文章讲了驱动的注册和提供的接口,以及ServiceManager的启动运行,很自然接下来我们需要思考客户端进程应该如何获取到sm提供的服务(注册服务和查询服务),本篇从native层c++角度继续分析。

我们需要时刻牢记,应用程序跨进程只能依靠某种介质(比如socket是靠网卡、binder是靠驱动、内存共享是靠物理内存),直接软件去架构是无法做到的,在分析ServiceManager时也说了,sm是一个标准的Binder Server,因之,客户端进程想要访问ServiceManager进程,必然也是要跨进程的,显然,客户端进程需要靠驱动来访问。根据手头现有的信息,客户端进程需要做的工作如下:

  • 打开Binder驱动
  • 执行mmap映射内存
  • 客户端进程使用Binder驱动去访问ServiceManager(sm的handler为0)
  • 获得结果

不必怀疑,就这么简单,从宏观来说就是这几步,话说回来,frameworks是干啥用的?是给整个系统提供封装服务的,如果某个进程要跨进程传递十万次数据,难道开十万次驱动并mmap映射吗?会耗尽资源的,显然有很大的优化空间。一个进程可以只打开一次Binder驱动并mmap做内存映射,而进程内无论跨进程传递多少次数据,用几个线程去执行,都复用共享这一个Binder通道,顺手复习一下ServiceManager是怎么使用Binder驱动的?直接用binder_open去打开Binder的驱动设备节点/dev/binder,其他进程是学不来SM这种任性的方式了,frameworks是怎么封装的?

1、native客户端进程到底是怎么打开Binder驱动的

随意翻一个native层某个进程的main函数,大概率会出现这么几句代码:

sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();

而native层获取Binder Server的代码怎么写的?

sp<IServiceManager> sm = defaultServiceManager();//step1,获取sm服务
sp<IBinder> binder = sm->getService(String16(ServerName));
sp<IServerName> service = interface_cast<IServerName>(binder);

/* /frameworks/native/libs/binder/IServiceManager.cpp */
sp<IServiceManager> defaultServiceManager()
  {
      std::call_once(gSmOnce, []() {
          sp<AidlServiceManager> sm = nullptr;
          while (sm == nullptr) {
              sm = interface_cast<AidlServiceManager>(ProcessState::self()->getContextObject(nullptr));//step2,sm代理对象是从这里拿到的,这个接口非常有用留到1.1小节中的ProcessState展开
              if (sm == nullptr) {
                  ALOGE("Waiting 1s on context object on %s.", ProcessState::self()->getDriverName().c_str());
                  sleep(1);
              }
          }  
          gDefaultServiceManager = sp<ServiceManagerShim>::make(sm);//step3
      });
  
      return gDefaultServiceManager;
  }

看step1、2、3,也就是说step里的IServiceManager类型的sm变量,就是BpBinder,我们一直说sp sm是ServiceManager在本地的代理服务,换句话说BpBinder这个类,是Android系统native层上的一个通用Binder框架下客户端的代理模板类,它的父类是IBinder,这是目前推导出来的结论。
从step2可知,sm是从ProcessState::self()->getContextObject(nullptr)里拿出来的,现在引出了ProcessState类,刚刚一直在说优化的目的是让进程只打开一次Binder驱动。首先我们要推出一个概念:

Binder框架体系里,凡是去调用getService,说明这个对象或者指针是在客户端上,它能够调用服务提供的函数,就得是个proxy。也就是说defaultServiceManager()返回的对象指针是ServiceManager的代理。

由ProcessState::self()有理由推测,它是单例类,追进去看

1.1、ProcessState

先看ProcessState构造函数,翻源码:

/frameworks/native/libs/binder/ProcessState.cpp
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) // 1M - 8k
ProcessState::ProcessState(const char *driver)
      : mDriverName(String8(driver))
      , mDriverFD(open_driver(driver))//step1,打开Binder驱动
      , mVMStart(MAP_FAILED)
      , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
      , mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
      , mExecutingThreadsCount(0)
      , mWaitingForThreads(0)
      , mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
      , mStarvationStartTimeMs(0)
      , mThreadPoolStarted(false)
      , mThreadPoolSeq(1)
      , mCallRestriction(CallRestriction::NONE)
  {
      if (mDriverFD >= 0) {
          // mmap the binder, providing a chunk of virtual address space to receive transactions.
          mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);//step2,执行mmap映射内存
  }

step1和step2很清晰,打开Binder驱动、mmap内存映射,确认无误frameworks的对进程访问Binder封装就是ProcessState,ProcessState本身是单例类,可以确保只有一次访问。
补充个面试知识点:
1.1.1、mmap映射内存时传递的大小是BINDER_VM_SIZE,宏定义是1M - 8k,因此我们在开发过程中,一次Binder调用的数据总和不能超过这个大小。 这就是面试问跨进程或者Intent传递数据有没有size限制,是多少的原因。就在于一个进程打开Binder驱动,mmap映射内存时传递的size就是BINDER_VMSIZE宏定义:1 * 1024 * 1024 - 4096 * 2。

在1.1之前我们说getContextObject这个函数非常有用,它是在ProcessState类中,

  /** /frameworks/native/libs/binder/ProcessState.cpp*/
  sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
  {
      sp<IBinder> context = getStrongProxyForHandle(0);  
      ...
      return context;
  }
  sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
  {
      sp<IBinder> result;  
      AutoMutex _l(mLock);  
      handle_entry* e = lookupHandleLocked(handle);/*此函数是查找一个Vector表
      * MHandlerToObject,这里保存了这个进程已经建立的Binder信息*/
  
      if (e != nullptr) {/*如果上面表中没查到,会自动添加一个,所以
      这里变量e正常情况都不为空*/
          IBinder* b = e->binder;
          if (b == nullptr || !e->refs->attemptIncWeak(this)) {
          /*在老版程序中,这里直接new BpBinder,现在改成直接调用transact,*/
              if (handle == 0) {
                  Parcel data;
                  status_t status = IPCThreadState::self()->transact(
                          0, IBinder::PING_TRANSACTION, data, nullptr, 0);   //step1,出现了IPCThreadState,看起来它也是单例类,数据是从这个地方传给Binder驱动
                  if (status == DEAD_OBJECT)
                     return nullptr;
              }  
              sp<BpBinder> b = BpBinder::create(handle);//出现了BpBinder
              e->binder = b.get();
              if (b) e->refs = b->getWeakRefs();
              result = b;
          } else {
              result.force_set(b);
              e->refs->decWeak(this);
          }
      }
      return result;
  }

来看step1这里,IPCThreadState::self()->transact 开始向Binder驱动注入查询的代码,这个是重点!!!,我们留到IPCThreadState类中讲,

总结一下,客户端进程通过defaultServiceManager函数,使用ProcessState类的构造函数打开了驱动并做好了mmap内存映射,这个函数里需要拿到sm服务的proxy,而这个proxy和真正的ServiceManager所提供的功能必须完全一样,例如addService、getService、listServices等,把这些提炼成proxy的接口,IServiceManager呼之欲出,也对应上了获取sm服务的代码。 接下来就是sm的本地代理函数开始调用getSeries,开始查询Binder Server的handle,然后开始真实的通信数据了,而在Binder驱动章节分析时,我们说过Binder驱动支持多线程的IPC业务,每个线程都应该与Binder驱动自由沟通的权利,上述三句main函数代码也表明,应当还有一个IPCThreadState类真正的在与Binder驱动进行实际命令的通信。配一张目前的流程图
在这里插入图片描述

1.2、IPCThreadState

简单介绍下IPCThreadState,这是个单例类,但它是线程内的单例类,就是说同一个线程内只会new一次,构造函数也只会被调用一次,而跨线程的时候,每个线程都有自己的线程缓存副本,各线程直接并不共享数据,这就是在线程栈上实例化类的,怎么实现呢?有个TLS(Thread Local Storage)概念,可以查资料学习下,Handel、looper、Message线程同步消息里有这个概念,很重要也很有用,学会了可以在诸多场景下使用。言归正传,到这里开始回到IPCThreadState::self()->transact(),

/* /frameworks/native/libs/binder/IServiceManager.cpp */
sp defaultServiceManager()->ProcessState::self()->getContextObject(nullptr)->getStrongProxyForHandle(0)->ProcessState::getStrongProxyForHandle->IPCThreadState::self()->transact

进入transact函数中,

status_t IPCThreadState::transact(int32_t handle,
                                    uint32_t code, const Parcel& data,
                                    Parcel* reply, uint32_t flags){
   ...
   flags |= TF_ACCEPT_FDS;
   err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);//step1,整理数据,打包成Binder驱动协议规定的格式,只是把数据存入mOuth中
   if (reply) {
              err = waitForResponse(reply);//step2,这里发送命令
          } else {
              Parcel fakeReply;//step3,做一个假的reply对象
              err = waitForResponse(&fakeReply);
          }
}

这个函数一开始会对data进行检查,Transaction有四种flags :

  1. TF_ONE_WAY:当前业务是异步,无需等待;
  2. TF_ROOT_OBJECT:包含对象是根对象;
  3. TF_STATUS_CODE:包含内容是32-bit的状态值
  4. TF_ACCEPT_FDS = 0x10:允许回复中包含文件描述符

初始flags是0,因此这里的值是TF_ACCEPT_FDS ,看几步step,到目前为止,transact只是把data按照Binder驱动协议的要求填写好了,还有一些工作要做:

  • BC_TRANSACTION属于BINDER_WRITE_READ的子命令,因此数据外面还要包裹一层描述信息
  • 数据现在还没有发出来
  • 之前我们说基于Binder的IPC大多数是阻塞式的,目前还未看到是怎么实现的

接着来看waitForResponse

/* /frameworks/native/libs/binder/IPCThreadState.cpp */
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
  {
      uint32_t cmd;
      int32_t err;
      while (1) {
          if ((err=talkWithDriver()) < NO_ERROR) break;
          err = mIn.errorCheck();
          if (err < NO_ERROR) break;
          if (mIn.dataAvail() == 0) continue;//mIn无数据,继续分析
          cmd = (uint32_t)mIn.readInt32();读取回复数据
      ...//后面是对数据的处理,一会接上来继续

这里的reply有数据,acquireResult是空的,talkWithDriver这个函数会将 mOut中已有的数据进行必要包装后发给驱动,当err = mIn.errorCheck();执行到,说明已经收到了Binder驱动的回复,通常说明Binder Server已经执行了相关请求,比如getService,并返回了结果。继续看talkWithDriver是怎么实现的:

/* /frameworks/native/libs/binder/IPCThreadState.cpp */
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    ...
    binder_write_read bwr;//读写都是这个结构体
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

当mIn需要读取(needRead)的时候,同时调用者又希望读取的时候(doReceive 为true),我们就不能写mOuth了。这里的读取和写入比较绕,多说几句,Parcel有几个重要的内部变量mData,mDataSize,mDataCapacity,mDataPos等。mData是指向某个内存地址的,表示parcel所包含的内部数据在内存的起始地址,而其他变量则是相对于mData来计算的,比如mDataSize是指当前parcel中已经有的数据量;mDataPos是当前已经处理的数据量。“处理”这个概念需要认真思考一下:

对于mIn来说----读取数据,就是处理,而对于mOut来说----写入才是处理,A进程给B进程传递Parcel,对于A来说是写入,需要操作的是mOut,对于B来说是读取,操作mIn所以这些变量在实际中是需要转换的。

//bwr写入wirte的大小
bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();
//bwr写入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;
}
//既不需要读,也不需要写的时候,直接返回
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
//真正与Binder驱动交互的是这一句,在驱动篇里我们应该很熟悉这个接口了
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)

    if (err >= NO_ERROR) {
          if (bwr.write_consumed > 0) {
              if (bwr.write_consumed < mOut.dataSize())
                  LOG_ALWAYS_FATAL("Driver did not consume write buffer. "
                                   "err: %s consumed: %zu of %zu",
                                   statusToString(err).c_str(),
                                   (size_t)bwr.write_consumed,
                                   mOut.dataSize());
              else {
                  mOut.setDataSize(0);
                  processPostWriteDerefs();
              }
          }
          if (bwr.read_consumed > 0) {
              mIn.setDataSize(bwr.read_consumed);
              mIn.setDataPosition(0);
          }

执行完ioctl后,通过bwr.write_consumed盒bwr.read_consumed可以知道Binder驱动对我们请求的BINDER_WIRITE_READ命令的处理情况,然后对mIn和mOut做善后清理。

  • bwr.write_consumed > 0
    说明Binder驱动消耗了mOut中的数据,所以需要把这部分数据移除掉,如果消耗的数据量小于mOut的总数据量,就单独去掉这部分数据,都消耗了,当然就直接mOut.setDataSize(0);
  • bwr.read_consumed > 0
    和上面差不多的意思,数目Binder驱动已经读取到了数据,并写入了mIn.data()所指向的内存地址,同时设置setDataSize和setDataPosition。

在驱动章节里,其实我们没详细分析binder_ioctl这个接口,因为只说概念记不住,看俩天就忘了,现在根据getService这个场景来深入分析binder_ioctl接口是怎么实现的,

/** \kernel\**kernel版本\drivers\android */
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;

我们知道,Binder 驱动的执行过程多是同步阻塞型,也就是说,通过驱动接口调用服务进程提供的接口函数,在函数执行结束时结果就会产生,不涉及回调机制,比如使用getService去想sm发起查询,返回的结果就是查询值,如何做到这样,就是暂时挂起调用者进程,直到目标进程返回结果后,Binder唤醒等待的进程:

ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
	if (ret)
		goto err_unlocked;

//wait_event_interruptible定义如下,condition满足会直接返回0,否则使用
//__wait_event_interruptible进入可中断的挂起状态
#define wait_event_interruptible(wq,condition)
({
    int __ret = 0;
    if(!condition){
        __wait_event_interruptible(wq,condition,__ret);
    }
    __ret;
})

#define wait_event_interruptible(wq,condition,ret)
do{
    DEFINE_WAIT(__wait);
    for(;;){
        prepare_to_wait(&wq,&__wait,TASK_INTERRUPTIBLE);
        if(condition){
            break;
        }
        if(!signal_pending(current)){
            schedule();
            continue;
        }
        ret = -ERESTARTSYS;
        break;
    }
    finish_wait(&wq,&__wait);
}while(0)

能看到有个for死循环,跳出循环的语句是condition满足要求,首先将自己设置成TASK_INTERRUPTIBLE,可中断挂起状态,然后进行schedule调度,也就是说,现在已经不是可运行状态,所以不会再分配CPU时间片,直到有人把它唤醒,再次检查condition是否满足,否则再次进入中断挂起状态。
binder_stop_on_user_error < 2,这里因为binder_stop_on_user_error是小于2的,所以不挂起:
binder_lock(func);此时ioctl已经取出之前为用户创建的proc结构体,计算出命令大小,继续

//向proc结构体中的threads链表查询是否已经添加了当前线程节点,如果没有则新插入当前线程的节点。thread链表是按pid大小排序的,可以加快查询速度
thread = binder_get_thread(proc);

//到这一步准备工作结束,可以处理具体命令
switch (cmd) {
	case BINDER_WRITE_READ:
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		if (ret)
			goto err;
		break;
}
static int binder_ioctl_write_read(struct file *filp,
				unsigned int cmd, unsigned long arg,
				struct binder_thread *thread)
{
	...
	if (size != sizeof(struct binder_write_read)) {//判断buffer大小
		ret = -EINVAL;
		goto out;
	}
	//从用户空间拷贝数据,还记得分析mmap接口时那张流程图吗?
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}
	//在talkWithDriver中,分析的W和R,驱动中也是分别判断是否可读和可写
	if (bwr.write_size > 0) {
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
		trace_binder_write_done(ret);
		if (ret < 0) {//成功返回0,出错返回负数
			bwr.read_consumed = 0;//出错的时候把已处理的数据大小置为0,
			//代表数据一个都没处理
			if (copy_to_user(ubuf, &bwr, sizeof(bwr)))//出错时,再把这些数据拷贝到用户空间,否则数据会丢失
				ret = -EFAULT;
			goto out;
		}
	}
	if (bwr.read_size > 0) {
		ret = binder_thread_read(proc, thread, bwr.read_buffer,
					 bwr.read_size,
					 &bwr.read_consumed,
					 filp->f_flags & O_NONBLOCK);
		trace_binder_read_done(ret);
		binder_inner_proc_lock(proc);
		if (!binder_worklist_empty_ilocked(&proc->todo))
			binder_wakeup_proc_ilocked(proc);
		binder_inner_proc_unlock(proc);
		if (ret < 0) {
			if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
				ret = -EFAULT;
			goto out;
		}
	}
}

不论是读取还是写入,Binder驱动只是数据的搬运工,真正处理请求的还是Binder的Client和Server双方。继续看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;
	struct binder_context *context = proc->context;
	void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
	void __user *ptr = buffer + *consumed;
	void __user *end = buffer + size;			

在getService这个功能流程下,

  • proc是调用者进程,
  • thread是调用者线程,
  • buffer是bwr.write_buffer,talkWithDriver中:bwr.write_buffer = (long unsigned int)mOut.data();在writeTransactionData中对mOut写入的数据主要是俩部分,

mOut.writeInt32(cmd);cmd是BC_TRANSACTION
mOut.write(&tr,sizeof(tr));tr是binder_transaction_data数据结构的变量

  • size是bwr.write_size
  • consumed是bwr.write_consumed

uint32_t cmd;
void __user *ptr = buffer + *consumed;//忽略已经处理的部分
void __user *end = buffer + size;//bufffer尾部
这里的ptr和end分别指向buffer需要处理的数据起点和终点

buffer中可以有不只一个命令和其对应的数据,因此接下来会循环处理命令:

while (ptr < end && thread->return_error.cmd == BR_OK) {
		int ret;

		if (get_user(cmd, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);
		trace_binder_command(cmd);
		if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) {
			atomic_inc(&binder_stats.bc[_IOC_NR(cmd)]);
			atomic_inc(&proc->stats.bc[_IOC_NR(cmd)]);
			atomic_inc(&thread->stats.bc[_IOC_NR(cmd)]);
		}
		...
		//因为这里的命令是BC_TRANSACTION,所以只看这个分支,循环体太长,大家可以自己追代码
		case BC_TRANSACTION:
		case BC_REPLY: {
		  //这组命令格式为 cmd | binder transaction_data
			struct binder_transaction_data tr;
			if (copy_from_user(&tr, ptr, sizeof(tr)))//很熟悉,用户空间拷贝tr结构体
				return -EFAULT;
			ptr += sizeof(tr);
			binder_transaction(proc, thread, &tr,
					   cmd == BC_REPLY, 0);//具体执行的命令
			break;
		}
}//结束binder_thread_write		

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply,
			       binder_size_t extra_buffers_size)
{
  //目标所在进程、线程,在我们当前分析的场景下就是ServiceManager的进程,显然,它们很重要  
	struct binder_proc *target_proc = NULL;
	struct binder_thread *target_thread = NULL;
	struct binder_node *target_node = NULL;
	struct binder_transaction *t;//代表一个transaction操作
	//表示一个未完成的操作,因为一个transaction通常是A和B两个进程的交互,当A向B发了请求,B需要一段时间执行;这个时候对于A来说就是一个未完成的操作,知道B返回了结果,Binder驱动才会再次启动A继续执行
	struct binder_work *tcomplete;
	//这函数要处理BC_TRANSACTION和BC_REPLY两种情况,所以需要把他们分开
	if (reply) {
	...//我们忽略reply的情况,暂时只看transaction
	}} else {
		if (tr->target.handle) {
		  //这是handle不为0的情况
			struct binder_ref *ref;
			ref = binder_get_ref_olocked(proc, tr->target.handle,true);
			target_node = binder_get_node_refs_for_txn(ref->node, &target_proc,&return_error);
		}	else {
		//这是handle为0的情况,也就是ServiceManager,就能直接使用
		//binder_context_mgr_node这个全局变量
		target_node = context->binder_context_mgr_node;//step1,取到目标对象对于的target_node
		if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
			struct binder_transaction *tmp;
			tmp = thread->transaction_stack;
			...
			while (tmp) {
			 if (from && from->proc == target_proc) {
					atomic_inc(&from->tmp_ref);
					target_thread = from;//step2,取到目标对象的进程和现场
					spin_unlock(&tmp->lock);
					break;
				}
				spin_unlock(&tmp->lock);
				tmp = tmp->from_parent;
			 }
			}
		...
		//生成一个binder_transaction变量t,用于描述本次要进行的transaction,target_thread->todo,这样当目标对象被唤醒时,他就可以从这个队列中取出需要做的工作
		t = kzalloc(sizeof(*t), GFP_KERNEL);
		//生成一个binder_work变量,用于说明当前调用者现场有一个未完成的transaction,它最后会被加入本线程的todo队列中,
		tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
		...
		if (!reply && !(tr->flags & TF_ONE_WAY))
		  t->from = thread;
		else
		  t->from = NULL;//谁发起
	    t->sender_euid = task_euid(proc->tsk);
	    t->to_proc = target_proc;//目标对象的进程,ServiceManager
	    t->to_thread = target_thread;//目标对象的线程,ServiceManager
	    t->code = tr->code;//面向Binder Server的请求码,getService服务对应的是GET_SERVICE_TRANSACTION
	    t->flags = tr->flags;//后边还有很多,就是填写transaction的数据
	    t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
		tr->offsets_size, extra_buffers_size,
		!reply && (t->flags & TF_ONE_WAY));//为了完成本条transaction所申请的内存,也就是Binder驱动中mmap管理的内存区域,这就是驱动一次拷贝就能够传递数据,因t->buffer指向的内存空间和目标对象是共享的
		binder_alloc_copy_to_buffer(&target_proc->alloc,
					    t->buffer, buf_offset,
					    secctx, secctx_sz);
		t->work.type = BINDER_WORK_TRANSACTION;
		tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;    

后续把binder_transaction变量t放到目标的处理队列中,tcomplete放到调用者自己的todo队列,然后开始唤醒目标对象,也就是ServiceManager(驱动的代码实在是太长了,这里暂时用逻辑概括处理,哪天有心情了再把唤醒ServiceManager这块代码框架补上),唤醒sm后,sm接着read操作读取出具体请求后,用binder_parse解析,最后用BR_REPLY返回Binder驱动,发起getService请求的Binder Client在等待SM的过程中会休眠,直到被Binder驱动唤醒,它和Service Name一样也在read中睡眠,因此醒来可以继续读取。本次得到的就是对ServiceManager对请求的执行结果。程序先把结果填充到reply这个Parcel中,然后通过驱动返回给调用者进程,最后经过类型转换为IBinder对象后传给调用者。

2、binder在native层的系统封装

上面说的都是源码框架的原理流程,我们在开发过程中实际上是使用不到这些东西的,当然会了原理自己强行撸轮子也没问题,就得大量编写代码,native层也做了很多封装,现在开始我们从实战角度出发,看看怎么使用c++的binder机制去实现Binder Server和client。

首先我们知道binder是一种跨进程技术,根基在kernel内核的驱动上,native的c++可以使用,application层的Java必然也得使用,而Java不可能直接用到驱动代码去,需要通过hal层的c++代码转接,也就是说,Java代码要使用binder,需要通过JNI复用native层的代码,frameworks的功效又体现出来了,binder在frameworks中分为c++和Java两部分,c++部分的头文件在/frameworks/native/include/binder,实现部分在/frameworks/native/libs/binder,源码编译完后,会生成libbinder.so动态库。

在libbinder中,binder分为proxy和native两部分,根据proxy合理推测一下,proxy当然是客户端,native就成服务端了,实际正是如此,这里就引申出来一个小技巧,libbinder上,所有你看到带p的,它就属于客户端阵营,带n的属于服务端阵营(也就是native)。native字面理解,本地的,就是说我们在理解的时候,站在服务端这一侧,称之为本地端,相对应proxy就是远程端,先列举各种使用到的模板类

类名说明
BpRefBaseRefBase的子类,提供remote()接口获取远程binder
IInterfacebinder服务接口的父类,IServicemanager这个类就是继承它,同时提供给C/S两端使用
BpInterface客户端使用的基类,泛型中传入自己写的IXX(例如IServicemanager,父类就是IInterface),这个类提供客户端调用的接口
BnInterface服务端使用的基类,IServerName接口集中的接口函数必须在服务进程中实现
IBinderBinder对象的基类,BBinder、BpBinder、BinderProxy都是继承它
BpBinderBpBinder代表远程的客户端Binder,使用remote()->transact函数来发数据
BBinder代表本地的服务端Binder, 提供了onTransact接口来接收请求

我们从IBinder入手,BnBinder和BpBinder都是它的子类,介绍其重要接口函数:

接口说明
localBinder获取本地Binder对象
remoteBinder获取远程Binder对象
transaction进行一次Binder操作
queryLocalInterface获取本地Binder对象,失败则返回NULL
getInterfaceDescriptor获取Binder服务的唯一标识符
isBinderAlive查询Binder是否还活着
pingBinder发送PING_TRANSACTION给Binder服务

IBinder类中,最重要的就是transact接口,上面的文章我们详细分析了接口是如何从上层传递到驱动

transact(int32_t handle,uint32_t code, const Parcel& data,
        Parcel* reply, uint32_t flags)
//IBinder中定义了uint32_t code允许的范围:
//FIRST_CALL_TRANSACTION  = 0x00000001,
//LAST_CALL_TRANSACTION   = 0x00ffffff,

handle就是服务端的句柄,一般情况Binder服务会提供很多接口函数,那么服务端是怎么确定客户端调用的是哪个,就靠code,其实就是C/S两端共同约定一个码,用来指代接口函数(就当它是函数别名),BBinder对象代表本地Binder,它描述了服务的提供方,所有Binder服务的实现者都要继承这个类(的子类,也就是BnInterface),在继承类中,最重要的就是实现onTransact方法,因为这个方法是所有请求的入口。因此,这个方法是和BpBinder中的transact方法对应的,这个方法同样也有一个uint32_t code参数,在这个方法的实现中,由服务提供者通过code对请求的接口进行区分,然后调用具体实现服务的方法。
我们继续看BpReBase,IInterface,BpInterface和BnInterface。每个Binder服务都是为了某个功能而实现的,因此其本身会定义一套接口集(通常是C++的一个类)来描述自己提供的所有功能。而Binder服务既有自身实现服务的类,也要有给客户端进程调用的类。为了便于开发,这两中类里面的服务接口应当是一致的,例如:假设服务实现方提供了一个接口为add(int a, int b)的服务方法,那么其远程接口中也应当有一个add(int a, int b)方法。因此为了实现方便,本地实现类和远程接口类需要有一个公共的描述服务接口的基类(即上图中的IXXXService)来继承。而这个基类通常是IInterface的子类,IInterface的定义如下:

class IInterface : public virtual RefBase
{
public:
            IInterface();
            static sp<IBinder>  asBinder(const IInterface*);
            static sp<IBinder>  asBinder(const sp<IInterface>&);
 
protected:
    virtual                     ~IInterface();
    virtual IBinder*            onAsBinder() = 0;
};

让IXXXService继承IInterface类就是因为集成了子类必须要实现的虚函数onAsBinder,在本地端,返回的就是服务端this,在客户端,返回的就是远程服务对象,onAsBinder方法被两个静态方法asBinder方法调用。有了这些接口之后,在代码中便可以直接通过IXXX::asBinder方法获取到不用区分本地还是远程的IBinder对象。这个在跨进程传递Binder对象的时候有很大的作用(因为不用区分具体细节,只要直接调用和传递就好)。接着来看BpInterface和BnInterface的定义

template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);
    virtual const String16&     getInterfaceDescriptor() const;
protected:
    virtual IBinder*            onAsBinder();
};
// ----------------------------------------------------------------------
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
                                BpInterface(const sp<IBinder>& remote);
 
protected:
    virtual IBinder*            onAsBinder();
};

这两个类都是模板类,它们在继承自INTERFACE的基础上各自继承了另外一个类。这里的INTERFACE便是我们Binder服务接口的基类。另外,BnInterface继承了BBinder类,由此可以通过复写onTransact方法来提供实现。BpInterface继承了BpRefBase,通过这个类的remote方法可以获取到指向服务实现方的句柄。在客户端接口的实现类中,每个接口将数据封装到Parcel后,都会调用remote()->transact来发送请求,而这里其实就是调用的BpBinder的transact方法,这样请求便通过Binder到达了服务实现方的onTransact中。

基于Binder框架开发的服务,除了满足上文提到的类名规则之外,还需要遵守其他一些共同的规约:

1、为了进行服务的区分,每个Binder服务需要指定一个唯一的标识,这个标识通过getInterfaceDescriptor返回,类型是一个字符串。通常,Binder服务会在类中定义static const android::String16 descriptor;这样一个常量来描述这个标识符,然后在getInterfaceDescriptor方法中返回这个常量。
2、为了便于调用者获取到调用接口,服务接口的公共基类需要提供一个android::sp asInterface方法来返回基类对象指针。

由于上面提到的这两点对于所有Binder服务的实现逻辑都是类似的。为了简化开发者的重复工作,在libbinder中,定义了两个宏来简化这些重复工作,非常重要也很难看懂,只能重复无限次去看,直到有一天你会发现,顿悟了:

//这个宏是在IXXService的头文件使用,类似JavaBean中的get或者是虚函数
#define DECLARE_META_INTERFACE(INTERFACE)                            
    static const android::String16 descriptor;                       
    static android::sp<I##INTERFACE> asInterface(const android::sp<android::IBinder>& obj);               
    virtual const android::String16& getInterfaceDescriptor() const;
    I##INTERFACE();                                                  
    virtual ~I##INTERFACE(); 

//这个宏在cpp文件中实现,类似JavaBean中的set或者是虚函数的实现
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                    
    const android::String16 I##INTERFACE::descriptor(NAME);          
    const android::String16&                                         
            I##INTERFACE::getInterfaceDescriptor() const {           
        return I##INTERFACE::descriptor;                             
    }                                                                
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(             
            const android::sp<android::IBinder>& obj)                
    {                                                                
        android::sp<I##INTERFACE> intr;                              
        if (obj != NULL) {                                           
            intr = static_cast<I##INTERFACE*>(                       
                obj->queryLocalInterface(                            
                        I##INTERFACE::descriptor).get());            
            if (intr == NULL) {                                      
                intr = new Bp##INTERFACE(obj);                       
            }                                                        
        }                                                            
        return intr;                                                 
    }                                                                
    I##INTERFACE::I##INTERFACE() { }                                 
    I##INTERFACE::~I##INTERFACE() { }

上述基本上把Binder在native层和驱动的数据流转过程给讲差不多了,但实际上我们还是难以写出自已的Binder Server,这不仅是我的博客描述太low的原因,还有就是软件思路这个东西,看的时候觉得学会了,如果不动手敲,三天就会忘,因此接下来我们真实做一套Binder跨进程的demo。

3、实战Binder跨进程demo,手撸Binder Server、Binder Client

我们之前说过binder最终是编译成一个so库,而自定义的Binder跨进程,其实也是编译成一个so库,供Server端和Client端集成并使用,当然你也可以不编这个库,直接把代码搬到两端,就像我们在java中使用某个jar包、sdk,可以在我们自己的项目中链接这个jar包,对自己狠一点直接把jar包里所有的代码全复制到自己项目中来(显然没人这么干),AIDL大家应该都用过,不论它原理是什么,总归C/S两端都会拷贝使用,这个so库也可以理解成某种基类,因为最终,binder的那些接口,是要Server端去真正实现的。先来编写so库。

3.1跨进程的Binder so库

//这是头文件
#ifndef IBINDERIPCDEMOSERVICE_H
#define IBINDERIPCDEMOSERVICE_H

#include <stdint.h>
#include <sys/types.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <binder/IInterface.h>
#define IBINDER_IPCDEMO_SERVICE_NAME  "IBinderIPCDemoService"
namespace android
{
  enum {
    FUNC_ADD_NUM = IBinder::FIRST_CALL_TRANSACTION,
};
// ----------------------------------------------------------------------------
class IBinderIPCDemoService : public IInterface
{
public:
    DECLARE_META_INTERFACE(BinderIPCDemoService);

    virtual void func_add_num(const int32_t a,const int32_t b, const void *msg, const int32_t msgLen) = 0;
};

// ----------------------------------------------------------------------------

class BnBinderIPCDemoService : public BnInterface<IBinderIPCDemoService>
{
  public:
      virtual status_t onTransact(uint32_t code,
                                  const Parcel &data,
                                  Parcel *reply,
                                  uint32_t flags = 0);
  };

}; //namespace android

#endif//IBINDERIPCDEMOSERVICE_H

//这是cpp实现
#include <stdint.h>
#include <sys/types.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <utils/Vector.h>
#include <utils/Timers.h>
#include <utils/Log.h>
#include <binder/Parcel.h>
#include <binder/IInterface.h>
#include "IBinderIPCDemoService.h"

namespace android {

class BpBinderIPCDemoService : public BpInterface<IBinderIPCDemoService>
{
public:
    explicit BpBinderIPCDemoService(const sp<IBinder>& impl)
        : BpInterface<IBinderIPCDemoService>(impl)
    {
    }

    void func_add_num(const int32_t a,const int32_t b const void *msg, const int32_t msgLen)
    {
      Parcel data;
      data.writeInterfaceToken(IBinderIPCDemoService::getInterfaceDescriptor());
      data.writeInt32(a);
      data.writeInt32(b);
      data.writeInt32(msgLen);
      data.write(msg, msgLen);
      remote()->transact(FUNC_ADD_NUM, data, NULL);
    }
    
};

IMPLEMENT_META_INTERFACE(BinderIPCDemoService, "android.os.IBinderIPCDemoService");
// ----------------------------------------------------------------------
status_t BnBinderIPCDemoService::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    ALOGE("%s: Enter, code = %d", __func__, code);    
    switch(code)
    {
      case FUNC_ADD_NUM:{
        CHECK_INTERFACE(IBinderIPCDemoService, data, reply);
        int32_t a = data.readInt32();
        int32_t b = data.readInt32();
        int32_t msgLen = data.readInt32();
        char msg[msgLen];
        data.read(msg,msgLen);
        func_add_num(a,b, msg, msgLen);
        return OK;
      }
    }
    
    return BBinder::onTransact(code, data, reply, flags);
}

// ----------------------------------------------------------------------------
}; // namespace android

//这是Android.mk文件
# BinderIPCDemo service lib
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
				
LOCAL_SRC_FILES:= \
    BinderIPCDemo.cpp

LOCAL_SHARED_LIBRARIES:= \
    libcutils \
    liblog \
    libutils \
    libbinder

LOCAL_MODULE:= libBinderIPCDemoservice
LOCAL_MODULE_TAGS := optional

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)

头文件和cpp实现都有了,编译放到哪个目录都行,不过要注意,如果你的项目中客户端和服务端需要全编就自动打包进镜像Image的时候,就要特别注意这个so库得先生成,不然你的C/S两端在集成这个so库的时候可能会因为链接时找不到so库报错。

3.2 Binder Server端

首先一个要点是Binder Server需要发布,也就是把服务添加进ServiceManager里,Client端才能在SM中查询到,而在BinderService类中,提供了publishAndJoinThreadPool方法来简化服务的发布,其代码如下:

static void publishAndJoinThreadPool(bool allowIsolated = false) {
   publish(allowIsolated);
   joinThreadPool();
}
 
static status_t publish(bool allowIsolated = false) {
   sp<IServiceManager> sm(defaultServiceManager());
   return sm->addService(
           String16(SERVICE::getServiceName()),
           new SERVICE(), allowIsolated);
}
 
...
 
static void joinThreadPool() {
   sp<ProcessState> ps(ProcessState::self());
   ps->startThreadPool();
   ps->giveThreadPoolName();
   IPCThreadState::self()->joinThreadPool();
}

由此可见,Binder服务的发布其实有三个步骤:

1、通过IServiceManager::addService在ServiceManager中进行服务的注册
2、通过ProcessState::startThreadPool启动线程池
3、通过IPCThreadState::joinThreadPool将主线程加入的Binder中

Server端实现接口函数的时候,把上面的头文件给引用进来,然后继承BnBinderIPCDemoService这个类,在mk中把上述生成的so库链接进来,

//头文件
//记得要引入BnBinderIPCDemoService它的头文件,不然找不到这个服务端
namespace android{
class BinderIPCDemoService  : public BnBinderIPCDemoService, public BinderService<BinderIPCDemoService> 
{                    
  public:
    BinderIPCDemoService() ANDROID_API;
    virtual ~BinderIPCDemoService();
    static char const* getServiceName() ANDROID_API { return BINDER_IPCDEMO_SERVICE_NAME; }
    void func_add_num(const int32_t a,const int32_t b const void *msg, const int32_t msgLen)
};
}

//cpp 实现类就很简单,具体实现func_add_num这个函数逻辑就行,就类似Java接口中定义方法了,你去实现这个接口,这里就不写了

3.3 Binder Client端

这里其实可以分俩部分,一种是Client端也在native层,使用c++;一种是Client端是在Java层,在Java层的就需要额外在做一套AIDL接口文档,然后在frameworks里声明这个服务(字符串去声明服务名),然后AIDL编译后就能链接,像Server一样集成这个so库,

3.3.1 Client端在native层

先说native层,这个就比较简单,client的mk中链接这个so库,LOCAL_SHARED_LIBRARIES := libBinderIPCDemoservice,这些头文件引入进来,

//IBINDER_IPCDEMO_SERVICE_NAME这个宏是在so库的头文件上定义的
sp<IBinder> binder = defaultServiceManager()->getService(String16(IBINDER_IPCDEMO_SERVICE_NAME));
sp<IBinderIPCDemoService> IBinderIPCDemoService = interface_cast<IBinderIPCDemoService>(binder);

这样就拿到了Binder Server,可以用IBinderIPCDemoService去调用服务端的接口了over

3.3.2 Client端在Java层

这里稍微复杂点,如果我们这个服务要做成系统级别的,类似相机什么的随时可以getService,就得在frameworks\base\core\java\android\content\Context.java类中声明成员变量

    /*frameworks\base\core\java\android\content\Context.java*/
    public static final String IBINDERIPCDEMOSERVICE = "BinderIPCDemoservice";
    @StringDef(suffix = { "_SERVICE" }, value = { IBINDERIPCDEMOSERVICE })

然后在frameworks\base\core\java\android\os中放入AIDL文件

package android.os;
interface IBinderIPCDemoService{
    void func_add_num(int a, int b, in byte[] msg, int msgLen);
}

这样设置之后,先编译下frameworks模块,让AIDL文件编译出来,Java层客户端进程中就能getService找到自定义的服务

IBinder b = ServiceManager.getService(Context.IBINDERIPCDEMOSERVICE);
m_IBinderIPCDemoService = IBinderIPCDemoService.Stub.asInterface(b);

其实AIDL不是说非要放frameworks模块里,放自己的客户端进程也行,服务名称也不是非要在Context.java里声明,客户端进程直接给传字符串也没问题,就看你做的这个服务是不是想立足frameworks或者说Android系统上给所有应用提供服务接口,如果只是你这一个客户端进程使用,放自己进程也无所谓。
写到这里,Binder最重要的一些概念和框架就都讲完了,怎么使用也展示完成了,现在已经四篇文章篇幅非常的多,可能有些概念还是没说清楚,欢迎留言讨论,笔者可以在各位开发的遇到问题的时候相互交流,谢谢,最后还会有一篇文章来分析从Java的角度自上到jni复用c++框架,和纯Java语音的跨进程-AIDL作为Binder机制的结尾。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值