操作系统内存管理基础

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//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进程(不被系统管理)

你可能感兴趣的:(操作系统内存管理基础)