diff --git a/Android.mk b/Android.mk index 4cedadcb77646..b8ec9797b724d 100644 --- a/Android.mk +++ b/Android.mk @@ -730,6 +730,7 @@ aidl_files := \ frameworks/base/core/java/android/os/DropBoxManager.aidl \ frameworks/base/core/java/android/os/Bundle.aidl \ frameworks/base/core/java/android/os/Debug.aidl \ + frameworks/base/core/java/android/os/SharedMemory.aidl \ frameworks/base/core/java/android/os/StrictMode.aidl \ frameworks/base/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl \ frameworks/base/core/java/android/net/Network.aidl \ diff --git a/api/current.txt b/api/current.txt index 40cb282cb1c13..b2fdbabe717a0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -31022,12 +31022,12 @@ package android.os { public class MemoryFile { ctor public MemoryFile(java.lang.String, int) throws java.io.IOException; - method public synchronized boolean allowPurging(boolean) throws java.io.IOException; + method public deprecated synchronized boolean allowPurging(boolean) throws java.io.IOException; method public void close(); - method protected void finalize(); + method public java.io.FileDescriptor getFileDescriptor() throws java.io.IOException; method public java.io.InputStream getInputStream(); method public java.io.OutputStream getOutputStream(); - method public boolean isPurgingAllowed(); + method public deprecated boolean isPurgingAllowed(); method public int length(); method public int readBytes(byte[], int, int, int) throws java.io.IOException; method public void writeBytes(byte[], int, int, int) throws java.io.IOException; @@ -31452,6 +31452,22 @@ package android.os { field public static final android.os.Parcelable.Creator CREATOR; } + public final class SharedMemory implements java.io.Closeable android.os.Parcelable { + method public void close(); + method public static android.os.SharedMemory create(java.lang.String, int) throws android.system.ErrnoException; + method public int describeContents(); + method public int getFd(); + method public java.io.FileDescriptor getFileDescriptor(); + method public int getSize(); + method public java.nio.ByteBuffer map(int, int, int) throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadOnly() throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadWrite() throws android.system.ErrnoException; + method public boolean setProtect(int); + method public static void unmap(java.nio.ByteBuffer); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public class StatFs { ctor public StatFs(java.lang.String); method public deprecated int getAvailableBlocks(); diff --git a/api/system-current.txt b/api/system-current.txt index 3231b4d40dfe9..53a8a30cadd83 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -33771,12 +33771,12 @@ package android.os { public class MemoryFile { ctor public MemoryFile(java.lang.String, int) throws java.io.IOException; - method public synchronized boolean allowPurging(boolean) throws java.io.IOException; + method public deprecated synchronized boolean allowPurging(boolean) throws java.io.IOException; method public void close(); - method protected void finalize(); + method public java.io.FileDescriptor getFileDescriptor() throws java.io.IOException; method public java.io.InputStream getInputStream(); method public java.io.OutputStream getOutputStream(); - method public boolean isPurgingAllowed(); + method public deprecated boolean isPurgingAllowed(); method public int length(); method public int readBytes(byte[], int, int, int) throws java.io.IOException; method public void writeBytes(byte[], int, int, int) throws java.io.IOException; @@ -34230,6 +34230,22 @@ package android.os { field public static final android.os.Parcelable.Creator CREATOR; } + public final class SharedMemory implements java.io.Closeable android.os.Parcelable { + method public void close(); + method public static android.os.SharedMemory create(java.lang.String, int) throws android.system.ErrnoException; + method public int describeContents(); + method public int getFd(); + method public java.io.FileDescriptor getFileDescriptor(); + method public int getSize(); + method public java.nio.ByteBuffer map(int, int, int) throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadOnly() throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadWrite() throws android.system.ErrnoException; + method public boolean setProtect(int); + method public static void unmap(java.nio.ByteBuffer); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public class StatFs { ctor public StatFs(java.lang.String); method public deprecated int getAvailableBlocks(); diff --git a/api/test-current.txt b/api/test-current.txt index bc05da67c38f3..1424f9f3f8791 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -31153,12 +31153,12 @@ package android.os { public class MemoryFile { ctor public MemoryFile(java.lang.String, int) throws java.io.IOException; - method public synchronized boolean allowPurging(boolean) throws java.io.IOException; + method public deprecated synchronized boolean allowPurging(boolean) throws java.io.IOException; method public void close(); - method protected void finalize(); + method public java.io.FileDescriptor getFileDescriptor() throws java.io.IOException; method public java.io.InputStream getInputStream(); method public java.io.OutputStream getOutputStream(); - method public boolean isPurgingAllowed(); + method public deprecated boolean isPurgingAllowed(); method public int length(); method public int readBytes(byte[], int, int, int) throws java.io.IOException; method public void writeBytes(byte[], int, int, int) throws java.io.IOException; @@ -31584,6 +31584,22 @@ package android.os { field public static final android.os.Parcelable.Creator CREATOR; } + public final class SharedMemory implements java.io.Closeable android.os.Parcelable { + method public void close(); + method public static android.os.SharedMemory create(java.lang.String, int) throws android.system.ErrnoException; + method public int describeContents(); + method public int getFd(); + method public java.io.FileDescriptor getFileDescriptor(); + method public int getSize(); + method public java.nio.ByteBuffer map(int, int, int) throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadOnly() throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadWrite() throws android.system.ErrnoException; + method public boolean setProtect(int); + method public static void unmap(java.nio.ByteBuffer); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public class StatFs { ctor public StatFs(java.lang.String); method public deprecated int getAvailableBlocks(); diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java index 6cec55a4a09eb..531670fcd85b0 100644 --- a/core/java/android/os/MemoryFile.java +++ b/core/java/android/os/MemoryFile.java @@ -16,68 +16,50 @@ package android.os; -import android.util.Log; +import android.system.ErrnoException; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; /** - * MemoryFile is a wrapper for the Linux ashmem driver. - * MemoryFiles are backed by shared memory, which can be optionally - * set to be purgeable. + * MemoryFile is a wrapper for {@link SharedMemory} which can optionally be set to purgeable. + * + * Applications should generally prefer to use {@link SharedMemory} which offers more flexible + * access & control over the shared memory region than MemoryFile does. + * * Purgeable files may have their contents reclaimed by the kernel * in low memory conditions (only if allowPurging is set to true). * After a file is purged, attempts to read or write the file will * cause an IOException to be thrown. */ -public class MemoryFile -{ +public class MemoryFile { private static String TAG = "MemoryFile"; - // mmap(2) protection flags from - private static final int PROT_READ = 0x1; - private static final int PROT_WRITE = 0x2; - - private static native FileDescriptor native_open(String name, int length) throws IOException; - // returns memory address for ashmem region - private static native long native_mmap(FileDescriptor fd, int length, int mode) - throws IOException; - private static native void native_munmap(long addr, int length) throws IOException; - private static native void native_close(FileDescriptor fd); - private static native int native_read(FileDescriptor fd, long address, byte[] buffer, - int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; - private static native void native_write(FileDescriptor fd, long address, byte[] buffer, - int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; - private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; + // Returns 'true' if purged, 'false' otherwise + private static native boolean native_pin(FileDescriptor fd, boolean pin) throws IOException; private static native int native_get_size(FileDescriptor fd) throws IOException; - private FileDescriptor mFD; // ashmem file descriptor - private long mAddress; // address of ashmem memory - private int mLength; // total length of our ashmem region + private SharedMemory mSharedMemory; + private ByteBuffer mMapping; private boolean mAllowPurging = false; // true if our ashmem region is unpinned /** * 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, must be non-negative. + * @param length of the memory file in bytes, must be positive. * @throws IOException if the memory file could not be created. */ public MemoryFile(String name, int length) throws IOException { - mLength = length; - if (length >= 0) { - mFD = native_open(name, length); - } else { - throw new IOException("Invalid length: " + length); - } - - if (length > 0) { - mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE); - } else { - mAddress = 0; + try { + mMapping = mSharedMemory.mapReadWrite(); + mSharedMemory = SharedMemory.create(name, length); + } catch (ErrnoException ex) { + ex.rethrowAsIOException(); } } @@ -87,9 +69,7 @@ public class MemoryFile */ public void close() { deactivate(); - if (!isClosed()) { - native_close(mFD); - } + mSharedMemory.close(); } /** @@ -100,35 +80,30 @@ public class MemoryFile * @hide */ void deactivate() { - if (!isDeactivated()) { - try { - native_munmap(mAddress, mLength); - mAddress = 0; - } catch (IOException ex) { - Log.e(TAG, ex.toString()); + if (mMapping != null) { + SharedMemory.unmap(mMapping); + mMapping = null; + } + } + + private void checkActive() throws IOException { + if (mMapping == null) { + throw new IOException("MemoryFile has been deactivated"); + } + } + + private void beginAccess() throws IOException { + checkActive(); + if (mAllowPurging) { + if (native_pin(mSharedMemory.getFileDescriptor(), true)) { + throw new IOException("MemoryFile has been purged"); } } } - /** - * Checks whether the memory file has been deactivated. - */ - private boolean isDeactivated() { - return mAddress == 0; - } - - /** - * Checks whether the memory file has been closed. - */ - private boolean isClosed() { - return !mFD.valid(); - } - - @Override - protected void finalize() { - if (!isClosed()) { - Log.e(TAG, "MemoryFile.finalize() called while ashmem still open"); - close(); + private void endAccess() throws IOException { + if (mAllowPurging) { + native_pin(mSharedMemory.getFileDescriptor(), false); } } @@ -138,14 +113,19 @@ public class MemoryFile * @return file length. */ public int length() { - return mLength; + return mSharedMemory.getSize(); } /** * Is memory file purging enabled? * * @return true if the file may be purged. + * + * @deprecated Purgable is considered generally fragile and hard to use safely. Applications + * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)} + * to react to memory events and release shared memory regions as appropriate. */ + @Deprecated public boolean isPurgingAllowed() { return mAllowPurging; } @@ -156,11 +136,16 @@ public class MemoryFile * @param allowPurging true if the operating system can purge the contents * of the file in low memory situations * @return previous value of allowPurging + * + * @deprecated Purgable is considered generally fragile and hard to use safely. Applications + * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)} + * to react to memory events and release shared memory regions as appropriate. */ + @Deprecated synchronized public boolean allowPurging(boolean allowPurging) throws IOException { boolean oldValue = mAllowPurging; if (oldValue != allowPurging) { - native_pin(mFD, !allowPurging); + native_pin(mSharedMemory.getFileDescriptor(), !allowPurging); mAllowPurging = allowPurging; } return oldValue; @@ -197,16 +182,14 @@ public class MemoryFile */ 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."); + beginAccess(); + try { + mMapping.position(srcOffset); + mMapping.get(buffer, destOffset, count); + } finally { + endAccess(); } - 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); + return count; } /** @@ -221,16 +204,13 @@ public class MemoryFile */ 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."); + beginAccess(); + try { + mMapping.position(destOffset); + mMapping.put(buffer, srcOffset, count); + } finally { + endAccess(); } - 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); } /** @@ -239,11 +219,9 @@ public class MemoryFile * The returned file descriptor is not duplicated. * * @throws IOException If the memory file has been closed. - * - * @hide */ public FileDescriptor getFileDescriptor() throws IOException { - return mFD; + return mSharedMemory.getFileDescriptor(); } /** @@ -266,10 +244,10 @@ public class MemoryFile @Override public int available() throws IOException { - if (mOffset >= mLength) { + if (mOffset >= mSharedMemory.getSize()) { return 0; } - return mLength - mOffset; + return mSharedMemory.getSize() - mOffset; } @Override @@ -319,8 +297,8 @@ public class MemoryFile @Override public long skip(long n) throws IOException { - if (mOffset + n > mLength) { - n = mLength - mOffset; + if (mOffset + n > mSharedMemory.getSize()) { + n = mSharedMemory.getSize() - mOffset; } mOffset += n; return n; diff --git a/core/java/android/os/SharedMemory.aidl b/core/java/android/os/SharedMemory.aidl new file mode 100644 index 0000000000000..b7c695e5f15c5 --- /dev/null +++ b/core/java/android/os/SharedMemory.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android/os/SharedMemory.aidl +** +** Copyright 2017, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.os; + +parcelable SharedMemory; \ No newline at end of file diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java new file mode 100644 index 0000000000000..712bbaa101aae --- /dev/null +++ b/core/java/android/os/SharedMemory.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import dalvik.system.VMRuntime; + +import java.io.Closeable; +import java.io.FileDescriptor; +import java.nio.ByteBuffer; +import java.nio.DirectByteBuffer; + +import sun.misc.Cleaner; + +/** + * SharedMemory enables the creation, mapping, and protection control over anonymous shared memory. + */ +public final class SharedMemory implements Parcelable, Closeable { + + private final FileDescriptor mFileDescriptor; + private final int mSize; + private final MemoryRegistration mMemoryRegistration; + private Cleaner mCleaner; + + private SharedMemory(FileDescriptor fd) { + // This constructor is only used internally so it should be impossible to hit any of the + // exceptions unless something goes horribly wrong. + if (fd == null) { + throw new IllegalArgumentException( + "Unable to create SharedMemory from a null FileDescriptor"); + } + if (!fd.valid()) { + throw new IllegalArgumentException( + "Unable to create SharedMemory from closed FileDescriptor"); + } + mFileDescriptor = fd; + mSize = nGetSize(mFileDescriptor); + if (mSize <= 0) { + throw new IllegalArgumentException("FileDescriptor is not a valid ashmem fd"); + } + + mMemoryRegistration = new MemoryRegistration(mSize); + mCleaner = Cleaner.create(this, new Closer(mFileDescriptor, mMemoryRegistration)); + } + + /** + * Creates an anonymous SharedMemory instance with the provided debug name and size. The name + * is only used for debugging purposes and can help identify what the shared memory is used + * for when inspecting memory maps for the processes that have mapped this SharedMemory + * instance. + * + * @param name The debug name to use for this SharedMemory instance. This can be null, however + * a debug name is recommended to help identify memory usage when using tools + * such as lsof or examining /proc/[pid]/maps + * @param size The size of the shared memory to create. Must be greater than 0. + * @return A SharedMemory instance of the requested size + * @throws ErrnoException if the requested allocation fails. + */ + public static @NonNull SharedMemory create(@Nullable String name, int size) + throws ErrnoException { + if (size <= 0) { + throw new IllegalArgumentException("Size must be greater than zero"); + } + return new SharedMemory(nCreate(name, size)); + } + + private void checkOpen() { + if (!mFileDescriptor.valid()) { + throw new IllegalStateException("SharedMemory is closed"); + } + } + + private static final int PROT_MASK = OsConstants.PROT_READ | OsConstants.PROT_WRITE + | OsConstants.PROT_EXEC | OsConstants.PROT_NONE; + + private static void validateProt(int prot) { + if ((prot & ~PROT_MASK) != 0) { + throw new IllegalArgumentException("Invalid prot value"); + } + } + + /** + * Sets the protection on the shared memory to the combination specified in prot, which + * is either a bitwise-or'd combination of {@link android.system.OsConstants#PROT_READ}, + * {@link android.system.OsConstants#PROT_WRITE}, {@link android.system.OsConstants#PROT_EXEC} + * from {@link android.system.OsConstants}, or {@link android.system.OsConstants#PROT_NONE}, + * to remove all further access. + * + * Note that protection can only ever be removed, not added. By default shared memory + * is created with protection set to PROT_READ | PROT_WRITE | PROT_EXEC. The protection + * passed here also only applies to any mappings created after calling this method. Existing + * mmaps of the shared memory retain whatever protection they had when they were created. + * + * A common usage of this is to share a read-only copy of the data with something else. To do + * that first create the read/write mapping with PROT_READ | PROT_WRITE, + * then call setProtect(PROT_READ) to remove write capability, then send the SharedMemory + * to another process. That process will only be able to mmap with PROT_READ. + * + * @param prot Any bitwise-or'ed combination of + * {@link android.system.OsConstants#PROT_READ}, + * {@link android.system.OsConstants#PROT_WRITE}, and + * {@link android.system.OsConstants#PROT_EXEC}; or + * {@link android.system.OsConstants#PROT_NONE} + * @return Whether or not the requested protection was applied. Returns true on success, + * false if the requested protection was broader than the existing protection. + */ + public boolean setProtect(int prot) { + checkOpen(); + validateProt(prot); + int errno = nSetProt(mFileDescriptor, prot); + return errno == 0; + } + + /** + * Returns the backing {@link FileDescriptor} for this SharedMemory object. The SharedMemory + * instance retains ownership of the FileDescriptor. + * + * This FileDescriptor is interoperable with the ASharedMemory NDK APIs. + * + * @return Returns the FileDescriptor associated with this object. + */ + public @NonNull FileDescriptor getFileDescriptor() { + return mFileDescriptor; + } + + /** + * Returns the backing native fd int for this SharedMemory object. The SharedMemory + * instance retains ownership of the fd. + * + * This fd is interoperable with the ASharedMemory NDK APIs. + * + * @return Returns the native fd associated with this object, or -1 if it is already closed. + */ + public int getFd() { + return mFileDescriptor.getInt$(); + } + + /** + * @return The size of the SharedMemory region. + */ + public int getSize() { + checkOpen(); + return mSize; + } + + /** + * Creates a read/write mapping of the entire shared memory region. This requires the the + * protection level of the shared memory is at least PROT_READ|PROT_WRITE or the map will fail. + * + * Use {@link #map(int, int, int)} to have more control over the mapping if desired. + * This is equivalent to map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, getSize()) + * + * @return A ByteBuffer mapping + * @throws ErrnoException if the mmap call failed. + */ + public @NonNull ByteBuffer mapReadWrite() throws ErrnoException { + return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize); + } + + /** + * Creates a read-only mapping of the entire shared memory region. This requires the the + * protection level of the shared memory is at least PROT_READ or the map will fail. + * + * Use {@link #map(int, int, int)} to have more control over the mapping if desired. + * This is equivalent to map(OsConstants.PROT_READ, 0, getSize()) + * + * @return A ByteBuffer mapping + * @throws ErrnoException if the mmap call failed. + */ + public @NonNull ByteBuffer mapReadOnly() throws ErrnoException { + return map(OsConstants.PROT_READ, 0, mSize); + } + + /** + * Creates an mmap of the SharedMemory with the specified prot, offset, and length. + * + * @param prot A bitwise-or'd combination of PROT_READ, PROT_WRITE, PROT_EXEC, or PROT_NONE. + * @param offset The offset into the shared memory to begin mapping + * @param length The length of the region to map + * @return A ByteBuffer mapping. + * @throws ErrnoException if the mmap call failed. + */ + public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException { + checkOpen(); + validateProt(prot); + if (offset < 0) { + throw new IllegalArgumentException("Offset must be > 0"); + } + if (length <= 0) { + throw new IllegalArgumentException("Length must be > 0"); + } + if (offset + length > mSize) { + throw new IllegalArgumentException("offset + length must not exceed getSize()"); + } + long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset); + boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0; + Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire()); + return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly); + } + + /** + * Unmaps a buffer previously returned by {@link #map(int, int, int)} + * @param buffer The buffer to unmap + */ + public static void unmap(@NonNull ByteBuffer buffer) { + if (buffer instanceof DirectByteBuffer) { + Cleaner cleaner = ((DirectByteBuffer) buffer).cleaner(); + if (cleaner != null) { + cleaner.clean(); + } + } else { + throw new IllegalArgumentException( + "ByteBuffer wasn't created by #map(int, int, int); can't unmap"); + } + } + + /** + * Close the backing {@link FileDescriptor} of this SharedMemory instance. Note that all + * open mappings of the shared memory will remain valid and may continue to be used. The + * shared memory will not be freed until all file descriptor handles are closed and all + * memory mappings are unmapped. + */ + @Override + public void close() { + if (mCleaner != null) { + mCleaner.clean(); + mCleaner = null; + } + } + + @Override + public int describeContents() { + return CONTENTS_FILE_DESCRIPTOR; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + checkOpen(); + dest.writeFileDescriptor(mFileDescriptor); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public SharedMemory createFromParcel(Parcel source) { + FileDescriptor descriptor = source.readRawFileDescriptor(); + return new SharedMemory(descriptor); + } + + @Override + public SharedMemory[] newArray(int size) { + return new SharedMemory[size]; + } + }; + + /** + * Cleaner that closes the FD + */ + private static final class Closer implements Runnable { + private FileDescriptor mFd; + private MemoryRegistration mMemoryReference; + + private Closer(FileDescriptor fd, MemoryRegistration memoryReference) { + mFd = fd; + mMemoryReference = memoryReference; + } + + @Override + public void run() { + try { + Os.close(mFd); + } catch (ErrnoException e) { /* swallow error */ } + mMemoryReference.release(); + mMemoryReference = null; + } + } + + /** + * Cleaner that munmap regions + */ + private static final class Unmapper implements Runnable { + private long mAddress; + private int mSize; + private MemoryRegistration mMemoryReference; + + private Unmapper(long address, int size, MemoryRegistration memoryReference) { + mAddress = address; + mSize = size; + mMemoryReference = memoryReference; + } + + @Override + public void run() { + try { + Os.munmap(mAddress, mSize); + } catch (ErrnoException e) { /* swallow exception */ } + mMemoryReference.release(); + mMemoryReference = null; + } + } + + /** + * Helper class that ensures that the native allocation pressure against the VM heap stays + * active until the FD is closed as well as all mappings from that FD are closed. + */ + private static final class MemoryRegistration { + private int mSize; + private int mReferenceCount; + + private MemoryRegistration(int size) { + mSize = size; + mReferenceCount = 1; + VMRuntime.getRuntime().registerNativeAllocation(mSize); + } + + public synchronized MemoryRegistration acquire() { + mReferenceCount++; + return this; + } + + public synchronized void release() { + mReferenceCount--; + if (mReferenceCount == 0) { + VMRuntime.getRuntime().registerNativeFree(mSize); + } + } + } + + private static native FileDescriptor nCreate(String name, int size) throws ErrnoException; + private static native int nGetSize(FileDescriptor fd); + private static native int nSetProt(FileDescriptor fd, int prot); +} diff --git a/core/jni/Android.bp b/core/jni/Android.bp index c5279e10d93f7..e8030eeb97a61 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -90,6 +90,7 @@ cc_library_shared { "android_os_Parcel.cpp", "android_os_SELinux.cpp", "android_os_seccomp.cpp", + "android_os_SharedMemory.cpp", "android_os_SystemClock.cpp", "android_os_SystemProperties.cpp", "android_os_Trace.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 659f47debaf12..a8ba13c4ec11f 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -168,6 +168,7 @@ extern int register_android_os_Trace(JNIEnv* env); extern int register_android_os_FileObserver(JNIEnv *env); extern int register_android_os_UEventObserver(JNIEnv* env); extern int register_android_os_MemoryFile(JNIEnv* env); +extern int register_android_os_SharedMemory(JNIEnv* env); extern int register_android_net_LocalSocketImpl(JNIEnv* env); extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_net_TrafficStats(JNIEnv* env); @@ -1393,6 +1394,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_net_NetworkUtils), REG_JNI(register_android_net_TrafficStats), REG_JNI(register_android_os_MemoryFile), + REG_JNI(register_android_os_SharedMemory), REG_JNI(register_com_android_internal_os_PathClassLoaderFactory), REG_JNI(register_com_android_internal_os_Zygote), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp index c198a733103dd..fdc9f9dc74642 100644 --- a/core/jni/android_os_MemoryFile.cpp +++ b/core/jni/android_os_MemoryFile.cpp @@ -26,95 +26,14 @@ namespace android { -static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length) -{ - const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL); - - 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); -} - -static jlong android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, - jint length, jint prot) -{ - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - void* result = mmap(NULL, length, prot, MAP_SHARED, fd, 0); - if (result == MAP_FAILED) { - jniThrowException(env, "java/io/IOException", "mmap failed"); - } - return reinterpret_cast(result); -} - -static void android_os_MemoryFile_munmap(JNIEnv* env, jobject clazz, jlong addr, jint length) -{ - int result = munmap(reinterpret_cast(addr), length); - if (result < 0) - jniThrowException(env, "java/io/IOException", "munmap failed"); -} - -static void android_os_MemoryFile_close(JNIEnv* env, jobject clazz, jobject fileDescriptor) -{ - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - if (fd >= 0) { - jniSetFileDescriptorOfFD(env, fileDescriptor, -1); - close(fd); - } -} - -static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz, - jobject fileDescriptor, jlong 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; -} - -static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz, - jobject fileDescriptor, jlong 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; -} - -static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor, jboolean pin) -{ +static jboolean android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor, + jboolean pin) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); int result = (pin ? ashmem_pin_region(fd, 0, 0) : ashmem_unpin_region(fd, 0, 0)); if (result < 0) { jniThrowException(env, "java/io/IOException", NULL); } + return result == ASHMEM_WAS_PURGED; } static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz, @@ -138,19 +57,12 @@ static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz, } static const JNINativeMethod methods[] = { - {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, - {"native_mmap", "(Ljava/io/FileDescriptor;II)J", (void*)android_os_MemoryFile_mmap}, - {"native_munmap", "(JI)V", (void*)android_os_MemoryFile_munmap}, - {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, - {"native_read", "(Ljava/io/FileDescriptor;J[BIIIZ)I", (void*)android_os_MemoryFile_read}, - {"native_write", "(Ljava/io/FileDescriptor;J[BIIIZ)V", (void*)android_os_MemoryFile_write}, - {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, + {"native_pin", "(Ljava/io/FileDescriptor;Z)Z", (void*)android_os_MemoryFile_pin}, {"native_get_size", "(Ljava/io/FileDescriptor;)I", (void*)android_os_MemoryFile_get_size} }; -int register_android_os_MemoryFile(JNIEnv* env) -{ +int register_android_os_MemoryFile(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/os/MemoryFile", methods, NELEM(methods)); } diff --git a/core/jni/android_os_SharedMemory.cpp b/core/jni/android_os_SharedMemory.cpp new file mode 100644 index 0000000000000..24d08112275eb --- /dev/null +++ b/core/jni/android_os_SharedMemory.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "SharedMemory" + +#include "core_jni_helpers.h" + +#include +#include +#include "JNIHelp.h" +#include "JniConstants.h" +#include "ScopedLocalRef.h" + +#include +#include +#include +#include + +namespace { + +static void throwErrnoException(JNIEnv* env, const char* functionName, int error) { + static jmethodID ctor = env->GetMethodID(JniConstants::errnoExceptionClass, + "", "(Ljava/lang/String;I)V"); + + ScopedLocalRef detailMessage(env, env->NewStringUTF(functionName)); + if (detailMessage.get() == NULL) { + // Not really much we can do here. We're probably dead in the water, + // but let's try to stumble on... + env->ExceptionClear(); + } + + jobject exception = env->NewObject(JniConstants::errnoExceptionClass, ctor, + detailMessage.get(), error); + env->Throw(reinterpret_cast(exception)); +} + +static jobject SharedMemory_create(JNIEnv* env, jobject, jstring jname, jint size) { + + // Name is optional so we can't use ScopedUtfChars for this as it throws NPE on null + const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr; + + int fd = ashmem_create_region(name, size); + + // Capture the error, if there is one, before calling ReleaseStringUTFChars + int err = fd < 0 ? errno : 0; + + if (name) { + env->ReleaseStringUTFChars(jname, name); + } + + if (fd < 0) { + throwErrnoException(env, "SharedMemory_create", err); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jint SharedMemory_getSize(JNIEnv* env, jobject, jobject fileDescriptor) { + int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + if (!ashmem_valid(fd)) { + return -1; + } + size_t size = ashmem_get_size_region(fd); + return static_cast(std::min(size, static_cast(std::numeric_limits::max()))); +} + +static jint SharedMemory_setProt(JNIEnv* env, jobject, jobject fileDescriptor, jint prot) { + int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + int err = 0; + if (ashmem_set_prot_region(fd, prot)) { + err = errno; + } + return err; +} + +static const JNINativeMethod methods[] = { + {"nCreate", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)SharedMemory_create}, + {"nGetSize", "(Ljava/io/FileDescriptor;)I", (void*)SharedMemory_getSize}, + {"nSetProt", "(Ljava/io/FileDescriptor;I)I", (void*)SharedMemory_setProt}, +}; + +} // anonymous namespace + +namespace android { + +int register_android_os_SharedMemory(JNIEnv* env) +{ + return RegisterMethodsOrDie(env, "android/os/SharedMemory", methods, NELEM(methods)); +} + +} // namespace android