Android Runtime内存映射(mmap)与虚拟内存管理原理(50)

133 阅读26分钟

码字不易,请大佬们点点关注,谢谢~

一、内存映射与虚拟内存管理概述

Android Runtime(ART)的内存映射(mmap)与虚拟内存管理是保障应用程序高效运行的核心机制。内存映射通过mmap系统调用,将文件或设备数据映射到进程的虚拟地址空间,实现高效的数据访问;虚拟内存管理则通过页表、缺页中断等技术,在有限的物理内存中支持大规模的虚拟地址空间。这些机制在源码中主要分布于art/runtime/memory/目录,理解其原理对于优化应用性能、解决内存相关问题具有重要意义。

二、虚拟内存管理基础架构

2.1 核心组件构成

ART虚拟内存管理由多个关键组件协同工作:

  • 内存映射管理器(Memory Mapping Manager):位于art/runtime/memory/memory_mapping.cc,负责处理mmapmunmap等内存映射操作,管理虚拟地址与物理内存的映射关系。
// MemoryMapping类管理内存映射操作
class MemoryMapping {
public:
    // 执行内存映射操作
    static void* MapMemory(size_t size, int prot, int flags, int fd, off_t offset) {
        // 调用系统mmap函数进行内存映射
        void* result = mmap(nullptr, size, prot, flags, fd, offset);
        if (result == MAP_FAILED) {
            PLOG(ERROR) << "Failed to map memory";
            return nullptr;
        }
        return result;
    }

    // 解除内存映射
    static int UnmapMemory(void* addr, size_t size) {
        int result = munmap(addr, size);
        if (result == -1) {
            PLOG(ERROR) << "Failed to unmap memory";
        }
        return result;
    }
};
  • 页表管理器(Page Table Manager):在art/runtime/memory/page_table_manager.cc中实现,负责维护虚拟地址到物理地址的映射表(页表),并处理页表的创建、更新和查询操作。
// PageTableManager类管理页表
class PageTableManager {
public:
    // 创建新的页表
    PageTable* CreatePageTable() {
        PageTable* page_table = new PageTable();
        // 初始化页表项
        for (size_t i = 0; i < kNumPageTableEntries; ++i) {
            page_table->entries_[i] = nullptr;
        }
        return page_table;
    }

    // 根据虚拟地址查询页表项
    PageTableEntry* LookupPageTableEntry(PageTable* page_table, void* virtual_address) {
        size_t index = CalculatePageTableIndex(virtual_address);
        return page_table->entries_[index];
    }

private:
    // 计算虚拟地址对应的页表项索引
    size_t CalculatePageTableIndex(void* virtual_address) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(virtual_address);
        return addr / kPageSize % kNumPageTableEntries;
    }
};
  • 缺页中断处理程序(Page Fault Handler):在art/runtime/memory/page_fault_handler.cc中定义,当访问未映射或未加载到物理内存的虚拟地址时,触发缺页中断并执行相应处理逻辑。

2.2 关键数据结构

虚拟内存管理依赖多种数据结构:

  • 页表(Page Table):用于存储虚拟页到物理页的映射关系。在art/runtime/memory/page_table.h中定义:
// PageTable类表示页表
class PageTable {
public:
    PageTableEntry* entries_[kNumPageTableEntries];  // 页表项数组
};

// PageTableEntry结构体表示页表项
struct PageTableEntry {
    void* physical_address;  // 物理页地址
    bool is_present;  // 页面是否在物理内存中
    bool is_dirty;  // 页面是否被修改
    bool is_readable;  // 页面是否可读
    bool is_writable;  // 页面是否可写
};
  • 内存区域(Memory Region):用于描述一段连续的虚拟地址空间及其属性,在art/runtime/memory/memory_region.h中定义:
// MemoryRegion类表示内存区域
class MemoryRegion {
public:
    MemoryRegion(void* start, void* end, int prot)
        : start_(start), end_(end), prot_(prot) {}

    void* start() const { return start_; }  // 区域起始地址
    void* end() const { return end_; }  // 区域结束地址
    int prot() const { return prot_; }  // 区域保护属性(可读、可写等)

private:
    void* start_;
    void* end_;
    int prot_;
};

这些数据结构为虚拟内存的高效管理提供了基础支持。

三、内存映射(mmap)原理与实现

3.1 mmap系统调用流程

mmap系统调用在art/runtime/memory/memory_mapping.cc中通过封装实现:

void* MemoryMapping::MapMemory(size_t size, int prot, int flags, int fd, off_t offset) {
    // 参数检查
    if (size == 0) {
        return MAP_FAILED;
    }
    // 调用系统mmap函数
    void* result = mmap(nullptr, size, prot, flags, fd, offset);
    if (result == MAP_FAILED) {
        // 记录错误日志
        PLOG(ERROR) << "mmap failed: size=" << size << ", prot=" << prot << ", flags=" << flags << ", fd=" << fd << ", offset=" << offset;
    }
    return result;
}

mmap调用流程如下:

  1. 参数解析:检查映射大小、保护属性、标志位、文件描述符和偏移量的合法性。
  2. 虚拟地址分配:操作系统为进程分配一段未使用的虚拟地址空间。
  3. 建立映射关系:在页表中创建虚拟页与文件数据块或物理内存的映射条目。
  4. 返回结果:将分配的虚拟地址返回给调用者。

