Linux进程空间
一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来
二者区别:
进程间,用户空间的数据不可共享,所以用户空间 = 不可共享空间
进程间,内核空间的数据可共享,所以内核空间 = 可共享空间
所有进程共用1个内核空间
进程内 用户空间 & 内核空间 进行交互 需通过 系统调用,主要通过函数:
copy_from_user():将用户空间的数据拷贝到内核空间
copy_to_user():将内核空间的数据拷贝到用户空间
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/296f5df2f60a840f108cc0b7ddc48e3b.png)
Kernel space 是 Linux 内核的运行空间,User space 是用户程序的运行空间。 为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。
Kernel space 可以执行任意命令,调用系统的一切资源; User space 只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称 system call),才能向内核发出指令。
跨进程通信的基本原理
而Binder的作用则是:连接两个进程,实现了mmap()系统调用,主要负责创建数据接收的缓存空间 & 管理数据接收缓存
注:传统的跨进程通信需拷贝数据2次,但Binder机制只需1次,主要是使用到了内存映射,具体下面会详细说明
内核模块/驱动
通过系统调用,用户空间可以访问内核空间,那么如果一个用户空间想与另外一个用户空间进行通信怎么办呢?很自然想到的是让操作系统内核添加支持;传统的 Linux 通信机制,比如 Socket,管道等都是内核支持的;但是 Binder 并不是 Linux 内核的一部分,它是怎么做到访问内核空间的呢? Linux 的动态可加载内核模块(Loadable Kernel Module,LKM)机制解决了这个问题;模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。这样,Android系统可以通过添加一个内核模块运行在内核空间,用户进程之间的通过这个模块作为桥梁,就可以完成通信了。
在 Android 系统中,这个运行在内核空间的,负责各个用户进程通过 Binder 通信的内核模块叫做 Binder 驱动;
驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。
相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作
驱动就是操作硬件的接口,为了支持Binder通信过程,Binder 使用了一种“硬件”,因此这个模块被称之为驱动。
熟悉了上面这些概念,我们再来看下上面的图,用户空间中 binder_open(), binder_mmap(), binder_ioctl() 这些方法通过 system call 来调用内核空间 Binder 驱动中的方法。内核空间与用户空间共享内存通过 copy_from_user(), copy_to_user() 内核方法来完成用户空间与内核空间内存的数据传输。 Binder驱动中有一个全局的 binder_procs 链表保存了服务端的进程信息。
Binder 架构
Binder 通信采用 C/S 架构,从组件视角来说,包含 Client、 Server、 ServiceManager 以及 Binder 驱动,其中 ServiceManager 用于管理系统中的各种服务。
Binder 在 framework 层进行了封装,通过 JNI 技术调用 Native(C/C++)层的 Binder 架构。
Binder 在 Native 层以 ioctl 的方式与 Binder 驱动通讯。
简单模型图:
Binder 机制
注册服务端
首先需要注册服务端,只有注册了服务端,客户端才有通讯的目标,服务端通过 ServiceManager 注册服务,注册的过程就是向 Binder 驱动的全局链表 binder_procs 中插入服务端的信息(binder_proc 结构体,每个 binder_proc 结构体中都有 todo 任务队列),然后向 ServiceManager 的 svcinfo 列表中缓存一下注册的服务。
Server进程 创建 一个 Binder 对象,Binder 实体是 Server进程 在 Binder 驱动中的存在形式,该对象保存 Server 和 ServiceManager 的信息(保存在内核空间中),Binder 驱动通过 内核空间的Binder 实体找到用户空间的Server对象;注册服务后,Binder驱动持有 Server进程创建的Binder实体
客户端获取到服务
客户端就跟服务端通讯,通讯之前需要先获取到服务,拿到服务的代理,也可以理解为引用。Client进程 使用 某个 service前(此处是 相加函数),须 通过Binder驱动 向 ServiceManager进程 获取相应的Service信息,比如下面的代码:
//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
获取服务端的方式就是通过 ServiceManager 向 svcinfo 列表中查询一下返回服务端的代理,svcinfo 列表就是所有已注册服务的通讯录,保存了所有注册的服务信息。
向服务端发送请求
Client根据获取到的 Service信息,通过Binder代理对象 BinderProxy 将请求参数发送给 ServiceManager,通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态,然后 Binder 驱动向服务端的 todo 队列里面插入一条事务,执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成了一次通讯。
简单来说就是:
Client进程 根据获取到的 Service信息(Binder代理对象),通过Binder驱动 建立与 该Service所在Server进程通信的链路,并开始使用服务
过程描述
Client进程 将参数发送到Server进程
Server进程 根据Client进程要求调用 目标方法
Server进程 将目标方法的结果返回给Client进程
下面是某位博主以表格和示意图的方式总结下过程:
Binder 进程与线程
对于底层Binder驱动,通过 binder_procs 链表记录所有创建的 binder_proc 结构体,binder 驱动层的每一个 binder_proc 结构体都与用户空间的一个用于 binder 通信的进程一一对应,且每个进程有且只有一个 ProcessState 对象,这是通过单例模式来保证的。在每个进程中可以有很多个线程,每个线程对应一个 IPCThreadState 对象,IPCThreadState 对象也是单例模式,即一个线程对应一个 IPCThreadState 对象,在 Binder 驱动层也有与之相对应的结构,那就是 Binder_thread 结构体。在 binder_proc 结构体中通过成员变量 rb_root threads,来记录当前进程内所有的 binder_thread。
Binder 线程池:每个 Server 进程在启动时创建一个 binder 线程池,并向其中注册一个 Binder 线程;之后 Server 进程也可以向 binder 线程池注册新的线程,或者 Binder 驱动在探测到没有空闲 binder 线程时主动向 Server 进程注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为16个 binder 线程,例如 Android 的 system_server 进程就存在16个线程。对于所有 Client 端进程的 binder 请求都是交由 Server 端进程的 binder 线程来处理的。
ServiceManager
ServiceManager 启动
ServiceManager 的作用很简单就是提供了查询服务和注册服务的功能。下面我们来看一下 ServiceManager 启动的过程。
ServiceManager 分为 framework 层和 native 层,framework 层只是对 native 层进行了封装方便调用,图上展示的是 native 层的 ServiceManager 启动过程。
ServiceManager 的启动是系统在开机时,init 进程解析 init.rc 文件调用 service_manager.c 中的 main() 方法入口启动的。 native 层有一个 binder.c 封装了一些与 Binder 驱动交互的方法。
ServiceManager 的启动分为三步,首先打开驱动创建全局链表 binder_procs,然后将自己当前进程信息保存到 binder_procs 链表,最后开启 loop 不断的处理共享内存中的数据,并处理 BR_xxx 命令(ioctl 的命令,BR 可以理解为 binder reply 驱动处理完的响应)。
ServiceManager启动流程:
打开binder驱动,并调用mmap()方法分配128k的内存映射空间:binder_open();
通知binder驱动使其成为守护进程:binder_become_context_manager();
验证selinux权限,判断进程是否有权注册或查看指定服务;
进入循环状态,等待Client端的请求:binder_loop()。
ServiceManger意义:
ServiceManger集中管理系统内的所有服务,通过权限控制进程是否有权注册服务;
ServiceManager能通过字符串名称来查找对应的Service,操作方便;
当Server进程异常退出,只需告知ServiceManager,每个Client通过查询ServiceManager可获取Server进程
的情况,降低所有Client进程直接检测会导致负载过重。
ServiceManager 注册服务
注册 MediaPlayerService 服务端,我们通过 ServiceManager 的 addService() 方法来注册服务。
首先 ServiceManager 向 Binder 驱动发送 BC_TRANSACTION 命令(ioctl 的命令,BC 可以理解为 binder client 客户端发过来的请求命令)携带 ADD_SERVICE_TRANSACTION 命令,同时注册服务的线程进入等待状态 waitForResponse()。 Binder 驱动收到请求命令向 ServiceManager 的 todo 队列里面添加一条注册服务的事务。事务的任务就是创建服务端进程 binder_node 信息并插入到 binder_procs 链表中。
事务处理完之后发送 BR_TRANSACTION 命令,ServiceManager 收到命令后向 svcinfo 列表中添加已经注册的服务。最后发送 BR_REPLY 命令唤醒等待的线程,通知注册成功。
ServiceManager 获取服务
获取Service Manager是通过defaultServiceManager()方法来完成,当进程注册服务(addService)或 获取服务(getService)的过程之前,都需要先调用defaultServiceManager()方法来获取gDefaultServiceManager对象。
对于gDefaultServiceManager对象,如果存在则直接返回;如果不存在则创建该对象,创建过程包括调用open()打开binder驱动设备,利用mmap()映射内核的地址空间。
获取服务的过程与注册类似,相反的过程。通过 ServiceManager 的 getService() 方法来注册服务。
首先 ServiceManager 向 Binder 驱动发送 BC_TRANSACTION 命令携带 CHECK_SERVICE_TRANSACTION 命令,同时获取服务的线程进入等待状态 waitForResponse()。
Binder 驱动收到请求命令向 ServiceManager 的发送 BC_TRANSACTION 查询已注册的服务,查询到直接响应 BR_REPLY 唤醒等待的线程。若查询不到将与 binder_procs 链表中的服务进行一次通讯再响应
进行一次完整通讯
我们在使用 Binder 时基本都是调用 framework 层封装好的方法,AIDL 就是 framework 层提供的傻瓜式是使用方式。假设服务已经注册完,我们来看看客户端怎么执行服务端的方法。
首先我们通过 ServiceManager 获取到服务端的 BinderProxy 代理对象,通过调用 BinderProxy 将参数,方法标识(例如:TRANSACTION_test,AIDL中自动生成)传给 ServiceManager,同时客户端线程进入等待状态。
ServiceManager 将用户空间的参数等请求数据复制到内核空间,并向服务端插入一条执行执行方法的事务。事务执行完通知 ServiceManager 将执行结果从内核空间复制到用户空间,并唤醒等待的线程,响应结果,通讯结束。
Android进程间通信(IPC)机制Binder简要介绍和学习计划
https://blog.csdn.net/luoshengyang/article/details/6618363