细读《深入理解 Android 内核设计思想》(二)内存管理

本文深入探讨了Android内核的内存管理,包括虚拟内存的工作原理,如逻辑地址、线性地址和物理地址的转换,以及Copy on Write技术。此外,文章还介绍了Android特有的Low Memory Killer机制,它在系统内存紧张时,通过评分系统决定优先级较低的进程进行杀掉。同时,讲解了Ashmem驱动和MemoryFile原理,展示了Android如何实现进程间内存共享。通过这些知识,有助于理解Android系统的内存管理策略及其在处理大数据和进程保活问题上的优势。
摘要由CSDN通过智能技术生成

对冗余挑拣重点,对重点深入补充,输出结构清晰的精简版

1. 操作系统内存管理基础
虚拟内存
内存分配与回收
mmap
Copy on Write
2. Android 内存管理
Low Memory Killer
Ashmem 驱动
MemoryFile 原理
3. 总结

操作系统内存管理基础

不论什么操作系统,内存管理都是绝对的重点和难点。内存管理旨在为系统中所有 Task 提供稳定可靠的内存分配、释放和保护机制。你可能会疑问,学习 Android 系统有必要了解 Linux Kernel 的内存管理机制吗?

是的!不论是 Android 的音频系统、GUI 系统,还是 Binder 的实现机理等,都是和内存管理息息相关的。

虚拟内存

虚拟内存就是当内存资源不足时,借用硬盘中的一部分的空间,充当内存使用。系统会挑选优先级低的内存数据放入硬盘,后续若要用到硬盘中的数据,系统会产生一次缺页中断,然后把数据交换回内存中。

要理解虚拟内存机制,就要理解三种地址空间,分别是逻辑地址、线性地址和物理地址:

1.逻辑地址(Logical Address)
逻辑地址是程序编译后产生的地址,也称为相对地址,由两部分组成:

  • 段选择子(Segment Selector):描述逻辑地址所处的段
  • Offset:描述所在段内的偏移值

2.线性地址(Linear Address)
线性地址是由逻辑地址经过分段机制转换后得到的。

大致转换过程为:通过段选择子确定段的基地址,然后结合 Offset 得到线性地址。

3.物理地址(Physical Address)
物理地址就是指机器真实的物理内存地址,任何操作系统,最终都要通过物理地址来访问内存。

若系统开启了分页机制,则在得到线性地址后需要通过分页机制转换后,才能得到物理地址。

简单来说,由逻辑地址得到物理地址过程如下:

  • 逻辑地址 -> 分段机制转换 -> 线性地址 -> 分页机制转换 -> 物理地址

内存分配与回收

内存的分配与回收是操作系统的重要组成部分,需要解决的核心问题包括:

  • 操作系统应保证应用程序的硬件无关性,硬件差异不能体现在应用程序上
  • 内存划分的区域、分配粒度、最小单位,管理区分已使用和未使用的内存,回收等等
  • 优化内存碎片,考虑整体机制的高效性

mmap

mmap(Memory Map) 可以将某个设备或文件映射到应用进程的内存空间中,这样应用程序访问这块内存,相当于直接对设备/文件读写,不再需要 read、write 等 IO 操作。

mmap 函数如下:

//映射成功返回0,否则返回错误码
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
  • addr:指文件/设备应该映射到进程空间的哪个起始地址
  • len:指被映射到进程空间的内存块大小
  • prot:指定被映射内存的访问权限,包括 PROT_READ(可读)、PROT_WRITE(可写) 等
  • flags:指定程序对内存块所做改变造成的影响,包括 MAP_SHARED(保存到文件) 等
  • fd:被映射到进程空间的文件描述符
  • offset:指定从文件的哪一部分开始映射

源码见 http://androidxref.com/9.0.0_r3/xref/bionic/libc/bionic/mmap.cpp 。mmap 可用于跨进程通信,Linux Kernel 和 Android 中就频繁的用到了这个函数,比如 Android 的 Binder 驱动,下面分析 MemoryFile 原理时还会提到这个函数。

Copy on Write

Copy on Write(写时拷贝) 是指如果有多个调用者要请求同一资源,他们会获取到相同的指向这一资源的指针,直到某个调用者需修改资源时,系统才会复制一份副本给该调用者,而其他调用者仍使用最初的资源。

如果调用者不需要修改资源,就不会建立副本,多个调用者共享读取同一份资源。