3.2 内存映射类型与应用场景

mmap支持多种映射类型,适用于不同场景:

  • 文件映射(File Mapping):将文件数据映射到虚拟内存,实现高效的文件读写。在art/runtime/dex/file_mapping.cc中用于加载DEX文件:
// 加载DEX文件到内存
std::unique_ptr<DexFile> LoadDexFile(const std::string& file_path) {
    int fd = open(file_path.c_str(), O_RDONLY);
    if (fd < 0) {
        return nullptr;
    }
    // 使用mmap映射文件
    void* dex_memory = MemoryMapping::MapMemory(0, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    if (dex_memory == MAP_FAILED) {
        return nullptr;
    }
    // 创建DexFile对象
    return std::unique_ptr<DexFile>(new DexFile(dex_memory));
}
  • 匿名映射(Anonymous Mapping):不关联文件,用于分配进程的堆内存、栈内存等。在art/runtime/gc/heap.cc中用于堆内存初始化:
bool Heap::AllocateBaseMemory() {
    // 计算初始堆内存大小
    size_t initial_size = CalculateInitialHeapSize();
    // 使用匿名mmap分配内存
    void* base_memory = MemoryMapping::MapMemory(initial_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (base_memory == MAP_FAILED) {
        return false;
    }
    base_address_ = reinterpret_cast<uint8_t*>(base_memory);
    end_address_ = base_address_ + initial_size;
    return true;
}
  • 共享映射(Shared Mapping):多个进程共享同一段内存,用于进程间通信。在art/runtime/ipc/shared_memory.cc中实现:
// 创建共享内存区域
SharedMemory* CreateSharedMemory(size_t size) {
    // 使用共享标志创建mmap
    void* memory = MemoryMapping::MapMemory(size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (memory == MAP_FAILED) {
        return nullptr;
    }
    return new SharedMemory(memory, size);
}

四、虚拟内存地址空间布局

4.1 进程虚拟地址空间划分

每个Android进程的虚拟地址空间被划分为多个区域,在art/runtime/memory/process_memory_layout.cc中定义:

// 定义进程虚拟地址空间布局
class ProcessMemoryLayout {
public:
    ProcessMemoryLayout() {
        // 栈空间
        stack_start_ = reinterpret_cast<void*>(kStackAddress);
        stack_end_ = stack_start_ - kStackSize;

        // 堆空间
        heap_start_ = nullptr;
        heap_end_ = nullptr;

        // 代码段
        code_start_ = reinterpret_cast<void*>(kCodeAddress);
        code_end_ = code_start_ + kCodeSize;

        // 数据段
        data_start_ = reinterpret_cast<void*>(kDataAddress);
        data_end_ = data_start_ + kDataSize;
    }

    void* stack_start() const { return stack_start_; }
    void* stack_end() const { return stack_end_; }
    void* heap_start() const { return heap_start_; }
    void* heap_end() const { return heap_end_; }
    void* code_start() const { return code_start_; }
    void* code_end() const { return code_end_; }
    void* data_start() const { return data_start_; }
    void* data_end() const { return data_end_; }

private:
    void* stack_start_;
    void* stack_end_;
    void* heap_start_;
    void* heap_end_;
    void* code_start_;
    void* code_end_;
    void* data_start_;
    void* data_end_;
    static const size_t kStackSize = 8 * 1024 * 1024;  // 8MB栈空间
    static const size_t kCodeSize = 1 * 1024 * 1024;  // 1MB代码段
    static const size_t kDataSize = 2 * 1024 * 1024;  // 2MB数据段
    static const uintptr_t kStackAddress = 0xbe000000;
    static const uintptr_t kCodeAddress = 0x00000000;
    static const uintptr_t kDataAddress = 0x01000000;
};

主要区域包括:

  • 栈(Stack):存储函数调用栈帧、局部变量等,向下增长。
  • 堆(Heap):用于动态内存分配,向上增长。
  • 代码段(Text Segment):存储可执行代码。
  • 数据段(Data Segment):存储全局变量和静态变量。

4.2 内存区域管理与保护

ART通过MemoryRegion类管理虚拟地址空间中的各个区域,并设置保护属性:

class MemoryRegionManager {
public:
    MemoryRegionManager() {
        // 初始化代码段区域
        code_region_ = MemoryRegion(code_start_, code_end_, PROT_READ | PROT_EXEC);
        // 初始化数据段区域
        data_region_ = MemoryRegion(data_start_, data_end_, PROT_READ | PROT_WRITE);
        // 初始化堆区域
        heap_region_ = MemoryRegion(nullptr, nullptr, PROT_READ | PROT_WRITE);
        // 初始化栈区域
        stack_region_ = MemoryRegion(stack_end_, stack_start_, PROT_READ | PROT_WRITE);
    }

    // 检查地址是否在指定区域内
    bool IsAddressInRegion(void* address, const MemoryRegion& region) const {
        return address >= region.start() && address < region.end();
    }

    // 检查地址是否具有指定访问权限
    bool HasAccessPermission(void* address, int prot) const {
        // 根据地址查找对应的区域
        MemoryRegion region = GetRegionForAddress(address);
        return (region.prot() & prot) == prot;
    }

private:
    MemoryRegion code_region_;
    MemoryRegion data_region_;
    MemoryRegion heap_region_;
    MemoryRegion stack_region_;

    MemoryRegion GetRegionForAddress(void* address) const {
        // 根据地址返回对应的内存区域
    }
};

通过设置保护属性(如只读、读写、可执行),防止非法内存访问,保障进程安全性。

五、页表与地址转换

5.1 页表结构与工作原理

页表是虚拟内存管理的核心数据结构,用于将虚拟地址转换为物理地址。在art/runtime/memory/page_table.cc中:

// 虚拟地址转换为物理地址
void* PageTableManager::TranslateVirtualAddress(PageTable* page_table, void* virtual_address) {
    PageTableEntry* entry = LookupPageTableEntry(page_table, virtual_address);
    if (entry == nullptr || !entry->is_present) {
        // 页面未映射或不在物理内存中,触发缺页中断
        TriggerPageFault(virtual_address);
        return nullptr;
    }
    // 计算物理地址
    size_t offset = reinterpret_cast<uintptr_t>(virtual_address) % kPageSize;
    return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(entry->physical_address) + offset);
}

// 触发缺页中断
void PageTableManager::TriggerPageFault(void* virtual_address) {
    // 调用缺页中断处理程序
    PageFaultHandler::HandlePageFault(virtual_address);
}

地址转换过程如下:

  1. 计算页表索引:根据虚拟地址计算对应的页表项索引。
  2. 查询页表项:从页表中获取对应页表项。
  3. 检查页面状态:若页面存在且有效,计算物理地址;否则触发缺页中断。

5.2 多级页表优化

为减少页表占用的内存空间,ART采用多级页表结构。在art/runtime/memory/multi_level_page_table.cc中:

// 二级页表结构示例
class TwoLevelPageTable {
public:
    TwoLevelPageTable() {
        // 初始化一级页表
        level1_table_ = new PageTableEntry[kLevel1TableSize];
        for (size_t i = 0; i < kLevel1TableSize; ++i) {
            level1_table_[i].physical_address = nullptr;
            level1_table_[i].is_present = false;
        }
    }

    // 查找二级页表项
    PageTableEntry* LookupEntry(void* virtual_address) {
        // 计算一级页表索引
        size_t level1_index = CalculateLevel1Index(virtual_address);
        PageTableEntry* level1_entry = &level1_table_[level1_index];
        if (!level1_entry->is_present) {
            return nullptr;
        }
        // 计算二级页表索引
        size_t level2_index = CalculateLevel2Index(virtual_address);
        PageTable* level2_table = reinterpret_cast<PageTable*>(level1_entry->physical_address);
        return &level2_table->entries_[level2_index];
    }

private:
    PageTableEntry* level1_table_;
    static const size_t kLevel1TableSize = 1024;
    static const size_t kLevel2TableSize = 1024;

    size_t CalculateLevel1Index(void* virtual_address) {
        // 计算一级页表索引
    }

    size_t CalculateLevel2Index(void* virtual_address) {
        // 计算二级页表索引
    }
};

多级页表通过分层映射,仅加载需要的页表项,减少内存开销,尤其适用于大地址空间场景。

六、缺页中断处理机制

6.1 缺页中断触发条件

缺页中断在以下情况触发:

  1. 访问未映射的虚拟地址:当进程访问未通过mmap分配或映射的虚拟地址时。
  2. 页面未加载到物理内存:虚拟地址对应的页面在页表中标记为“不在物理内存”。 在art/runtime/memory/page_fault_handler.cc中:
void PageFaultHandler::HandlePageFault(void* virtual_address) {
    // 检查虚拟地址是否合法
    if (!IsValidVirtualAddress(virtual_address)) {
        // 非法地址,终止进程
        TerminateProcess();
        return;
    }
    // 尝试分配物理页面并建立映射
    if (!AllocateAndMapPage(virtual_address)) {
        // 分配失败,抛出内存不足异常
        ThrowOutOfMemoryError();
    }
}

bool PageFaultHandler::IsValidVirtualAddress(void* virtual_address) {
    // 检查地址是否在进程虚拟地址空间范围内
}

bool PageFaultHandler::AllocateAndMapPage(void* virtual_address) {
    // 分配物理页面
    void* physical_page = AllocatePhysicalPage();
    if (physical_page == nullptr) {
        return false;
    }
    // 更新
bool PageFaultHandler::AllocateAndMapPage(void* virtual_address) {
    // 分配物理页面
    void* physical_page = AllocatePhysicalPage();
    if (physical_page == nullptr) {
        return false;
    }
    // 更新页表,建立虚拟地址到物理地址的映射
    PageTable* page_table = GetCurrentProcessPageTable();
    size_t index = CalculatePageTableIndex(virtual_address);
    PageTableEntry* entry = &page_table->entries_[index];
    entry->physical_address = physical_page;
    entry->is_present = true;
    entry->is_dirty = false;
    entry->is_readable = true;
    entry->is_writable = true;
    return true;
}

6.2 缺页中断处理流程

缺页中断处理在 art/runtime/memory/page_fault_handler.cc 中有着严谨的流程,具体如下:

  1. 保存现场:当缺页中断发生时,系统首先保存当前进程的执行状态,包括寄存器值、程序计数器等信息,以便中断处理完成后能恢复进程执行 。虽然 Android 系统源码中未专门针对此步骤独立成函数,但在底层中断处理逻辑中,会通过硬件或操作系统底层机制自动完成相关寄存器状态的压栈操作。
  2. 中断判断与处理
void PageFaultHandler::HandlePageFault(void* virtual_address) {
    // 检查虚拟地址所属区域
    MemoryRegion region = MemoryRegionManager::GetRegionForAddress(virtual_address);
    if (region.start() == nullptr) {
        // 非法区域访问,记录错误日志并终止进程
        LOG(ERROR) << "Invalid memory access at address: " << virtual_address;
        TerminateProcess();
        return;
    }
    // 判断是否为文件映射导致的缺页
    if (IsFileBackedMapping(virtual_address)) {
        // 从文件中读取数据到物理页面
        LoadDataFromFile(virtual_address);
    } else {
        // 匿名映射或其他情况,分配新的物理页面
        if (!AllocateAndMapPage(virtual_address)) {
            // 内存分配失败,触发OOM处理
            HandleOutOfMemory();
        }
    }
}

在上述代码中,IsFileBackedMapping 函数用于判断虚拟地址对应的内存映射是否关联文件。如果是文件映射导致的缺页,LoadDataFromFile 函数会根据文件偏移量,将对应的数据读取到新分配的物理页面中。对于匿名映射等其他情况,则通过 AllocateAndMapPage 函数分配物理页面并建立映射。若内存分配失败,HandleOutOfMemory 函数会触发内存不足的处理逻辑,如终止后台进程、尝试释放内存等。 3. 恢复现场:完成页面加载或映射建立后,系统恢复之前保存的进程执行状态,将寄存器值、程序计数器等信息从栈中弹出,使进程从发生缺页中断的位置继续执行 。同样,此步骤在底层中断返回机制中自动实现,无需上层代码显式处理。

6.3 缺页中断优化策略

为减少缺页中断对系统性能的影响,ART 采用多种优化策略:

  • 预取(Prefetching):在 art/runtime/memory/prefetching.cc 中实现,通过分析程序的内存访问模式,提前将可能访问到的页面加载到物理内存中。
class MemoryPrefetcher {
public:
    void PrefetchPages(void* start_address, size_t num_pages) {
        for (size_t i = 0; i < num_pages; ++i) {
            void* address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(start_address) + i * kPageSize);
            // 尝试提前分配并映射页面,但不实际访问
            AllocateAndMapPage(address);
        }
    }
};

预取策略适用于顺序访问内存的场景,如遍历大型数组或文件读取,能有效降低后续访问时的缺页中断概率。

  • 页面置换算法优化:当物理内存不足时,需要选择合适的页面置换出去,为新页面腾出空间。ART 采用改进的 LRU(最近最少使用)算法,在 art/runtime/memory/page_replacement.cc 中实现。
class PageReplacementPolicy {
public:
    void* SelectPageToReplace() {
        // 遍历页表,找到最近最少使用的页面
        PageTable* page_table = GetCurrentProcessPageTable();
        PageTableEntry* least_used_entry = nullptr;
        time_t least_used_time = std::numeric_limits<time_t>::max();
        for (size_t i = 0; i < kNumPageTableEntries; ++i) {
            PageTableEntry* entry = &page_table->entries_[i];
            if (entry->is_present && entry->last_access_time < least_used_time) {
                least_used_entry = entry;
                least_used_time = entry->last_access_time;
            }
        }
        return least_used_entry != nullptr ? least_used_entry->physical_address : nullptr;
    }
};

该算法通过记录页面的最后访问时间,优先置换长时间未被访问的页面,提高内存使用效率。

七、内存映射与虚拟内存的协同工作

7.1 mmap 与页表的交互

内存映射操作与页表紧密协作,在 art/runtime/memory/memory_mapping.ccart/runtime/memory/page_table_manager.cc 的代码交互中得以体现。 当执行 mmap 操作时,不仅会分配虚拟地址空间,还会在页表中创建相应的映射条目:

void* MemoryMapping::MapMemory(size_t size, int prot, int flags, int fd, off_t offset) {
    void* virtual_address = mmap(nullptr, size, prot, flags, fd, offset);
    if (virtual_address != MAP_FAILED) {
        // 为新映射的虚拟地址范围创建页表项
        PageTableManager manager;
        PageTable* page_table = GetCurrentProcessPageTable();
        size_t end_index = CalculatePageTableIndex(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(virtual_address) + size));
        for (size_t i = CalculatePageTableIndex(virtual_address); i <= end_index; ++i) {
            PageTableEntry* entry = &page_table->entries_[i];
            entry->is_present = false;  // 初始时页面未加载到物理内存
            entry->is_dirty = false;
            entry->is_readable = (prot & PROT_READ) != 0;
            entry->is_writable = (prot & PROT_WRITE) != 0;
        }
    }
    return virtual_address;
}

上述代码在 mmap 成功分配虚拟地址后,根据映射的大小和范围,在页表中初始化对应的页表项。此时页面状态为未加载到物理内存(is_present = false),后续当进程访问这些虚拟地址触发缺页中断时,再进行物理页面的分配和映射建立。

而当进程访问已映射的虚拟地址时,页表负责将虚拟地址转换为物理地址:

void* PageTableManager::TranslateVirtualAddress(PageTable* page_table, void* virtual_address) {
    PageTableEntry* entry = LookupPageTableEntry(page_table, virtual_address);
    if (entry == nullptr || !entry->is_present) {
        // 触发缺页中断
        PageFaultHandler::HandlePageFault(virtual_address);
        // 重新查询页表项,因为缺页中断处理后可能已建立映射
        entry = LookupPageTableEntry(page_table, virtual_address);
    }
    // 计算物理地址
    size_t offset = reinterpret_cast<uintptr_t>(virtual_address) % kPageSize;
    return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(entry->physical_address) + offset);
}

在地址转换过程中,如果页表项不存在或页面未在物理内存中,会触发缺页中断,由缺页中断处理机制完成物理页面的加载和映射更新,之后再次查询页表完成地址转换。

7.2 内存映射对虚拟内存管理的影响

内存映射操作直接影响虚拟内存的布局和使用:

  • 改变地址空间布局:每次 mmap 都会在虚拟地址空间中新增一段连续的区域,无论是文件映射还是匿名映射,都需要操作系统合理分配虚拟地址,确保不与已有的区域冲突。例如,在 Android 应用启动时,通过 mmap 加载 DEX 文件、动态库等,会在虚拟地址空间中为这些模块分配相应的区域,在 art/runtime/process_startup.cc 中可以看到相关操作:
void LoadAppLibraries() {
    // 加载DEX文件
    std::unique_ptr<DexFile> dex_file = LoadDexFile("app.dex");
    // 加载动态库
    for (const std::string& lib_path : GetLibraryPaths()) {
        void* lib_address = MemoryMapping::MapMemory(0, PROT_READ | PROT_EXEC, MAP_PRIVATE, open(lib_path.c_str(), O_RDONLY), 0);
        if (lib_address != MAP_FAILED) {
            // 记录库的加载地址等信息
            RegisterLoadedLibrary(lib_address);
        }
    }
}
  • 影响内存分配策略:大量的内存映射操作可能导致虚拟地址空间碎片化,影响后续的内存分配。例如,当多次使用 mmap 分配小尺寸的内存区域后,虚拟地址空间中会出现许多不连续的空闲区域。为应对这种情况,ART 的内存分配器在 art/runtime/gc/allocator/allocator.cc 中会采用更复杂的策略,如寻找合适的空闲区域、合并相邻的空闲区域等,以提高内存分配的成功率。
