Android匿名共用記憶體對外Android系統的匿名共用記憶體子系統的主體是以驅動程式的形式實現在核心空間的,同時在應用程式架構層提供了Java調用介面。在Android應用程式架構層,提供了一個MemoryFile介面來封裝了匿名共用記憶體檔案的建立和使用,它實現在frameworks/base/core/java/android/os/MemoryFile.java
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
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函數來完成的。
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方法,定義如下:
@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方法來實現匿名共用記憶體資料的讀取過程。
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為匿名共用記憶體映射到進程地址空間中的起始地址。
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方法,一個用於向匿名共用記憶體寫入多位元組資料,另一個則唯寫入一個位元組資料。這裡簡單介紹多位元組資料寫入過程:
public void write(byte buffer[], int offset, int count) throws IOException {
writeBytes(buffer, offset, mOffset, count);
mOffset += count;
}
參數buffer是指寫入匿名共用記憶體中的位元組數組,offset指定資料buffer開始寫的位移量,參數count指定寫入匿名共用記憶體的位元組長度,函數調用MemoryFile的writeBytes函數來完成資料寫入。
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
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完成資料的拷貝操作。
MemoryFile主要的構造方法 MemoryFile(String name, int length) ,這裡第二個參數為檔案大小,需要說明的是Android的MemoryFile和傳統的mmap還有一點點區別,畢竟是手機,它內部的記憶體管理方式ashmem會從核心中回收資源。畢竟目前部分低端機型的RAM也比較吃緊。
synchronized boolean allowPurging(boolean allowPurging) //允許ashmem清理記憶體,安全執行緒同步的方式。
void close() //關閉,因為在Linux內部mmap佔用一個控制代碼,不用時一定要釋放了
InputStream getInputStream() 返回讀取的內容用Java層的InputStream儲存
OutputStream getOutputStream() 把一個OutputSream寫入到MemoryFile中
boolean isPurgingAllowed() //判斷是否允許清理
int length() //返回記憶體對應檔大小
下面就是我們熟悉的,讀寫細節,主要是對字元數組的操作,這裡大家要計算好每個檔案類型的佔用,同時考慮到效率對於自己分配的大小考慮粒度對齊。
int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
應用場合:對於I/O需要頻繁操作的,主要是和外部儲存相關的I/O操作,MemoryFile通過將 NAND或SD卡上的檔案,分段映射到記憶體中進行修改處理,這樣就用高速的RAM代替了ROM或SD卡,效能自然提高不少,對於Android手機而言同時還減少了電量消耗。