Linux 的 fork() 函数就是 Copy on Write 的,实际开销很小,主要是给子进程创建进程描述符等,并且推迟甚至免除了数据拷贝操作。比如 fork() 后子进程需立即调用 exec() 装载新程序到进程的内存空间,即不需要父进程的任何数据,这种情况 Copy on Write 技术就避免了不必要的数据拷贝,从而提升了运行速度。

Android 内存管理

Low Memory Killer

Linux Kernel 有自己的内存监控机制,即 OOMKiller。当系统的可用内存达到临界值时,OOMKiller 就会按照优先级从低到高杀掉进程。优先级该如何衡量呢?OOMKiller 会综合进程当前消耗内存、进程占用 CPU 时间、进程类型等因素,对进程实时评分。分值存储在 /proc/{PID}/oom_score 中,可通过 cat 命令查看。分值越低的进程,优先级越高,被杀死的概率越小。

基于 Linux 内核 OOMKiller 的核心思想,Android 系统拓展出了自己的内存监控体系,相比 Linux 达到临界值才触发,Android 实现了不同梯级的 Killer。Android 系统为此开发了专门的驱动,名为 Low Memory Killer,源码在内核的 /drivers/staging/android/Lowmemorykiller.c 中。

Lowmemorykiller.c 中有如下定义:

static int lowmem_adj[6] = {
   0, 1, 6, 12};
static int lowmem_adj_size = 4; //页大小
static size_t lowmem_minfree[6] = {
    //元素使用时以 lowmem_adj_size 为单位
    3 * 512, //6MB
    2 * 1024, //8MB
    4 * 1024, //16MB
    16 * 1024//64MB
};

lowmem_minfree 定义了可用内存容量对应的不同梯级。lowmem_adj 与 lowmem_minfree 中的梯级一一对应,表示处于某梯级时需要被处理的 adj 值。adj 值用来描述进程的优先级,取值范围为 -17~15,数字越小表示进程优先级越高,被杀死的概率越小。

比如当可用内存低于 64MB 时,即 lowmem_minfree 第 4 梯级,对应于 lowmem_adj 的 12,那就会清理掉优先级低于 12(即 adj>12)的进程。

上面这两个数组中梯级的定义只是系统的预定义值,Android 系统还提供了相应的文件供我们修改这两组值,路径为:

/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree

可以在 init.rc(系统启动时由 init 进程解析的一个脚本) 中,这样修改:

write /sys/module/lowmemorykiller/parameters/adj        0, 8
write /sys/module/lowmemorykiller/parameters/minfree 1024, 4096

另外 ActivityManagerService 中有一个 updateOomLevels 方法也是通过修改这两个文件来实现的,AMS 在运行时会根据当前的系统配置自动调整 adj 和 minfree,以尽可能适配不同的硬件设备。

了解了 Low Memory Killer 的梯级规则后,来看下 Android 进程的 adj 值含义:

ADJ 说明
HIDDEN_APP_MAX_AD = 15 只运行了不可见 Activity 的进程
HIDDEN_APP_MIN_ADJ = 9 只运行了不可见 Activity 的进程
SERVICE_B_ADJ = 8 B list of Service
PREVIOUS_APP_ADJ = 7 用户的上一个产生交互的进程
HOME_APP_ADJ = 6 Launcher 进程
SERVICE_ADJ = 5 当前运行了 application service 的进程
BACKUP_APP_ADJ = 4 用于承载 backup 相关操作的进程
HEAVY_WEIGHT_APP_ADJ = 3 重量级应用程序进程
PERCEPTIBLE_APP_ADJ = 2 能被用户感觉但不可见,如后台运行的音乐播放器
VISIBLE_APP_ADJ = 1 有前台可见的 Activity
FOREGROUND_APP_ADJ = 0 当前正在前台运行与用户交互的进程
PERSISTENT_PROC_ADJ = -12 Persistent 性质的进程,如 telephony
SYSTEM_ADJ = -16 系统进程

除了表格中系统的评定标准,有没有办法改变某一进程的 adj 值呢?和修改上面的 adj、minfree 梯级类似,进程的 adj 值也可以通过写文件的方式来修改,路径为 /proc/{PID}/oom_adj,比如 init.rc 中:

write /proc/1/oom_adj -16

另外还可以在 AndroidManifest.xml 中给 application 添加 “android:persistent=true” 属性。

Ashmem 驱动

Anonymous Shared Memory 匿名共享内存是 Android 特有的内存共享机制,它可以将指定的物理内存分别映射到各个进程自己的虚拟地址空间中,从而便捷的实现进程间内存共享,Ashmem 的实现依赖 Ashmem 设备节点。

<
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值