void* Allocator::Allocate(size_t size) {
    // 优先从空闲链表中查找合适的空闲块
    FreeList* free_list = GetFreeListForSize(size);
    if (free_list != nullptr && free_list->HasFreeBlock(size)) {
        return free_list->AllocateBlock(size);
    }
    // 若空闲链表中无合适块,尝试合并相邻空闲块
    MergeAdjacentFreeBlocks();
    free_list = GetFreeListForSize(size);
    if (free_list != nullptr && free_list->HasFreeBlock(size)) {
        return free_list->AllocateBlock(size);
    }
    // 仍无合适块,请求堆扩展或通过mmap分配新区域
    return RequestHeapGrowthOrMmap(size);
}

此外,内存映射还会影响虚拟内存管理中的页面置换策略,因为不同映射类型(如文件映射和匿名映射)的页面在置换时的优先级和处理方式不同 。对于文件映射的页面,在物理内存不足时,可以将其内容写回文件后置换;而匿名映射的页面则需要更谨慎处理,避免数据丢失。

八、内存映射与虚拟内存的安全机制

8.1 访问权限控制

ART 通过内存映射的保护属性和页表中的权限标志位,实现对内存访问的严格控制。 在进行 mmap 操作时,可以指定内存区域的保护属性(prot 参数),如 PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)等,在 art/runtime/memory/memory_mapping.cc 中有明确体现:

