Android Runtime内存共享与访问控制原理剖析(71)

码字不易,请大佬们点点关注跟关注下公众号,谢谢~

一、Android Runtime内存架构基础

Android Runtime(ART)的内存架构设计是实现高效内存共享与严格访问控制的基础。ART采用多层次内存管理策略,将内存划分为多个功能区域,每个区域有特定的用途和管理机制。

1.1 内存区域划分

ART的内存主要分为以下几个核心区域:

  • 堆内存(Heap):用于存储Java对象实例,是内存占用的主要部分。堆内存又进一步分为年轻代(Young Generation)、老年代(Old Generation)和大对象空间(Large Object Space)。年轻代用于存储新创建的对象,采用复制算法进行垃圾回收;老年代用于存储存活时间较长的对象,采用标记-整理算法进行回收;大对象空间则专门存储大型对象,避免对其他区域的内存布局产生影响。
  • 非堆内存(Non-Heap):包括方法区(Method Area)、JIT编译缓存(JIT Code Cache)、线程栈(Thread Stacks)等。方法区存储类的元数据信息,如类结构、方法字节码等;JIT编译缓存存储JIT编译器生成的机器码;线程栈则为每个线程提供独立的执行栈空间。
  • 本地内存(Native Memory):用于存储JNI调用、本地库、图形处理等需要的内存。本地内存不受Java堆内存限制,但需要开发者手动管理,否则容易导致内存泄漏。

在ART源码中,这些内存区域的划分和管理由art/runtime/memory目录下的代码实现。例如,堆内存的管理主要由heap.hheap.cc文件中的Heap类负责:

cpp 复制代码
// art/runtime/heap.h
class Heap {
 public:
  // 初始化堆内存
  bool Init();
  // 分配对象内存
  mirror::Object* AllocateObject(Thread* self, mirror::Class* clazz, size_t byte_count);
  // 垃圾回收操作
  void CollectGarbage(bool clear_soft_references);
  // 获取堆内存各区域的信息
  Space* GetYoungSpace() const;
  Space* GetOldSpace() const;
  Space* GetLargeObjectSpace() const;
  // 其他堆内存管理方法
  // ...
};

1.2 内存管理核心组件

ART的内存管理涉及多个核心组件,这些组件协同工作,实现内存的分配、回收、共享和访问控制:

  • 内存分配器(Allocator) :负责实际的内存分配操作。ART提供了多种分配器,如TLAB(Thread-Local Allocation Buffer)分配器用于快速分配小对象,大型对象则直接在大对象空间分配。在art/runtime/mem_map.hmem_map.cc中,定义了内存映射相关的操作,为内存分配提供底层支持。
  • 垃圾回收器(Garbage Collector) :负责回收不再使用的对象所占用的内存。ART支持多种垃圾回收算法,如标记-清除(Mark-Sweep)、标记-整理(Mark-Compact)和增量式垃圾回收(Incremental GC)等。垃圾回收器的实现主要在art/runtime/gc目录下。
  • 内存映射管理器(Memory Mapper) :负责将物理内存映射到进程的地址空间。通过内存映射,不同进程可以共享同一块物理内存,实现内存共享。内存映射管理器的代码主要在art/runtime/mem_map.cc中。
  • 访问控制器(Access Controller):负责控制对内存的访问权限,确保内存访问的安全性。访问控制器通过Linux内核的内存保护机制和ART自身的检查逻辑来实现访问控制。

这些组件相互协作,形成了一个完整的内存管理系统,为Android应用提供高效、安全的内存环境。

二、内存共享机制原理

2.1 进程间内存共享基础

在Android系统中,进程间内存共享是提高内存使用效率、减少内存占用的重要手段。ART通过多种方式实现进程间内存共享:

  • 共享库映射(Shared Library Mapping) :Android应用通常会依赖大量的共享库,如系统库、第三方库等。这些共享库在内存中只需要加载一份,多个进程可以通过内存映射共享同一份库代码,从而节省大量内存。在ART中,共享库的加载和映射由art/runtime/dlfcn目录下的代码实现。
  • Zygote机制 :Android系统通过Zygote进程预加载和初始化常用的系统资源和类,新创建的应用进程通过复制Zygote进程的地址空间来快速启动。由于采用了写时复制(Copy-On-Write)技术,新进程与Zygote进程可以共享大部分内存,只有在需要修改内存时才会复制一份副本,大大减少了内存开销。Zygote机制的实现主要在frameworks/base/core/jniart/runtime目录下。
  • 匿名共享内存(Ashmem) :Android提供了Ashmem机制,允许进程创建可共享的内存区域。通过Ashmem,进程可以将数据存储在共享内存中,其他进程可以通过文件描述符访问该内存区域,实现高效的数据共享。Ashmem的实现主要在system/core/libcutils/ashmem-dev.cframeworks/native/libs/binder目录下。

2.2 Zygote机制与内存共享

Zygote进程是Android系统中一个特殊的进程,它在系统启动时创建,预加载和初始化常用的系统资源和类。当需要创建新的应用进程时,Zygote进程会通过fork()系统调用复制自身,生成新的应用进程。由于fork()采用写时复制技术,新进程与Zygote进程可以共享大部分内存,只有在需要修改内存时才会复制一份副本。

在ART中,Zygote进程的初始化和应用进程的创建由art/runtime/zygote目录下的代码实现。例如,zygote_main.cc中的ZygoteInit函数负责初始化Zygote进程:

cpp 复制代码
// art/runtime/zygote/zygote_main.cc
void ZygoteInit(int argc, char* argv[]) {
  // 初始化运行时环境
  RuntimeInit(argc, argv);
  // 预加载类和资源
  PreloadClasses();
  PreloadResources();
  // 启动Zygote服务器,等待创建新进程的请求
  StartZygoteServer();
}

当Zygote进程收到创建新应用进程的请求时,会调用fork()系统调用创建新进程。在art/runtime/zygote/zygote_child.cc中,ForkAndSpecializeCommon函数实现了这一过程:

cpp 复制代码
// art/runtime/zygote/zygote_child.cc
pid_t ForkAndSpecializeCommon(...) {
  // 准备fork前的操作
  PrepareForFork();
  // 调用fork系统调用
  pid_t pid = fork();
  if (pid == 0) {
    // 子进程执行的代码
    PostForkChild(...);
  } else if (pid > 0) {
    // 父进程执行的代码
    PostForkParent(pid, ...);
  } else {
    // fork失败的处理
    HandleForkError();
  }
  return pid;
}

由于写时复制技术的存在,新创建的应用进程与Zygote进程共享同一块物理内存,直到其中一个进程需要修改内存。这种机制使得多个应用进程可以共享Zygote进程预加载的系统资源和类,大大减少了内存占用。

2.3 匿名共享内存(Ashmem)实现

Ashmem(Anonymous Shared Memory)是Android提供的一种高效的进程间内存共享机制。通过Ashmem,进程可以创建可共享的内存区域,并通过文件描述符将该内存区域传递给其他进程,实现数据共享。

在ART中,Ashmem的使用主要涉及以下几个步骤:

  1. 创建共享内存区域 :进程通过ashmem_create_region()函数创建一个匿名共享内存区域,并指定该区域的大小。
  2. 映射内存 :进程使用mmap()函数将创建的共享内存区域映射到自己的地址空间。
  3. 传递文件描述符:进程通过Binder或其他IPC机制将共享内存区域的文件描述符传递给其他进程。
  4. 其他进程映射内存 :其他进程接收到文件描述符后,使用mmap()函数将该共享内存区域映射到自己的地址空间,从而实现与创建进程共享同一块内存。

Ashmem的实现主要在system/core/libcutils/ashmem-dev.c文件中。例如,ashmem_create_region()函数的实现如下:

c 复制代码
// system/core/libcutils/ashmem-dev.c
int ashmem_create_region(const char *name, size_t size) {
  int fd;
  // 打开ashmem设备文件
  fd = open(ASHMEM_DEVICE, O_RDWR);
  if (fd < 0) {
    return fd;
  }
  // 设置共享内存区域的大小
  if (ioctl(fd, ASHMEM_SET_SIZE, size) < 0) {
    close(fd);
    return -1;
  }
  // 如果指定了名称,设置共享内存区域的名称
  if (name) {
    if (ioctl(fd, ASHMEM_SET_NAME, name) < 0) {
      close(fd);
      return -1;
    }
  }
  return fd;
}

通过Ashmem,ART可以在不同进程之间高效地共享数据,例如在应用和系统服务之间传递大型数据结构,或者在多进程应用的不同进程之间共享资源。

三、内存访问控制原理

3.1 基于Linux内核的内存访问控制

Android系统基于Linux内核,因此继承了Linux的内存访问控制机制。Linux通过虚拟内存管理和内存保护机制,为每个进程提供独立的地址空间,并控制对内存的访问权限。

在Linux中,内存访问控制主要通过以下几个方面实现:

  • 虚拟内存管理:Linux将物理内存划分为多个页框(Page Frame),并为每个进程维护一个虚拟地址空间到物理内存的映射关系。每个进程只能访问自己虚拟地址空间内的内存,无法直接访问其他进程的内存。
  • 内存保护位:Linux为每个内存页设置了保护位,包括读(Read)、写(Write)和执行(Execute)权限。当进程试图以违反保护位的方式访问内存时,Linux内核会触发一个段错误(Segmentation Fault),终止该进程。
  • 进程隔离:Linux通过进程ID(PID)和用户ID(UID)来隔离不同的进程。每个进程都有自己独立的地址空间和权限,无法直接访问其他进程的内存,除非通过特定的IPC机制。

在ART中,这些Linux内核的内存访问控制机制被用于保护Java堆内存、非堆内存和本地内存。例如,ART在分配内存时,会通过mmap()系统调用指定内存页的保护位,确保内存只能被授权的方式访问。

3.2 ART自身的内存访问控制机制

除了依赖Linux内核的内存访问控制机制外,ART还实现了自身的内存访问控制逻辑,以提供更细粒度的内存保护。

3.2.1 对象访问检查

ART在访问Java对象时,会进行一系列的检查,确保访问的合法性。例如,在访问对象的字段或调用对象的方法时,ART会检查对象是否为null,以及当前线程是否有权限访问该对象。

art/runtime/mirror/object.hobject.cc中,定义了对象访问的相关方法。例如,CheckValidObject()函数用于检查对象是否有效:

cpp 复制代码
// art/runtime/mirror/object.cc
void CheckValidObject(mirror::Object* obj) {
  // 检查对象是否为null
  if (obj == nullptr) {
    ThrowNullPointerException();
    return;
  }
  // 检查对象是否已被回收
  if (obj->IsDeleted()) {
    ThrowIllegalStateException("Object has been deleted");
    return;
  }
  // 其他有效性检查
  // ...
}
3.2.2 内存边界检查

ART在访问数组等连续内存区域时,会进行内存边界检查,防止越界访问。例如,在访问数组元素时,ART会检查索引是否在合法范围内。

art/runtime/mirror/array.harray.cc中,定义了数组访问的相关方法。例如,CheckArrayBounds()函数用于检查数组索引是否越界:

cpp 复制代码
// art/runtime/mirror/array.cc
bool CheckArrayBounds(mirror::Array* array, size_t index) {
  size_t length = array->GetLength();
  if (index >= length) {
    // 索引越界,抛出ArrayIndexOutOfBoundsException异常
    ThrowArrayIndexOutOfBoundsException(index, length);
    return false;
  }
  return true;
}
3.2.3 安全点机制

ART使用安全点(Safepoint)机制来确保在执行某些关键操作时,所有线程都处于安全状态。安全点是程序执行过程中的特定位置,在这些位置上,线程的状态可以被安全地检查和修改。

当ART需要执行垃圾回收、类加载等操作时,会要求所有线程到达安全点并暂停执行。在安全点期间,ART可以安全地检查和修改线程的状态,包括内存访问状态。

安全点机制的实现主要在art/runtime/safepoint.hsafepoint.cc中。例如,Safepoint::Synchronize()函数用于同步所有线程到安全点:

