404 lines
16 KiB
Java
404 lines
16 KiB
Java
/*
|
|
* 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.compat.annotation.UnsupportedAppUsage;
|
|
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.io.IOException;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.DirectByteBuffer;
|
|
import java.nio.NioUtils;
|
|
|
|
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(mFileDescriptor,
|
|
new Closer(mFileDescriptor.getInt$(), 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");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an instance from existing shared memory passed as {@link ParcelFileDescriptor}.
|
|
*
|
|
* <p> The {@code fd} should be a shared memory created from
|
|
{@code SharedMemory or ASharedMemory}. This can be useful when shared memory is passed as
|
|
file descriptor through JNI or binder service implemented in cpp.
|
|
* <p> Note that newly created {@code SharedMemory} takes ownership of passed {@code fd} and
|
|
* the original {@code fd} becomes detached (Check {@link ParcelFileDescriptor#detachFd()}).
|
|
* If the caller wants to use the file descriptor after the call, the caller should duplicate
|
|
* the file descriptor (Check {@link ParcelFileDescriptor#dup()}) and pass the duped version
|
|
* instead.
|
|
*
|
|
* @param fd File descriptor of shared memory passed as {@link ParcelFileDescriptor}.
|
|
*/
|
|
public static @NonNull SharedMemory fromFileDescriptor(@NonNull ParcelFileDescriptor fd) {
|
|
FileDescriptor f = new FileDescriptor();
|
|
f.setInt$(fd.detachFd());
|
|
return new SharedMemory(f);
|
|
}
|
|
|
|
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.
|
|
*
|
|
* @hide Exists only for MemoryFile interop
|
|
*/
|
|
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.
|
|
*
|
|
* @hide Exposed for native ASharedMemory_dupFromJava()
|
|
*/
|
|
@UnsupportedAppUsage(trackingBug = 171971817)
|
|
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. This will
|
|
* always produce a new ByteBuffer window to the backing shared memory region. Every call
|
|
* to map() may be paired with a call to {@link #unmap(ByteBuffer)} when the ByteBuffer
|
|
* returned by map() is no longer needed.
|
|
*
|
|
* @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. Must be >= 0 and less than
|
|
* getSize().
|
|
* @param length The length of the region to map. Must be > 0 and offset + length must not
|
|
* exceed getSize().
|
|
* @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)}. This will immediately
|
|
* release the backing memory of the ByteBuffer, invalidating all references to it. Only
|
|
* call this method if there are no duplicates of the ByteBuffer in use and don't
|
|
* access the ByteBuffer after calling this method.
|
|
*
|
|
* @param buffer The buffer to unmap
|
|
*/
|
|
public static void unmap(@NonNull ByteBuffer buffer) {
|
|
if (buffer instanceof DirectByteBuffer) {
|
|
NioUtils.freeDirectBuffer(buffer);
|
|
} 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() {
|
|
mFileDescriptor.setInt$(-1);
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Returns a dup'd ParcelFileDescriptor from the SharedMemory FileDescriptor.
|
|
* This obeys standard POSIX semantics, where the
|
|
* new file descriptor shared state such as file position with the
|
|
* original file descriptor.
|
|
* TODO: propose this method as a public or system API for next release to achieve parity with
|
|
* NDK ASharedMemory_dupFromJava.
|
|
*
|
|
* @hide
|
|
*/
|
|
public ParcelFileDescriptor getFdDup() throws IOException {
|
|
return ParcelFileDescriptor.dup(mFileDescriptor);
|
|
}
|
|
|
|
public static final @android.annotation.NonNull Parcelable.Creator<SharedMemory> CREATOR =
|
|
new Parcelable.Creator<SharedMemory>() {
|
|
@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 int mFd;
|
|
private MemoryRegistration mMemoryReference;
|
|
|
|
private Closer(int fd, MemoryRegistration memoryReference) {
|
|
mFd = fd;
|
|
mMemoryReference = memoryReference;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
FileDescriptor fd = new FileDescriptor();
|
|
fd.setInt$(mFd);
|
|
Os.close(fd);
|
|
} 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);
|
|
}
|