Android系统的Ashmem匿名共享内存子系统分析(4)- Ashmem子系统的 Java访问接口

news/2024/10/30 22:16:09/

声明

  • 其实对于Android系统的Ashmem匿名共享内存系统早就有分析的想法,记得2019年6、7月份Mr.Deng离职期间约定一起对其进行研究的,但因为我个人问题没能实施这个计划,留下些许遗憾…
  • 文中参考了很多书籍及博客内容,可能涉及的比较多先不具体列出来了;
  • 本文使用的代码是LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:cm-14.1 Android系统启动过程分析(1)-如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译、刷机
  • 若对JNI不熟悉,可参考此专栏:Androd系统的JNI与NDK

1 Ashmem的架构

  Android 系统实现的 Ashmem 匿名共享内存子系统,用来在应用程序之间共享数据。Ashmem 与传统的Linux系统实现的共享内存一样,都是基于内核提供的临时文件系统tmpfs实现的,但是 Ashmem 对内存块进行了更为精细化的管理。应用程序可以动态地将一块匿名共享内存划分为若干个小块,当这些小块内存不再需要使用时,它们就可以被内存管理系统回收。通过这种动态的、分而治之的内存管理方式,Android系统就能够有效地使用系统内存,适应内存较小的移动设备环境。

  匿名共享内存系统是以Ashmem驱动程序为基础的,系统中所有的匿名共享内存都由Ashmem驱动程序负责分配和管理。Android系统在 Native 层提供了 C/C++ 调用接口和 Framework 层提供了 Java 调用接口。

  • 在Framework 层中,提供了两个C++类 MemoryBase 和 MemoryHeapBase,以及一个 Java 类 MemoryFile 来使用匿名共享内存。
  • 在运行时库 cutils 中,主要提供了三个C函数 ashmem_create_region、ashmem_pin_region 和 ashmem_unpin_region 来访问 Ashmem 驱动程序。

  Ashmem驱动程序在启动时,会创建一个 /dev/ashmem 设备文件,这样,运行时库 cutils 中的匿名共享内存接口就可以通过文件操作函数 open 和 ioctl 等来访问 Ashmem 驱动程序。
在这里插入图片描述

  传统的 Linux 系统使用一个整数来标志一块共享内存,而 Android 系统则使用一个文件描述符来标志一块匿名共享内存。使用文件描述符来描述一块匿名共享内存有两个好处:

  1. 可以方便地将它映射到进程的地址空间,从而可以直接访问它的内容;
  2. 可以使用 Binder 进程间通信机制来传输这个文件描述符,从而实现在不同的应用程序之间共享一块匿名内存。

  Binder 进程间通信机制使用一个类型为 BINDER_TYPE_FD 的 Binder 对象来描述一个文件描述符,当 Binder 驱动程序发现进程间通信数据中包含有这种 Binder 对象时,就会将对应的文件描述符复制到目标进程中,从而实现在两个进程中共享同一个文件。

2 Ashmem子系统 Java 访问接口分析

  在Android的 Framework 层中通过使用接口 MemoryFile 来封装匿名共享内存文件的创建和使用接口 MemoryFile 在:frameworks/base/core/java/android/os/MemoryFile.java,具体实现代码如下所示:

public class MemoryFile
{private static String TAG = "MemoryFile";// mmap(2) protection flags from <sys/mman.h>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 regionprivate 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;private static native int native_get_size(FileDescriptor fd) throws IOException;private FileDescriptor mFD;        // ashmem file descriptorprivate long mAddress;   // address of ashmem memoryprivate int mLength;    // total length of our ashmem regionprivate 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.* @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;}}/*** Closes the memory file. If there are no other open references to the memory* file, it will be deleted.*/public void close() {deactivate();if (!isClosed()) {native_close(mFD);}}/*** Unmaps the memory file from the process's memory space, but does not close it.* After this method has been called, read and write operations through this object* will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.** @hide*/void deactivate() {if (!isDeactivated()) {try {native_munmap(mAddress, mLength);mAddress = 0;} catch (IOException ex) {Log.e(TAG, ex.toString());}}}/*** 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();}@Overrideprotected void finalize() {if (!isClosed()) {Log.e(TAG, "MemoryFile.finalize() called while ashmem still open");close();}}/*** Returns the length of the memory file.** @return file length.*/public int length() {return mLength;}/*** Is memory file purging enabled?** @return true if the file may be purged.*/public boolean isPurgingAllowed() {return mAllowPurging;}/*** Enables or disables purging of the memory file.** @param allowPurging true if the operating system can purge the contents* of the file in low memory situations* @return previous value of allowPurging*/synchronized public boolean allowPurging(boolean allowPurging) throws IOException {boolean oldValue = mAllowPurging;if (oldValue != allowPurging) {native_pin(mFD, !allowPurging);mAllowPurging = allowPurging;}return oldValue;}/*** Creates a new InputStream for reading from the memory file.*@return InputStream*/public InputStream getInputStream() {return new MemoryInputStream();}/*** Creates a new OutputStream for writing to the memory file.*@return OutputStream*/public OutputStream getOutputStream() {return new MemoryOutputStream();}/*** Reads bytes from the memory file.* Will throw an IOException if the file has been purged.** @param buffer byte array to read bytes into.* @param srcOffset offset into the memory file to read from.* @param destOffset offset into the byte array buffer to read into.* @param count number of bytes to read.* @return number of bytes read.* @throws IOException if the memory file has been purged or deactivated.*/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.");}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);}/*** Write bytes to the memory file.* Will throw an IOException if the file has been purged.** @param buffer byte array to write bytes from.* @param srcOffset offset into the byte array buffer to write from.* @param destOffset offset  into the memory file to write to.* @param count number of bytes to write.* @throws IOException if the memory file has been purged or deactivated.*/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.");}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);}/*** Gets a FileDescriptor for the memory file.** The returned file descriptor is not duplicated.** @throws IOException If the memory file has been closed.** @hide*/public FileDescriptor getFileDescriptor() throws IOException {return mFD;}/*** Returns the size of the memory file that the file descriptor refers to,* or -1 if the file descriptor does not refer to a memory file.** @throws IOException If <code>fd</code> is not a valid file descriptor.** @hide*/public static int getSize(FileDescriptor fd) throws IOException {return native_get_size(fd);}private class MemoryInputStream extends InputStream {private int mMark = 0;private int mOffset = 0;private byte[] mSingleByte;@Overridepublic int available() throws IOException {if (mOffset >= mLength) {return 0;}return mLength - mOffset;}@Overridepublic boolean markSupported() {return true;}@Overridepublic void mark(int readlimit) {mMark = mOffset;}@Overridepublic void reset() throws IOException {mOffset = mMark;}@Overridepublic int read() throws IOException {if (mSingleByte == null) {mSingleByte = new byte[1];}int result = read(mSingleByte, 0, 1);if (result != 1) {return -1;}return mSingleByte[0];}@Overridepublic int read(byte buffer[], int offset, int count) throws IOException {if (offset < 0 || count < 0 || offset + count > buffer.length) {// readBytes() also does this check, but we need to do it before// changing count.throw new IndexOutOfBoundsException();}count = Math.min(count, available());if (count < 1) {return -1;}int result = readBytes(buffer, mOffset, offset, count);if (result > 0) {mOffset += result;}return result;}@Overridepublic long skip(long n) throws IOException {if (mOffset + n > mLength) {n = mLength - mOffset;}mOffset += n;return n;}}private class MemoryOutputStream extends OutputStream {private int mOffset = 0;private byte[] mSingleByte;@Overridepublic void write(byte buffer[], int offset, int count) throws IOException {writeBytes(buffer, offset, mOffset, count);mOffset += count;}@Overridepublic void write(int oneByte) throws IOException {if (mSingleByte == null) {mSingleByte = new byte[1];}mSingleByte[0] = (byte)oneByte;write(mSingleByte, 0, 1);}}
}

  构造方法 MemoryFile 以指定的字符串调用了JNI方法 native_open,目的是建立一个匿名共享内存文件,这样可以得到一个文件描述符。然后使用这个文件描述符为参数调用JNI方法natvie_mmap,并把匿名共享内存文件映射到进程空间中这样就可以通过映射得到地址空间的方式直接访问内存数据。
  成员函数 readBytes 用于读取某一块匿名共享内存的内容,成员函数 writeBytes 用于写入某一块匿名共享内存的内容,成员函数 isDeactivated 用于保证匿名共享内存已经被映射到进程的地址空间中。

  以上native方法对应实现在源码文件:frameworkslbaselcorejniandroid_os_MemoryFile.cpp 中,具体实现代码如下所示:

