binder运行机制--从应用到原理再到实践

之前网上看的 binder 相关的文章,总是有的地方没说清楚,本文目的是以最少的文字说运行机制清楚。

应用

先把代码下载下来,跑一跑,看看现场,了解一下binder如何使用。

原理

在弄清楚原理先,先了解一下binder由来,在了解binder运行机制,最后分析代码。

由来

我们知道,在Android系统里,底层是linux操作系统,而linux操作系统本身是提供了很对跨进程的通信方式,比如:管道,system V IPC,socket等。这些方法都需要两次拷贝,当进行大量的进程间通信时,会影响系统效率。故Android的设计者就想到使用 binder 机制实现进程间的通信,且是一次拷贝。

如何实现一次拷贝

【前提知识】要想弄明白为什么可以一次拷贝,就需要先知道:

(1)linux系统分内核空间和用户空间,每个进程都有一个4G大小的虚拟地址空间,在这个4G大小的虚拟地址空间中,前0~3G为用户空间,每个进程的用户空间之间是相互独立的,互不相干。而3G~4G为内核空间,因为每个进程都可以从用户态切换到内核态,因此,内核空间(准确的说内核绝大部分空间,内核栈不能共享)对于所有进程来说,可以说是共享的。进程A拷贝数据到进程B的步骤为:先将进程A的数据拷贝到内核空间,再从内核空间拷贝数据到进程B中,所以需要两次拷贝。

(2)理解什么是用户态,什么是内核态。进程在创建的时候运用在用户空间,这个时候叫用户态,当进程调用系统调用(open,read、close等)时,进入内核空间,此时叫内核态。因此每个进程都有两个栈,即用户栈和内核栈。

(3)需要先了解linux的MMU(内存管理单元),其作用是将虚拟地址映射到物理地址。我们知道linux系统中,程序运行时使用的是虚拟地址,这样在真正进行读写时,需要将虚拟地址映射到物理地址。这里需要说明,进程的用户空间和内核空间是针对虚拟地址而言,而不是物理地址。

下图说明两次拷贝的过程:

从上图可以看到,经过了两次拷贝,那这个过程可不可以优化呢,即实现一次拷贝,见下图:

由上图可知,进程B直接把地址映射到内核空间(这里应该理解为,进程B的“接受数据”地址和内核缓存区地址 映射到同一个内存地址中),这样就可以实现一次拷贝。

binder运行机制

先下结论,binder运行机制如下图:

说明1:Client进程、Server进程 & Service Manager 进程之间的交互 都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互。

原因:

(1)Client进程、Server进程 & Service Manager进程属于进程空间的用户空间,不可进行进程间交互
(2)Binder驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互

虚线表示:不可以直接交互。

说明2:  Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)。所以,在进行跨进程通信时,开发者只需自定义Client &  Server 进程 并 显式使用上图的3个步骤,最终借助 Android的基本架构功能就可完成进程间通信。

说明3:Binder请求的线程管理

Server进程会创建很多线程来处理Binder请求。

Binder模型的线程管理,采用Binder驱动的线程池,并由Binder驱动自身进行管理,而不是由Server进程来管理的。

一个进程的Binder线程数默认最大是16,超过的请求会被阻塞,等待空闲的Binder线程。所以,在进程间通信时处理并发问题时,如使用ContentProvider时,它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程同时工作。

代码分析

binder驱动的源码在linux内核(版本为4.4.52)中的 drivers/android/binder.c 

// 驱动入口函数
device_initcall(binder_init);

驱动入口函数为 device_initcall(),而不是 module_init(),这样做的目的是,不想成为 支持动态编译。

//驱动注册
static struct miscdevice binder_miscdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "binder",
	.fops = &binder_fops   // Binder驱动支持的文件操作
};

static int __init binder_init(void)
{
    ....
    ret = misc_register(&binder_miscdev);  // 驱动注册
    ....
}

misc device 是特殊的字符设备,也称为杂项设备。

// 填写 file_operation 结构体
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,
};

这里比较重要的函数有:binder_open,binder_mmap,binder_ioctl。

实践

经过上面的分析,对 binder 有大致的了解后,知道 binder驱动 运行在linux系统中,那么可不可以将binder移植到ubuntu下,用insmod 命令加载到内核中,在用户空间中创建两个两个进程和一个server,来模拟binder运行机制呢?

参考

本文主要参考:

https://www.jianshu.com/p/4ee3fd07da14,浅显易懂,过于啰嗦,给的实例只有部分源码,且其中说 Service Manager 属于用户空间,经过查看Android源码,确实是在用户空间,(老罗的那种图不对,说Service Manager 是内核空间)

https://blog.csdn.net/u011240877/article/details/72801425,上一个的代码分析中,对函数的解析比较少,这个对主要函数的解析很详细。可惜这个没有实例分析。

最完善的:https://blog.csdn.net/universus/article/details/6211589

猜你喜欢

转载自blog.csdn.net/gs344937933/article/details/83281072