void* MemoryMapping::MapMemory(size_t size, int prot, int flags, int fd, off_t offset) {
    // 调用系统mmap函数并传递保护属性
    void* result = mmap(nullptr, size, prot, flags, fd, offset);
    return result;
}

这些保护属性会被记录在页表项中:

void PageTableManager::CreatePageTableEntry(void* virtual_address, void* physical_address, int prot) {
    PageTable* page_table = GetCurrentProcessPageTable();
    size_t index = CalculatePageTableIndex(virtual_address);
    PageTableEntry* entry = &page_table->entries_[index];
    entry->physical_address = physical_address;
    entry->is_present = true;
    entry->is_dirty = false;
    entry->is_readable = (prot & PROT_READ) != 0;
    entry->is_writable = (prot & PROT_WRITE) != 0;
    entry->is_executable = (prot & PROT_EXEC) != 0;
}

当进程访问内存时,页表管理器会检查页表项中的权限标志位与访问操作是否匹配:

bool PageTableManager::CheckAccessPermission(PageTable* page_table, void* virtual_address, int access_type) {
    PageTableEntry* entry = LookupPageTableEntry(page_table, virtual_address);
    if (entry == nullptr ||!entry->is_present) {
        return false;
    }
    if (access_type == ACCESS_READ &&!entry->is_readable) {
        return false;
    }
    if (access_type == ACCESS_WRITE &&!entry->is_writable) {
        return false;
    }
    if (access_type == ACCESS_EXEC &&!entry->is_executable) {
        return false;
    }
    return true;
}

