在Android 匿名共享內存驅動源碼分析中介紹了匿名共享內存的驅動實現過程,本文在Android匿名共享內存驅動基礎上,介紹Android匿名共享內存對外Android系統的匿名共享內存子系統的主體是以驅動程序的形式實現在內核空間的,同時在應用程序框架層提供了Java調用接口。在Android應用程序框架層,提供了一個MemoryFile接口來封裝了匿名共享內存文件的創建和使用,它實現在frameworks/base/core/java/android/os/MemoryFile.java
[cpp]
public MemoryFile(String name, int length) throws IOException {
mLength = length;
//打開"/dev/ashmem"設備文件
mFD = native_open(name, length);
if (length > 0) {
//將打開的"/dev/ashmem"設備文件映射到進程虛擬地址空間中
mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
} else {
mAddress = 0;
}
}
native_open函數是一個本地函數,通過JNI實現在C++層,代碼位於frameworks\base\core\jni\android_os_MemoryFile.cpp
[cpp]
static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length)
{
//字符串轉換
const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL);
//打開設備文件"/dev/ashmem",並修改設備文件名稱及共享內存大小
int result = ashmem_create_region(namestr, length);
if (name)
env->ReleaseStringUTFChars(name, namestr);
if (result < 0) {
jniThrowException(env, "java/io/IOException", "ashmem_create_region failed");
return NULL;
}
//設備文件句柄轉換
return jniCreateFileDescriptor(env, result);
}
函數首先將Java層傳過來的你們共享內存名稱轉換為C++層的字符串,然後調用ashmem_create_region函數創建一個名為dev/ashmem/的匿名共享內存,並且修改該共享內存的名稱及大小,然後將創建的匿名共享內存設備文件句柄值返回到Java空間中。函數ashmem_create_region在Android 匿名共享內存C接口分析中有詳細分析,該接口函數就是用於創建一塊匿名共享內存。
在Java空間構造MemoryFile對象時,首先打開/dev/ashmem設備文件並在內核空間創建一個ashmem_area,接著需要將內核空間分配的共享內存地址映射到進程虛擬地址空間中來,映射過程是通過native_mmap函數來完成的。
[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來實現地址空間映射,注意標志位MAP_SHARED,表示該緩沖區以共享方式映射。映射過程是由Ashmem驅動來完成,Android 匿名共享內存驅動源碼分析詳細分析了Android匿名共享內存的實現過程。在構造MemoryFile對象時完成了匿名共享內存的創建及地址空間的映射過程,將創建的匿名共享內存的大小保存到MemoryFile的成員變量mLength中,成員變量mFD保存創建的匿名共享內存的文件描述符,成員變量mAddress保存匿名共享內存映射到進程地址空間的起始地址。有了這些信息後,就可以直接使用該匿名共享內存了。
匿名共享內存讀
對匿名共享內存的讀取操作,在Java空間被封裝成MemoryInputStream來完成,該類繼承於輸入流InputStream,並對外提供了read方法,定義如下:
[cpp]
@Override
public int read() throws IOException {
if (mSingleByte == null) {
mSingleByte = new byte[1];
}
int result = read(mSingleByte, 0, 1);
if (result != 1) {
return -1;
}
return mSingleByte[0];
}
@Override
public int read(byte buffer[], int offset, int count) throws IOException {
if (offset < 0 || count < 0 || offset + count > buffer.length) {
// readBytes() also does this check, but we need to do it before
// changing count.
throw new IndexOutOfBoundsException();
}
count = Math.min(count, available());
if (count < 1) {
return -1;
}
int result = readBytes(buffer, mOffset, offset, count);
if (result > 0) {
mOffset += result;
}
return result;
}
MemoryInputStream類提供了兩個read重載方法,第一個無參read方法調用有參read方法來讀取1字節的數據,而有參read方法的數據讀取過程是調用MemoryInputStream的外部類MemoryFile的readBytes方法來實現匿名共享內存數據的讀取過程。
[cpp]
public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
throws IOException {
if (isDeactivated()) {
throw new IOException("Can't read from deactivated memory file.");
}
if (destOffset < 0 || destOffset > buffer.length || count < 0
|| count > buffer.length - destOffset
|| srcOffset < 0 || srcOffset > mLength
|| count > mLength - srcOffset) {
throw new IndexOutOfBoundsException();
}
return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
}
該函數也僅僅作了一些判斷,然後直接調用本地方法native_read在C++空間完成數據讀取,在構造MemoryFile對象時,已經打開並映射了dev/ashmem設備文件,因此在這裡直接將打開該設備文件得到的文件句柄值傳到C++空間,以正確讀取指定的匿名共享內存中的內容,mAddress為匿名共享內存映射到進程地址空間中的起始地址。
[cpp]
static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz,
jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
jint count, jboolean unpinned)
{
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;
}
匿名共享內存寫
將指定數據寫入到匿名共享內存中,對匿名共享內存的寫操作使用MemoryOutputStream來封裝,該類提供了兩個重載的write方法,一個用於向匿名共享內存寫入多字節數據,另一個則只寫入一個字節數據。這裡簡單介紹多字節數據寫入過程:
[cpp]
public void write(byte buffer[], int offset, int count) throws IOException {
writeBytes(buffer, offset, mOffset, count);
mOffset += count;
}
參數buffer是指寫入匿名共享內存中的字節數組,offset指定數據buffer開始寫的偏移量,參數count指定寫入匿名共享內存的字節長度,函數調用MemoryFile的writeBytes函數來完成數據寫入。
[cpp]
public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
throws IOException {
if (isDeactivated()) {
throw new IOException("Can't write to deactivated memory file.");
}
if (srcOffset < 0 || srcOffset > buffer.length || count < 0
|| count > buffer.length - srcOffset
|| destOffset < 0 || destOffset > mLength
|| count > mLength - destOffset) {
throw new IndexOutOfBoundsException();
}
native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
}
該函數首先檢驗參數的正確性,然後調用native方法native_write通過JNI轉入C++完成數據寫入,第一個參數是匿名共享內存的文件描述符,第二個參數是匿名共享內存映射到進程地址空間的基地值,後面三個參數上面已經介紹了,最後一個參數mAllowPurging表示是否允許內存回收
上圖描述了匿名共享內存的寫入過程,本質上就是將buffer中指定位置開始的數據拷貝到匿名共享內存指定的偏移位置
frameworks\base\core\jni\android_os_MemoryFile.cpp
[cpp]
static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz,
jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
jint count, jboolean unpinned)
{
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->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset);
if (unpinned) {
ashmem_unpin_region(fd, 0, 0);
}
return count;
}
數據寫入過程是通過JNI函數GetByteArrayRegion完成數據的拷貝操作。