一、前语
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的不同进程来说,内核占用了从0xC0000000
到0xFFFFFFFF
的1G内存空间,用户空间占用从0
到0xBFFFFFFF
的3G内存空间,不同的进程运用的内核空间是相同的,这也是Binder驱动能运转的基本要素,由于内核空间是公用的,所以IPC才得以完成,不同的进程能够经过陷入内核态,然后让内核进行数据操控与传递,然后完成进程间的通讯,一般来说,IPC至少需要两次内核与用户空间之间的数据复制,而Binder只需要一次数据复制,这使得数据传输速率大大增加,这一点咱们在后边打开。
三、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
IPCThreadState
与ProcessState
相似,都是单例,其他地方也是经过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
目标,这儿引入了两个目标: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
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
,这两个数据结构的基本结构如下图所示(省略了部分字段)。
将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_proc
与binder_alloc
,后边咱们还会遇到其他数据结构,在整个Binder驱动中,了解里面的数据结构是了解整个Binder驱动的要害。
四、总结
Binder驱动是挂载到虚拟设备节点上的,挂载成功之后,用户进程会经过体系调用初始化进程对应的数据结构以及后续会运用到的数据结构,并经过mmap
请求一块内存,用于后续的进程间通讯,这块内存在驱动层也会有对应的数据结构来存储。