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 long native_mmap(FileDescriptor fd, int length, int mode) 47 throws IOException; native_munmap(long addr, int length)48 private static native void native_munmap(long addr, int length) throws IOException; native_close(FileDescriptor fd)49 private static native void native_close(FileDescriptor fd); native_read(FileDescriptor fd, long address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned)50 private static native int native_read(FileDescriptor fd, long address, byte[] buffer, 51 int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; native_write(FileDescriptor fd, long address, byte[] buffer, int srcOffset, int destOffset, int count, boolean isUnpinned)52 private static native void native_write(FileDescriptor fd, long 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_size(FileDescriptor fd)55 private static native int native_get_size(FileDescriptor fd) throws IOException; 56 57 private FileDescriptor mFD; // ashmem file descriptor 58 private long 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 62 /** 63 * Allocates a new ashmem region. The region is initially not purgable. 64 * 65 * @param name optional name for the file (can be null). 66 * @param length of the memory file in bytes, must be non-negative. 67 * @throws IOException if the memory file could not be created. 68 */ MemoryFile(String name, int length)69 public MemoryFile(String name, int length) throws IOException { 70 mLength = length; 71 if (length >= 0) { 72 mFD = native_open(name, length); 73 } else { 74 throw new IOException("Invalid length: " + length); 75 } 76 77 if (length > 0) { 78 mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE); 79 } else { 80 mAddress = 0; 81 } 82 } 83 84 /** 85 * Closes the memory file. If there are no other open references to the memory 86 * file, it will be deleted. 87 */ close()88 public void close() { 89 deactivate(); 90 if (!isClosed()) { 91 native_close(mFD); 92 } 93 } 94 95 /** 96 * Unmaps the memory file from the process's memory space, but does not close it. 97 * After this method has been called, read and write operations through this object 98 * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor. 99 * 100 * @hide 101 */ deactivate()102 void deactivate() { 103 if (!isDeactivated()) { 104 try { 105 native_munmap(mAddress, mLength); 106 mAddress = 0; 107 } catch (IOException ex) { 108 Log.e(TAG, ex.toString()); 109 } 110 } 111 } 112 113 /** 114 * Checks whether the memory file has been deactivated. 115 */ isDeactivated()116 private boolean isDeactivated() { 117 return mAddress == 0; 118 } 119 120 /** 121 * Checks whether the memory file has been closed. 122 */ isClosed()123 private boolean isClosed() { 124 return !mFD.valid(); 125 } 126 127 @Override finalize()128 protected void finalize() { 129 if (!isClosed()) { 130 Log.e(TAG, "MemoryFile.finalize() called while ashmem still open"); 131 close(); 132 } 133 } 134 135 /** 136 * Returns the length of the memory file. 137 * 138 * @return file length. 139 */ length()140 public int length() { 141 return mLength; 142 } 143 144 /** 145 * Is memory file purging enabled? 146 * 147 * @return true if the file may be purged. 148 */ isPurgingAllowed()149 public boolean isPurgingAllowed() { 150 return mAllowPurging; 151 } 152 153 /** 154 * Enables or disables purging of the memory file. 155 * 156 * @param allowPurging true if the operating system can purge the contents 157 * of the file in low memory situations 158 * @return previous value of allowPurging 159 */ allowPurging(boolean allowPurging)160 synchronized public boolean allowPurging(boolean allowPurging) throws IOException { 161 boolean oldValue = mAllowPurging; 162 if (oldValue != allowPurging) { 163 native_pin(mFD, !allowPurging); 164 mAllowPurging = allowPurging; 165 } 166 return oldValue; 167 } 168 169 /** 170 * Creates a new InputStream for reading from the memory file. 171 * 172 @return InputStream 173 */ getInputStream()174 public InputStream getInputStream() { 175 return new MemoryInputStream(); 176 } 177 178 /** 179 * Creates a new OutputStream for writing to the memory file. 180 * 181 @return OutputStream 182 */ getOutputStream()183 public OutputStream getOutputStream() { 184 return new MemoryOutputStream(); 185 } 186 187 /** 188 * Reads bytes from the memory file. 189 * Will throw an IOException if the file has been purged. 190 * 191 * @param buffer byte array to read bytes into. 192 * @param srcOffset offset into the memory file to read from. 193 * @param destOffset offset into the byte array buffer to read into. 194 * @param count number of bytes to read. 195 * @return number of bytes read. 196 * @throws IOException if the memory file has been purged or deactivated. 197 */ readBytes(byte[] buffer, int srcOffset, int destOffset, int count)198 public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count) 199 throws IOException { 200 if (isDeactivated()) { 201 throw new IOException("Can't read from deactivated memory file."); 202 } 203 if (destOffset < 0 || destOffset > buffer.length || count < 0 204 || count > buffer.length - destOffset 205 || srcOffset < 0 || srcOffset > mLength 206 || count > mLength - srcOffset) { 207 throw new IndexOutOfBoundsException(); 208 } 209 return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); 210 } 211 212 /** 213 * Write bytes to the memory file. 214 * Will throw an IOException if the file has been purged. 215 * 216 * @param buffer byte array to write bytes from. 217 * @param srcOffset offset into the byte array buffer to write from. 218 * @param destOffset offset into the memory file to write to. 219 * @param count number of bytes to write. 220 * @throws IOException if the memory file has been purged or deactivated. 221 */ writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)222 public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count) 223 throws IOException { 224 if (isDeactivated()) { 225 throw new IOException("Can't write to deactivated memory file."); 226 } 227 if (srcOffset < 0 || srcOffset > buffer.length || count < 0 228 || count > buffer.length - srcOffset 229 || destOffset < 0 || destOffset > mLength 230 || count > mLength - destOffset) { 231 throw new IndexOutOfBoundsException(); 232 } 233 native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); 234 } 235 236 /** 237 * Gets a FileDescriptor for the memory file. 238 * 239 * The returned file descriptor is not duplicated. 240 * 241 * @throws IOException If the memory file has been closed. 242 * 243 * @hide 244 */ getFileDescriptor()245 public FileDescriptor getFileDescriptor() throws IOException { 246 return mFD; 247 } 248 249 /** 250 * Returns the size of the memory file that the file descriptor refers to, 251 * or -1 if the file descriptor does not refer to a memory file. 252 * 253 * @throws IOException If <code>fd</code> is not a valid file descriptor. 254 * 255 * @hide 256 */ getSize(FileDescriptor fd)257 public static int getSize(FileDescriptor fd) throws IOException { 258 return native_get_size(fd); 259 } 260 261 private class MemoryInputStream extends InputStream { 262 263 private int mMark = 0; 264 private int mOffset = 0; 265 private byte[] mSingleByte; 266 267 @Override available()268 public int available() throws IOException { 269 if (mOffset >= mLength) { 270 return 0; 271 } 272 return mLength - mOffset; 273 } 274 275 @Override markSupported()276 public boolean markSupported() { 277 return true; 278 } 279 280 @Override mark(int readlimit)281 public void mark(int readlimit) { 282 mMark = mOffset; 283 } 284 285 @Override reset()286 public void reset() throws IOException { 287 mOffset = mMark; 288 } 289 290 @Override read()291 public int read() throws IOException { 292 if (mSingleByte == null) { 293 mSingleByte = new byte[1]; 294 } 295 int result = read(mSingleByte, 0, 1); 296 if (result != 1) { 297 return -1; 298 } 299 return mSingleByte[0]; 300 } 301 302 @Override read(byte buffer[], int offset, int count)303 public int read(byte buffer[], int offset, int count) throws IOException { 304 if (offset < 0 || count < 0 || offset + count > buffer.length) { 305 // readBytes() also does this check, but we need to do it before 306 // changing count. 307 throw new IndexOutOfBoundsException(); 308 } 309 count = Math.min(count, available()); 310 if (count < 1) { 311 return -1; 312 } 313 int result = readBytes(buffer, mOffset, offset, count); 314 if (result > 0) { 315 mOffset += result; 316 } 317 return result; 318 } 319 320 @Override skip(long n)321 public long skip(long n) throws IOException { 322 if (mOffset + n > mLength) { 323 n = mLength - mOffset; 324 } 325 mOffset += n; 326 return n; 327 } 328 } 329 330 private class MemoryOutputStream extends OutputStream { 331 332 private int mOffset = 0; 333 private byte[] mSingleByte; 334 335 @Override write(byte buffer[], int offset, int count)336 public void write(byte buffer[], int offset, int count) throws IOException { 337 writeBytes(buffer, offset, mOffset, count); 338 mOffset += count; 339 } 340 341 @Override write(int oneByte)342 public void write(int oneByte) throws IOException { 343 if (mSingleByte == null) { 344 mSingleByte = new byte[1]; 345 } 346 mSingleByte[0] = (byte)oneByte; 347 write(mSingleByte, 0, 1); 348 } 349 } 350 } 351