仔细观察Binder和mmap;分析Android进程间通信
前言
Binder是Android系统中的一种IPC(进程间通信)机制,它使得不同进程中的组件能够互相交互和通信。在Binder中,一个进程中的客户端和另一个进程中的服务器之间通常通过Binder驱动程序进行通信。这种通信方式能够提供安全性和效率。
在Android中,Binder被广泛用于各种不同的场景。例如,系统服务、应用程序组件以及多进程通信等等。Binder的使用方式主要是通过调用API来完成,例如创建Binder服务、绑定Binder服务、传递数据等等。同时,也可以使用类似AIDL(Android界面描述语言)这样的工具来简化Binder的使用过程,使数据传输的过程变得更加便捷。
Binder原理
Binder其基本原理是利用Binder驱动程序,在不同进程间建立一个通道,数据可以在进程间传递。
在Binder的通信中,有三种角色:客户端、服务端和Binder驱动程序。当客户端需要和服务端通信时,它需要绑定到服务端的Binder对象上。此时,Binder驱动程序就会对象进行引用计数,并返回一个Binder代理对象(Proxy),客户端通过该代理对象来调用服务端提供的方法。通过Binder代理对象,客户端可以将请求消息发送给服务端,并等待服务端的回复。
服务端收到来自客户端的请求后,它需要将请求数据解包,并执行所需的操作。执行完操作后,服务端需要将结果打包并返回给客户端,同时,服务端还要对Binder对象自身进行引用计数的处理。当服务端不再需要该Binder对象时,它会释放掉该对象并同时通知Binder驱动程序进行相应的处理。
在Binder的消息传递中,还涉及到了Binder的底层传输机制及其相关概念,例如Binder节点、Binder线程以及Binder缓存等等。了解这些概念可以帮助开发人员更好地理解和使用Binder。
Binder使用代码示例
下面是一个简单的Binder使用示例,创建一个简单的Binder服务,通过客户端调用服务端提供的方法从而传输数据。
首先,创建一个AIDL文件,定义服务端提供的接口,例如:
//定义Binder服务端接口
interface IMyService {
int add(int a, int b);
}
然后,实现这个接口,创建一个Binder服务端类:
public class MyService extends Service {
private final IBinder mBinder = new MyBinder();
public class MyBinder extends Binder {
public MyService getService() {
//返回当前服务端对象
return MyService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
//服务端实现接口方法
public int add(int a, int b) {
return a + b;
}
}
最后,在客户端中绑定这个服务,并通过服务端提供的接口进行数据传输:
public class MainActivity extends AppCompatActivity {
//定义服务端接口对象
private IMyService myService;
//定义服务端连接对象
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//获取服务端接口对象
myService = IMyService.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
myService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//绑定服务端
Intent intent = new Intent(this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
//调用服务端接口
if (myService != null) {
try {
int result = myService.add(1, 2);
Toast.makeText(this, "result=" + result, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
//解除服务端绑定
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
在这个示例中,服务端实现了IMyService接口,并提供了一个add方法,在客户端通过服务端提供的接口数据传输。在客户端中,首先绑定服务端,然后通过服务端接口调用服务端方法并传递参数,获取服务端返回的数据,并最终解除服务端的绑定。
这就是一个简单的Binder示例,展示了Binder的基本使用方式。
Binder核心函数mmap源码分析
Binder是一种IPC(进程间通信)机制,常用于Android系统中进程间通信。mmap是Binder中的一个核心函数,用于映射数据到内存中。
函数原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数说明:
- addr:指向欲映射的内存起始地址,通常设为0,代表由操作系统自动选择新的空闲地址。
- length:欲映射的内存大小。
- prot:内存保护标志。PROT_READ表示页面可读取;PROT_WRITE表示页面可写入;PROT_EXEC表示页面可执行。
- flags:内存映射标志,通常设置为MAP_SHARED或MAP_PRIVATE。MAP_SHARED表示映射后对内存的写入操作会被同步到磁盘上的文件中;MAP_PRIVATE表示映射后的修改不会同步到磁盘文件。
- fd:需要映射到内存中的文件描述符。
- offset:从文件开始处的偏移量。
在Binder中,mmap函数主要是用于映射Binder驱动程序中的共享内存(Shared Memory)区域。Binder驱动程序使用共享内存来缓存传输的数据,减少系统调用次数和内存拷贝次数,提高系统性能。应用程序可以通过mmap函数将共享内存映射到自己的地址空间中,以便于对其进行读写操作。
具体实现可以参考Binder驱动程序中的mmap函数实现。
在Binder大量的IPC通信中,mmap函数的调用频率非常高,因此在实现中要考虑到内存的释放和回收,避免内存泄漏和内存缓慢释放等问题。
mmap内存映射详解
mmap是一种将磁盘文件或其它进程的内存映射到进程地址空间的方式。它可以将一个文件的部分或全部内容关联到在进程地址空间的一段连续的虚拟内存区域(也叫做内存映射文件)。这种方式可以提高文件的访问速度,因为每次访问的时候,内核可以直接将虚拟内存页中的内容读取到内存中,而不需要通过对文件的读取操作。
需要注意的是,mmap只对常规文件有效,而对于其它类型的文件(如设备文件),将会产生不可预知的后果。而且,在将一个文件映射到进程地址空间之前,首先需要打开文件。
mmap的原型如下:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
其中各个参数的含义如下:
- addr:指向欲映射的内存起始地址的指针,通常设为NULL表示由系统自动分配,或者显式指定为某个地址。
- length: 欲映射的内存区域的长度(以字节为单位),通常与文件的大小相对应。需要注意的是,对于一个映射到文件的内存区域而言,其长度必须是页面大小的整数倍。在Linux系统上,默认页的大小是4KB。
- prot:映射区域的保护方式。该参数可取的值有:
- PROT_READ: 表示映射区域可读。
- PROT_WRITE:表示映射区域可写。
- PROT_EXEC:表示映射区域可执行。
在这些值中的任意组合即可。比如,如果映射区域既可读又可写,则prot参数应设置为PROT_READ | PROT_WRITE。
- flags:用于描述映射区域的类型和属性。可取的值有:
- MAP_SHARED:表示映射区域被多个进程共享。对该区域的写操作将直接影响到磁盘文件和所有共享内存中该区域的内容,而对该区域的读操作将从磁盘或共享内存中读取数据。
- MAP_PRIVATE:表示映射区域是进程私有的。对该区域的写操作将只影响到该进程的内存中,而不会影响到磁盘上的文件或其它进程中共享内存的同一区域。
- MAP_FIXED:表示映射区域的起始地址可指定。如果addr参数非空,则系统会尝试将映射区域起始地址定位到该地址。如果该地址范围内已经存在其它对象,则映射失败。
- MAP_ANONYMOUS:表示映射的区域不与文件关联而是由内核分配的一块匿名内存区域。在使用该标志位进行映射时,fd和off参数必须为0。
- MAP_DENYWRITE:表示不允许将文件写操作映射到该区域。只有对共享内存有效,通常用于防止进程试图修改映射区域中的代码段。
- fd:文件描述符,用于指定要映射的文件。
- offset:文件偏移量,用于指定映射区域在文件中的起始位置。
mmap函数成功执行后,会返回映射区域的起始地址,如果失败则返回MAP_FAILED。在使用完映射区域后,需要调用munmap函数将映射区域解除。
在使用mmap函数时,需要注意以下几点:
- 映射区域的长度必须是页面大小的整数倍。在Linux系统中,默认页面大小为4KB。
- 对于多进程访问同一内存映射区域的情况,要保证使用的是共享内存,否则会导致数据不同步的问题。
- 映射区域的起始地址必须按页对齐,否则可能会引起内存访问错误。
- 映射区域的保护方式和属性一旦确定,就不能更改。
- 映射区域是一块连续的虚拟内存,但实际的物理内存可能不是连续的。如果虚拟内存超过了实际物理内存,那么会出现内存换页的情况。
- 使用了MAP_ANONYMOUS标志位的映射区域不会关联到磁盘文件,因此不能用于持久化存储。只有使用了MAP_SHARED和MAP_PRIVATE标志位的映射区域才能用于文件的读写操作。
综上所述,mmap函数是一种方便高效的内存映射方式,它可以将磁盘文件或共享内存映射到进程的地址空间中,以提高文件的读写效率,并通过对内存映射区域的操作来对文件进行读写操作。但是要注意参数的设置,以及对映射区域的内存管理。更多有关Android的技术framework通信机制,这里可以参考《Android核心 技术手册》这个技术文档。详细 内容请点击查看。
总结一下
Binder和mmap都是与内存操作相关的技术,但它们的应用场景和实现方式有所不同。
Binder是一种进程间通信机制,常用于Android系统中不同进程之间的通信。在Binder机制中,一个进程可以将自己的一个或多个对象注册到Binder驱动中,其他进程可以通过向Binder驱动发送消息,实现与这些对象交互的目的。Binder机制实际上是基于共享内存和进程间信号量等底层技术实现的,具体地,Binder驱动会为每个进程分配一段共享内存作为缓冲区,用于存放进程间传递的消息和数据。
与Binder不同,mmap是一种内存映射机制,常用于将文件映射到应用程序的内存空间中,实现文件的随机访问。在Linux系统中,mmap函数可以将一个文件映射到一个进程的虚拟地址空间中,实现文件的读写操作。当应用程序访问映射区域时,Linux内核会自动将访问转换为磁盘上相应的文件操作,从而实现了透明的读写操作。