Linux系统中,有驱动的扩展接口。Google在Linux的基础,增加一个Binder驱动,也就是前面几讲一直在说binder“内核”。
我们先来看一下Binder的内核注册的过程吧
在binder的内核文件中,Linux系统会自动调用 binder_init 的初始化方法,在这个初始化函数中,有一个 misc_register(&binder_miscdev) 函数。misc_register 是一个系统级别的函数,而参数binder_miscdev是一个结构体。我们来看一下 &binder_miscdev 具体是什么。
static struct miscdevice binder_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "binder",
.fops = &binder_fops
};
可以看到,binder_miscdev 中声明了内核的类型,定义了关于内核通信的文件为“binder”,还有一个 .fops 的结构体。我们再来看一下 .fops 结构体长什么样。
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
在linux里注册为内核的时候,.fops 可以理解成一个文件映射的关系。还记得在 第一讲的时候 提到的Linux系统中的一切皆文件吗?上层和内核通信,也是通过“一切皆文件”的方式。.fops是在内核注册的时候,声明的一些API。
例如:
.open = binder_open,
当外部通过open(“binder”) 操作“binder”时,便会回调到 binder_open 这个函数。
例如:
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
当外部通过 ioctl 操作 binder 文件时,便会回调到 binder_ioctl 这个函数。
ok,binder的内核注册到这里就差不多了。
在上一讲中,提到了Binder的通信模型,其中有一个非常重要的角色是:ServiceManager。那就是让我们来看看,ServiceManager是如何向Binder内核注册的。
ServiceManager的源码解析
ServiceManager是一个进程,由系统调用ServiceManager的main方法。
main方法如下:
int main(int argc, char** argv)
{
struct binder_state *bs;
union selinux_callback cb;
char *driver;
if (argc > 1) {
driver = argv[1];
} else {
driver = "/dev/binder";
}
bs = binder_open(driver, 128*1024);
........ 省略一些代码
if (binder_become_context_manager(bs)) {
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
........ 省略一些代码
binder_loop(bs, svcmgr_handler);
return 0;
}
首先我们可以看到,main会调用 binder_open(),传入的参数 为:/dev/binder ,128 * 1024。
我们来看一下 binder_open 函数的实现。我们在ServiceManager中,是搜索不到 binder_open这个函数的。那我们看一下 ServiceManager 引入了哪些头文件。
.... 省略
#include "binder.h"
.... 省略
可以看到,这里导入了binder.h 文件,那我们去binder.c文件中看一下,有没有 binder_open 的函数。是可以找到的。
struct binder_state *binder_open(const char* driver, size_t mapsize)
{
struct binder_state *bs;
struct binder_version vers;
bs = malloc(sizeof(*bs));
if (!bs) {
errno = ENOMEM;
return NULL;
}
bs->fd = open(driver, O_RDWR | O_CLOEXEC);
..... 省略代码
bs->mapsize = mapsize;
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
..... 省略代码
return bs;
}
binder_open 函数,最主要做了两件事,第一件:打开driver文件,在这里一般为:“/dev/binder”。第二件事:与内核层建立“内存映射”。
ServiceManager 在调用 binder_open 时,传入的 mapsize 为 128 * 1024,所以,来了,敲黑板滑重点,ServiceManager与内核的共享内存大小为 128KB。
ok,回到ServiceManager的main方法上。在main方法里,调用完binder_open 之后,还调用了 binder_become_context_manager(); ,我们看一下这个函数的实现。
int binder_become_context_manager(struct binder_state *bs)
{
return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
这里只是向内核发送一个 BINDER_SET_CONTEXT_MGR 事件。在第一部分对于内核层的分析时我们讲到了,ioctl 对应的就是内核层的binder_ioctl函数,去内核层看一下 binder_ioctl 关于BINDER_SET_CONTEXT_MGR 事件的处理。
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
.... 省略一些代码
case BINDER_SET_CONTEXT_MGR:
// 1. 首先判断 管理者是否已经被注册
if (binder_context_mgr_node != NULL) {
pr_err("BINDER_SET_CONTEXT_MGR already set\n");
ret = -EBUSY;
goto err;
}
.... 省略一些代码
// 2. 创建一个handle = 0 的节点 储存在 binder_context_mgr_node 中
binder_context_mgr_node = binder_new_node(proc, 0, 0);
.... 省略一些代码
break;
.... 省略一些代码
}
上面代码注册可以得出结论:1、管理者只能注册一次。2、管理者的handle 为 0
我们在这里简单的看一下,创建的服务在binder内核,是以什么形式存储的吧。
来,上代码!
static struct binder_node *binder_new_node(struct binder_proc *proc,
binder_uintptr_t ptr,
binder_uintptr_t cookie)
{
struct rb_node **p = &proc->nodes.rb_node;
struct rb_node *parent = NULL;
struct binder_node *node;
while (*p) {
parent = *p;
node = rb_entry(parent, struct binder_node, rb_node);
if (ptr < node->ptr)
p = &(*p)->rb_left;
else if (ptr > node->ptr)
p = &(*p)->rb_right;
else
return NULL;
}
.... 省略代码
return node;
}
上述代码可以看到,节点的载体是 “rb_node”,而 rb_node 是红黑树的节点。在binder内核里,维护着装载节点的红黑树,这里通过遍历红黑树来找到新的节点该插入的地方。
ok 回到ServiceManager的main方法上。在进行了 binder_become_context_manager 后,还调用了一个 binder_loop 函数,这个函数里面有一个死循环,一直接收着 内核层 的消息。
到此,ServiceManager的初始化以及向binder驱动注册为管理者的流程已经结束了。
最后总结一下。
第一:binder内核在初始化的时候,定义了内核与“/dev/binder”文件之间的函数映射关系。
第二:ServiceManager在注册的时候,首先进行了内存映射,大小为128KB
第三:然后把自己注册成管理者,管理者只能注册一次,且handle为0。
第四:注册为管理者后,会进行无限循环,接收来着内核的消息。
第五:内核有一颗红黑树,储存着所有服务。
2022年08月05日 补充--------------
Android 可以跨进程传输文件fd,但是fd为进程私有,如何做到跨进程通信呢?
看代码得到,在binder内核中做了处理,先在目标进程创建一个新的fd,然后进行获取一个file加入目标进程的数组,然后修改fp的handler。
binder.c 源码:https://android.googlesource.com/kernel/msm/+/android-msm-anthias-3.10-lollipop-mr1-wear-release/drivers/staging/android/binder.c
case BINDER_TYPE_FD: {
int target_fd;
struct file *file;
file = fget(fp->handle);
if (security_binder_transfer_file(proc->tsk, target_proc->tsk, file) < 0) {
fput(file);
return_error = BR_FAILED_REPLY;
goto err_get_unused_fd_failed;
}
target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
task_fd_install(target_proc, target_fd, file);
trace_binder_transaction_fd(t, fp->handle, target_fd);
binder_debug(BINDER_DEBUG_TRANSACTION,
" fd %d -> %d\n", fp->handle, target_fd);
/* TODO: fput? */
fp->handle = target_fd;
} break;