1. 内存地址映射
Android 整体架构和实现机制如Binder、音频系统、GUI系统都与内存管理息息相关。内存管理主要涉及到以下几个方面:
虚拟内存
计算机早期,内存资源是有限的,有些电脑的内存很小,可能无法将程序完整的加载到内存中运行。
于是想了一个办法,将外部存储器的部分空间作为内存的扩展,然后由系统按照一定的算法将暂时不用的数据放在磁盘中,用到时去磁盘中加载到内存。
按需加载的思想,而且这一切步骤都由系统内核自动完成,对用户完全透明。
主要涉及到三种地址空间概念:
-
逻辑地址
也称相对地址,是程序在编译后产生的地址。分为两部分,段选择子和偏移。这和硬件内存的设计思路是相通的,有片选信号和偏移组成。程序最终是运行在物理内存上的,对于有段页式内存管理的机器,由逻辑地址转换到物理内存还需要经过两个步骤。
【逻辑地址】---分段转换机制--->【先线性地址】---- 分页机制 ---> 【物理地址】
然而实际上Linux只实现了分页机制,没有分段。
-
线性地址
逻辑地址转换后的产物,逻辑地址的段选择子中包含了段描述符,描述符记录了线性地址的基地址。基地址 + 线性偏移就得到了线性地址。
-
物理地址
是真实的物理内存地址,物理地址是以固定大小的内存块为基本的操作对象,通常是4KB,称之为页。再说回前面的,对于虚拟内存,访问时,没有物理内存中访问到的页,被认为是缺失页,此时会触发中断,由操作系统去磁盘中置换相应的页到物理内存中。就此完成了虚拟内存的功能。
内存分配与回收
对于操作系统而言,内存管理是其中重要组成部分。并且操作系统做的了,硬件无关性,且能动态分配和回收内存。对Android来说,针对Native层更多的要依靠开发者的人工管理,当然C++的智能指针是一个突破点。Java层的内存则是由JVM通过一整套系统的回收算法,统一管理。但是Java代码的编写依然需要严格的遵循使用规范,否则也可能引起内存泄漏等问题。
2. mmap(Memory Map)通信
mmap可将某个设备或文件映射到应用进程的内存空间,实现进程的直接操作,节省了R/W的拷贝过程,提高了通信效率。比如Binder通信机制,在驱动层便是使用了mmap,将parcel对象映射到共同的地址,以完成进程间通信。
具体使用方式可以参见man文档
man mmap
3. 写时拷贝技术(Copy On Write)
这是Linux中非常关键的技术,设计思想还是按需创建,多个对象起始时,共享某个资源,这时候这些对象不会拥有资源的拷贝,直到这个资源被某个对象修改。典型的使用场景是我们fork() 进程时,如果不调用execve() ,都会共享父进程的资源,调用之后子进程才会拥有自己的资源空间。
4. 内存回收机制 Android Memory Killer
Android 系统在应用进程资源并不会立即被清除,这也就导致了一个问题,内存占用将成倍增加。怎么解决这个问题?
我们知道Linux在内核态实现自己的内存监控机制,OOMKiller。当系统内存占用达到临界值时,OOMKiller就按照优先级从低到高按照优先级顺序杀掉进程。
影响因素有:
- 进程消耗的内存 |
- CPU占用时间 | ------> oom_score //proc/<PID>/oom_score
- oom_adj |
Android在OOMKiller基础上实现进一步的细分,并专门开发了一个驱动命名为 Low Memory Killer ( /drivers/staging/android/:computer_mouse: Lowmemorykiller.c )
4.1 Low Memory Killer 驱动加载过程
static struct shrinker lowmem_shrinker = {
.scan_objects = lowmem_scan,
.count_objects = lowmem_count,
.seeks = DEFAULT_SEEKS * 16
};
static int __init lowmem_init(void)
{
// 向内核线程kswapd 注册shrinker 监听回调
// 当内存页低于一定阈值,就会回调lowmem_shrinker结构体里对应的函数
register_shrinker(&lowmem_shrinker);
return 0;
}
static void __exit lowmem_exit(void)
{
unregister_shrinker(&lowmem_shrinker);
}
//定义驱动加载和卸载时候的处理函数 lowmem_init
module_init(lowmem_init)
module_exit(lowmem_exit)
驱动加载时候,注册了处理函数lowmem_init() ,处理函数运行时向内核线程注册lowmem_shrinker结构体指针,我们可以看到 这个结构体中包含了两个函数指针,分别是lowmem_scan
和 lowmem_count
,这两个函数会在内核发现系统中空闲的页数低于阈值时候被回调。其中lowmem_scan
承载了驱动的关键任务检测和回收。
4.2 lowmem_scan
检测和回收逻辑
// 代码比较长 这里筛检了主要逻辑
static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
{
// 待检测的进程
struct task_struct *tsk;
// 需要回收的进程
struct task_struct *selected = NULL;
// 根据当前状态初始化必要的变量
int array_size = ARRAY_SIZE(lowmem_adj);
int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
int other_file = global_page_state(NR_FILE_PAGES) -
global_page_state(NR_SHMEM) -
total_swapcache_pages();
//1. RCU(Read-Copy Update)上锁
rcu_read_lock();
//2.遍历所有进程
for_each_process(tsk) {
struct task_struct *p;
short oom_score_adj;
if (tsk->flags & PF_KTHREAD)
continue;
p = find_lock_task_mm(tsk);
if (!p)
continue;
//3.获取p的oom_score_adj 如果比最小阈值 还小, 那么暂时还不用杀;继续循环
oom_score_adj = p->signal->oom_score_adj;
if (oom_score_adj < min_score_adj) {
task_unlock(p);
continue;
}
tasksize = get_mm_rss(p->mm);
task_unlock(p);
//4.RSS <= 0 实际物理内存<=0; 不占内存 也暂时不杀
if (tasksize <= 0)
continue;
if (selected) {
if (oom_score_adj < selected_oom_score_adj)
continue;
if (oom_score_adj == selected_oom_score_adj &&
tasksize <= selected_tasksize)
continue;
}
//5.以上情况都没留住 那么锁定目标
selected = p;
selected_tasksize = tasksize;
selected_oom_score_adj = oom_score_adj;
}
if (selected) {
set_tsk_thread_flag(selected, TIF_MEMDIE);
//6.发送 -9 SIGNAL 消息杀掉进程
send_sig(SIGKILL, selected, 0);
}
//7.RCU(Read-Copy Update)解锁
rcu_read_unlock();
return rem;
}
核心就是一个遍历进程的循环,将进程的内存占用状态和oom_score_adj分值进行相应的比较,当前系统状态下,分数够高就会被杀掉。
4.3 adj的维护
oom_score_adj
这个值我们看到是从进程内部取的,那么adj
谁维护的呢?
在ActivityManagerService中有一个对象
// frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
OomAdjuster mOomAdjuster;
看名字是不是很有感? 没错就是这个类中维护了adj的状态,具体这里不分析了有兴趣可以取对应目录查看。
名称 | 取值 | 解释 |
---|---|---|
UNKNOWN_ADJ | 16 | 一般指将要会缓存进程,无法获取确定值 |
CACHED_APP_MAX_ADJ | 15 | 不可见进程的adj最大值 |
CACHED_APP_MIN_ADJ | 9 | 不可见进程的adj最小值 |
SERVICE_B_AD | 8 | B List中的Service(较老的、使用可能性更小) |
PREVIOUS_APP_ADJ | 7 | 上一个App的进程(往往通过按返回键) |
HOME_APP_ADJ | 6 | Home进程 |
SERVICE_ADJ | 5 | 服务进程(Service process) |
HEAVY_WEIGHT_APP_ADJ | 4 | 后台的重量级进程,system/rootdir/init.rc文件中设置 |
BACKUP_APP_ADJ | 3 | 备份进程 |
PERCEPTIBLE_APP_ADJ | 2 | 可感知进程,比如后台音乐播放 |
VISIBLE_APP_ADJ | 1 | 可见进程(Visible process) |
FOREGROUND_APP_ADJ | 0 | 前台进程(Foreground process |
PERSISTENT_SERVICE_ADJ | -11 | 关联着系统或persistent进程 |
PERSISTENT_PROC_ADJ | -12 | 系统persistent进程,比如telephony |
SYSTEM_ADJ | -16 | 系统进程 |
NATIVE_ADJ | -17 | native进程(不被系统管理) |
网友评论