by hzwusibo 20190504
1.Linux内核基础知识:
(1)用户空间/内核空间:
用户空间指的是用户程序所运行的空间,内核空间是 Linux 内核的运行空间,为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。
(2)系统调用
Linux将用户空间和内核空间隔离是有其道理的,用户软件良莠不齐,要是它们乱搞把系统玩坏了怎么办?因此对于某些特权操作必须交给安全可靠的内核来执行。
但是虽然是隔离的,但是总有一些用户软件需要访问内核空间,比如应用程序访问文件,网络是很常见的事情,怎么办呢?
用户空间访问内核空间的唯一方式就是系统调用;通过这个统一入口接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。
(3)Binder驱动:
传统的 Linux 通信机制,比如 Socket,管道等都是内核支持的;
Binder怎么做到访问内核空间? Linux 的动态可加载内核模块(Loadable Kernel Module,LKM)机制解决了这个问题;模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。Android系统可以通过添加一个内核模块运行在内核空间,用户进程之间的通过这个模块作为桥梁,就可以完成通信了。
负责各个用户进程通过 Binder 通信的内核模块叫做 Binder 驱动;
2.Binder机制:
Android使用的Linux内核拥有着非常多的跨进程通信机制,比如说,socket,管道之类的,那么为什么还要单独为Android创造一个Binder呢?
原因:
(1)出于性能上的考虑:Android是移动设备,相比于PC机的性能会有差距,Binder相比较于传统的socket/管道通信而言,更加高效,它在IPC时,只需要数据拷贝1次,而传统的socket之类的需要2次;
(2)出于安全上的考虑Binder支持通信双方进行身份校验,极大的保障了安全性:传统的进程间通信对于通信双方的身份没有进行严格的验证。
第二,Binder的通信模型:
我们其实可以进程通信的双方一方称为服务端进程,另一方称为客户端进程,我们知道,由于进程隔离的存在,在不进行进程间通信的方式的情况下,客户端进程是无法访问服务端进程的;
可以举个例子:A和B要打电话,A首先要有B的电话号码,电话号码在通信录获取,通信录中保存了B的昵称,电话号码之类的信息,得到电话号码之后,还需要电话基站的支持,A和B才可以进行电话交流;这里,我们发现,一次简单的打电话的过程,除了A和B两个主体对象外,还存在两个隐含的但是必不可少的东西,通信录和电话基站;
从上面的基础知识我们可以知道,两个进程之间如果需要进行通信,那么就必须需要内核的帮助,而这个内核模块就是Binder驱动。
我们可以做个类比,对于A和B相当于两个进程,他们要打电话就相当于要进行通信,其中电话基站就想到与Binder驱动,而通信录则相当于其中的一个ServerManager
ServerManager其实就是一个进程,它里面维护了一张表,表里面存储的是向他注册过的进程信息,在通信之初,首先需要有一个进程向驱动申请成为ServerManager,当内核驱动同意之后,这个成为ServerManager的进程就负责管理所有需要通信的进程信息,当客户端进程要访问服务端进程时,服务端进程首先会向ServerManager注册,让ServerManager保存自己的有关信息,当ServerManger保存完毕后,客户端进程就会通过Binder驱动向ServerManger查询服务端进程的信息,ServerManage就会将服务端进程的信息返回给客户端进程,客户端与服务端进程之间就可以通过这些信息,利用Binder驱动来进行通信了;
总结来说,Binder通信机制分三步:
第一步:ServerManager在其内部维护一张表;
第二步:服务端进程向ServerManager注册信息;
第三步:客户端进程向ServerManager取得信息,通过Binder驱动与服务端进程通信;
Binder的通信原理。
客户端想调用服务端的的add方法,返回一个Object对象
首先,Server会先向SeverManager注册一张表,这个表中就存储了相关信息,告诉ServerManager我这里有一个返回值为Object的add方法,
client向ServerManager中查询Server端有没有一个返回值为Object的add方法,由于进程之间的通信都是在内核中进行的,驱动会在数据传输时返回一个代理对象,不会返回给client真正的server的Object的对象,这个代理对象里包含了一个add方法,要注意,代理对象的add方法是一个空方法,它要做的是将参数包装好之后交给内核驱动来实现
Binder驱动收到代理对象的add方法之后,会在ServerManager表中查询存在有这个方法,Binder驱动就会将代理对象替换成server端的对象,调用server端的add方法,将结果返回给客户端。
什么是Binder呢? 有三种角度来理解:
(1)通常意义上来说,Binder就是指Andriod的通信机制;
(2)对于服务端进程来说,Binder指的是Binder本地对象,对于客户端进程来说,Binder指的是Binder代理对象。
(3)对于传输过程来说,Binder是可以进行跨进程传递的对象;
1、首先 Binder 驱动在内核空间创建一个数据接收缓存区;
2、接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
3、发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区(通过序列化,反序列化拷贝),由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
补充:
Binder与内存映射mmap
Binder IPC 是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。
比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘-->内核空间-->用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。
而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
一次完整的 Binder IPC 通信过程通常是这样:
首先 Binder 驱动在内核空间创建一个数据接收缓存区;
接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
如下图:
作者:Hanking65203
链接:https://juejin.cn/post/6844904115777044488
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Bindler 入门