• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.util.Log;
20 
21 import java.io.FileDescriptor;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 
26 
27 /**
28  * MemoryFile is a wrapper for the Linux ashmem driver.
29  * MemoryFiles are backed by shared memory, which can be optionally
30  * set to be purgeable.
31  * Purgeable files may have their contents reclaimed by the kernel
32  * in low memory conditions (only if allowPurging is set to true).
33  * After a file is purged, attempts to read or write the file will
34  * cause an IOException to be thrown.
35  */
36 public class MemoryFile
37 {
38     private static String TAG = "MemoryFile";
39 
40     // mmap(2) protection flags from <sys/mman.h>
41     private static final int PROT_READ = 0x1;
42     private static final int PROT_WRITE = 0x2;
43 
native_open(String name, int length)44     private static native FileDescriptor native_open(String name, int length) throws IOException;
45     // returns memory address for ashmem region
native_mmap(FileDescriptor fd, int length, int mode)46     private static native int native_mmap(FileDescriptor fd, int length, int mode)
47             throws IOException;
native_munmap(int addr, int length)48     private static native void native_munmap(int addr, int length) throws IOException;
native_close(FileDescriptor fd)49     private static native void native_close(FileDescriptor fd);
native_read(FileDescriptor fd, int address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned)50     private static native int native_read(FileDescriptor fd, int address, byte[] buffer,
51             int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
native_write(FileDescriptor fd, int address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned)52     private static native void native_write(FileDescriptor fd, int address, byte[] buffer,
53             int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
native_pin(FileDescriptor fd, boolean pin)54     private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
native_get_mapped_size(FileDescriptor fd)55     private static native int native_get_mapped_size(FileDescriptor fd) throws IOException;
56 
57     private FileDescriptor mFD;        // ashmem file descriptor
58     private int mAddress;   // address of ashmem memory
59     private int mLength;    // total length of our ashmem region
60     private boolean mAllowPurging = false;  // true if our ashmem region is unpinned
61     private final boolean mOwnsRegion;  // false if this is a ref to an existing ashmem region
62 
63     /**
64      * Allocates a new ashmem region. The region is initially not purgable.
65      *
66      * @param name optional name for the file (can be null).
67      * @param length of the memory file in bytes.
68      * @throws IOException if the memory file could not be created.
69      */
MemoryFile(String name, int length)70     public MemoryFile(String name, int length) throws IOException {
71         mLength = length;
72         mFD = native_open(name, length);
73         mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
74         mOwnsRegion = true;
75     }
76 
77     /**
78      * Creates a reference to an existing memory file. Changes to the original file
79      * will be available through this reference.
80      * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail.
81      *
82      * @param fd File descriptor for an existing memory file, as returned by
83      *        {@link #getFileDescriptor()}. This file descriptor will be closed
84      *        by {@link #close()}.
85      * @param length Length of the memory file in bytes.
86      * @param mode File mode. Currently only "r" for read-only access is supported.
87      * @throws NullPointerException if <code>fd</code> is null.
88      * @throws IOException If <code>fd</code> does not refer to an existing memory file,
89      *         or if the file mode of the existing memory file is more restrictive
90      *         than <code>mode</code>.
91      *
92      * @hide
93      */
MemoryFile(FileDescriptor fd, int length, String mode)94     public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException {
95         if (fd == null) {
96             throw new NullPointerException("File descriptor is null.");
97         }
98         if (!isMemoryFile(fd)) {
99             throw new IllegalArgumentException("Not a memory file.");
100         }
101         mLength = length;
102         mFD = fd;
103         mAddress = native_mmap(mFD, length, modeToProt(mode));
104         mOwnsRegion = false;
105     }
106 
107     /**
108      * Closes the memory file. If there are no other open references to the memory
109      * file, it will be deleted.
110      */
close()111     public void close() {
112         deactivate();
113         if (!isClosed()) {
114             native_close(mFD);
115         }
116     }
117 
118     /**
119      * Unmaps the memory file from the process's memory space, but does not close it.
120      * After this method has been called, read and write operations through this object
121      * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.
122      *
123      * @hide
124      */
deactivate()125     public void deactivate() {
126         if (!isDeactivated()) {
127             try {
128                 native_munmap(mAddress, mLength);
129                 mAddress = 0;
130             } catch (IOException ex) {
131                 Log.e(TAG, ex.toString());
132             }
133         }
134     }
135 
136     /**
137      * Checks whether the memory file has been deactivated.
138      */
isDeactivated()139     private boolean isDeactivated() {
140         return mAddress == 0;
141     }
142 
143     /**
144      * Checks whether the memory file has been closed.
145      */
isClosed()146     private boolean isClosed() {
147         return !mFD.valid();
148     }
149 
150     @Override
finalize()151     protected void finalize() {
152         if (!isClosed()) {
153             Log.e(TAG, "MemoryFile.finalize() called while ashmem still open");
154             close();
155         }
156     }
157 
158     /**
159      * Returns the length of the memory file.
160      *
161      * @return file length.
162      */
length()163     public int length() {
164         return mLength;
165     }
166 
167     /**
168      * Is memory file purging enabled?
169      *
170      * @return true if the file may be purged.
171      */
isPurgingAllowed()172     public boolean isPurgingAllowed() {
173         return mAllowPurging;
174     }
175 
176     /**
177      * Enables or disables purging of the memory file.
178      *
179      * @param allowPurging true if the operating system can purge the contents
180      * of the file in low memory situations
181      * @return previous value of allowPurging
182      */
allowPurging(boolean allowPurging)183     synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
184         if (!mOwnsRegion) {
185             throw new IOException("Only the owner can make ashmem regions purgable.");
186         }
187         boolean oldValue = mAllowPurging;
188         if (oldValue != allowPurging) {
189             native_pin(mFD, !allowPurging);
190             mAllowPurging = allowPurging;
191         }
192         return oldValue;
193     }
194 
195     /**
196      * Creates a new InputStream for reading from the memory file.
197      *
198      @return InputStream
199      */
getInputStream()200     public InputStream getInputStream() {
201         return new MemoryInputStream();
202     }
203 
204     /**
205      * Creates a new OutputStream for writing to the memory file.
206      *
207      @return OutputStream
208      */
getOutputStream()209      public OutputStream getOutputStream() {
210         return new MemoryOutputStream();
211     }
212 
213     /**
214      * Reads bytes from the memory file.
215      * Will throw an IOException if the file has been purged.
216      *
217      * @param buffer byte array to read bytes into.
218      * @param srcOffset offset into the memory file to read from.
219      * @param destOffset offset into the byte array buffer to read into.
220      * @param count number of bytes to read.
221      * @return number of bytes read.
222      * @throws IOException if the memory file has been purged or deactivated.
223      */
readBytes(byte[] buffer, int srcOffset, int destOffset, int count)224     public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
225             throws IOException {
226         if (isDeactivated()) {
227             throw new IOException("Can't read from deactivated memory file.");
228         }
229         if (destOffset < 0 || destOffset > buffer.length || count < 0
230                 || count > buffer.length - destOffset
231                 || srcOffset < 0 || srcOffset > mLength
232                 || count > mLength - srcOffset) {
233             throw new IndexOutOfBoundsException();
234         }
235         return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
236     }
237 
238     /**
239      * Write bytes to the memory file.
240      * Will throw an IOException if the file has been purged.
241      *
242      * @param buffer byte array to write bytes from.
243      * @param srcOffset offset into the byte array buffer to write from.
244      * @param destOffset offset  into the memory file to write to.
245      * @param count number of bytes to write.
246      * @throws IOException if the memory file has been purged or deactivated.
247      */
writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)248     public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
249             throws IOException {
250         if (isDeactivated()) {
251             throw new IOException("Can't write to deactivated memory file.");
252         }
253         if (srcOffset < 0 || srcOffset > buffer.length || count < 0
254                 || count > buffer.length - srcOffset
255                 || destOffset < 0 || destOffset > mLength
256                 || count > mLength - destOffset) {
257             throw new IndexOutOfBoundsException();
258         }
259         native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
260     }
261 
262     /**
263      * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()}
264      * for caveats. This must be here to allow classes outside <code>android.os</code< to
265      * make ParcelFileDescriptors from MemoryFiles, as
266      * {@link ParcelFileDescriptor#ParcelFileDescriptor(FileDescriptor)} is package private.
267      *
268      *
269      * @return The file descriptor owned by this memory file object.
270      *         The file descriptor is not duplicated.
271      * @throws IOException If the memory file has been closed.
272      *
273      * @hide
274      */
getParcelFileDescriptor()275     public ParcelFileDescriptor getParcelFileDescriptor() throws IOException {
276         return new ParcelFileDescriptor(getFileDescriptor());
277     }
278 
279     /**
280      * Gets a FileDescriptor for the memory file. Note that this file descriptor
281      * is only safe to pass to {@link #MemoryFile(FileDescriptor,int)}). It
282      * should not be used with file descriptor operations that expect a file descriptor
283      * for a normal file.
284      *
285      * The returned file descriptor is not duplicated.
286      *
287      * @throws IOException If the memory file has been closed.
288      *
289      * @hide
290      */
getFileDescriptor()291     public FileDescriptor getFileDescriptor() throws IOException {
292         return mFD;
293     }
294 
295     /**
296      * Checks whether the given file descriptor refers to a memory file.
297      *
298      * @throws IOException If <code>fd</code> is not a valid file descriptor.
299      *
300      * @hide
301      */
isMemoryFile(FileDescriptor fd)302     public static boolean isMemoryFile(FileDescriptor fd) throws IOException {
303         return (native_get_mapped_size(fd) >= 0);
304     }
305 
306     /**
307      * Returns the size of the memory file, rounded up to a page boundary, that
308      * the file descriptor refers to, or -1 if the file descriptor does not
309      * refer to a memory file.
310      *
311      * @throws IOException If <code>fd</code> is not a valid file descriptor.
312      *
313      * @hide
314      */
getMappedSize(FileDescriptor fd)315     public static int getMappedSize(FileDescriptor fd) throws IOException {
316         return native_get_mapped_size(fd);
317     }
318 
319     /**
320      * Converts a file mode string to a <code>prot</code> value as expected by
321      * native_mmap().
322      *
323      * @throws IllegalArgumentException if the file mode is invalid.
324      */
modeToProt(String mode)325     private static int modeToProt(String mode) {
326         if ("r".equals(mode)) {
327             return PROT_READ;
328         } else {
329             throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'");
330         }
331     }
332 
333     private class MemoryInputStream extends InputStream {
334 
335         private int mMark = 0;
336         private int mOffset = 0;
337         private byte[] mSingleByte;
338 
339         @Override
available()340         public int available() throws IOException {
341             if (mOffset >= mLength) {
342                 return 0;
343             }
344             return mLength - mOffset;
345         }
346 
347         @Override
markSupported()348         public boolean markSupported() {
349             return true;
350         }
351 
352         @Override
mark(int readlimit)353         public void mark(int readlimit) {
354             mMark = mOffset;
355         }
356 
357         @Override
reset()358         public void reset() throws IOException {
359             mOffset = mMark;
360         }
361 
362         @Override
read()363         public int read() throws IOException {
364             if (mSingleByte == null) {
365                 mSingleByte = new byte[1];
366             }
367             int result = read(mSingleByte, 0, 1);
368             if (result != 1) {
369                 return -1;
370             }
371             return mSingleByte[0];
372         }
373 
374         @Override
read(byte buffer[], int offset, int count)375         public int read(byte buffer[], int offset, int count) throws IOException {
376             if (offset < 0 || count < 0 || offset + count > buffer.length) {
377                 // readBytes() also does this check, but we need to do it before
378                 // changing count.
379                 throw new IndexOutOfBoundsException();
380             }
381             count = Math.min(count, available());
382             if (count < 1) {
383                 return -1;
384             }
385             int result = readBytes(buffer, mOffset, offset, count);
386             if (result > 0) {
387                 mOffset += result;
388             }
389             return result;
390         }
391 
392         @Override
skip(long n)393         public long skip(long n) throws IOException {
394             if (mOffset + n > mLength) {
395                 n = mLength - mOffset;
396             }
397             mOffset += n;
398             return n;
399         }
400     }
401 
402     private class MemoryOutputStream extends OutputStream {
403 
404         private int mOffset = 0;
405         private byte[] mSingleByte;
406 
407         @Override
write(byte buffer[], int offset, int count)408         public void write(byte buffer[], int offset, int count) throws IOException {
409             writeBytes(buffer, offset, mOffset, count);
410         }
411 
412         @Override
write(int oneByte)413         public void write(int oneByte) throws IOException {
414             if (mSingleByte == null) {
415                 mSingleByte = new byte[1];
416             }
417             mSingleByte[0] = (byte)oneByte;
418             write(mSingleByte, 0, 1);
419         }
420     }
421 }
422