Android使用Ashmem機制進行跨進程共享內存
導語:
在Android系統中,提供了獨特的匿名共享內存子系統Ashmem(Anonymous Shared Memory),它以驅動程序的形式實現在內核空間中。它有兩個特點:
1.一是能夠輔助內存管理系統來有效地管理不再使用的內存塊
2.二是它通過Binder進程間通信機制來實現進程間的內存共享。
本文中,我們將通過實例來簡要介紹Android系統的匿名共享內存的使用方法,使得我們對Android系統的匿名共享內存機制有一個感性的認識,為進一步學習它的源代碼實現打下基礎。
案例原理:
Android系統的匿名共享內存子系統的主體是以驅動程序的形式存在。在系統運行時庫層和應用程序框架層提供了訪問接口,其中,在系統運行時庫層提供了C/C++調用接口,而在應用程序框架層提供了Java調用接口。
這里,我們將直接通過應用程序框架層提供的Java調用接口來說明匿名共享內存子系統Ashmem的使用方法,畢竟我們在Android開發應用程序時,是基於Java語言的,而實際上,應用程序框架層的Java調用接口是通過JNI方法來調用系統運行時庫層的C/C++調用接口,最后進入到內核空間的Ashmem驅動程序去的。
我們在這里舉的例子是一個名為Ashmem的應用程序,它包含了一個Server端和一個Client端實現,
1. Server端是以Service的形式實現的,在這里Service里面,創建一個匿名共享內存文件
2. Client是一個Activity,這個Activity通過Binder進程間通信機制獲得前面這個Service創建的匿名共享內存文件的句柄,從而實現共享。
在Android應用程序框架層,提供了一個MemoryFile接口來封裝了匿名共享內存文件的創建和使用,它實現在frameworks/base/core/java/android/os/MemoryFile.java文件中。
下面,我們就來看看Server端是如何通過MemoryFile類來創建匿名共享內存文件的以及Client是如何獲得這個匿名共享內存文件的句柄的。
在MemoryFile類中,提供了兩種創建匿名共享內存的方法,我們通過MemoryFile類的構造函數來看看這兩種使用方法:
public class MemoryFile
{
......
/**
* Allocates a new ashmem region. The region is initially not purgable.
*
* @param name optional name for the file (can be null).
* @param length of the memory file in bytes.
* @throws IOException if the memory file could not be created.
*/
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;
}
/**
* Creates a reference to an existing memory file. Changes to the original file
* will be available through this reference.
* Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail.
*
* @param fd File descriptor for an existing memory file, as returned by
* {@link #getFileDescriptor()}. This file descriptor will be closed
* by {@link #close()}.
* @param length Length of the memory file in bytes.
* @param mode File mode. Currently only "r" for read-only access is supported.
* @throws NullPointerException if <code>fd</code> is null.
* @throws IOException If <code>fd</code> does not refer to an existing memory file,
* or if the file mode of the existing memory file is more restrictive
* than <code>mode</code>.
*
* @hide
*/
public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException {
if (fd == null) {
throw new NullPointerException("File descriptor is null.");
}
if (!isMemoryFile(fd)) {
throw new IllegalArgumentException("Not a memory file.");
}
mLength = length;
mFD = fd;
mAddress = native_mmap(mFD, length, modeToProt(mode));
mOwnsRegion = false;
}
......
}
viewcopy
兩個構造函數的主要區別是第一個參數
1.第一種構造方法是以指定的字符串調用JNI方法native_open來創建一個匿名共享內存文件,得到一個文件描述符,接着就以這個文件描述符為參數調用JNI方法natvie_mmap把這個匿名共享內存文件映射在進程空間中,然后就可以通過這個映射后得到的地址空間來直接訪問內存數據了;
2.第二種構造方法是以指定的文件描述符來直接調用JNI方法natvie_mmap把這個匿名共享內存文件映射在進程空間中,然后進行訪問,而這個文件描述符就必須要是一個匿名共享內存文件的文件描述符,這是通過一個內部函數isMemoryFile來驗證的,而這個內部函數isMemoryFile也是通過JNI方法調用來進一步驗證的。前面所提到的這些JNI方法調用,最終都是通過系統運行時庫層進入到內核空間的Ashmem驅動程序中去,不過這里我們不關心這些JNI方法、系統運行庫層調用以及Ashmem驅動程序的具體實現,在接下來的兩篇文章中,我們將會着重介紹,這里我們只關注MemoryFile這個類的使用方法。
前面我們說到,我們在這里舉的例子包含了一個Server端和一個Client端實現,
1. Server端就是通過第一個構造函數來創建一個匿名共享內存文件
2. Client端過Binder進程間通信機制來向Server請求獲取這個匿名共享內存的文件描述符,有了這個文件描述符之后,就可以通過后面一個構造函數來共享這個內存文件了。 然后Client和Server之間就可以通過這個這個匿名內存共享數據了。
案例實現:
首先在源代碼工程的packages/experimental目錄下創建一個應用程序工程目錄Ashmem。它定義了一個路徑為shy.luo.ashmem的package,這個例子的源代碼主要就是實現在這里了。
將會逐一介紹這個package里面的文件。這里要用到的Binder進程間通信接口定義在src/shy/luo/ashmem/IMemoryService.java文件中:
package shy.luo.ashmem;
import android.util.Log;
import android.os.IInterface;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
public interface IMemoryService extends IInterface {
public static abstract class Stub extends Binder implements IMemoryService {
private static final String DESCRIPTOR = "shy.luo.ashmem.IMemoryService";
public Stub() {
attachInterface(this, DESCRIPTOR);
}
public static IMemoryService asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IInterface iin = (IInterface)obj.queryLocalInterface(DESCRIPTOR);
if (iin != null && iin instanceof IMemoryService) {
return (IMemoryService)iin;
}
return new IMemoryService.Stub.Proxy(obj);
}
public IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getFileDescriptor: {
data.enforceInterface(DESCRIPTOR);
ParcelFileDescriptor result = this.getFileDescriptor();
reply.writeNoException();
if (result != null) {
reply.writeInt(1);
result.writeToParcel(reply, 0);
} else {
reply.writeInt(0);
}
return true;
}
case TRANSACTION_setValue: {
data.enforceInterface(DESCRIPTOR);
int val = data.readInt();
setValue(val);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements IMemoryService {
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
public IBinder asBinder() {
return mRemote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
public ParcelFileDescriptor getFileDescriptor() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
ParcelFileDescriptor result;
try {
data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getFileDescriptor, data, reply, 0);
reply.readException();
if (0 != reply.readInt()) {
result = ParcelFileDescriptor.CREATOR.createFromParcel(reply);
} else {
result = null;
}
} finally {
reply.recycle();
data.recycle();
}
return result;
}
public void setValue(int val) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
data.writeInt(val);
mRemote.transact(Stub.TRANSACTION_setValue, data, reply, 0);
reply.readException();
} finally {
reply.recycle();
data.recycle();
}
}
}
static final int TRANSACTION_getFileDescriptor = IBinder.FIRST_CALL_TRANSACTION + 0;
static final int TRANSACTION_setValue = IBinder.FIRST_CALL_TRANSACTION + 1;
}
public ParcelFileDescriptor getFileDescriptor() throws RemoteException;
public void setValue(int val) throws RemoteException;
}
view pcopy
public ParcelFileDescriptor getFileDescriptor() throws RemoteException;
public void setValue(int val) throws RemoteException;
package shy.luo.ashmem;
import java.io.FileDescriptor;
import java.io.IOException;
import android.os.Parcel;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.util.Log;
public class MemoryService extends IMemoryService.Stub {
private final static String LOG_TAG = "shy.luo.ashmem.MemoryService";
private MemoryFile file = null;
public MemoryService() {
try {
file = new MemoryFile("Ashmem", 4);
setValue(0);
}
catch(IOException ex) {
Log.i(LOG_TAG, "Failed to create memory file.");
ex.printStackTrace();
}
}
public ParcelFileDescriptor getFileDescriptor() {
Log.i(LOG_TAG, "Get File Descriptor.");
ParcelFileDescriptor pfd = null;
try {
pfd = file.getParcelFileDescriptor();
} catch(IOException ex) {
Log.i(LOG_TAG, "Failed to get file descriptor.");
ex.printStackTrace();
}
return pfd;
}
public void setValue(int val) {
if(file == null) {
return;
}
byte[] buffer = new byte[4];
buffer[0] = (byte)((val >>> 24) & 0xFF);
buffer[1] = (byte)((val >>> 16) & 0xFF);
buffer[2] = (byte)((val >>> 8) & 0xFF);
buffer[3] = (byte)(val & 0xFF);
try {
file.writeBytes(buffer, 0, 0, 4);
Log.i(LOG_TAG, "Set value " + val + " to memory file. ");
}
catch(IOException ex) {
Log.i(LOG_TAG, "Failed to write bytes to memory file.");
ex.printStackTrace();
}
}
}
view pcopy
這里還實現了IMemoryService.Stub的兩個接口getFileDescriptor和setVal,一個用來獲取匿名共享內存文件的文件描述符,一個來往匿名共享內存文件中寫入一個整數,其中,接口getFileDescriptor的返回值是一個ParcelFileDescriptor。在Java中,是用FileDescriptor類來表示一個文件描述符的,而ParcelFileDescriptor是用來序列化FileDescriptor的,以便在進程間調用時傳輸。
定義好本地服務好,就要定義一個Server來啟動這個服務了。這里定義的Server實現在src/shy/luo/ashmem/Server.java文件中:
package shy.luo.ashmem;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.os.ServiceManager;
public class Server extends Service {
private final static String LOG_TAG = "shy.luo.ashmem.Server";
private MemoryService memoryService = null;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
Log.i(LOG_TAG, "Create Memory Service...");
memoryService = new MemoryService();
try {
ServiceManager.addService("AnonymousSharedMemory", memoryService);
Log.i(LOG_TAG, "Succeed to add memory service.");
} catch (RuntimeException ex) {
Log.i(LOG_TAG, "Failed to add Memory Service.");
ex.printStackTrace();
}
}
@Override
public void onStart(Intent intent, int startId) {
Log.i(LOG_TAG, "Start Memory Service.");
}
@Override
public void onDestroy() {
Log.i(LOG_TAG, "Destroy Memory Service.");
}
}
package shy.luo.ashmem;
import java.io.FileDescriptor;
import java.io.IOException;
import shy.luo.ashmem.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.os.ServiceManager;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class Client extends Activity implements OnClickListener {
private final static String LOG_TAG = "shy.luo.ashmem.Client";
IMemoryService memoryService = null;
MemoryFile memoryFile = null;
private EditText valueText = null;
private Button readButton = null;
private Button writeButton = null;
private Button clearButton = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
IMemoryService ms = getMemoryService();
if(ms == null) {
startService(new Intent("shy.luo.ashmem.server"));
} else {
Log.i(LOG_TAG, "Memory Service has started.");
}
valueText = (EditText)findViewById(R.id.edit_value);
readButton = (Button)findViewById(R.id.button_read);
writeButton = (Button)findViewById(R.id.button_write);
clearButton = (Button)findViewById(R.id.button_clear);
readButton.setOnClickListener(this);
writeButton.setOnClickListener(this);
clearButton.setOnClickListener(this);
Log.i(LOG_TAG, "Client Activity Created.");
}
@Override
public void onResume() {
super.onResume();
Log.i(LOG_TAG, "Client Activity Resumed.");
}
@Override
public void onPause() {
super.onPause();
Log.i(LOG_TAG, "Client Activity Paused.");
}
@Override
public void onClick(View v) {
if(v.equals(readButton)) {
int val = 0;
MemoryFile mf = getMemoryFile();
if(mf != null) {
try {
byte[] buffer = new byte[4];
mf.readBytes(buffer, 0, 0, 4);
val = (buffer[0] << 24) | ((buffer[1] & 0xFF) << 16) | ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF);
} catch(IOException ex) {
Log.i(LOG_TAG, "Failed to read bytes from memory file.");
ex.printStackTrace();
}
}
String text = String.valueOf(val);
valueText.setText(text);
} else if(v.equals(writeButton)) {
String text = valueText.getText().toString();
int val = Integer.parseInt(text);
IMemoryService ms = getMemoryService();
if(ms != null) {
try {
ms.setValue(val);
} catch(RemoteException ex) {
Log.i(LOG_TAG, "Failed to set value to memory service.");
ex.printStackTrace();
}
}
} else if(v.equals(clearButton)) {
String text = "";
valueText.setText(text);
}
}
private IMemoryService getMemoryService() {
if(memoryService != null) {
return memoryService;
}
memoryService = IMemoryService.Stub.asInterface(
ServiceManager.getService("AnonymousSharedMemory"));
Log.i(LOG_TAG, memoryService != null ? "Succeed to get memeory service." : "Failed to get memory service.");
return memoryService;
}
private MemoryFile getMemoryFile() {
if(memoryFile != null) {
return memoryFile;
}
IMemoryService ms = getMemoryService();
if(ms != null) {
try {
ParcelFileDescriptor pfd = ms.getFileDescriptor();
if(pfd == null) {
Log.i(LOG_TAG, "Failed to get memory file descriptor.");
return null;
}
try {
FileDescriptor fd = pfd.getFileDescriptor();
if(fd == null) {
Log.i(LOG_TAG, "Failed to get memeory file descriptor.");
return null;
}
memoryFile = new MemoryFile(fd, 4, "r");
} catch(IOException ex) {
Log.i(LOG_TAG, "Failed to create memory file.");
ex.printStackTrace();
}
} catch(RemoteException ex) {
Log.i(LOG_TAG, "Failed to get file descriptor from memory service.");
ex.printStackTrace();
}
}
return memoryFile;
}
}
view pcopy
這個Activity在onCreate時,會通過startService接口來啟動我們前面定義的Server進程。調用startService時,需要指定要啟動的服務的名稱,這里就是"shy.luo.ashmem.server"了,后面我們會在程序的描述文件AndroidManifest.xml看到前面的Server類是如何和名稱"shy.luo.ashmem.server"關聯起來的。關於調用startService函數來啟動自定義服務的過程,可以參考Android系統在新進程中啟動自定義服務過程(startService)的原理分析一文。
內部函數getMemoryService用來獲取IMemoryService。如果是第一次調用該函數,則會通過ServiceManager的getService接口來獲得這個IMemoryService接口,然后保存在類成員變量memoryService中,以后再調用這個函數時,就可以直接返回memoryService了。
內部函數getMemoryFile用來從MemoryService中獲得匿名共享內存文件的描述符。同樣,如果是第一次調用該函數,則會通過IMemoryService的getFileDescriptor接口來獲得MemoryService中的匿名共享內存文件的描述符,然后用這個文件描述符來創建一個MemoryFile實例,並保存在類成員變量memoryFile中,以后再調用這個函數時,就可以直接返回memoryFile了。
有了memoryService和memoryFile后,我們就可以在Client端訪問Server端創建的匿名共享內存了。點擊Read按鈕時,就通過memoryFile的readBytes接口把共享內存中的整數讀出來,並顯示在文本框中;點擊Write按鈕時,就通過memoryService這個代理類的setVal接口來調用MemoryService的本地實現類的setVal服務,從而把文本框中的數值寫到Server端創建的匿名共享內存中去;點擊Clear按鈕時,就會清空文本框的內容。這樣,我們就可以通過Read和Write按鈕來驗證我們是否在Client和Server兩個進程中實現內存共享了。
現在,我們再來看看Client界面的配置文件,它定義在res/layout/main.xml文件中:
這樣,界面的相關配置文件就介紹完了。
我們還要再來看程序描述文件AndroidManifest.xml的相關配置,它位於Ashmem目錄下:
這樣,整個例子的源代碼實現就介紹完了,接下來就要編譯了。有關如何單獨編譯Android源代碼工程的模塊,以及如何打包system.img,請參考如何單獨編譯Android源代碼中的模塊一文。
執行以下命令進行編譯和打包:
再接下來,就是運行模擬器來運行我們的例子了。關於如何在Android源代碼工程中運行模擬器,請參考在Ubuntu上下載、編譯和安裝Android最新源代碼一文。
執行以下命令啟動模擬器:
點擊Ashmem圖標,啟動Ashmem應用程序,界面如下:
這樣,我們就可以驗證程序的功能了,看看是否實現了在兩個進程中通過使用Android系統的匿名共享內存機制來共享內存數據的功能。
通過這個例子的學習,相信讀者對Android系統匿名共享內存子系統Ashmem有了一個大概的認識,但是,這種認識還是停留在表面上。我們在文章開始時就提到,Android系統匿名共享內存子系統Ashmem兩個特點,一是能夠輔助內存管理系統來有效地管理不再使用的內存塊,二是它通過Binder進程間通信機制來實現進程間的內存共享。第二個特點我們在上面這個例子中看到了,但是似乎還不夠深入,我們知道,在Linux系統中,文件描述符其實就是一個整數,它是用來索引進程保存在內核空間的打開文件數據結構的,而且,這個文件描述符只是在進程內有效,也就是說,在不同的進程中,相同的文件描述符的值,代表的可能是不同的打開文件,既然是這樣,把Server進程中的文件描述符傳給Client進程,似乎就沒有用了,但是不用擔心,在傳輸過程中,Binder驅動程序會幫我們處理好一切,保證Client進程拿到的文件描述符是在本進程中有效的,並且它指向就是Server進程創建的匿名共享內存文件。至於第一個特點,我們也准備在后續學習Android系統匿名共享內存子系統Ashmem時,再詳細介紹。