消息通信
Linux:
- 管道:在创建时分配一个page大小的内存,缓存区大小比较有限;
- 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;
- 共享内存:无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
- 套接字:作为更通用的接口,传输效率低,主要用于不通机器或跨网络的通信;
- 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 信号: 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等; Android:
- Binder
- Socket
- Handler
- 使用mmap只拷贝一次内存使Binder变的高效
- binder_mmap通过加锁,保证一次只有一个进程分配内存,保证多进程间的并发访问。 虚拟进程地址空间(vm_area_struct)和虚拟内核地址空间(vm_struct)都映射到同一块物理内存空间。当Client端与Server端发送数据时,Client(作为数据发送端)先从自己的进程空间把IPC通信数据copy_from_user拷贝到内核空间,而Server端(作为数据接收端)与内核共享数据,不再需要拷贝数据,而是通过内存地址空间的偏移量,即可获悉内存地址,整个过程只发生一次内存拷贝
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系
共享内存实现方式复杂,没有客户与服务端之别, 需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题;从这稳定性角度看,Binder架构优越于共享内存
-
效率:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝。 而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程
-
稳定性:共享内存不需要拷贝,Binder的性能仅次于共享内存。共享内存的性能优于Binder,那为什么不采用共享内存呢,因为共享内存需要处理并发同步问题,容易出现死锁和资源竞争(C/S同时访问一个资源),稳定性较差。Socket虽然是基于C/S架构的,但是它主要是用于网络间的通信且传输效率较低。Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好。
-
安全性:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID,且在Binder通信时会根据UID/PID进行有效性检测。
- 打开binder驱动,并调用mmap()方法分配128k的内存映射空间:binder_open();
- 通知binder驱动使其成为守护进程:binder_become_context_manager();
- 验证selinux权限,判断进程是否有权注册或查看指定服务;
- 进入循环状态,等待Client端的请求:binder_loop()。
- 注册服务的过程,根据服务名称,但同一个服务已注册,重新注册前会先移除之前的注册信息;
- 死亡通知: 当binder所在进程死亡后,会调用binder_release方法,然后调用binder_node_release.这个过程便会发出死亡通知的回调.
Binder线程池是通过ProcessState中的startThreadPool进行创建的,通过mThreadPoolStarted变量控制每个进程有且只有一个Binder线程池,在创建时会新建一个用于与主线程通信的binder线程 Binder线程创建与其所在进程的创建中产生,默认地,每个进程的binder线程池的线程个数上限为15,创建的binder线程个数上限为8
1M-8k,8K是为了内存优化
- Binder进程:对于底层Binder驱动,通过binder_procs链表记录所有创建的binder_proc结构体,binder驱动层的每一个binder_proc结构体都与用户空间的一个用于binder通信的进程一一对应,且每个进程有且只有一个ProcessState对象,这是通过单例模式来保证的。在每个进程中可以有很多个线程,在每个进程中可以有很多个线程,每个线程对应一个IPCThreadState对象,IPCThreadState对象也是单例模式,即一个线程对应一个IPCThreadState对象\n
- Binder线程:每个Server进程在启动时会创建一个binder线程池,并向其中注册一个Binder线程;之后Server进程也可以向binder线程池注册新的线程,或者Binder驱动在探测到没有空闲binder线程时会主动向Server进程注册新的的binder线程。对于一个Server进程有一个最大Binder线程数限制,默认为16个binder线程
- 服务的启动方式
- 系统服务都是由SystemServer进程启动并管理的
- bindService等启动是由AMS启动并管理的
- 服务的注册与管理
- 系统服务一般都是通过ServiceManager的addService进行注册的,这些服务一般都是需要拥有特定的权限才能注册到ServiceManager
- bindService启动的服务可以算是注册到ActivityManagerService
- 服务的请求使用方式
- 系统服务一般都是通过ServiceManager的getService得到服务的句柄,这个过程其实就是去ServiceManager中查询注册系统服务
- bindService启动的服务,主要是去ActivityManagerService中去查找相应的Service组件,最终会将Service内部Binder的句柄传给Client
- Client在请求Server端服务的过程中,是需要返回结果的,即使是你看不到返回数据,其实还是会有个成功与失败的处理结果返回给Client,这就是所说的Client端是同步的
- 在服务端在被唤醒后,就去处理请求,处理结束后,服务端就将结果返回给正在等待的Client线程,将结果写入到Client的内核空间后,服务端就会直接返回了,不会再等待Client端的确认,这就是所说的服务端是异步的
每个接收端进程都有一个todo队列,用于保存发送端进程发送过来的binder请求,这类请求可以由接收端进程的任意一个空闲的binder线程处理;接收端进程存在一个或多个binder线程,在每个binder线程里都有一个todo队列,也是用于保存发送端进程发送过来的binder请求,这类请求只能由当前binder线程来处理
//作用是清空远程调用端的uid和pid,用当前本地进程的uid和pid替代;返回值是远程调用端的uid和pid
public static final native long clearCallingIdentity();
//作用是恢复远程调用端的uid和pid信息,正好是`clearCallingIdentity`的反过程;
public static final native void restoreCallingIdentity(long token);
当服务端发生错误,比如进程终止时,就会抛出death异常
//ITest.aidl
interface ITest{
void test();
}
//TestServers.java
public class TestService extends Service {
private static final String TAG = "gyzboy";
private Binder mBinder = new ITest.Stub() {
@Override
public void test() throws RemoteException {
Log.i(TAG, "server");
}
};
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind");
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
Log.i(TAG,e.toString());
}
//结束自己
android.os.Process.killProcess(android.os.Process.myPid());
}
}).start();
return mBinder;
}
}
//客户端
public class MainActivity extends Activity {
private static final String TAG = "frank";
private ServiceConnection mCon;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.gyzboy.deathrecipientdemo","com.gyzboy.deathrecipientdemo.TestService"));
intent.setAction("com.gyzboy.test");
final DeathRecipient deathHandle = new DeathRecipient() {
@Override
public void binderDied() {
Log.i(TAG, "binder is died");//当服务端sleep后执行
}
};
mCon = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected "+name.toShortString());
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
Log.i(TAG, "onServiceConnected "+name.toShortString()+" "+service.getInterfaceDescriptor());
service.linkToDeath(deathHandle, 0);//绑定deathHandle,当服务端停止时接收消息
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
bindService(intent,mCon,Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mCon);
}
}
- zygote:用于孵化进程,system_server创建进程是通过socket向zygote进程发起请求;
- installd:用于安装App的守护进程,上层PackageManagerService很多实现最终都是交给它来完成;
- lmkd:lowmemorykiller的守护进程,Java层的LowMemoryKiller最终都是由lmkd来完成;
- adbd:这个也不用说,用于服务adb;
- logcatd:这个不用说,用于服务logcat;
- vold:即volume Daemon,是存储类的守护进程,用于负责如USB、Sdcard等存储设备的事件处理。
Binder/Socket用于进程间通信,而Handler消息机制用于同进程的线程间通信,Handler消息机制是由一组MessageQueue、Message、Looper、Handler共同组成的,为了方便且称之为Handler消息机制
- Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;Message中有一个用于处理消息的Handler;msg.target
- MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);MessageQueue有一组待处理的Message;
- Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);Handler中有Looper和MessageQueue。
- Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。Looper有一个MessageQueue消息队列;
不可以,在Looper.prepare中会创建MessageQueue对象,一个线程只能有一个MessageQueue队列
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
if (msg.callback != null) {//post消息是会自动绑定msg.callback=runnable
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
Message.callback.run > handler.callback.handleMessage > handleMessage
- 同步消息。也就是普通的消息。
- 异步消息。通过setAsynchronous(true)设置的消息。
- 同步屏障消息。通过postSyncBarrier方法添加的消息,特点是target为空,也就是没有对应的handler。
正常情况下,同步消息和异步消息都是正常被处理,也就是根据时间when来取消息,处理消息。当遇到同步屏障消息的时候,就开始从消息队列里面去找异步消息,找到了再根据时间决定阻塞还是返回消息
同步屏障机制,提高消息的优先级,hide方法
空的死循环并不会导致应用卡死,导致应用卡死的是在循环里面做的事情 不会消耗大量资源,在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
ActivityThread不是一个线程,他并不继承Thread类 进程与线程在Linux层面的区别就是是否可以共享资源,所以ActivityThread所依附的线程或者进程可以理解为通过zygote进程fork出来的应用进程
如果向handler中发送一个延时消息,那么在activity销毁时,消息就会一直存在与looper的死循环中 如何处理:1.在activity销毁时调用removeMessage 2.将handler改为静态的
在enqueueMessage时回去根据when决定将when=0的消息插入到头部