Android的进程间通信(五) 之 Binder内核层以及ServiceManager初始化流程

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;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值