操作系统内存管理基础

1. 内存地址映射

Android 整体架构和实现机制如Binder、音频系统、GUI系统都与内存管理息息相关。内存管理主要涉及到以下几个方面:

虚拟内存

计算机早期,内存资源是有限的,有些电脑的内存很小,可能无法将程序完整的加载到内存中运行。

于是想了一个办法,将外部存储器的部分空间作为内存的扩展,然后由系统按照一定的算法将暂时不用的数据放在磁盘中,用到时去磁盘中加载到内存。

按需加载的思想,而且这一切步骤都由系统内核自动完成,对用户完全透明。

主要涉及到三种地址空间概念:

  1. 逻辑地址

    也称相对地址,是程序在编译后产生的地址。分为两部分,段选择子和偏移。这和硬件内存的设计思路是相通的,有片选信号和偏移组成。程序最终是运行在物理内存上的,对于有段页式内存管理的机器,由逻辑地址转换到物理内存还需要经过两个步骤。

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

    然而实际上Linux只实现了分页机制,没有分段。

  2. 线性地址

    逻辑地址转换后的产物,逻辑地址的段选择子中包含了段描述符,描述符记录了线性地址的基地址。基地址 + 线性偏移就得到了线性地址。

  3. 物理地址

    是真实的物理内存地址,物理地址是以固定大小的内存块为基本的操作对象,通常是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就按照优先级从低到高按照优先级顺序杀掉进程。

影响因素有:

  1. 进程消耗的内存 |
  2. CPU占用时间 | ------> oom_score //proc/<PID>/oom_score
  3. 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_scanlowmem_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进程(不被系统管理)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352