一、前语

Binder驱动层是整个Binder机制的中心,可是相对处于比较底层的位置,关于Android开发者来说,或许就仅仅知道闻名的“一次复制”了,可是一次复制是怎么完成的?恐怕大多数人也不甚了解。这一系列的文章会对整个Binder机制做一个相对深入的剖析,咱们先从驱动层开始。一切的代码都基于Android12。

二、Binder驱动是什么?

一般来说,驱动是针关于具体设备而言的,例如音频驱动、蓝牙驱动,可是Binder驱动并不是针对具体的设备,而是针对虚拟设备,作为一个字符节点而存在的,它的设备节点叫做/dev/binder,在手机的运转目录下面,也能够看到这个设备:


emulator64_x86_64_arm64:/ $ ls -l /dev/binder
lrwxrwxrwx 1 root root 20 2023-02-26 04:05 /dev/binder -> /dev/binderfs/binder

Binder驱动是运转在内核态的,以32位体系为例,关于Linux的不同进程来说,内核占用了从0xC00000000xFFFFFFFF的1G内存空间,用户空间占用从00xBFFFFFFF的3G内存空间,不同的进程运用的内核空间是相同的,这也是Binder驱动能运转的基本要素,由于内核空间是公用的,所以IPC才得以完成,不同的进程能够经过陷入内核态,然后让内核进行数据操控与传递,然后完成进程间的通讯,一般来说,IPC至少需要两次内核与用户空间之间的数据复制,而Binder只需要一次数据复制,这使得数据传输速率大大增加,这一点咱们在后边打开。

Binder - 1、初始化
图2.1 – 进程虚拟地址空间散布

Binder - 1、初始化
图2.2 – 进程虚拟地址空间具体阐明

三、Binder驱动的注册与初始化


Binder驱动层代码位于Kernel中
\kernel\drivers\android
\kernel\include\uapi\linux\android

3.1 驱动的挂载

咱们在binder.c中能够看到Binder驱动的初始化代码:


device_initcall(binder_init);
static int __init binder_init(void)
static int __init init_binder_device(const char *name)

这几个函数是一条调用链,其间的__init修饰符代表将此段代码放到.init.text段中,等初始化结束之后,这部分代码会被初始化,咱们对驱动的初始化并不关心,重点看怎么调用。在init_binder_device函数中,会注册一系列的办法:


const struct file_operations binder_fops = {
  .owner = THIS_MODULE,
  .poll = binder_poll,
  //进程经过ioctl调用下来
  .unlocked_ioctl = binder_ioctl,
  .compat_ioctl = compat_ptr_ioctl,
  //进程履行mmap
  .mmap = binder_mmap,
  //进程履行open体系调用
  .open = binder_open,
  .flush = binder_flush,
  .release = binder_release,
};

这些体系机制怎么完成比较复杂,但对咱们的剖析没有影响,咱们只要知道几个主要函数即可:

  • binder_open 用户进程调用open时,驱动会调用此办法,调用之后,用户进程能够拿到相应的fd

  • binder_mmap 用户进程在open之后,拿到相应的fd,经过此fd进行mmap,就会调用到此办法

  • binder_ioctl 用户进程与驱动之间真正的数据交互,用户进程调用ioctl办法就能够调用到此办法

在Linux中,一切都是文件,比及/dev/binder挂载结束之后,用户进程就能够经过上述的体系调用进行对Binder驱动的操控了。咱们先看下结构层的完成。

3.2 用户进程的初始化

在剖析之前,咱们首要介绍两个类:

\frameworks\native\libs\binder\ProcessState.cpp

\frameworks\native\libs\binder\IPCThreadState.cpp

这两个类是framework层与驱动进行交互的中心代码,ProcessState望文生义,代表进程的状况,运用的是单例,每个应用会初始化一次,它负责打开Binder驱动、保存一些数据与状况。IPCThreadState负责与Binder驱动进行通讯,是结构层与驱动层的桥梁。

3.2.1 ProcessState

咱们先看ProcessState的初始化,ProcessState并不是直接new出来的,而是有个self办法:


sp<ProcessState> ProcessState::self()
{
  return init(kDefaultDriver, false /*requireDefault*/);
}

调用到init办法:


#ifdef __ANDROID_VNDK__
const char* kDefaultDriver = "/dev/vndbinder";
#else
const char* kDefaultDriver = "/dev/binder";
#endif
sp<ProcessState> ProcessState::init(const char *driver, bool requireDefault)
{
  [[clang::no_destroy]] static sp<ProcessState> gProcess;
  [[clang::no_destroy]] static std::mutex gProcessMutex;
  // ...
  [[clang::no_destroy]] static std::once_flag gProcessOnce;
  //只调用一次
  std::call_once(gProcessOnce, [&](){
    if (access(driver, R_OK) == -1) {
      ALOGE("Binder driver %s is unavailable. Using /dev/binder instead.", driver);
      driver = "/dev/binder";
    }
    std::lock_guard<std::mutex> l(gProcessMutex);
    gProcess = sp<ProcessState>::make(driver);
  });
  // ...
  return gProcess;
}

