Binder - 1、初始化

一、前言

Binder驱动层是整个Binder机制的核心,但是相对处于比较底层的位置,对于Android开发者来说,可能就只是知道著名的"一次拷贝"了,但是一次拷贝是如何实现的?恐怕大多数人也不甚了解。这一系列的文章会对整个Binder机制做一个相对深入的分析,我们先从驱动层开始。所有的代码都基于Android12。

二、Binder驱动是什么?

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

bash 复制代码
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只需要一次数据拷贝,这使得数据传输速率大大增加,这一点我们在后面展开。

图2.1 - 进程虚拟地址空间分布


图2.2 - 进程虚拟地址空间详细说明

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

makefile 复制代码
Binder驱动层代码位于Kernel中

\kernel\drivers\android

\kernel\include\uapi\linux\android

3.1 驱动的挂载

我们在binder.c中可以看到Binder驱动的初始化代码:

c++ 复制代码
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函数中,会注册一系列的方法:

c++ 复制代码
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方法:

c++ 复制代码
sp<ProcessState> ProcessState::self()

{

    return init(kDefaultDriver, false /*requireDefault*/);

}

调用到init方法:

c++ 复制代码
#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的构造函数:

c++ 复制代码
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方法:

c++ 复制代码
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进行调用:

c++ 复制代码
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新建一个对象,后续所有的调用都是基于这个对象。

c++ 复制代码
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

c++ 复制代码
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,这两个数据结构的基本结构如下图所示(省略了部分字段)。

图3.1 - binder_proc的结构


图3.2 - binder_alloc的结构

binder_proc初始化完毕之后,我们来看下binder_mmap的逻辑:

3.3.2 binder_mmap

c++ 复制代码
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方法:

c++ 复制代码
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驱动的关键。

图3.3 - binder初始化时序图

四、总结

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

相关推荐
Estar.Lee12 分钟前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯1 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey2 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!4 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟5 小时前
Android音频采集
android·音视频
小白也想学C6 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程6 小时前
初级数据结构——树
android·java·数据结构
闲暇部落8 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX10 小时前
Android 分区相关介绍
android
大白要努力!11 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle