美文网首页
操作系统内存管理基础

操作系统内存管理基础

作者: 知止乎尔 | 来源:发表于2020-06-12 00:24 被阅读0次

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

    相关文章

      网友评论

          本文标题:操作系统内存管理基础

          本文链接:https://www.haomeiwen.com/subject/xtywtktx.html