如果访问操作违反了权限设置,系统会触发段错误(Segmentation Fault),终止进程的非法访问行为,保障系统安全。

8.2 防止内存泄漏与非法访问

为防止内存泄漏,ART 结合垃圾回收机制和内存映射的生命周期管理。对于匿名映射的内存区域,当对应的对象或资源不再被引用时,垃圾回收机制会触发内存释放操作,通过 munmap 解除内存映射并释放物理内存 。在 art/runtime/gc/heap.cc 中垃圾回收的相关逻辑里,会检查对象所占用的内存区域是否通过 mmap 分配,并进行相应处理:

void Heap::FreeObject(Object* object) {
    // 获取对象占用的内存区域
    MemoryRegion region = object->GetMemoryRegion();
    if (region.start() != nullptr) {
        // 解除内存映射
        MemoryMapping::UnmapMemory(region.start(), region.end() - region.start());
    }
    // 其他对象释放相关操作
    //...
}

对于文件映射,当文件关闭或不再需要映射时,同样会调用 munmap 释放内存。

为防止非法访问,除了上述的访问权限控制外,ART 还通过地址空间布局随机化(ASLR,Address Space Layout Randomization)技术增加攻击难度。在 Android 系统启动阶段,会随机分配进程虚拟地址空间中各个区域的起始地址,使得攻击者难以预测和利用特定的内存地址。虽然在 ART 源码中没有直接体现 ASLR 的核心实现(其主要在操作系统内核层面完成),但 ART 的内存管理机制会适应这种随机化布局,确保内存映射和地址转换的正确性。

九、内存映射与虚拟内存管理的性能优化

9.1 减少内存映射开销

内存映射操作本身存在一定的开销,包括系统调用开销、页表更新开销等。为减少这些开销,ART 采用以下策略:

  • 批量映射:在加载多个文件或分配大块连续内存时,尽量采用批量 mmap 操作,减少系统调用次数。例如,在加载应用的多个动态库时,art/runtime/process_startup.cc 中可以先统计所有库的大小和相关参数,然后一次性调用 mmap 分配足够的虚拟地址空间,再分别进行库文件到该空间的映射 。
void LoadMultipleLibraries(const std::vector<std::string>& lib_paths) {
    size_t total_size = CalculateTotalLibrarySize(lib_paths);
    void* base_address = MemoryMapping::MapMemory(total_size, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_FIXED, -1, 0);
    if (base_address != MAP_FAILED) {
        size_t offset = 0;
        for (const std::string& lib_path : lib_paths) {
            size_t lib_size = GetLibrarySize(lib_path);
            void* lib_address = base_address + offset;
            // 将库文件映射到指定地址
            MemoryMapping::MapMemory(lib_size, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_FIXED, open(lib_path.c_str(), O_RDONLY), 0, lib_address);
            offset += lib_size;
        }
    }
}
  • 复用映射区域:对于一些频繁使用且大小固定的内存区域,如线程栈空间,可以在进程启动时一次性分配并复用,避免每次创建线程时都进行 mmap 操作。在 art/runtime/thread.cc 中创建线程时:
Thread* CreateThread() {
    static void* stack_base = MemoryMapping::MapMemory(kStackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (stack_base == MAP_FAILED) {
        return nullptr;
    }
    Thread* thread = new Thread(stack_base, stack_base + kStackSize);
    return thread;
}

通过复用已映射的栈空间,减少了内存映射开销,提高线程创建效率。

9.2 优化虚拟内存地址转换

虚拟内存地址转换的效率直接影响内存访问速度,ART 从多个方面进行优化:

  • TLB(Translation Lookaside Buffer,转换后备缓冲区)利用:TLB 是 CPU 中用于缓存最近使用的页表项的高速缓存,能显著加速地址转换。ART 通过减少页表项的频繁更新和合理布局内存访问模式,提高 TLB 命中率。在源码层面,虽然不会直接操作 TLB,但内存管理策略会间接影响其性能。例如,当进程按顺序访问连续的内存区域时,TLB 更容易命中,因此在内存分配和映射时,尽量保证数据的连续性。在 art/runtime/gc/allocator/allocator.cc 中,对于连续内存分配请求,会优先从连续的空闲内存块中获取:
void* Allocator::AllocateContinuous(size_t size) {
    FreeList* large_free_list = GetFreeListForLargeSize(size);
    if (large_free_list != nullptr) {
        FreeBlock* current = large_free_list->GetHead();
        while (current != nullptr) {
            if (current->size_ >= size) {
                if (IsContiguous(current)) {
                    return large_free_list->AllocateBlock(size);
                }
            }
            current = current->next_;
        }
    }
    return nullptr;
}
bool Allocator::IsContiguous(FreeBlock* block) {
    FreeBlock* prev = GetPreviousFreeBlock(block);
    FreeBlock* next = block->next_;
    if (prev != nullptr) {
        if (reinterpret_cast<uint8_t*>(prev->data_) + prev->size_ != reinterpret_cast<uint8_t*>(block->data_)) {
            return false;
        }
    }
    if (next != nullptr) {
        if (reinterpret_cast<uint8_t*>(block->data_) + block->size_ != reinterpret_cast<uint8_t*>(next->data_)) {
            return false;
        }
    }
    return true;
}
  • 页表结构优化:如前文所述,采用多级页表结构能减少页表占用的内存空间,同时提高地址转换效率。在访问大地址空间时,多级页表无需一次性加载整个页表,仅按需加载相关的页表层级。以二级页表为例,在 art/runtime/memory/multi_level_page_table.cc 中,通过两次索引查找即可定位到具体的页表项,相较于单级页表在大地址空间下的大量查找操作,效率大幅提升:
PageTableEntry* TwoLevelPageTable::LookupEntry(void* virtual_address) {
    size_t level1_index = CalculateLevel1Index(virtual_address);
    PageTableEntry* level1_entry = &level1_table_[level1_index];
    if (!level1_entry->is_present) {
        return nullptr;
    }
    size_t level2_index = CalculateLevel2Index(virtual_address);
    PageTable* level2_table = reinterpret_cast<PageTable*>(level1_entry->physical_address);
    return &level2_table->entries_[level2_index];
}

9.3 内存压缩技术应用

在 Android 设备物理内存紧张时,内存压缩技术能有效提升系统性能。ART 结合 Linux 内核的内存压缩机制,在必要时对不常访问的内存页面进行压缩,释放物理内存供其他进程使用。在 art/runtime/memory/memory_compression.cc 中实现了相关的触发和管理逻辑:

class MemoryCompressor {
public:
    void MonitorMemoryUsage() {
        while (true) {
            size_t free_memory = GetFreePhysicalMemory();
            if (free_memory < kLowMemoryThreshold) {
                TriggerMemoryCompression();
            }
            std::this_thread::sleep_for(std::chrono::seconds(5));
        }
    }
    void TriggerMemoryCompression() {
        PageTable* page_table = GetCurrentProcessPageTable();
        for (size_t i = 0; i < kNumPageTableEntries; ++i) {
            PageTableEntry* entry = &page_table->entries_[i];
            if (entry->is_present &&!entry->is_dirty && entry->last_access_time < kLongTimeThreshold) {
                void* physical_page = entry->physical_address;
                CompressPage(physical_page);
                entry->is_present = false;
                entry->compressed_data = GetCompressedData(physical_page);
            }
        }
    }
    void* DecompressPage(void* compressed_data) {
        // 解压缩页面数据,分配新的物理页面并写入数据
        void* new_physical_page = AllocatePhysicalPage();
        if (new_physical_page!= nullptr) {
            char* decompressed_data = Decompress(compressed_data);
            memcpy(new_physical_page, decompressed_data, kPageSize);
            free(decompressed_data);
        }
        return new_physical_page;
    }
private:
    static const size_t kLowMemoryThreshold = 1024 * 1024 * 100; // 100MB
    static const time_t kLongTimeThreshold = 60 * 60; // 1小时
};

当系统可用物理内存低于阈值时,TriggerMemoryCompression 函数会扫描页表,选择长时间未访问且未修改的页面进行压缩,并将压缩后的数据存储起来,同时标记页表项为非驻留状态。当后续再次访问这些页面时,通过 DecompressPage 函数进行解压缩,恢复页面数据,确保应用正常运行。

十、内存映射与虚拟内存管理的演进与未来趋势

10.1 新型内存硬件的适配

随着新型内存硬件(如 NVM,非易失性内存)的发展,Android Runtime 的内存映射与虚拟内存管理需要进行适配。NVM 具有非易失性、字节寻址等特性,与传统 DRAM 有较大差异。未来,ART 可能会在 art/runtime/memory/nvm_memory_mapping.cc 中新增针对 NVM 的内存映射接口和管理逻辑:

class NvmMemoryMapper {
public:
    void* MapNvmMemory(size_t size, int prot, int flags) {
        // 调用特定的 NVM 映射函数,可能需要与内核驱动交互
        void* result = NvmMap(size, prot, flags);
        if (result == MAP_FAILED) {
            PLOG(ERROR) << "Failed to map NVM memory";
            return nullptr;
        }
        // 初始化 NVM 页表项,设置非易失性等特殊属性
        InitializeNvmPageTableEntries(result, size);
        return result;
    }
    int UnmapNvmMemory(void* addr, size_t size) {
        // 调用 NVM 解除映射函数
        int result = NvmUnmap(addr, size);
        if (result == -1) {
            PLOG(ERROR) << "Failed to unmap NVM memory";
        }
        // 清理对应的 NVM 页表项
        CleanupNvmPageTableEntries(addr, size);
        return result;
    }
private:
    void InitializeNvmPageTableEntries(void* start, size_t size) {
        PageTable* page_table = GetCurrentProcessPageTable();
        size_t end_index = CalculatePageTableIndex(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(start) + size));
        for (size_t i = CalculatePageTableIndex(start); i <= end_index; ++i) {
            PageTableEntry* entry = &page_table->entries_[i];
            entry->is_present = true;
            entry->is_dirty = false;
            entry->is_readable = (prot & PROT_READ) != 0;
            entry->is_writable = (prot & PROT_WRITE) != 0;
            entry->is_nonvolatile = true; // 设置 NVM 页面的非易失性标志
        }
    }
    void CleanupNvmPageTableEntries(void* start, size_t size) {
        // 清除 NVM 相关的页表项设置
    }
};