#define LOG_TAG "MemoryFile"
#include <utils/Log.h>#include <cutils/ashmem.h>
#include "core_jni_helpers.h"
#include "JNIHelp.h"
#include <unistd.h>
#include <sys/mman.h>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<jlong>(result);
}static void android_os_MemoryFile_munmap(JNIEnv* env, jobject clazz, jlong addr, jint length)
{int result = munmap(reinterpret_cast<void *>(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)
{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);}
}static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz,jobject fileDescriptor) {int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);// Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region.// ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel// should return ENOTTY for all other valid file descriptorsint result = ashmem_get_size_region(fd);if (result < 0) {if (errno == ENOTTY) {// ENOTTY means that the ioctl does not apply to this object,// i.e., it is not an ashmem region.return (jint) -1;}// Some other error, throw exceptionjniThrowIOException(env, errno);return (jint) -1;}return (jint) result;
}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_get_size", "(Ljava/io/FileDescriptor;)I",(void*)android_os_MemoryFile_get_size}
};int register_android_os_MemoryFile(JNIEnv* env)
{return RegisterMethodsOrDie(env, "android/os/MemoryFile", methods, NELEM(methods));
}}

http://www.ppmy.cn/news/252352.html

相关文章

iPhone5s配置网易邮箱

1.在“设置-邮件、通讯录、日历-添加账户”选择163邮箱&#xff0c;填写正确的信息后&#xff0c;不管提示什么&#xff0c;都存储下来&#xff0c;然后进行设置 2.首先要在网页版的163邮箱进行设置&#xff0c;将POP3/SMTP/IMAP全部勾选&#xff0c;如果不能勾选&#xff0c;…

Iphone 5s 刷机 ---- 尝试篇

一、前言 两三年前有过一个魅族手机被锁的经历仍记忆犹新&#xff0c;有天手机屏幕锁密码突然忘了&#xff0c;经过几次尝试 结果手机就锁死了&#xff0c;要登录魅族账号才能解开&#xff0c;可是这个账号的密码我也忘了&#xff0c;再加之注册魅族账号的那个手机号换了。感觉…

iphone 5s设置方法

由于onavo在china地区被限制了&#xff0c;如果你直接安装app会出现“this app cant use in your country”的提示 由于天朝封杀了onavo&#xff0c;导致该应用下载仍然无法使用&#xff0c;但是现在可以通过直接安装onavo的描述文件不安装应用程序达到目的&#xff0c;亲测可用…

iPhone 5s指纹识别存在什么安全隐患?

当地时间9月10日&#xff0c;苹果终于发布了万众瞩目的iPhone 5s和iPhone 5c。其中&#xff0c;iPhone 5s一如此前剧透般在Home键上加入了全新指纹识别模块&#xff0c;原本HOME键内部的方框也随之消失&#xff0c;取而代之的是一个不锈钢指纹检测环。 据悉&#xff0c;在配置了…

iPhone5S等A7设备 任意版本降级iOS10.3.3教程(方法一)

众所周知苹果在发布新 iOS 版本后&#xff0c;就会对旧版本进行关闭验证通道&#xff0c;也就是升级后&#xff0c;不能降级。这让一些配置比较低的 iOS 老设备&#xff0c;升级新系统变得卡顿不流畅&#xff0c;也无法正常降级回去旧系统版本。 这让很多 iOS 用户很不爽&…

如何判断iPhone5 iPhone 5S iPhone6 iPhone6 plus?

不同的机型设配真的是老大难的问题&#xff0c;IOS和安卓运行机制也有很多的不同。记录一下对Iphone机型的判断吧。   首先是要判断IOS还是安卓&#xff0c;然后判断IOS的各个机型的话&#xff0c;获取屏幕宽度就可以了。这边查到一段代码&#xff1a; 主要用的是var events …

5s信号无服务器,iPhone5S A1528摔后无服务维修过程

一台iPhone5s 3G版,摔的比较严重。拿过来时后壳弯了,屏已经花了,触摸一半灵一半不灵。关键的问题是主板摔出毛病了,插卡就显示无服务。 先换屏。换屏后触摸都好用了,进入设置,具体分析无服务的故障起因。 正常情况下,进入设置会看到“运营商”一栏。现在没有,说明信号还…

iphone 5s case

10.1这几天在apple官网买了5s的case&#xff0c;太慢了&#xff0c;就又在taobao上买了一个&#xff08;还便宜几十元&#xff09;&#xff0c;收到taobao上的case时&#xff0c;觉得可能上当了&#xff0c;因为包装一点也不苹果&#xff0c;直到收到了apple发来的case才算安心…