一、前言
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的不同进程来说,内核占用了从0xC0000000
到0xFFFFFFFF
的1G内存空间,用户空间占用从0
到0xBFFFFFFF
的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
IPCThreadState
与ProcessState
类似,都是单例,其他地方也是通过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
对象,这里引入了两个对象:mIn
和mOut
,这两个都是Parcel
类型的对象,mIn
的使用场景是:作为被调用着,从Binder驱动写回来的数据,通过解析mIn
得到;mOut
的使用场景是:作为调用者,将cmd与数据写入mOut
,最终写入到Binder驱动中。
3.3 驱动open
kernel\drivers\android\binder.c
kernel\drivers\android\binder_alloc.c
我们现在看一下binder_open
与binder_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_proc
的proc_node
存入全局哈希表binder_procs
中
Tips:
这里可能会有一个比较迷惑的点,明明proc_node
没有被初始化过,为什么后面遍历的时候,可以从proc_node
拿进程的pid呢?
这是因为proc_node
是binder_proc
的第一个元素,而在结构体中,结构体的地址与第一个成员的地址是一样的,所以虽然存储的是proc_node
,但是可以通过指针地址访问到binder_proc
的所有元素,这正是C++相比Java更灵活的地方
这中间涉及了几个比较重要的数据结构,binder_proc
与binder_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_proc
与binder_alloc
,后面我们还会遇到别的数据结构,在整个Binder驱动中,理解里面的数据结构是理解整个Binder驱动的关键。
图3.3 - binder初始化时序图
四、总结
Binder驱动是挂载到虚拟设备节点上的,挂载成功之后,用户进程会通过系统调用初始化进程对应的数据结构以及后续会使用到的数据结构,并通过mmap
申请一块内存,用于后续的进程间通信,这块内存在驱动层也会有对应的数据结构来存储。