本文主要内容

  • Binder驱动接口
  • ServiceManager的发动
  • Binder规划理念
  • 总结

Binder驱动接口

Binder驱动一共有3个重要的接口:

  • binder_open,翻开binder驱动
  • binder_mmap,内存映射
  • binder_ioctl,与binder驱动通讯

binder_mmap是整套机制的基础,经过内存映射,不同进程间有一块同享内存,得以实现IPC。

进程间通信——Binder

binder_ioctl,类似于文件读写办法,它承当了Binder驱动的大部分事务,与Binder驱动通讯。

看上图,只需求把进程A的内存空间仿制到内核空间中(copy from user),内核空间中有了进程A的东西,然后经过内存映射给进程B读取,所以说Binder通讯只需求一次内存仿制

关于以上3个办法,后续再补充具体阐明。

ServiceManager的发动

ServiceManager是十分重要的体系服务,它类似于网络世界中的DNS(请拜见进程能通讯——智能指针)。 它是在init.rc中发动的,代码位于/framework/native/cmds/servicemanager 目录中,sm发动代码如下:

  //service_manager.c
int main(int argc, char** argv)
{
struct binder_state *bs;
char *driver;
if (argc > 1) {
    driver = argv[1];
} else {
    driver = "/dev/binder";
}
//翻开驱动
bs = binder_open(driver, 128*1024);
//将自己设置成context manager人物,ServiceManager体系中肯定只有一个
if (binder_become_context_manager(bs)) {
    ALOGE("cannot become context manager (%s)\n", strerror(errno));
    return -1;
}
//进入循环
binder_loop(bs, svcmgr_handler);
}

代码较类似于socket通讯server端代码,新建ServerSocket,发动死循环,等待连接。查看binder_open代码:

struct binder_state *binder_open(const char* driver, size_t mapsize)
{
  struct binder_state *bs;
  bs = malloc(sizeof(*bs));
  //真实地翻开binder驱动
  bs->fd = open(driver, O_RDWR | O_CLOEXEC);
  //内存映射
  bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
  return bs;
}

上一章中说到的binder驱动三大办法中,现在现已呈现俩了,binder_open办法中翻开binder驱动,并且完成内存映射。

接下来看看binder_loop办法:

void binder_loop(struct binder_state *bs, binder_handler func)
{
  for (;;) {
	  //调用ioctl,与binder驱动通讯,从驱动中读取数据,并存放在bwr指针中
      res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
	  //处理读取的指令
      res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
  }
}

binder_loop中存在一个死循环,循环中不断从驱动中读取指令,并处理指令。接着看binder_parse办法:

int binder_parse(struct binder_state *bs, struct binder_io *bio,
             uintptr_t ptr, size_t size, binder_handler func)
{
  while (ptr < end) {
      switch(cmd) {
      case BR_TRANSACTION: 
          struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
          if (func) {
			  //func是一个函数指针,在此调用函数并回来,此函数类似于onTransact
              res = func(bs, txn, &msg, &reply);
              if (txn->flags & TF_ONE_WAY) {
                  binder_free_buffer(bs, txn->data.ptr.buffer);
              } else {
                 binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
              }
          }
          ptr += sizeof(*txn);
          break;
      }
  }
}

binder_parse办法中,依据cmd的值履行对应的办法,当客户端调用transact办法时,ServiceManager会回调func函数。func是一个函数指针,它实质上是:

int svcmgr_handler(struct binder_state *bs,
               struct binder_transaction_data *txn,
               struct binder_io *msg,
               struct binder_io *reply)
{
  switch(txn->code) {
  case SVC_MGR_GET_SERVICE:
  case SVC_MGR_CHECK_SERVICE:
	  //调用do_find_service,查找对应的service binder
      handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
  case SVC_MGR_ADD_SERVICE:
	  //增加service
      do_add_service(bs, s, len, handle, txn->sender_euid,allow_isolated, txn->sender_pid)
  case SVC_MGR_LIST_SERVICES: {
	  //链表数据结构,回来节点
      si = svclist;
      while ((n-- > 0) && si)
          si = si->next;
  }
  return 0;
}

所以,当service manager从binder驱动中读取指令,并依据不同的指令履行不同的办法,最终将成果回来给binder驱动。

service manager与binder交互的逻辑,十分类似于socket通讯,先翻开binder驱动,再调用mmap办法映射内存,最终发动死循环,调用ioctl办法从binder驱动中获取指令,依据指令调用对应办法,回来成果给binder驱动。

Binder规划理念

如果是咱们自己来规划binder,那么咱们该怎么做?

很明显,参照service manager,在server端,用户需求翻开binder驱动,履行内存映射,建议死循环,与binder驱动通讯等等。client端使用仍是需求履行以上进程。进程适当繁锁,必须要进行相应封装,以使用用户开发。

并且以上代码均是c或c++代码,android上层用户更多地使用java,需求给java用户供给更友爱的使用方式。

binder主要在两个方面进行封装:

  • 封装与binder驱动交互的部分
  • 使用代理,proxy

1、ProcessState和IPCThreadState

ProcessState,专门管理每个使用进程中的Binder操作,包含与翻开驱动、内存映射等等,每个进程中只需求翻开一次驱动,履行一次内存映射即可。

使用进程中肯定会有多个线程,每个线程都可能需求进行进程间通讯,负责这个作业的便是IPCThreadState。实际上ProcessState仅仅负责翻开驱动、内存映射等,而与binder驱动实质进行通讯的是IPCThreadState。

2、proxy

binder中proxy无处不在,用户不便直接与service manager打交道,尤其是在java端,所以规划出proxy,然后更便利客户调用。

总结

关于binder机制,其实以前也曾写过一篇文章,Android binder机制(native服务篇),关于客户端部分如何与server端交互,请参照上文,今天再次阅读《深化理解android内核规划思维》,有了新的感悟,果然是常看常新。

有时往往代码特别多,不要害怕,宏大的代码,无数的文件往往仅仅作者在掩盖一些具体细节,在封装,便利用户调用而已,而繁琐的背面仅仅那一点点特别简单的道理,比方binder机制,中心原理和socket通讯几乎没有差别。只要咱们捉住主线,不要怕麻烦,全部都能尽在掌握当中

请允许我装逼一下,全部有为法,皆梦幻泡影,如雾亦如电,应作如是观。