这儿运用了StrongPointer(sp),可是限制只履行一次,所以也是一种单例,每个进程中只有一个目标存在,最终会履行到ProcessState的结构函数:


ProcessState::ProcessState(const char *driver)
  : mDriverName(String8(driver))
  //打开Binder驱动
  , mDriverFD(open_driver(driver))
  , 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.
    // 对拿到的fd进行mmap,最终进入驱动的binder_mmap中
    mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
    //...
  }
}

其他参数平平无奇,仅仅对属性的默许赋值,可是mDriverFD却调用了open_driver办法:


static int open_driver(const char *driver)
{
  //打开驱动,最终调用到驱动的binder_open办法
  int fd = open(driver, O_RDWR | O_CLOEXEC);
  if (fd >= 0) {
    int vers = 0;
    //获取驱动版本,调用到驱动的binder_ioctl办法
    status_t result = ioctl(fd, BINDER_VERSION, &vers);
    //...
    size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
    result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
    //...
    uint32_t enable = DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION;
    result = ioctl(fd, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable);
    //...
  }
  //...
  return fd;
}

结构函数初始化先调用open函数,拿到fd之后,履行mmap然后进入binder_mmap办法。//todo

3.2.2 IPCThreadState

IPCThreadStateProcessState相似,都是单例,其他地方也是经过IPCThreadState::self进行调用:


IPCThreadState* IPCThreadState::self()
{
  if (gHaveTLS.load(std::memory_order_acquire)) {
restart:
    const pthread_key_t k = gTLS;
    IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
    if (st) return st;
    return new IPCThreadState;
  }
 
  pthread_mutex_lock(&gTLSMutex);
  if (!gHaveTLS.load(std::memory_order_relaxed)) {
    int key_create_value = pthread_key_create(&gTLS, threadDestructor);
    if (key_create_value != 0) {
      pthread_mutex_unlock(&gTLSMutex);
      ALOGW("IPCThreadState::self() unable to create TLS key, expect a crash: %s\n",
          strerror(key_create_value));
      return nullptr;
    }
    gHaveTLS.store(true, std::memory_order_release);
  }
  pthread_mutex_unlock(&gTLSMutex);
  goto restart;
}

首要在ThreadLocalStorage中创建一个key,然后经过这个key新建一个目标,后续一切的调用都是基于这个目标。


IPCThreadState::IPCThreadState()
   : mProcess(ProcessState::self()),
    mServingStackPointer(nullptr),
    mWorkSource(kUnsetWorkSource),
    mPropagateWorkSource(false),
    mIsLooper(false),
    mIsFlushing(false),
    mStrictModePolicy(0),
    mLastTransactionBinderFlags(0),
    mCallRestriction(mProcess->mCallRestriction) {
  //...
  //设置buffer巨细
  mIn.setDataCapacity(256);
  mOut.setDataCapacity(256);
}

其间保持了一个ProcessState目标,这儿引入了两个目标:mInmOut,这两个都是Parcel类型的目标,mIn的运用场景是:作为被调用着,从Binder驱动写回来的数据,经过解析mIn得到;mOut的运用场景是:作为调用者,将cmd与数据写入mOut,最终写入到Binder驱动中。

3.3 驱动open

kernel\drivers\android\binder.c

kernel\drivers\android\binder_alloc.c

咱们现在看一下binder_openbinder_mmap:

3.3.1 binder_open


static int binder_open(struct inode *nodp, struct file *filp)
{
  //binder_proc在Binder驱动中代表一个进程
  struct binder_proc *proc, *itr;
  struct binder_device *binder_dev;
  struct binderfs_info *info;
  struct dentry *binder_binderfs_dir_entry_proc = NULL;
  bool existing_pid = false;
  //在内核的直接映射区请求内存
  proc = kzalloc(sizeof(*proc), GFP_KERNEL);
  if (proc == NULL)
    return -ENOMEM;
  //current在内核中,代表当时进程,经过group_leader拿到进程的task_struct
  get_task_struct(current->group_leader);
  //进程组的线程组长
  proc->tsk = current->group_leader;
  //初始化链表
  INIT_LIST_HEAD(&proc->todo);
  init_waitqueue_head(&proc->freeze_wait);
  //...
  refcount_inc(&binder_dev->ref);
  proc->context = &binder_dev->context;
  //初始化binder_alloc目标
  binder_alloc_init(&proc->alloc);
  proc->pid = current->group_leader->pid;
  //初始化链表
  INIT_LIST_HEAD(&proc->delivered_death);
  INIT_LIST_HEAD(&proc->waiting_threads);
  //存储到大局区域,后边能够直接经过访问private_data拿到该目标
  filp->private_data = proc;
  //将当时进程增加到binder_procs中
  hlist_add_head(&proc->proc_node, &binder_procs);
  //...
  return 0;
}