cpp 复制代码
// art/runtime/safepoint.cc
void Safepoint::Synchronize(Thread* self, Safepoint::Action action) {
  // 通知所有线程到达安全点
  NotifyAllThreadsToReachSafepoint();
  // 等待所有线程到达安全点
  WaitForAllThreadsToReachSafepoint();
  // 执行安全点操作
  ExecuteSafepointAction(action);
  // 允许所有线程继续执行
  AllowAllThreadsToContinue();
}

通过安全点机制,ART可以确保在执行关键操作时,内存访问处于可控状态,防止出现竞态条件和内存损坏等问题。

3.3 JNI访问控制

在JNI(Java Native Interface)调用中,ART实现了严格的访问控制机制,确保本地代码只能以安全的方式访问Java对象和内存。

3.3.1 JNI环境检查

当本地代码通过JNI访问Java对象时,首先需要获取JNI环境(JNIEnv)。ART会检查当前线程是否已经正确附加到Java虚拟机,并验证JNI环境的有效性。

art/runtime/jni/jni_env_ext.hjni_env_ext.cc中,定义了JNI环境的相关方法。例如,CheckJNIEnvValid()函数用于检查JNI环境是否有效:

cpp 复制代码
// art/runtime/jni/jni_env_ext.cc
bool CheckJNIEnvValid(JNIEnv* env) {
  // 检查JNI环境是否为null
  if (env == nullptr) {
    return false;
  }
  // 检查JNI环境是否属于当前线程
  Thread* self = Thread::Current();
  if (env != self->GetJniEnv()) {
    return false;
  }
  // 其他有效性检查
  // ...
  return true;
}
3.3.2 局部引用和全局引用管理

JNI提供了局部引用和全局引用机制,用于管理Java对象在本地代码中的生命周期。ART通过严格控制引用的创建和释放,确保本地代码只能访问合法的Java对象。

art/runtime/jni/object_reference.hobject_reference.cc中,定义了引用管理的相关方法。例如,NewLocalRef()函数用于创建一个局部引用:

cpp 复制代码
// art/runtime/jni/object_reference.cc
jobject NewLocalRef(JNIEnv* env, jobject obj) {
  // 检查JNI环境的有效性
  if (!CheckJNIEnvValid(env)) {
    return nullptr;
  }
  // 检查对象是否为null
  if (obj == nullptr) {
    return nullptr;
  }
  // 创建局部引用
  LocalReferenceTable* table = env->GetLocalReferenceTable();
  return table->AddLocalReference(obj);
}
3.3.3 内存访问限制

本地代码通过JNI访问Java对象时,ART会限制其访问权限,确保本地代码只能以合法的方式访问Java对象的字段和方法。例如,本地代码只能通过JNI提供的函数访问Java对象的字段,而不能直接访问对象的内存。

art/runtime/jni/jni_internal.hjni_internal.cc中,定义了JNI内部函数的实现。例如,GetIntField()函数用于获取Java对象的int类型字段:

cpp 复制代码
// art/runtime/jni/jni_internal.cc
jint GetIntField(JNIEnv* env, jobject obj, jfieldID fieldID) {
  // 检查JNI环境和对象的有效性
  if (!CheckJNIEnvValid(env) || obj == nullptr) {
    return 0;
  }
  // 检查字段ID的有效性
  if (!CheckFieldIDValid(fieldID)) {
    return 0;
  }
  // 通过JNIEnv获取字段的值
  return env->functions->GetIntField(env, obj, fieldID);
}

通过这些JNI访问控制机制,ART确保本地代码与Java代码之间的交互是安全的,防止本地代码对Java内存的非法访问和修改。

四、垃圾回收与内存共享的协同机制

4.1 垃圾回收对内存共享的影响

垃圾回收(Garbage Collection,GC)是ART内存管理的重要组成部分,它负责回收不再使用的对象所占用的内存。在多进程内存共享的场景下,垃圾回收会面临一些特殊的挑战。

当多个进程共享同一块内存时,每个进程对共享内存的使用情况可能不同。例如,一个进程可能已经不再使用某个共享对象,而另一个进程仍然在使用它。在这种情况下,垃圾回收器需要准确判断哪些对象仍然被引用,哪些对象可以被回收。

ART的垃圾回收器通过引用计数和可达性分析相结合的方式来解决这个问题。对于共享内存中的对象,ART会维护一个全局的引用计数,记录有多少个进程正在引用该对象。只有当所有进程都不再引用该对象时,垃圾回收器才会回收该对象所占用的内存。

art/runtime/gc/heap.hheap.cc中,定义了垃圾回收器的相关方法。例如,CollectGarbage()函数用于触发垃圾回收:

cpp 复制代码
// art/runtime/gc/heap.cc
void Heap::CollectGarbage(bool clear_soft_references) {
  // 暂停所有线程,确保垃圾回收过程中内存状态稳定
  ScopedSuspendAll ssa("GC");
  // 标记所有可达对象
  MarkRoots();
  MarkObjects();
  // 清除未标记的对象
  SweepObjects();
  // 整理内存(如果需要)
  if (ShouldCompact()) {
    CompactHeap();
  }
  // 恢复所有线程
}

4.2 跨进程对象引用管理

在多进程内存共享的场景下,进程之间可能会相互引用对方的对象。ART通过远程引用(Remote Reference)机制来管理跨进程的对象引用。

当一个进程需要引用另一个进程中的对象时,它会创建一个远程引用。远程引用包含一个指向目标对象的标识符,以及一些控制信息。通过远程引用,进程可以间接地访问另一个进程中的对象,而不需要直接持有该对象的指针。

远程引用的实现主要在art/runtime/remote_reference.hremote_reference.cc中。例如,CreateRemoteReference()函数用于创建一个远程引用:

cpp 复制代码
// art/runtime/remote_reference.cc
RemoteReference* CreateRemoteReference(mirror::Object* obj, ProcessId pid) {
  // 生成一个唯一的标识符
  ReferenceId id = GenerateUniqueReferenceId();
  // 创建远程引用对象
  RemoteReference* ref = new RemoteReference(id, obj, pid);
  // 注册远程引用
  RegisterRemoteReference(ref);
  return ref;
}

