进程通信是应用程序进程之间通过操作系统交换数据与服务对象的机制。
Client-Server方式对进程间通信机制在效率和安全性方面都是挑战。
效率问题。传统的管道,命名管道,网络与UNIX套接字,消息队列等需要多次复制数据(数据先从发送进程的用户区缓存复制到内核区缓存中,然后再从内核缓存复制到接收进程的用户区缓存中,单向传输至少有两次复制),系统开销大。传统的共享内存(shmem)机制无需将数据从用户空间到内核空间反复复制,属于低层机制,但应用程序直接控制十分复杂,因而难以使用。
安全问题。传统进程通信机制缺乏足够的安全措施:首先,传统进程通信的接收进程无法获得发送进程可靠的用户标识/进程标识(UID/PID),因而无法鉴别对方身份。Android的应用程序有自己的UID,可用于鉴别进程身份。在传统进程通信中,只能由发送进程在请求中自行填入UID与PID,容易被恶意程序利用,是不可靠的。只有内置在进程通信机制内的可靠的进程身份标记才能提供必要的安全保障。其次,传统进程通信的访问接入点是公开的,如FIFO与unix domain socket的路径名,socket的ip地址与端口号,ISystem V键值等,知道这些接入点的任何程序都可能试图建立连接,很难阻止恶意程序获得连接,如通过猜测地址获得连接等。
在系统设计安全方面,android的进程通信机制设计具备优于传统Linux的重要优势。
Android应用基于权限机制,定义进程通信的权限,相比传统Linux IPC具有更细粒度的权限控制。
Binder进程间通信机制具备类型安全的优势。开发者在编译应用程序时,使用android接口描述语言(AIDL)定义交换数据的类型,确保进程间通信的数据不会溢出越界污染进程空间。
Binder通过进程android的共享内存机制(Ashmem)实现高效率的进程通信,而不是采用传统的Linux/UNIX共享内存(Shared Memory),也具备特殊的安全含义。
进程通信机制的代码实现
匿名共享内存Ashmem(Anonymous Shared Memory)以驱动程序的形式在内核空间中实现,提供了辅助内核的内存回收(锁定/解锁)算法机制,有效地回收不再使用的内存。对于潜在的危险起到一定的防范作用。对于共享内存文件的创建和使用采用MemoryFile类来进行封装,MemoryFile类实现在frameworks/base/core/java/android/os/MemoryFile.java文件中。
1.MemoryFile类对象的创建
1)第一种是带有两个参数的MemoryFile构造方法。
Public MemoryFile( String name, int length), 该方法以指定的字符串为文件名通过调用JNI方法native_open来创建一个匿名共享内存文件,返回一个文件描述符表示所创建的文件。
Public MemoryFile( String name, int length) throws IOException {
mLength = length; //获取匿名共享内存大小
Mfd = native_open( name, length); //创建匿名共享文件
mAddress = native_mmap (mFD, length, PROT_READ | PROT_WRITE); //把匿名共享文件映射到进程空间
mOwnsRegion = true;
}
2) 第二种是带有三个参数的构造方法
Public MemoryFile( FileDescriptor fd, int length, String mode), 与第一种构造方法采用共享内存文件名为参数的方式有所不同,此构造方法以指定的文件描述符来调用JNI方法native_mmap.文件描述符必须是共享内存文件的文件描述符,这可以通过内部函数isMemoryFile来进行验证。
Public MemoryFile (FileDescriptor fd, int length, String mode) throws IOException
{
If (fd == null) { //对传入的fd进行判断
Throw new NullPointerException (“File descriptor is null . ”);
}
If (! isMemoryFile( fd)) {
//判断传入的fd是否是匿名共享文件fd
Throw new IllegalArgumentException( “Not a memory file. ”);
}
mLength = length; // 获取匿名共享文件大小
mFD = fd;
mAddress = native_mmap (mFD, length, modeToProt( mode));
//把匿名共享文件映射到进程空间
mOwnsRegion = false;
}
2.映射匿名共享内存
通过MemoryFile类的构造方法创建了共享内存文件以后,接下来需要把共享内存设备文件映射到进程空间,此操作通过调用JNI方法native_mmap来完成。这个JNI方法在frameworks/base/core/jni/android_os_MemoryFile.cpp文件中的实现如下:
Static jint android_os_MemoryFile_mmap (JNIEnv* env, jobject clazz, jobject fileDescriptor, jint length, jint prot)
{
//获取匿名共享内存文件描述符
Int fd = jniGetFDFromFileDescriptor (env, fileDescriptor);
//映射共享内存到进程空间
Jint result = (jint) mmap (NULL, length, prot, MAP_SHARED, fd, 0);
//对映射结果进行判断
If (!result)
jniThrowException(env, “java/io/IOException”, “mmap failed”);
return result;
}
在以上代码中,mmap方法中的fd可以通过open打开设备文件/dev/ashmem获得,mmap系统调用最终进入到内核空间的ashmem驱动程序的ashmem_mmap方法中实现:
Static int ashmem_mmap (struct file *file, struct vm_area_struct *vma)
{
Struct ashmem_area *asma = file->private_data;
Int ret = 0;
Mutex_lock ( &ashmem_mutex); //为ashmem上锁
//用户在调用mmaping之前需要设置ashmem大小
If (unlikely ((vma->vm_flags & ~asma -> pro_mask) & PROT_MASK)) {
Ret = -EPERM;
Goto out;
}
//如果共享内存文件为空,创建一个备份的共享内存文件
If (!asma -> file) {
Char *name = ASHMEM_NAME_DEF;
Struct file *vmfile;
If (asma -> name[ASHMEM_NAME_PREFIX_LEN] != ‘\0’)
Name = asma->name;
Vmfile = shmem_file_setup( name, asma->size, vma->vm_flags);
If (unlikely (IS_ERR(vmfile)))
{
Ret = PTR_ERR(vmfile);
Goto out;
}
Asma->file = vmfile;
}
Get_file (asma->file);
If (vma->wm_flags & VM_SHARED) //如果是共享内存,创建shmem
Shmem_set_file( vma, asma->file);
Else {
If (vma->vm_file)
Fput(vma->vm_file);
Vma->vm_file = asma->file;
}
Vma->vm_flags |= VM_CAN_NONLINEAR;
Out:
Mutex_unclock(&ashmem_mutex); //为ashmem解锁
Return ret;
}
函数ashmem_mmap执行完成后,就得到了虚拟空间的起始地址了,这个起始地址最终返回到应用程序框架层的MemoryFile类的构造函数中,保存在成员变量mAddress中,之后就可以对共享内存进行读写操作。
3.MemoryFile类的读,写操作方法
MemoryFile类提供了读写操作的 方法,分别为native_read和native_write,它们将在匿名共享内存的读写操作中被调用。MemoryFile类中定义了几个私有的变量,表示读,写操作所需要的信息,具体定义如下:
Private FileDescriptor mFD; //共享内存文件描述符
Private int mAddress; //共享内存地址
Private int mLength; //共享内存总共长度
Private Boolean mAllowPurging = false; //此标准位表示如果共享内存配置为unpin模式,此标志位为true,否则为false
1)MemoryFile类中的读操作为native_read方法
2)MemoryFile类中的写操作为native_write方法
4.MemoryFile的匿名共享内存读写操作
接下来可以在匿名共享内存空间进行读写操作了,MemoryFile的匿名共享内存读写操作通过调用JNI读操作和写操作native_read和native_write来实现,其实现过程定义在frameworks/base/core/jni/android_os_MemoryFile.cpp文件中。
1)MemoryFile的匿名共享内存读操作的代码实现如下:
Static jint android_os_MemoryFile_read( JNIEnv* env, jobject clazz, jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset, jint count, jboolean unpinned)
{
//获取匿名共享文件fd
Int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
If ( unpinned && ashmem_pin_region(fd, 0 ,0 ) == ASHMEM_WAS_PURGED)
{
//对要使用的共享内存锁定,如果锁定出报错异常
Ashmem_unpin_region( fd, 0 ,0 );
jniThrowException( env, “java/io/IOException”, “ashmem region was purged”);
return -1;
}
//从共享内存读取数据
Env->SetByteArrayRegion( buffer, destOffset, count, (const jbyte *)address + srcOffset);
If (unpinned) {
//解锁
Ashmem_unpin_region (fd, 0, 0);
}
Return count;
}
2)MemoryFile的匿名共享内存写操作的代码实现如下:
Static jint android_os_MemoryFile_write (JNIEnv* env, jobject clazz, jobject fileDescrtiptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset, jint count, jboolean unpinned)
{
//获取匿名共享内存描述符
Int fd = jniGetFDFormFileDescriptor( env, fileDescriptor);
If (unpinned && ashmem_pin_region (fd, 0, 0) == ASHMEM_WAS_PURGED)
{
//对要使用的共享内存锁定,如果出错报异常
Ashmem_unpin_region (fd, 0, 0);
jniThrowException( env, “java/io/IOException”, “ashmem region was purged”);
return -1;
}
//写数据到共享内存
Env->GetByteArrayRegion (buffer, srcOffset, count, (jbyte *)address + destOffset);
If (unpinned) { //对共享内存解锁
Ashmem_unpin_region( fd, 0, 0);
}
Return count;
}
5.锁定和解锁操作
System/core/libcutils/ashmem-dev.c
Int ashmem_pin_region (int fd, size_t offset, size_t len)
{
//设置需要锁定共享内存位移和大小
Struct ashmem_pin pin = { offset, len};
//使用ASHMEM_PIN命令,调用ioctl实现锁定功能
Return ioctl( fd, ASHMEM_PIN, &pin);
}
Int ashmem_unpin_region (int fd, size_t offset, size_t len)
{
//设置需要解锁共享内存位移和大小
Struct ashmem_pin pin = { offset, len};
//使用ASHMEM_UNPIN命令,调用ioctl实现解锁功能
Return ioctl( fd, ASHMEM_UNPIN, &pin);
}
Binder机制基于C/S构架设计,在应用层面,Client和Server直接通过Binder进行数据交互,Client对Server中的服务发出请求,Server打开Binder写入数据,Client通过Binder读取数据。Client和Server进程之间的数据通信通过Binder驱动程序间接实现,Client和Server通过open方法和ioctl文件操作方法与Binder驱动程序进行通信。