咱们能够看到,binder_open主要做了这么几件工作:

  • 1、新建一个binder_proc结构体,然后将当时进程的pid等信息保存到binder_proc

  • 2、初始化binder_proc的各项链表

  • 3、初始化binder_alloc目标

  • 4、将binder_proc存储到private_data

  • 5、将binder_procproc_node存入大局哈希表binder_procs

Tips:

这儿或许会有一个比较迷惑的点,分明proc_node没有被初始化过,为什么后边遍历的时候,能够从proc_node拿进程的pid呢?

这是因为proc_nodebinder_proc的第一个元素,而在结构体中,结构体的地址与第一个成员的地址是相同的,所以虽然存储的是proc_node,可是能够经过指针地址访问到binder_proc的一切元素,这正是C++比较Java更灵活的地方

这中心触及了几个比较重要的数据结构,binder_procbinder_alloc,这两个数据结构的基本结构如下图所示(省略了部分字段)。

Binder - 1、初始化
图3.1 – binder_proc的结构

Binder - 1、初始化
图3.2 – binder_alloc的结构

binder_proc初始化结束之后,咱们来看下binder_mmap的逻辑:

3.3.2 binder_mmap


static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
  //经过private_data拿到binder_proc
  struct binder_proc *proc = filp->private_data;
  vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
  vma->vm_flags &= ~VM_MAYWRITE;
  vma->vm_ops = &binder_vm_ops;
  vma->vm_private_data = proc;
  return binder_alloc_mmap_handler(&proc->alloc, vma);
}

拿到vma之后,调用binder_alloc_mmap_handler办法:


int binder_alloc_mmap_handler(struct binder_alloc *alloc,
         struct vm_area_struct *vma)
{
  int ret;
  struct binder_buffer *buffer;
  //初始化buffer_size,指的是vma的地址规模,不大于4M
  alloc->buffer_size = min_t(unsigned long, vma->vm_end - vma->vm_start,
         SZ_4M);
  //vma的开始地址,也就是一切buffer的基址
  alloc->buffer = (void __user *)vma->vm_start;
  //为biner_lru_page请求内存
  alloc->pages = kcalloc(alloc->buffer_size / PAGE_SIZE,
         sizeof(alloc->pages[0]),
         GFP_KERNEL);
  //初始化一个binder_buffer
  buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
  buffer->user_data = alloc->buffer;
  //加入alloc->buffers中
  list_add(&buffer->entry, &alloc->buffers);
  buffer->free = 1;
  //加入到free_buffer中
  binder_insert_free_buffer(alloc, buffer);
  alloc->free_async_space = alloc->buffer_size / 2;
  //将vma设置给binder_alloc
  binder_alloc_set_vma(alloc, vma);
  //增加引证计数,避免被释放
  mmgrab(alloc->vma_vm_mm);
  return 0;
}

能够看到binder_alloc_mmap_handler办法做了这样几件工作:

  • 1、初始化binder_alloc

  • 2、将取得的vma设置到binder_alloc里面

主要是关于binder_alloc的初始化。

3.4 驱动初始化结语

自此,驱动的初始化结束,这儿的初始化其实是针关于某个具体的进程而言的,咱们之前说到一切的用户进程的内核空间是通用的,Binder驱动挂载之后,用户进程经过ioctl调用到binder_open之后,仅仅将自己进程对应的binder_proc初始化,并增加到大局哈希表中去,这样的话,后续需要用到的话,驱动就能够直接从大局哈希表中找到对应的目标了。

咱们在这儿遇到两个非常重要的数据结构binder_procbinder_alloc,后边咱们还会遇到其他数据结构,在整个Binder驱动中,了解里面的数据结构是了解整个Binder驱动的要害。

Binder - 1、初始化
图3.3 – binder初始化时序图

四、总结

Binder驱动是挂载到虚拟设备节点上的,挂载成功之后,用户进程会经过体系调用初始化进程对应的数据结构以及后续会运用到的数据结构,并经过mmap请求一块内存,用于后续的进程间通讯,这块内存在驱动层也会有对应的数据结构来存储。