当垃圾回收器执行垃圾回收时,它会检查所有的远程引用,确保被引用的对象不会被错误地回收。只有当所有的远程引用都被释放后,对象才会被标记为可回收。

4.3 内存共享与增量式垃圾回收

为了减少垃圾回收对应用性能的影响,ART采用了增量式垃圾回收(Incremental GC)技术。增量式垃圾回收将垃圾回收过程分成多个小阶段,每个阶段只执行一部分垃圾回收工作,然后暂停一段时间,让应用程序继续执行。这样可以减少垃圾回收导致的应用停顿时间。

在内存共享的场景下,增量式垃圾回收需要特别注意跨进程的内存状态一致性。当一个进程执行增量式垃圾回收时,它需要确保其他进程对共享内存的修改不会影响垃圾回收的正确性。

ART通过内存屏障(Memory Barrier)和同步机制来解决这个问题。在执行增量式垃圾回收的关键阶段,ART会使用内存屏障来确保所有进程对共享内存的修改都已经被刷新到主内存中。同时,ART会通过同步机制协调多个进程的垃圾回收操作,确保它们不会相互干扰。

art/runtime/gc/incremental_gc.hincremental_gc.cc中,定义了增量式垃圾回收的相关方法。例如,PerformIncrementalGC()函数用于执行一次增量式垃圾回收:

cpp 复制代码
// art/runtime/gc/incremental_gc.cc
void IncrementalGC::PerformIncrementalGC() {
  // 检查是否需要执行增量式垃圾回收
  if (!ShouldPerformIncrementalGC()) {
    return;
  }
  // 获取全局锁,确保与其他进程的垃圾回收操作同步
  MutexLock mu(Thread::Current(), *gc_lock_);
  // 执行增量式垃圾回收的一个阶段
  PerformGCPhase(GcPhase::kMarking);
  // 释放全局锁
  // 应用程序可以继续执行,垃圾回收器会在后续阶段继续工作
}

通过增量式垃圾回收和跨进程同步机制,ART在保证内存共享正确性的同时,尽可能减少了垃圾回收对应用性能的影响。

五、内存映射与文件共享机制

5.1 文件映射与内存共享

除了匿名共享内存,ART还通过文件映射(File Mapping)机制实现内存共享。文件映射允许将文件内容直接映射到进程的地址空间,使得进程可以像访问内存一样访问文件内容。多个进程可以将同一个文件映射到各自的地址空间,从而实现内存共享。

在ART中,文件映射主要用于共享DEX文件、OAT文件等。DEX文件是Android应用的字节码文件,OAT文件是ART在应用安装时生成的优化后的机器码文件。通过将这些文件映射到内存中,多个应用进程可以共享同一份代码,减少内存占用。

文件映射的实现主要在art/runtime/mem_map.hmem_map.cc中。例如,MemMap::MapFile()函数用于将文件映射到内存:

cpp 复制代码
// art/runtime/mem_map.cc
bool MemMap::MapFile(size_t length, int prot, int flags, int fd, off_t offset,
                     const char* name) {
  // 调用mmap系统函数将文件映射到内存
  void* addr = mmap(nullptr, length, prot, flags, fd, offset);
  if (addr == MAP_FAILED) {
    // 映射失败,记录错误信息
    PLOG(ERROR) << "Failed to mmap " << name;
    return false;
  }
  // 初始化内存映射对象
  addr_ = addr;
  length_ = length;
  name_ = name;
  // ...
  return true;
}

5.2 DEX文件与OAT文件的共享机制

在Android系统中,应用的DEX文件和OAT文件通常是共享的资源。当多个应用使用相同的库时,它们可以共享这些库的DEX文件和OAT文件,从而减少内存占用。

ART通过以下方式实现DEX文件和OAT文件的共享:

  1. 文件映射:将DEX文件和OAT文件映射到内存中,多个进程可以共享同一份映射。
  2. 内容哈希验证:在加载DEX文件和OAT文件时,ART会计算文件的哈希值,并与预先计算的哈希值进行比较,确保文件内容的完整性和一致性。
  3. 共享类加载器:多个应用可以使用同一个类加载器来加载共享的类,从而确保类的定义在不同进程中是一致的。

art/runtime/dex_file.hdex_file.cc中,定义了DEX文件的加载和管理方法。例如,DexFile::Open()函数用于打开并加载DEX文件:

cpp 复制代码
// art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::Open(const uint8_t* base, size_t size,
                                              const std::string& location,
                                              uint32_t location_checksum,
                                              const OatDexFile* oat_dex_file,
                                              std::string* error_msg) {
  // 验证DEX文件的魔数和版本
  if (!VerifyMagicAndVersion(base, size, error_msg)) {
    return nullptr;
  }
  // 计算DEX文件的哈希值
  uint32_t computed_checksum = ComputeChecksum(base, size);
  // 验证哈希值
  if (location_checksum != 0 && computed_checksum != location_checksum) {
    *error_msg = StringPrintf("Checksum mismatch (expected %08x, got %08x)",
                              location_checksum, computed_checksum);
    return nullptr;
  }
  // 创建并初始化DexFile对象
  std::unique_ptr<const DexFile> dex_file(new DexFile(base, size, location,
                                                       computed_checksum, oat_dex_file));
  if (!dex_file->Init(error_msg)) {
    return nullptr;
  }
  return dex_file;
}

通过这些机制,ART确保了DEX文件和OAT文件在多个进程之间的高效共享,减少了内存占用,提高了系统性能。

5.3 内存映射的权限控制

在使用文件映射实现内存共享时,ART会严格控制内存映射的权限,确保进程只能以授权的方式访问共享内存。

当创建内存映射时,ART会通过mmap()系统函数的prot参数指定内存区域的访问权限,包括读(PROT_READ)、写(PROT_WRITE)和执行(PROT_EXEC)权限。例如,对于DEX文件和OAT文件的映射,通常只授予读和执行权限,禁止写入操作,以防止文件内容被修改。

art/runtime/mem_map.cc中,MemMap::MapFile()函数的实现中可以看到权限的设置:

