1 总览
想要使用binder完成进程间通信(IPC)或者完成远程过程调用(RPC),那么我们需要有如下三个要素:
- 源:即调用者(Client)
- 目的:即服务提供者(Server)。这里会有一个问题,client怎么知道我要向哪里发送数据呢?这里就需要用到ServiceManager,Server需要先注册到ServiceManager中,Client再向ServiceManager查询服务获得一个handle。
- 数据:Client想要调用Server的哪个方法,传输什么参数,返回什么结果,需要事先约定好协议,放在一个Buffer当中。
我绘制了一张Client、Server、ServiceManager以及Binder Driver四者的关系图,图中虚线都是RPC调用,实际都是通过ioctl与binder驱动进行交互,实现Client和Server通讯的功能。从图上可以看到,ServiceManager与Server/Client之间的调用也是虚线,也就说ServiceManager也是一个binder service,只不过是一个特殊的service。
2 Binder Driver
从上面可以看到,client和server之间的通讯都是通过与binder驱动的交互来完成的,所以如果不了解binder驱动,那么是很难理解binder的。binder驱动相关的代码参考 binder.c。
const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
我们在用户态调用的mmap、open、iotcl实际调用的是binder.c中定义的binder_mmap、binder_open、binder_ioctl。
接下来我们将以MediaServer为例,初步了解与binder驱动相关的系统调用。MediaServer的代码参考 main_mediaserver.cpp
int main(int argc __unused, char **argv __unused)
{
// 1. 打开binder驱动,mmap
sp<ProcessState> proc(ProcessState::self());
// 2. 获取servicemanager
sp<IServiceManager> sm(defaultServiceManager());
// 3. 注册到servicemanager
MediaPlayerService::instantiate();
// 4. 开始监听
::android::hardware::configureRpcThreadpool(16, false);
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
::android::hardware::joinRpcThreadpool();
}
2.1 binder_open
创建ProcessState
单例对象时,会先调用open打开binder驱动,代码可以参考 ProcessState.cpp
int fd = open(driver, O_RDWR | O_CLOEXEC);
这里就会进入内核态调用binder驱动的binder_open方法。
static HLIST_HEAD(binder_procs);
static int binder_open(struct inode *nodp, struct file *filp)
{
// 创建一个binder_proc指针并为其开辟空间
struct binder_proc *proc, *itr;
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
// 初始化binder_proc的成员
get_task_struct(current->group_leader);
proc->tsk = current->group_leader;
proc->cred = get_cred(filp->f_cred);
INIT_LIST_HEAD(&proc->todo);
// 初始化binder_alloc
binder_alloc_init(&proc->alloc);
// 记录当前调用进程的pid
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
INIT_LIST_HEAD(&proc->waiting_threads);
filp->private_data = proc;
// 将binder_proc加入到全局链表binder_procs当中
hlist_add_head(&proc->proc_node, &binder_procs);
return 0;
}
我这里删除了很多代码内容,只留下了binder_proc
的创建和初始化。我认为binder_open最重要的工作之一就是为当前的调用进程创建一个binder_proc
对象,该对象中存有进程的pid,有了它binder驱动就可以从茫茫进程中找到目标进程了,至于如何寻找的我们后面再了解。
2.2 binder_mmap
执行完binder_open之后,ProcessState
会用回传的fd执行mmap
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,opened.value(), 0);
这里就会进入内核态调用binder驱动的binder_mmap方法。
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct binder_proc *proc = filp->private_data;
if (proc->tsk != current->group_leader)
return -EINVAL;
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);
}
binder_mmap的核心工作在binder_alloc_mmap_handler中完成,由于比较复杂,我对驱动也不了解,所以就不贴代码了。
我们要了解的是通过binder_mmap可以为当前进程开辟一块共享内存,可由用户态和内核态共享,每个进程都会有这样一块内存,内存信息存储在binder_proc
的binder_alloc
成员中,通过这块内存可以完成其他博文中所说的一次拷贝的功能,如何拷贝的我们后面再看。
2.3 binder_ioctl
通过binder_ioctl,用户态就可以与binder驱动实现通信了。
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
// 找到当前进程的binder_proc
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
void __user *ubuf = (void __user *)arg;
if (ret)
goto err_unlocked;
// 获取到线程,这里不在本次阅读的重点中,暂时忽略
thread = binder_get_thread(proc);
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}
switch (cmd) {
......
}
binder驱动会根据传入的fd、cmd以及对应的参数来执行对应的操作,这里暂时不去看里面具体执行了什么。
到这里binder driver就初步了解结束,我认为这篇比较重要的点在于,一个进程打开binder driver,driver就会为该进程创建一个binder_proc
对象,有了它就可以找到目标进程。
接下来我们先去了解ServiceManager,这篇先到这边。