Principle analysis of MemoryFile shared memory

The upper layer of Android provides some memory sharing tool classes, such as MemoryFile. Have you ever used it? Do you know how it works?

MemoryFile is a Java layer encapsulation of Ashmem. Let's learn MemoryFile and master its usage posture and underlying principle.

MemoryFile can be used as follows:

"In process A, apply for A piece of shared memory to write data, and prepare the file descriptor:"

MemoryFile memoryFile = new MemoryFile(name, size);
Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);
ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(des);

"In process B, get the file descriptor prepared in process A through the binder, and then directly read the data:"

FileDescriptor descriptor = pfd.getFileDescriptor();
FileInputStream fileInputStream = new FileInputStream(descriptor);;

As shown in the above code, it is as simple to use as file reading and writing, but we can't just stay at the superficial level. Next, let's look at how MemoryFile calls Ashmem driver function from Java API. First, let's look at the constructor of MemoryFile:

public MemoryFile(String name, int length) throws IOException {    
try { 
mSharedMemory = SharedMemory.create(name, length);        
mMapping = mSharedMemory.mapReadWrite();    
} catch (ErrnoException ex) {        

You can see that when you construct MemoryFile, you apply an anonymous shared memory through the SharedMemory create method, and the nCreate native method is invoked in the SharedMemory create method.

private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;

The corresponding native is implemented on Android_ os_ In sharedmemory.cpp, the implementation is as follows:

static jobject SharedMemory_create(JNIEnv* env, jobject, jstring jname, jint size) {    
const char* name = jname ? envGetStringUTFChars(jname, nullptr) : nullptr;   
int fd = ashmem_create_region(name, size); //Create anonymous shared memory     
 return jniCreateFileDescriptor(env, fd);

Then come to ashmem_create_region method. Its corresponding implementation is in ashmem-dev.cpp, as follows:

int ashmem_create_region(const char *name, size_t size){  
  int ret, save_errno;    //Create anonymous shared memory
int fd = __ashmem_open();    
 if (fd < 0) { return fd;}    
if (name) {       
 char buf[ASHMEM_NAME_LEN] = {0};     
   strlcpy(buf, name, sizeof(buf));     
   //  Set Ashmem name
ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf));  
       if (ret < 0) {goto error; }   

Setting the Ashmem name as above executes the IOCTL system call, which will be further called to   「ashmem_ioctl」   Drive function. Let's go on__ ashmem_open method:

static int __ashmem_open(){
int fd;   
fd = __ashmem_open_locked(); //Create anonymous shared memory
return fd;

Further call to__ ashmem_ open_ The locked method is as follows:

#define   ASHMEM_DEVICE   "/dev/ashmem"  // Ashmem   Device drive
static int __ashmem_open_locked(){
int ret;    
struct stat st;   
 int fd = TEMP_FAILURE_RETRY(          
      open(ASHMEM_DEVICE, O_RDWR | O_CLOEXEC)); //Create anonymous shared memory   
  ...    return fd;

In__ ashmem_ open_ In the locked method, the open() system call is invoked, just like ioctl, corresponding to the Ashmem device driver function.   「ashmem_open」   Drive function.

Through the above analysis, we know the Ashmem driven by Ashmem_ The open function is called step by step triggered by the create method of SharedMemory, during which Ashmem is also called_ IOCTL function.

And ashmem_ The MMAP driver function is triggered by the mapReadWrite method of SharedMemory. The process is analyzed below:

public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {    
return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);

public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {    
 long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);    
...    return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);}

The key is mFileDescriptor, which is the file descriptor returned after executing the SharedMemory create method to apply for anonymous shared memory. Continue to track mmap calls:

public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException {    
return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset);

public final class Libcore {    
private Libcore() { }    
public static Os rawOs = new Linux();  
  public static Os os = new BlockGuardOs(rawOs);

public native long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException;

In Libcore, BlockGuardOs is used to package Linux, but it is actually executed through Linux. Finally, it is called native mmap method in Linux. The corresponding implementation in native is mmap.cpp:

void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) {   
 return mmap64(addr, size, prot, flags, fd, static_cast<off64_t>(offset));

So far, the mapReadWrite method of SharedMemory calls the native mmap function. The key parameter passed is the file descriptor. Later, it will call ashmem in this way_ mmap:

  1. Through fd, you can find the associated equipment, that is, Ashmem equipment
  2. Call Ashmem of Ashmem device_ MMAP driver function

The key codes are as follows:

    error = file->f_op->mmap(file,vma);   

File stands for file or device driver, which refers to Ashmem device, f_op is the Ashmem device driver function set, that is, the registered Ashmem device description. So far, it is Ashmem_ The calling procedure of the MMAP driver function.

Posted by EWaveDev on Thu, 02 Dec 2021 19:55:07 -0800