cpp 复制代码
// art/runtime/mem_map.cc
bool MemMap::MapFile(size_t length, int prot, int flags, int fd, off_t offset,
                     const char* name) {
  // 设置内存映射的权限
  void* addr = mmap(nullptr, length, prot, flags, fd, offset);
  if (addr == MAP_FAILED) {
    PLOG(ERROR) << "Failed to mmap " << name;
    return false;
  }
  // ...
  return true;
}

通过严格控制内存映射的权限,ART确保了内存共享的安全性,防止进程对共享内存进行非法访问和修改。

六、内存隔离与安全机制

6.1 进程间内存隔离

Android系统通过Linux内核的进程隔离机制,确保不同进程之间的内存相互隔离,一个进程无法直接访问另一个进程的内存。这是Android系统安全的基础之一。

每个Android应用都运行在独立的进程中,拥有自己独立的用户ID(UID)和进程ID(PID)。Linux内核根据这些ID来控制进程对资源的访问权限,包括内存访问权限。每个进程只能访问自己地址空间内的内存,无法直接访问其他进程的内存。

ART在设计上充分利用了这种进程间内存隔离机制,确保应用之间的内存相互隔离,防止一个应用非法访问另一个应用的内存数据。例如,当一个应用通过JNI调用本地代码时,本地代码只能访问该应用的内存空间,无法访问其他应用的内存。

6.2 SELinux与内存访问控制

SELinux(Security-Enhanced Linux)是Android系统中一种基于Linux内核的强制访问控制(MAC)系统,它为系统提供了更细粒度的安全控制。在内存访问控制方面,SELinux可以限制进程对内存的访问权限,即使进程拥有root权限也不例外。

Android系统为不同的进程分配了不同的SELinux上下文(Context),每个上下文定义了该进程可以访问的资源和操作权限。例如,系统进程和应用进程拥有不同的SELinux上下文,应用进程之间也可能拥有不同的上下文,这取决于它们的权限级别和功能需求。

在ART中,SELinux的内存访问控制主要体现在以下几个方面:

  • 限制JNI调用权限:SELinux可以限制进程通过JNI调用本地代码的权限,防止恶意应用通过JNI访问敏感的本地功能。
  • 控制内存映射权限:SELinux可以限制进程创建和使用内存映射的权限,防止进程通过内存映射访问敏感数据。
  • 监控内存访问行为:SELinux可以监控进程的内存访问行为,记录可疑的内存访问操作,并在必要时阻止这些操作。

system/sepolicy目录下,定义了Android系统的SELinux策略文件。例如,app.te文件定义了应用进程的SELinux策略:

ini 复制代码
# system/sepolicy/app.te
# 定义应用进程的基本权限
type app, domain;
type app_data_file, file_type;

# 限制应用进程对内存的访问权限
allow app self:process { sigaction sigreturn };
allow app self:memory { read write };
# 其他权限定义
# ...

通过SELinux,Android系统实现了对内存访问的细粒度控制,提高了系统的安全性。

6.3 内存保护技术

除了进程隔离和SELinux,ART还采用了多种内存保护技术,进一步增强内存的安全性。

6.3.1 地址空间布局随机化(ASLR)

地址空间布局随机化(Address Space Layout Randomization,ASLR)是一种计算机安全技术,通过随机化进程的地址空间布局,使得攻击者难以预测内存中关键数据和代码的位置,从而增加了攻击的难度。

在Android系统中,ASLR默认是启用的。当进程启动时,Linux内核会随机化进程的地址空间布局,包括堆、栈、共享库等的起始地址。ART在设计上充分利用了ASLR技术,确保自身的代码和数据在内存中的位置是随机的。

art/runtime/init.cc中,可以看到ART对ASLR的支持:

cpp 复制代码
// art/runtime/init.cc
void InitRuntime() {
  // 初始化运行时环境
  // ...
  // 检查ASLR是否启用
  if (IsAslrEnabled()) {
    LOG(INFO) << "ASLR is enabled";
  } else {
    LOG(WARNING) << "ASLR is disabled, security may be compromised";
  }
  // ...
}
6.3.2 执行保护(NX位)

执行保护(No-eXecute,NX)是一种内存保护技术,通过为内存页设置执行保护位,防止恶意代码在数据段或堆栈中执行。在Android系统中,NX位默认是启用的。

ART在分配内存时,会根据内存区域的用途设置相应的保护位。例如,对于存储数据的内存区域,只设置读和写权限,禁止执行权限;对于存储代码的内存区域,设置读和执行权限,禁止写权限。

art/runtime/mem_map.cc中,MemMap::Protect()函数用于设置内存区域的保护位:

cpp 复制代码
// art/runtime/mem_map.cc
bool MemMap::Protect(int prot) {
  // 调用mprotect系统函数设置内存区域的保护位
  if (mprotect(addr_, length_, prot) != 0) {
    PLOG(ERROR) << "Failed to protect " << name_;
    return false;
  }
  return true;
}
6.3.3 堆内存保护

ART对堆内存实施了多种保护措施,防止堆溢出和其他内存安全漏洞。例如:

  • 边界检查:在访问堆内存时,ART会进行边界检查,确保不会越界访问。
  • 内存隔离:ART将堆内存划分为多个区域,不同类型的对象存储在不同的区域,防止对象之间的相互影响。
  • 内存清理:当对象被回收后,ART会清理对象占用的内存,防止内存中残留敏感数据。

art/runtime/gc/heap.hheap.cc中,定义了堆内存的保护和管理方法。例如,Heap::CheckHeap()函数用于检查堆内存的完整性:

cpp 复制代码
// art/runtime/gc/heap.cc
bool Heap::CheckHeap(bool verify_marks) {
  // 检查堆内存的完整性
  if (!CheckHeapStructure()) {
    LOG(ERROR) << "Heap structure check failed";
    return false;
  }
  // 验证对象的有效性
  if (!VerifyObjects(verify_marks)) {
    LOG(ERROR) << "Object verification failed";
    return false;
  }
  // 其他检查
  // ...
  return true;
}

通过这些内存保护技术,ART大大提高了内存的安全性,减少了因内存安全漏洞导致的系统风险。

