1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.system.ErrnoException;
22 import android.system.Os;
23 import android.system.OsConstants;
24 
25 import dalvik.system.VMRuntime;
26 
27 import java.io.Closeable;
28 import java.io.FileDescriptor;
29 import java.nio.ByteBuffer;
30 import java.nio.DirectByteBuffer;
31 import java.nio.NioUtils;
32 
33 import sun.misc.Cleaner;
34 
35 /**
36  * SharedMemory enables the creation, mapping, and protection control over anonymous shared memory.
37  */
38 public final class SharedMemory implements Parcelable, Closeable {
39 
40     private final FileDescriptor mFileDescriptor;
41     private final int mSize;
42     private final MemoryRegistration mMemoryRegistration;
43     private Cleaner mCleaner;
44 
SharedMemory(FileDescriptor fd)45     private SharedMemory(FileDescriptor fd) {
46         // This constructor is only used internally so it should be impossible to hit any of the
47         // exceptions unless something goes horribly wrong.
48         if (fd == null) {
49             throw new IllegalArgumentException(
50                     "Unable to create SharedMemory from a null FileDescriptor");
51         }
52         if (!fd.valid()) {
53             throw new IllegalArgumentException(
54                     "Unable to create SharedMemory from closed FileDescriptor");
55         }
56         mFileDescriptor = fd;
57         mSize = nGetSize(mFileDescriptor);
58         if (mSize <= 0) {
59             throw new IllegalArgumentException("FileDescriptor is not a valid ashmem fd");
60         }
61 
62         mMemoryRegistration = new MemoryRegistration(mSize);
63         mCleaner = Cleaner.create(mFileDescriptor,
64                 new Closer(mFileDescriptor, mMemoryRegistration));
65     }
66 
67     /**
68      * Creates an anonymous SharedMemory instance with the provided debug name and size. The name
69      * is only used for debugging purposes and can help identify what the shared memory is used
70      * for when inspecting memory maps for the processes that have mapped this SharedMemory
71      * instance.
72      *
73      * @param name The debug name to use for this SharedMemory instance. This can be null, however
74      *             a debug name is recommended to help identify memory usage when using tools
75      *             such as lsof or examining /proc/[pid]/maps
76      * @param size The size of the shared memory to create. Must be greater than 0.
77      * @return A SharedMemory instance of the requested size
78      * @throws ErrnoException if the requested allocation fails.
79      */
create(@ullable String name, int size)80     public static @NonNull SharedMemory create(@Nullable String name, int size)
81             throws ErrnoException {
82         if (size <= 0) {
83             throw new IllegalArgumentException("Size must be greater than zero");
84         }
85         return new SharedMemory(nCreate(name, size));
86     }
87 
checkOpen()88     private void checkOpen() {
89         if (!mFileDescriptor.valid()) {
90             throw new IllegalStateException("SharedMemory is closed");
91         }
92     }
93 
94     private static final int PROT_MASK = OsConstants.PROT_READ | OsConstants.PROT_WRITE
95             | OsConstants.PROT_EXEC | OsConstants.PROT_NONE;
96 
validateProt(int prot)97     private static void validateProt(int prot) {
98         if ((prot & ~PROT_MASK) != 0) {
99             throw new IllegalArgumentException("Invalid prot value");
100         }
101     }
102 
103     /**
104      * Sets the protection on the shared memory to the combination specified in prot, which
105      * is either a bitwise-or'd combination of {@link android.system.OsConstants#PROT_READ},
106      * {@link android.system.OsConstants#PROT_WRITE}, {@link android.system.OsConstants#PROT_EXEC}
107      * from {@link android.system.OsConstants}, or {@link android.system.OsConstants#PROT_NONE},
108      * to remove all further access.
109      *
110      * Note that protection can only ever be removed, not added. By default shared memory
111      * is created with protection set to PROT_READ | PROT_WRITE | PROT_EXEC. The protection
112      * passed here also only applies to any mappings created after calling this method. Existing
113      * mmaps of the shared memory retain whatever protection they had when they were created.
114      *
115      * A common usage of this is to share a read-only copy of the data with something else. To do
116      * that first create the read/write mapping with PROT_READ | PROT_WRITE,
117      * then call setProtect(PROT_READ) to remove write capability, then send the SharedMemory
118      * to another process. That process will only be able to mmap with PROT_READ.
119      *
120      * @param prot Any bitwise-or'ed combination of
121      *                  {@link android.system.OsConstants#PROT_READ},
122      *                  {@link android.system.OsConstants#PROT_WRITE}, and
123      *                  {@link android.system.OsConstants#PROT_EXEC}; or
124      *                  {@link android.system.OsConstants#PROT_NONE}
125      * @return Whether or not the requested protection was applied. Returns true on success,
126      * false if the requested protection was broader than the existing protection.
127      */
setProtect(int prot)128     public boolean setProtect(int prot) {
129         checkOpen();
130         validateProt(prot);
131         int errno = nSetProt(mFileDescriptor, prot);
132         return errno == 0;
133     }
134 
135     /**
136      * Returns the backing {@link FileDescriptor} for this SharedMemory object. The SharedMemory
137      * instance retains ownership of the FileDescriptor.
138      *
139      * This FileDescriptor is interoperable with the ASharedMemory NDK APIs.
140      *
141      * @return Returns the FileDescriptor associated with this object.
142      *
143      * @hide Exists only for MemoryFile interop
144      */
getFileDescriptor()145     public @NonNull FileDescriptor getFileDescriptor() {
146         return mFileDescriptor;
147     }
148 
149     /**
150      * Returns the backing native fd int for this SharedMemory object. The SharedMemory
151      * instance retains ownership of the fd.
152      *
153      * This fd is interoperable with the ASharedMemory NDK APIs.
154      *
155      * @return Returns the native fd associated with this object, or -1 if it is already closed.
156      *
157      * @hide Exposed for native ASharedMemory_dupFromJava()
158      */
getFd()159     public int getFd() {
160         return mFileDescriptor.getInt$();
161     }
162 
163     /**
164      * @return The size of the SharedMemory region.
165      */
getSize()166     public int getSize() {
167         checkOpen();
168         return mSize;
169     }
170 
171     /**
172      * Creates a read/write mapping of the entire shared memory region. This requires the the
173      * protection level of the shared memory is at least PROT_READ|PROT_WRITE or the map will fail.
174      *
175      * Use {@link #map(int, int, int)} to have more control over the mapping if desired.
176      * This is equivalent to map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, getSize())
177      *
178      * @return A ByteBuffer mapping
179      * @throws ErrnoException if the mmap call failed.
180      */
mapReadWrite()181     public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {
182         return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);
183     }
184 
185     /**
186      * Creates a read-only mapping of the entire shared memory region. This requires the the
187      * protection level of the shared memory is at least PROT_READ or the map will fail.
188      *
189      * Use {@link #map(int, int, int)} to have more control over the mapping if desired.
190      * This is equivalent to map(OsConstants.PROT_READ, 0, getSize())
191      *
192      * @return A ByteBuffer mapping
193      * @throws ErrnoException if the mmap call failed.
194      */
mapReadOnly()195     public @NonNull ByteBuffer mapReadOnly() throws ErrnoException {
196         return map(OsConstants.PROT_READ, 0, mSize);
197     }
198 
199     /**
200      * Creates an mmap of the SharedMemory with the specified prot, offset, and length. This will
201      * always produce a new ByteBuffer window to the backing shared memory region. Every call
202      * to map() may be paired with a call to {@link #unmap(ByteBuffer)} when the ByteBuffer
203      * returned by map() is no longer needed.
204      *
205      * @param prot A bitwise-or'd combination of PROT_READ, PROT_WRITE, PROT_EXEC, or PROT_NONE.
206      * @param offset The offset into the shared memory to begin mapping. Must be >= 0 and less than
207      *         getSize().
208      * @param length The length of the region to map. Must be > 0 and offset + length must not
209      *         exceed getSize().
210      * @return A ByteBuffer mapping.
211      * @throws ErrnoException if the mmap call failed.
212      */
map(int prot, int offset, int length)213     public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
214         checkOpen();
215         validateProt(prot);
216         if (offset < 0) {
217             throw new IllegalArgumentException("Offset must be >= 0");
218         }
219         if (length <= 0) {
220             throw new IllegalArgumentException("Length must be > 0");
221         }
222         if (offset + length > mSize) {
223             throw new IllegalArgumentException("offset + length must not exceed getSize()");
224         }
225         long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);
226         boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;
227         Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());
228         return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
229     }
230 
231     /**
232      * Unmaps a buffer previously returned by {@link #map(int, int, int)}. This will immediately
233      * release the backing memory of the ByteBuffer, invalidating all references to it. Only
234      * call this method if there are no duplicates of the ByteBuffer in use and don't
235      * access the ByteBuffer after calling this method.
236      *
237      * @param buffer The buffer to unmap
238      */
unmap(@onNull ByteBuffer buffer)239     public static void unmap(@NonNull ByteBuffer buffer) {
240         if (buffer instanceof DirectByteBuffer) {
241             NioUtils.freeDirectBuffer(buffer);
242         } else {
243             throw new IllegalArgumentException(
244                     "ByteBuffer wasn't created by #map(int, int, int); can't unmap");
245         }
246     }
247 
248     /**
249      * Close the backing {@link FileDescriptor} of this SharedMemory instance. Note that all
250      * open mappings of the shared memory will remain valid and may continue to be used. The
251      * shared memory will not be freed until all file descriptor handles are closed and all
252      * memory mappings are unmapped.
253      */
254     @Override
close()255     public void close() {
256         if (mCleaner != null) {
257             mCleaner.clean();
258             mCleaner = null;
259         }
260     }
261 
262     @Override
describeContents()263     public int describeContents() {
264         return CONTENTS_FILE_DESCRIPTOR;
265     }
266 
267     @Override
writeToParcel(@onNull Parcel dest, int flags)268     public void writeToParcel(@NonNull Parcel dest, int flags) {
269         checkOpen();
270         dest.writeFileDescriptor(mFileDescriptor);
271     }
272 
273     public static final Parcelable.Creator<SharedMemory> CREATOR =
274             new Parcelable.Creator<SharedMemory>() {
275         @Override
276         public SharedMemory createFromParcel(Parcel source) {
277             FileDescriptor descriptor = source.readRawFileDescriptor();
278             return new SharedMemory(descriptor);
279         }
280 
281         @Override
282         public SharedMemory[] newArray(int size) {
283             return new SharedMemory[size];
284         }
285     };
286 
287     /**
288      * Cleaner that closes the FD
289      */
290     private static final class Closer implements Runnable {
291         private FileDescriptor mFd;
292         private MemoryRegistration mMemoryReference;
293 
Closer(FileDescriptor fd, MemoryRegistration memoryReference)294         private Closer(FileDescriptor fd, MemoryRegistration memoryReference) {
295             mFd = fd;
296             mMemoryReference = memoryReference;
297         }
298 
299         @Override
run()300         public void run() {
301             try {
302                 Os.close(mFd);
303             } catch (ErrnoException e) { /* swallow error */ }
304             mMemoryReference.release();
305             mMemoryReference = null;
306         }
307     }
308 
309     /**
310      * Cleaner that munmap regions
311      */
312     private static final class Unmapper implements Runnable {
313         private long mAddress;
314         private int mSize;
315         private MemoryRegistration mMemoryReference;
316 
Unmapper(long address, int size, MemoryRegistration memoryReference)317         private Unmapper(long address, int size, MemoryRegistration memoryReference) {
318             mAddress = address;
319             mSize = size;
320             mMemoryReference = memoryReference;
321         }
322 
323         @Override
run()324         public void run() {
325             try {
326                 Os.munmap(mAddress, mSize);
327             } catch (ErrnoException e) { /* swallow exception */ }
328             mMemoryReference.release();
329             mMemoryReference = null;
330         }
331     }
332 
333     /**
334      * Helper class that ensures that the native allocation pressure against the VM heap stays
335      * active until the FD is closed as well as all mappings from that FD are closed.
336      */
337     private static final class MemoryRegistration {
338         private int mSize;
339         private int mReferenceCount;
340 
MemoryRegistration(int size)341         private MemoryRegistration(int size) {
342             mSize = size;
343             mReferenceCount = 1;
344             VMRuntime.getRuntime().registerNativeAllocation(mSize);
345         }
346 
acquire()347         public synchronized MemoryRegistration acquire() {
348             mReferenceCount++;
349             return this;
350         }
351 
release()352         public synchronized void release() {
353             mReferenceCount--;
354             if (mReferenceCount == 0) {
355                 VMRuntime.getRuntime().registerNativeFree(mSize);
356             }
357         }
358     }
359 
nCreate(String name, int size)360     private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;
nGetSize(FileDescriptor fd)361     private static native int nGetSize(FileDescriptor fd);
nSetProt(FileDescriptor fd, int prot)362     private static native int nSetProt(FileDescriptor fd, int prot);
363 }
364