进程通讯 & Binder机制 & Service 笔记

Linux进程通讯机制

Linux 系统中有万物皆文件的说法,虚拟文件系统(VFS)是 Linux 对外的接口,任何程序都必须通过这层接口来使用它。

为了避免系统安全问题(越权访问),进程间内存无法共享,相互独立的,数据交互就得采用特殊的通信机制(IPC)。

每个进程划分用户空间(不可共享)跟内核空间(可共享),其中所有进程都共享一个内核空间

Linux 系统中,主要通过 copy_from_user() 跟 copy_to_user() 函数来进行跨进程数据的交互。

交互流程

当 Client 向 Server 发起 IPC 请求时(交互),Client 会先将数据从用户空间拷贝到内核空间,驱动程序在将内核空间中的数据拷贝到 Server 中,完成 Client 向 Server 进程间数据传输。

缺点:性能低,需要两次内存拷贝,而且无法得知接收数据的大小,内存消耗大。

共享内存

通过 shmget() 函数申请内存共享(虚拟的临时文件),但不是在内核中;

通过 shmat() 函数把共享内存映射到用户空间,成功返回内存地址,通过这个地址来进行数据的读写

无需内存拷贝,使用简单,但不适合并发场景,数据同步不及时,所以通常跟信号量配合,达到同步的控制。

信号 Signal

需要拷贝两次内存,发送信号通知,可以跨进程接收,无法传输复杂数据,主要用于同步。

信号量 Semaphore

类似同步锁,资源竞争时互斥访问,可以看作一个计数器,用来记录内存存取状况;

根据数值来判断,资源在一个时刻只有一个进程(线程)所持有,如果有进程持有会进入休眠队列等待唤醒。

管道

需要拷贝两次内存,首先调用系统函数创建管道,同时会在内核中创建虚拟文件,通过对虚拟文件的读写(不能同时进行)来达到交互的目的,传输数据不能超过4k,否则会阻塞管道。

PIPE - 单向管道,一端只能读,另一端只能写,pipe是匿名的,只支持父子和兄弟进程之间的通信。

FIFO - 双向管道,可以读也可以写,能保证数据顺序,创建管道后需要调用 open 函数打开文件才能操作。

Android 主线程中的 Looper 唤醒,native 层的 Looper 使用的是 pipe 匿名管道,写入数据时会重新唤醒管道。

缺点:管道速度慢,且匿名管道只能父子通信,容量也有限。

消息队列

需要拷贝两次内存,队列是一个消息链表,消息都包含标识符,不同进程可以根据消息类型标识符来对这个链表进行操作。

队列本身是异步的,还可以实现模块之间的解耦,Handler 线程通信也使用了消息队列。

ContentProvider

底层通过 binder 机制实现,启动时会通过 AMS 注册服务,然后可以通过 URI 来获取 Binder 引用,从而获取到 Server 方法进行交互。

Socket

需要拷贝两次内存,开销大,不安全。

HTTP TCP UDP 概括总结

Binder 机制

Binder 是 Android IPC 的基础,内核层的驱动程序,像是一个粘合剂或者中转站,把 client、server、service manager(三者为不同进程)粘合一起。

虽然 Linux 中有管道、队列socket 等进程通信方式,但是 Binder 在性能(一次拷贝)、安全(app应用具有 id 标识可以更好鉴别身份)等方面更出色。

划分

Binder 内存被划分为用户空间(应用程序)和内核空间(内核和驱动),这样用户空间崩溃了,内核空间也不会受到影响;

Client、Server 和 ServiceManager(管理 Service 注册与查询) 都运行在用户空间上的不同进程中,Binder 驱动程序运行在内核空间。

Client、Server 和 ServiceManager的交互也是基于 Binder,Binder 跟 ServiceManager 属于系统自带。

Binder 交互

首先,Server 通过 Binder 向 Service Manager 注册服务,等于告诉 ServiceManager 它有什么功能;

然后 Client 调用 Binder 向 Service Manager 查询 Server 中的信息,Service Manager 查询完毕后返回一个 Proxy 代理对象;

最后 Client 通过代理对象,进行调用后在发送给 Binder 驱动,最后 Server 执行后把返回值发送给驱动,驱动再转发给 Client(App) 进程。

传统的跨进程交互需拷贝数据2次,Binder 只需要拷贝一次内存,主要是使用到了内存映射;

Binder 在内核空间和接收进程的用户空间中会创建一个共享内存,达到一次拷贝的目的。

mmap 内存映射

它可以将一个文件、一段物理内存,甚至内核空间映射到进程的虚拟内存地址;

把进程虚拟地址和文件物理地址进行关联,使得二者存在对映关系,进程就可以采用指针的方式操作这段内存。

Binder 就是调用 linux 系统下的函数 mmap,作用是虚拟内存区域和共享对象建立映射关系,提高数据的读、写,减少了数据拷贝次数,以达到内存复用。

start activity

Intent Activity 跳转使用的 Binder,由于 Binder 传输数据有大小限制的,数据超过大小就会报错(手写open,mmap就可以突破这个限制), Binder 主要用于频繁通信而存在。

content provider

底层使用 Binder,而 Binder 线程数默认最大为16,超过会阻塞线程,所以只能支持16个线程同时并发。

优缺点

高效 - 只需要拷贝一次内存;

安全性高 - 每个进程都有 UID,PID 身份标识,容易鉴别身份;

ALDL

通信接口定义语言,Android11(30) 的机型上,AIDL已不可用。

ALDL是一种 RPC 框架(远程过程调用),RPC 的另一个目的是对客户端只声明接口及方法,隐藏掉具体实现类,供客户端直接获取此接口实例。

Android 提供了 ALDL 来进行跨进程之间的通信,通过 Binder 实现的一个简化封装的工具。

Service

运行在主线程上,不可以执行耗时操作,否则会 ANR,不同 Activity 可以很好去控制 service。

使用

启动 service 可以在后台执行计算处理,绑定 service 可以跟组件进行交互。

start 方式开启(stop 关闭)的 service,在该 Activity 销毁后仍会运行,无法获取服务中的值;

onCreate -> onStartCommand -> onDestroy

bind 方式开启(unBind 关闭)的 service,依赖绑定的 Context,该 Context 所在活动销毁后会停止,可以通过 binder 获取返回结果。

onCreate -> onBind -> onUnBind -> onDestroy

默认为后台服务,容易被系统回收,也可以设置成前台服务,不容易被回收。

public class MyService extends Service {    

    @Override  
    public void onCreate() {  
        super.onCreate();  
        Notification notification = new Notification(...);  
        ...
        startForeground(1, notification);  
    }   
} 

交互

1.通过 intent 传递,onStartCommand 中获取。

2.通过 binder 对象传递:

bindService 时把 ServiceConnection(重写方法中获取到 binder 对象) 类当做参数,Service 中 onBind 返回自定义的 binder 对象。

    @Override
    public IBinder onBind(Intent intent) {
       return new MyBinder();
    }
    public class MyBinder extends android.os.Binder{
        public void setData(int count){
            MyService.this.count = count;
        }
    }

3.回调传值。

关闭

Service 必须在既没有跟 Activity 关联又处于停止状态时才会被销毁。

如果同时使用了 start 跟 bind 方式开启服务,需要同时调用两者的关闭方法去停止服务。

 

热门相关:首席的独宠新娘