七、内存共享与访问控制的性能优化

7.1 内存访问性能优化

ART在实现内存共享与访问控制的同时,也注重性能优化,确保这些机制不会对应用的性能产生过大影响。

7.1.1 缓存机制

ART通过缓存机制减少内存访问的开销。例如,对于JNI方法的查找和绑定,ART会缓存已查找的方法ID和函数地址,避免每次调用都进行完整的查找过程。

art/runtime/jni/jni_method_table.hjni_method_table.cc中,定义了JNI方法的缓存机制。例如,JniMethodTable::GetOrCreateMethodID()函数会先检查缓存中是否存在该方法ID:

cpp 复制代码
// art/runtime/jni/jni_method_table.cc
jmethodID JniMethodTable::GetOrCreateMethodID(jclass clazz, const char* name, const char* sig) {
  // 检查缓存中是否存在该方法ID
  std::pair<jclass, std::string> key(clazz, std::string(name) + " " + std::string(sig));
  auto it = method_id_cache_.find(key);
  if (it != method_id_cache_.end()) {
    return it->second;
  }
  // 缓存中不存在,创建新的方法ID
  jmethodID method_id = env_->GetMethodID(clazz, name, sig);
  if (method_id != nullptr) {
    method_id_cache_[key] = method_id;
  }
  return method_id;
}
7.1.2 预取技术

ART使用预取(Prefetch)技术提前将可能需要访问的内存数据加载到缓存中,减少内存访问的延迟。例如,在垃圾回收

7.2 内存共享的性能优化策略

7.2.1 共享内存区域的预分配与动态调整

在多进程共享内存场景中,ART通过预分配和动态调整策略提升性能。对于频繁使用的共享内存区域,如Zygote进程与应用进程间共享的系统类数据,ART会在系统启动阶段预分配固定大小的内存空间。在art/runtime/zygote/zygote_main.cc中,Zygote进程初始化时会根据历史数据和系统配置,估算并分配共享内存:

cpp 复制代码
// art/runtime/zygote/zygote_main.cc
void ZygoteInit(int argc, char* argv[]) {
    // 预分配共享内存用于存储预加载的类和资源
    size_t preallocated_size = CalculatePreallocationSize(); 
    if (!AllocateSharedMemory(preallocated_size)) {
        LOG(ERROR) << "Failed to preallocate shared memory";
        // 处理分配失败情况
    }
    // 其他初始化操作
    //...
}

当共享内存使用量接近阈值时,ART通过动态调整机制扩充内存。在art/runtime/memory/shared_memory_manager.cc中,实现了共享内存的动态扩容逻辑:

cpp 复制代码
// art/runtime/memory/shared_memory_manager.cc
bool SharedMemoryManager::ResizeSharedMemory(size_t new_size) {
    // 检查新大小是否合理
    if (new_size <= current_size_) {
        return true;
    }
    // 调用底层系统接口扩充内存
    if (!SystemResizeMemory(shared_fd_, new_size)) {
        LOG(ERROR) << "Failed to resize shared memory";
        return false;
    }
    current_size_ = new_size;
    return true;
}

这种预分配与动态调整策略减少了频繁内存分配与回收带来的开销,同时避免内存碎片化问题。

7.2.2 共享内存的零拷贝技术应用

为减少共享内存数据传输的性能损耗,ART引入零拷贝(Zero - Copy)技术。在进程间传递共享内存数据时,避免数据在内存中的多次复制。例如,在匿名共享内存(Ashmem)机制中,当一个进程将共享内存文件描述符传递给另一个进程时,数据实际存储位置并未改变,仅传递了内存映射的引用。在system/core/libcutils/ashmem-dev.c中,Ashmem创建和传递过程体现了零拷贝思想:

c 复制代码
// system/core/libcutils/ashmem-dev.c
int ashmem_create_region(const char *name, size_t size) {
    // 创建Ashmem区域,返回文件描述符
    int fd = open(ASHMEM_DEVICE, O_RDWR); 
    if (fd < 0) {
        return fd;
    }
    // 设置内存大小和名称
    if (ioctl(fd, ASHMEM_SET_SIZE, size) < 0 || (name && ioctl(fd, ASHMEM_SET_NAME, name) < 0)) {
        close(fd);
        return -1;
    }
    return fd;
}

接收进程通过dup()系统调用复制文件描述符,直接映射同一块物理内存,无需数据拷贝:

c 复制代码
// 接收进程复制文件描述符
int received_fd = dup(sent_fd); 
void* mapped_memory = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, received_fd, 0);

零拷贝技术大幅提升了共享内存数据传输效率,尤其适用于大数据量场景。

7.3 访问控制的性能优化实现

7.3.1 快速访问路径优化

ART为频繁的内存访问操作构建快速访问路径。对于对象字段的访问,在art/runtime/mirror/object.cc中,通过内联汇编和字段偏移预计算优化访问速度:

cpp 复制代码
// art/runtime/mirror/object.cc
template <typename T>
inline T Object::ReadField(size_t offset) const {
    // 使用内联汇编直接从内存地址读取字段值
    uintptr_t addr = reinterpret_cast<uintptr_t>(this) + offset; 
    T value;
    __asm__ volatile("ldr %0, [%1]" : "=r"(value) : "r"(addr));
    return value;
}

对于数组元素访问,通过边界检查缓存和SIMD指令优化。在art/runtime/mirror/array.cc中,GetIntArrayRegion函数利用SIMD指令一次读取多个数组元素:

cpp 复制代码
// art/runtime/mirror/array.cc
void Array::GetIntArrayRegion(int* dest, size_t start, size_t len) const {
    // 检查边界
    if (start + len > GetLength()) {
        // 处理越界
    }
    // 使用SIMD指令批量读取数据
    __asm__ volatile(
        "vld1q.u32 {q0 - q3}, [%0]"
        :
        : "r"(reinterpret_cast<const int*>(data_) + start)
        : "memory"
    );
    // 存储数据到目标数组
    //...
}

这些优化减少了访问控制检查和数据读取的开销。

7.3.2 权限检查的缓存与批量处理

