对冗余挑拣重点,对重点深入补充,输出结构清晰的精简版
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 设备节点。
<