此外,虚拟内存管理中的页表结构和缺页中断处理也需要针对 NVM 的特性进行调整,例如在页面置换时,考虑 NVM 页面的持久化需求,避免不必要的数据写入。

10.2 智能化内存管理

未来,借助机器学习和人工智能技术,Android Runtime 的内存管理将更加智能。可以在 art/runtime/memory/ml_memory_manager.cc 中构建基于机器学习的内存管理模型:

class MlMemoryManager {
public:
    MlMemoryManager() {
        // 加载预训练的内存管理模型
        std::unique_ptr<tflite::FlatBufferModel> model = 
            tflite::FlatBufferModel::BuildFromFile("memory_management_model.tflite");
        tflite::InterpreterBuilder(*model, resolver_)(&interpreter_);
        interpreter_->AllocateTensors();
    }
    void* AllocateMemory(size_t size, int type) {
        // 提取内存分配特征,如请求大小、时间、进程状态等
        std::vector<float> features = ExtractAllocationFeatures(size, type);
        // 设置模型输入
        SetInputTensorData(features);
        // 运行模型进行预测
        interpreter_->Invoke();
        // 获取预测结果,选择最佳的内存分配策略
        std::vector<float> output = GetOutputTensorData();
        int strategy_index = static_cast<int>(output[0]);
        if (strategy_index == 0) {
            return AllocateFromPool(size);
        } else if (strategy_index == 1) {
            return MemoryMapping::MapMemory(size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        }
        return nullptr;
    }
private:
    std::vector<float> ExtractAllocationFeatures(size_t size, int type) {
        // 从系统状态和请求信息中提取特征
        std::vector<float> features;
        features.push_back(static_cast<float>(size));
        features.push_back(static_cast<float>(type));
        // 添加其他特征,如当前内存使用率、进程优先级等
        features.push_back(static_cast<float>(GetCurrentMemoryUsage()));
        features.push_back(static_cast<float>(GetCurrentProcessPriority()));
        return features;
    }
    void SetInputTensorData(const std::vector<float>& data) {
        TfLiteTensor* input_tensor = interpreter_->input(0);
        float* input_data = tflite::GetTensorData<float>(input_tensor);
        for (size_t i = 0; i < data.size(); ++i) {
            input_data[i] = data[i];
        }
    }
    std::vector<float> GetOutputTensorData() {
        TfLiteTensor* output_tensor = interpreter_->output(0);
        return std::vector<float>(tflite::GetTensorData<float>(output_tensor),
                                  tflite::GetTensorData<float>(output_tensor) + output_tensor->bytes / sizeof(float));
    }
    tflite::ops::builtin::BuiltinOpResolver resolver_;
    std::unique_ptr<tflite::Interpreter> interpreter_;
};

通过分析历史内存使用数据和应用行为模式,模型可以预测内存需求,提前进行内存映射或释放,动态调整内存分配策略,优化虚拟内存布局,从而显著提升系统整体性能和资源利用率。

10.3 跨设备内存协同管理

在物联网(IoT)和边缘计算场景下,未来 Android 设备可能需要与周边设备进行内存协同管理。ART 可以引入跨设备内存映射机制,在 art/runtime/memory/cross_device_memory_mapping.cc 中实现设备间的内存共享和数据交换:

class CrossDeviceMemoryMapper {
public:
    void* MapRemoteMemory(DeviceId device_id, size_t size, int prot, int flags) {
        // 通过网络协议与远程设备建立连接,请求内存映射
        Connection* conn = EstablishConnection(device_id);
        if (conn == nullptr) {
            return MAP_FAILED;
        }
        // 发送内存映射请求并获取虚拟地址
        void* virtual_address = SendMemoryMapRequest(conn, size, prot, flags);
        if (virtual_address == MAP_FAILED) {
            CloseConnection(conn);
            return MAP_FAILED;
        }
        // 在本地页表中建立映射关系
        CreateLocalPageTableEntries(virtual_address, size);
        return virtual_address;
    }
    int UnmapRemoteMemory(void* addr, size_t size) {
        // 发送解除映射请求
        int result = SendMemoryUnmapRequest(addr, size);
        if (result == -1) {
            return -1;
        }
        // 清理本地页表项
        CleanupLocalPageTableEntries(addr, size);
        return 0;
    }
private:
    Connection* EstablishConnection(DeviceId device_id) {
        // 实现与远程设备的网络连接建立逻辑
    }
    void* SendMemoryMapRequest(Connection* conn, size_t size, int prot, int flags) {
        // 通过网络发送内存映射请求并处理响应
    }
    int SendMemoryUnmapRequest(void* addr, size_t size) {
        // 通过网络发送解除映射请求并处理响应
    }
    void CreateLocalPageTableEntries(void* start, size_t size) {
        // 在本地页表中创建远程内存的映射条目
    }
    void CleanupLocalPageTableEntries(void* start, size_t size) {
        // 清理本地页表中与远程内存相关的条目
    }
};

这种跨设备内存映射机制可以实现设备间的资源共享,例如在计算资源紧张时,将部分内存密集型任务的数据存储在周边设备的内存中,提升整体系统的运行效率和响应速度 。