ART缓存对象和内存区域的访问权限信息,减少重复检查。在art/runtime/access_control/permission_cache.cc中,实现了权限缓存机制:

cpp 复制代码
// art/runtime/access_control/permission_cache.cc
bool PermissionCache::CheckPermission(const Object* obj, int access_type) {
    // 检查缓存中是否存在权限信息
    auto it = cache_.find(obj); 
    if (it != cache_.end()) {
        return it->second & access_type;
    }
    // 缓存未命中,进行实际权限检查
    bool result = DoPermissionCheck(obj, access_type); 
    // 更新缓存
    cache_[obj] = result? access_type : 0; 
    return result;
}

对于批量内存访问操作,ART将多次权限检查合并为一次。例如在垃圾回收标记阶段,在art/runtime/gc/heap.cc中,MarkObjects函数将多个对象的访问权限检查合并:

cpp 复制代码
// art/runtime/gc/heap.cc
void Heap::MarkObjects() {
    // 批量检查对象访问权限并标记
    for (auto* obj : live_objects_) { 
        if (PermissionCache::CheckPermission(obj, kReadPermission)) {
            MarkObject(obj);
        }
    }
}

这种方式显著降低了权限检查的性能开销。

八、不同场景下的内存共享与访问控制

8.1 系统服务与应用间的内存交互

在Android系统中,系统服务(如AMS、WMS)与应用之间存在大量内存交互。以ActivityManagerService(AMS)为例,当应用启动Activity时,AMS需要与应用进程共享Intent数据、Activity生命周期信息等。ART通过Binder机制和共享内存实现高效交互。

在Binder通信过程中,数据传输涉及内存共享与访问控制。在frameworks/native/libs/binder/Binder.cpp中,Binder驱动处理数据传递时,会验证发送方和接收方的权限:

cpp 复制代码
// frameworks/native/libs/binder/Binder.cpp
status_t BinderDriver::transact(int32_t handle, uint32_t code,
                                const Parcel& data, Parcel* reply, uint32_t flags) {
    // 检查发送方权限
    if (!CheckSenderPermission()) {
        return PERMISSION_DENIED;
    }
    // 检查接收方权限
    if (!CheckReceiverPermission(handle)) {
        return PERMISSION_DENIED;
    }
    // 处理数据传输
    //...
}

对于大数据量交互,如SurfaceFlinger与应用间的图形数据传递,采用匿名共享内存(Ashmem)。SurfaceFlinger创建共享内存区域存储图形缓冲区,应用进程通过Binder获取内存文件描述符并映射到自身地址空间。在frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp中:

cpp 复制代码
// frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::createSharedBuffer(int size) {
    // 创建Ashmem共享内存
    int ashmem_fd = ashmem_create_region("SurfaceBuffer", size); 
    if (ashmem_fd < 0) {
        // 处理创建失败
    }
    // 映射内存到SurfaceFlinger进程
    void* buffer = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmem_fd, 0); 
    // 通过Binder传递文件描述符给应用进程
    SendFdToApp(ashmem_fd); 
}

这种方式既保证了数据共享的高效性,又通过权限检查确保内存访问的安全性。

8.2 多进程应用的内存协同

对于多进程应用(如包含多个Service、进程间通信频繁的应用),ART提供特殊的内存共享与访问控制方案。应用可通过android:process属性开启多个进程,进程间通过SharedPreferencesContentProvider或自定义Binder服务进行内存协同。

ContentProvider为例,在数据共享时涉及内存管理与权限控制。在android/content/ContentProvider.java中,ContentProviderquery方法处理数据请求:

java 复制代码
// android/content/ContentProvider.java
public Cursor query(Uri uri, String[] projection, String selection,
                    String[] selectionArgs, String sortOrder) {
    // 检查调用方权限
    if (!checkCallingPermission(permission)) {
        throw new SecurityException("Permission denied");
    }
    // 从共享内存或数据库获取数据
    Cursor cursor = getDataFromSharedMemory(uri); 
    if (cursor == null) {
        cursor = queryDatabase(uri);
    }
    return cursor;
}

在底层实现中,若采用共享内存存储数据,ART通过文件映射和权限位设置确保数据安全。在art/runtime/content_provider_storage.cc中:

cpp 复制代码
// art/runtime/content_provider_storage.cc
bool ContentProviderStorage::MapSharedMemory() {
    // 打开共享内存文件
    int fd = open(shared_memory_path_, O_RDONLY); 
    if (fd < 0) {
        return false;
    }
    // 映射内存并设置权限
    storage_ = mmap(nullptr, size_, PROT_READ, MAP_SHARED, fd, 0); 
    if (storage_ == MAP_FAILED) {
        close(fd);
        return false;
    }
    return true;
}

多进程应用通过这些机制实现内存协同,同时保证数据的一致性和安全性。

8.3 图形与多媒体处理中的内存管理

在图形与多媒体处理领域,如OpenGL ES渲染、音视频编解码,内存共享与访问控制至关重要。以OpenGL ES为例,应用与GPU之间通过共享内存传递图形数据。ART利用EGL(OpenGL ES for Embedded Systems)接口实现内存共享。

frameworks/native/opengl/egl/EGLDisplay.cpp中,EGL创建共享Surface时:

cpp 复制代码
// frameworks/native/opengl/egl/EGLDisplay.cpp
EGLSurface EGLDisplay::CreateSharedSurface(EGLConfig config, EGLSurface share_surface) {
    // 检查权限
    if (!CheckCreateSurfacePermission()) {
        return EGL_NO_SURFACE;
    }
    // 创建共享内存区域用于存储图形数据
    int ashmem_fd = CreateSharedMemoryForSurface(); 
    if (ashmem_fd < 0) {
        return EGL_NO_SURFACE;
    }
    // 初始化EGLSurface并关联共享内存
    EGLSurface surface = new EGLSurface(config, ashmem_fd); 
    return surface;
}

音视频编解码过程中,如MediaCodec组件,通过ByteBuffer进行内存共享。在frameworks/av/media/libmedia/MediaCodec.cpp中:

cpp 复制代码
// frameworks/av/media/libmedia/MediaCodec.cpp
status_t MediaCodec::dequeueInputBuffer(int* index) {
    // 检查输入缓冲区访问权限
    if (!CheckInputBufferPermission()) {
        return PERMISSION_DENIED;
    }
    // 获取共享内存中的输入缓冲区
    ByteBuffer* buffer = GetInputBufferFromSharedMemory(); 
    if (buffer == nullptr) {
        return NO_MEMORY;
    }
    *index = buffer->getIndex();
    return OK;
}

通过这些机制,在高吞吐量的图形与多媒体处理中,实现了高效的内存共享与严格的访问控制。

九、内存共享与访问控制的错误处理机制

9.1 内存共享错误场景与处理

在内存共享过程中,可能出现多种错误情况。例如,共享内存创建失败、文件描述符传递错误、内存映射错误等。ART针对不同错误场景设计了相应的处理策略。

当共享内存创建失败时,如ashmem_create_region返回错误,在art/runtime/memory/shared_memory_manager.cc中:

cpp 复制代码
// art/runtime/memory/shared_memory_manager.cc
bool SharedMemoryManager::CreateSharedMemory(size_t size) {
    int fd = ashmem_create_region("ART_Shared_Mem", size);
    if (fd < 0) {
        LOG(ERROR) << "Failed to create shared memory: " << strerror(errno);
        // 尝试重新创建或切换备用方案
        if (RetryCreateSharedMemory()) {
            return true;
        }
        return false;
    }
    shared_fd_ = fd;
    return true;
}

文件描述符传递错误时,在Binder通信中,frameworks/native/libs/binder/IPCThreadState.cpp会捕获错误并处理:

cpp 复制代码
// frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data,
                                 Parcel* reply, uint32_t flags) {
    // 检查文件描述符有效性
    if (data.hasFileDescriptors()) {
        for (size_t i = 0; i < data.getFileDescriptorCount(); ++i) {
            int fd = data.readFileDescriptor();
            if (fd < 0 ||!IsValidFd(fd)) {
                return BAD_VALUE;
            }
        }
    }
    // 处理数据传输
    //...
}

通过这些错误处理机制,确保内存共享出现问题时系统的稳定性。

9.2 访问控制违规处理

当发生内存访问控制违规时,如权限不足、越界访问等,ART会采取相应措施。对于权限不足的情况,在JNI调用中,art/runtime/jni/jni_env_ext.cc会抛出异常:

cpp 复制代码
// art/runtime/jni/jni_env_ext.cc
jobject JNIEnvExt::NewObject(jclass clazz, jmethodID methodID,...) {
    // 检查创建对象权限
    if (!CheckCreateObjectPermission(clazz)) {
        ThrowSecurityException("Permission denied to create object");
        return nullptr;
    }
    // 创建对象
    //...
}

对于越界访问,在数组操作中,art/runtime/mirror/array.cc会终止操作并记录错误:

cpp 复制代码
// art/runtime/mirror/array.cc
void Array::SetIntArrayRegion(const int* src, size_t start, size_t len) const {
    if (start + len > GetLength()) {
        LOG(ERROR) << "Array index out of bounds";
        // 抛出ArrayIndexOutOfBoundsException或采取其他处理
        return;
    }
    // 设置数组元素
    //...
}

此外,对于严重的访问控制违规,ART会触发进程终止机制,在art/runtime/runtime.cc中:

cpp 复制代码
// art/runtime/runtime.cc
void Runtime::HandleFatalAccessViolation() {
    LOG(ERROR) << "Fatal access violation, terminating process";
    // 记录错误日志
    LogStackTrace();
    // 终止进程
    TerminateProcess();
}

这些处理机制保障了内存访问的安全性和系统的稳定性。

十、内存共享与访问控制的未来发展趋势

10.1 新技术融合

未来,Android Runtime的内存共享与访问控制将融合更多新技术。随着人工智能和机器学习在移动端的普及,模型推理过程中需要高效的内存共享来传递中间数据。例如,TensorFlow Lite等框架与ART结合时,可能会采用更先进的内存共享技术,如基于硬件加速器(如GPU、NPU)的共享内存池,实现数据在CPU与加速器之间的零拷贝传输。

同时,量子计算的发展也可能对内存管理带来挑战与机遇。虽然短期内量子计算不会直接应用于移动端,但未来的计算架构演进可能要求ART支持更复杂的内存共享模型,以适应量子比特的存储和处理需求。

10.2 安全增强方向

在安全方面,内存共享与访问控制将进一步增强。随着物联网设备的普及,设备间的数据交互增多,内存共享的安全风险也随之增加。ART可能会引入更严格的访问控制策略,如基于属性的访问控制(ABAC),根据用户、设备、环境等多维度属性动态调整内存访问权限。

此外,对内存攻击的防护将更加深入。针对新型内存攻击手段(如侧信道攻击、内存泄漏利用攻击),ART可能会采用内存加密、动态地址随机化增强等技术。例如,对共享内存区域进行端到端加密,确保数据在传输和存储过程中的安全性;通过更频繁、更复杂的地址空间布局随机化,增加攻击者预测内存

相关推荐
_一条咸鱼_35 分钟前
Vulkan入门教程:源码级解析
android·面试·android jetpack
嘉小华38 分钟前
ThreadLocal 详解
android
前端小巷子43 分钟前
深入解析CSRF攻击
前端·安全·面试
每天开心44 分钟前
🧙‍♂️闭包应用场景之--防抖和节流
前端·javascript·面试
DoraBigHead1 小时前
小Dora 的 JavaScript 修炼日记 · Day 1:变量三兄弟与作用域迷宫
前端·javascript·面试
wkj0011 小时前
php 如何通过mysqli操作数据库?
android·数据库·php
DoraBigHead2 小时前
this 的前世今生:谁在叫我,我听谁的
前端·javascript·面试
测试开发技术3 小时前
如何在 Pytest 中调用其他用例返回的接口参数?
面试·自动化·pytest·接口·接口测试·api测试
kymjs张涛3 小时前
零一开源|前沿技术周报 #7
android·前端·ios
倔强青铜三4 小时前
苦练Python第10天:for 循环与 range() 函数
人工智能·python·面试