Android 13 - binder阅读(1)- binder driver

1 总览

想要使用binder完成进程间通信(IPC)或者完成远程过程调用(RPC),那么我们需要有如下三个要素:

  1. 源:即调用者(Client)
  2. 目的:即服务提供者(Server)。这里会有一个问题,client怎么知道我要向哪里发送数据呢?这里就需要用到ServiceManager,Server需要先注册到ServiceManager中,Client再向ServiceManager查询服务获得一个handle。
  3. 数据: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,
};

我们在用户态调用的mmapopeniotcl实际调用的是binder.c中定义的binder_mmapbinder_openbinder_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)
{
	// 1. 创建一个binder_proc指针并为其开辟空间
	struct binder_proc *proc, *itr;
	struct binder_device *binder_dev;
	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);
	// 2. 获取binder driver里的binder_device
	binder_dev = container_of(filp->private_data, struct binder_device, miscdev);
	refcount_inc(&binder_dev->ref);
	// 将binder_proc中的binder_context指向binder驱动的binder_context
	proc->context = &binder_dev->context;
	// 3. 初始化binder_alloc
	binder_alloc_init(&proc->alloc);
	// 4. 记录当前调用进程的pid
	proc->pid = current->group_leader->pid;
	INIT_LIST_HEAD(&proc->delivered_death);
	INIT_LIST_HEAD(&proc->waiting_threads);
	// 5. 将binder_proc记录到fd当中
	filp->private_data = proc;
	// 6. 将binder_proc加入到全局链表binder_procs当中
	hlist_add_head(&proc->proc_node, &binder_procs);
	return 0;
}

我这里删除了很多代码内容,只留下了我认为比较重要的步骤:

  1. 为调用进程创建一个binder_proc对象,binder驱动会为每个进程都创建这样一个对象
  2. 获取binder驱动中的binder_device,该对象是属于binder设备的,于binder_init中创建并初始化。这里获取binder_device是因为它里面存储有一个binder_context成员,这个成员就是ServiceManager,这个后面会讲到;
  3. binder_proc初始化成员binder_alloc,后续mmap分配的内存将会记录在该成员中;
  4. 将当前进程pid记录到binder_proc,有了它binder驱动就可以从茫茫进程中找到目标进程了;
  5. binder_proc记录到fd当中,后续每次iotcl都会通过该fd获取到binder_proc
  6. binder_proc加入到全局链表binder_procs当中;

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_procbinder_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;
	}
	// 执行cmd
	switch (cmd) {
		......
	}

binder驱动会根据传入的fd、cmd以及对应的参数来执行对应的操作,这里暂时不去看里面具体执行了什么。


到这里binder驱动就初步了解结束,我认为这篇笔记比较重要的点在于理解binder_proc的作用:binder驱动会为每个调用进程(无论是源进程还是目标进程)都创建一个binder_proc,并且记录到全局变量当中,后续可以通过binder_proc中的pid实现目标进程的查找。

可能这里对新同学来说还比较抽象,不过不要着急,后面我们慢慢看。

根据MediaServer的调用顺序,打开binder驱动后,会去获取ServiceManager,所以接下来我们先去了解ServiceManager的创建,获取以及调用。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值