Android Q app内存压缩优化方案介绍

原创文章,谢绝转载!

Android Q新增了部分系统性能优化方案,这里简单学习下,本篇文章先分析app compaction。

一、愿景:

在保证后台进程尽量不被杀的基础上减少它们的内存占用。

二、思路:

AMS与Kernel层联动对满足一定条件的App进行内存压缩。
google官方样例数据:占用1.8G内存的游戏,压缩后只占700M。该功能在高端机上没有明显的卡顿和延迟。

三、源码分析

androidQ上,AMS中引入了OomAdjuster来统一管理oomadj相关逻辑。

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public ActivityManagerService(Injector injector, ServiceThread handlerThread) {
    ...
    mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
    ...
}

在AMS构造方法中实例化OomAdjuster

OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
    ...
    mAppCompact = new AppCompactor(mService);
    ...
}

在OomAdjuster构造方法中实例化AppCompactor

void initSettings() {
    mAppCompact.init();
}

初始化mAppCompact

public final void installSystemProviders() {
    ...
    mOomAdjuster.initSettings();
    ...
}

接下来看看AppCompactor这个核心类
frameworks/base/services/core/java/com/android/server/am/AppCompactor.java

public AppCompactor(ActivityManagerService am) {
    mAm = am;
    mCompactionThread = new ServiceThread("CompactionThread",
            THREAD_PRIORITY_FOREGROUND, true);
    mProcStateThrottle = new HashSet<>();
}

创建了一个loop线程,并且优先级还挺高。

/**
* Reads phenotype config to determine whether app compaction is enabled or not and
* starts the background thread if necessary.
*/
public void init() {
    DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
            ActivityThread.currentApplication().getMainExecutor(), mOnFlagsChangedListener);
    synchronized (mPhenotypeFlagLock) {
        updateUseCompaction();
        updateCompactionActions();
        updateCompactionThrottles();
        updateStatsdSampleRate();
        updateFullRssThrottle();
        updateFullDeltaRssThrottle();
        updateProcStateThrottle();
    }
    Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
            Process.THREAD_GROUP_SYSTEM);
}

做了一些初始化操作,例如::

/**
* Reads the flag value from DeviceConfig to determine whether app compaction
* should be enabled, and starts the compaction thread if needed.
*/
@GuardedBy("mPhenotypeFlagLock")
private void updateUseCompaction() {
    mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
    if (mUseCompaction && !mCompactionThread.isAlive()) {
        mCompactionThread.start();
        mCompactionHandler = new MemCompactionHandler();
    }
}

决定是否做压缩, 默认该功能没有打开。

下面来看看FW的核心压缩逻辑:
调用点在OomAdjuster的applyOomAdjLocked:

private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
        long nowElapsed) {
...
    //DeviceConfig配置决定它做压缩 同时系统已经完成booting
if (mAppCompact.useCompaction() && mService.mBooted) {
    // Cached and prev/home compaction
    if (app.curAdj != app.setAdj) {//当前adj有变化
        // Perform a minor compaction when a perceptible app becomes the prev/home app
        // Perform a major compaction when any app enters cached
        // reminder: here, setAdj is previous state, curAdj is upcoming state
        if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
                (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
                        app.curAdj == ProcessList.HOME_APP_ADJ)) {
            [mAppCompact.compactAppSome(app);](http://mAppCompact.compactAppSome(app);)
        } else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
                        || app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
                && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
                && app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
            [mAppCompact.compactAppFull(app);](http://mAppCompact.compactAppFull(app);)
        }
    } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
            && app.setAdj < ProcessList.FOREGROUND_APP_ADJ
            // Because these can fire independent of oom_adj/procstate changes, we need
            // to throttle the actual dispatch of these requests in addition to the
            // processing of the requests. As a result, there is throttling both here
            // and in AppCompactor.
            && mAppCompact.shouldCompactPersistent(app, now)) {
        [mAppCompact.compactAppPersistent(app);](http://mAppCompact.compactAppPersistent(app);)
    } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
            && app.getCurProcState()
                == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
            && mAppCompact.shouldCompactBFGS(app, now)) {
        [mAppCompact.compactAppBfgs(app);](http://mAppCompact.compactAppBfgs(app);)
    }
}
...
}

逻辑总结:

  • 可感知进程变为PREVIOUS(700)或HOME(600)进程,则执行compactAppSome,做一次minor compaction。

  • 非cache进程进入到cache区间:CACHE_MAX(999)-CACHE_MIN(900)之间,则执行compactAppFull,做一次major compaction。

  • 当前手机是非awake状态且进程优先级高于前台,并且满足shouldCompactPersistent逻辑:即当前进程从来没压缩过,或者距离上次压缩时间>10min.则执行compactAppPersistent。

  • 当前手机是非awake状态且进程有前台服务,并且满足shouldCompactBFGS逻辑,与shouldCompactPersistent一致。则执行compactAppBfgs。

对应的这几个方法:

@GuardedBy("mAm")
void compactAppSome(ProcessRecord app) {
    app.reqCompactAction = COMPACT_PROCESS_SOME;
    mPendingCompactionProcesses.add(app);
    mCompactionHandler.sendMessage(
        mCompactionHandler.obtainMessage(
            COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
}

@GuardedBy("mAm")
void compactAppFull(ProcessRecord app) {
    app.reqCompactAction = COMPACT_PROCESS_FULL;
    mPendingCompactionProcesses.add(app);
    mCompactionHandler.sendMessage(
        mCompactionHandler.obtainMessage(
            COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
}

@GuardedBy("mAm")
void compactAppPersistent(ProcessRecord app) {
    app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
    mPendingCompactionProcesses.add(app);
    mCompactionHandler.sendMessage(
            mCompactionHandler.obtainMessage(
                COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
}

@GuardedBy("mAm")
void compactAppBfgs(ProcessRecord app) {
    app.reqCompactAction = COMPACT_PROCESS_BFGS;
    mPendingCompactionProcesses.add(app);
    mCompactionHandler.sendMessage(
            mCompactionHandler.obtainMessage(
                COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
}

另外这里还有一个方法需要关注下,它的调用在AMS的finishBooting()

@GuardedBy("mAm")
void compactAllSystem() {
    if (mUseCompaction) {
        mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
                                          COMPACT_SYSTEM_MSG));
    }
}

发送消息

private final class MemCompactionHandler extends Handler {
    private MemCompactionHandler() {
        super(mCompactionThread.getLooper());
    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case COMPACT_PROCESS_MSG: {
                long start = SystemClock.uptimeMillis();
                ProcessRecord proc;
                int pid;
                String action;
                final String name;
                int pendingAction, lastCompactAction;
                long lastCompactTime;
                LastCompactionStats lastCompactionStats;
                int lastOomAdj = msg.arg1;
                int procState = msg.arg2;
                synchronized (mAm) {
                    proc = mPendingCompactionProcesses.remove(0);
                    pendingAction = proc.reqCompactAction;
                    pid = proc.pid;
                    name = proc.processName;
                    // don't compact if the process has returned to perceptible
                    // and this is only a cached/home/prev compaction
                    if ((pendingAction == COMPACT_PROCESS_SOME
                            || pendingAction == COMPACT_PROCESS_FULL)
                            && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
                        if (DEBUG_COMPACTION) {
                            Slog.d(TAG_AM,
                                    "Skipping compaction as process " + name + " is "
                                    + "now perceptible.");
                        }
                        return;
                    }
                    lastCompactAction = proc.lastCompactAction;
                    lastCompactTime = proc.lastCompactTime;
                    // remove rather than get so that insertion order will be updated when we
                    // put the post-compaction stats back into the map.
                    lastCompactionStats = mLastCompactionStats.remove(pid);
                }
                if (pid == 0) {
                    // not a real process, either one being launched or one being killed
                    return;
                }
                // basic throttling
                // use the Phenotype flag knobs to determine whether current/prevous
                // compaction combo should be throtted or not
                // Note that we explicitly don't take mPhenotypeFlagLock here as the flags
                // should very seldom change, and taking the risk of using the wrong action is
                // preferable to taking the lock for every single compaction action.
                if (lastCompactTime != 0) {
                    if (pendingAction == COMPACT_PROCESS_SOME) {
                        if ((lastCompactAction == COMPACT_PROCESS_SOME
                                && (start - lastCompactTime < mCompactThrottleSomeSome))
                                || (lastCompactAction == COMPACT_PROCESS_FULL
                                    && (start - lastCompactTime
                                            < mCompactThrottleSomeFull))) {
                            if (DEBUG_COMPACTION) {
                                Slog.d(TAG_AM, "Skipping some compaction for " + name
                                        + ": too soon. throttle=" + mCompactThrottleSomeSome
                                        + "/" + mCompactThrottleSomeFull + " last="
                                        + (start - lastCompactTime) + "ms ago");
                            }
                            return;
                        }
                    } else if (pendingAction == COMPACT_PROCESS_FULL) {
                        if ((lastCompactAction == COMPACT_PROCESS_SOME
                                && (start - lastCompactTime < mCompactThrottleFullSome))
                                || (lastCompactAction == COMPACT_PROCESS_FULL
                                    && (start - lastCompactTime
                                            < mCompactThrottleFullFull))) {
                            if (DEBUG_COMPACTION) {
                                Slog.d(TAG_AM, "Skipping full compaction for " + name
                                        + ": too soon. throttle=" + mCompactThrottleFullSome
                                        + "/" + mCompactThrottleFullFull + " last="
                                        + (start - lastCompactTime) + "ms ago");
                            }
                            return;
                        }
                    } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
                        if (start - lastCompactTime < mCompactThrottlePersistent) {
                            if (DEBUG_COMPACTION) {
                                Slog.d(TAG_AM, "Skipping persistent compaction for " + name
                                        + ": too soon. throttle=" + mCompactThrottlePersistent
                                        + " last=" + (start - lastCompactTime) + "ms ago");
                            }
                            return;
                        }
                    } else if (pendingAction == COMPACT_PROCESS_BFGS) {
                        if (start - lastCompactTime < mCompactThrottleBFGS) {
                            if (DEBUG_COMPACTION) {
                                Slog.d(TAG_AM, "Skipping bfgs compaction for " + name
                                        + ": too soon. throttle=" + mCompactThrottleBFGS
                                        + " last=" + (start - lastCompactTime) + "ms ago");
                            }
                            return;
                        }
                    }
                }
                //这里action最终只分为了两种:
                //some对应"file"
                //full、presistent、bfgs都对应"all"
                switch (pendingAction) {
                    case COMPACT_PROCESS_SOME:
                        action = mCompactActionSome;
                        break;
                    // For the time being, treat these as equivalent.
                    case COMPACT_PROCESS_FULL:
                    case COMPACT_PROCESS_PERSISTENT:
                    case COMPACT_PROCESS_BFGS:
                        action = mCompactActionFull;
                        break;
                    default:
                        action = COMPACT_ACTION_NONE;
                        break;
                }
                if (COMPACT_ACTION_NONE.equals(action)) {
                    return;
                }
                if (mProcStateThrottle.contains(procState)) {
                    if (DEBUG_COMPACTION) {
                        Slog.d(TAG_AM, "Skipping full compaction for process " + name
                                + "; proc state is " + procState);
                    }
                    return;
                }
                long[] rssBefore = Process.getRss(pid);
                long anonRssBefore = rssBefore[2];
                if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
                        && rssBefore[3] == 0) {
                    if (DEBUG_COMPACTION) {
                        Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid
                                + " with no memory usage. Dead?");
                    }
                    return;
                }
                if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
                    if (mFullAnonRssThrottleKb > 0L
                            && anonRssBefore < mFullAnonRssThrottleKb) {
                        if (DEBUG_COMPACTION) {
                            Slog.d(TAG_AM, "Skipping full compaction for process "
                                    + name + "; anon RSS is too small: " + anonRssBefore
                                    + "KB.");
                        }
                        return;
                    }
                    if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
                        long[] lastRss = lastCompactionStats.getRssAfterCompaction();
                        long absDelta = Math.abs(rssBefore[1] - lastRss[1])
                                + Math.abs(rssBefore[2] - lastRss[2])
                                + Math.abs(rssBefore[3] - lastRss[3]);
                        if (absDelta <= mFullDeltaRssThrottleKb) {
                            if (DEBUG_COMPACTION) {
                                Slog.d(TAG_AM, "Skipping full compaction for process "
                                        + name + "; abs delta is too small: " + absDelta
                                        + "KB.");
                            }
                            return;
                        }
                    }
                }
                // Now we've passed through all the throttles and are going to compact, update
                // bookkeeping.
                switch (pendingAction) {
                    case COMPACT_PROCESS_SOME:
                        mSomeCompactionCount++;
                        break;
                    case COMPACT_PROCESS_FULL:
                        mFullCompactionCount++;
                        break;
                    case COMPACT_PROCESS_PERSISTENT:
                        mPersistentCompactionCount++;
                        break;
                    case COMPACT_PROCESS_BFGS:
                        mBfgsCompactionCount++;
                        break;
                    default:
                        break;
                }
                try {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
                            + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
                            + ": " + name);
                    long zramFreeKbBefore = Debug.getZramFreeKb();
                    //在/proc/pid/reclaim文件中写入节点值
                    FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
                    fos.write(action.getBytes());
                    fos.close();
                    long[] rssAfter = Process.getRss(pid);
                    long end = SystemClock.uptimeMillis();
                    long time = end - start;
                    long zramFreeKbAfter = Debug.getZramFreeKb();
                    EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
                            rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
                            rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
                            rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
                            lastCompactAction, lastCompactTime, lastOomAdj, procState,
                            zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
                    // Note that as above not taking mPhenoTypeFlagLock here to avoid locking
                    // on every single compaction for a flag that will seldom change and the
                    // impact of reading the wrong value here is low.
                    if (mRandom.nextFloat() < mStatsdSampleRate) {
                        StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
                                rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
                                rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
                                lastCompactAction, lastCompactTime, lastOomAdj,
                                ActivityManager.processStateAmToProto(procState),
                                zramFreeKbBefore, zramFreeKbAfter);
                    }
                    synchronized (mAm) {
                        proc.lastCompactTime = end;
                        proc.lastCompactAction = pendingAction;
                    }
                    if (action.equals(COMPACT_ACTION_FULL)
                            || action.equals(COMPACT_ACTION_ANON)) {
                        mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
                    }
                } catch (Exception e) {
                    // nothing to do, presumably the process died
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                }
                break;
            }
            case COMPACT_SYSTEM_MSG: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem");
                compactSystem();//native方法
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            }
        }
    }
}

compactSystem是个native方法,对应JNI如下:

frameworks/base/services/core/jni/com_android_server_am_AppCompactor.cpp
// This performs per-process reclaim on all processes belonging to non-app UIDs.
// For the most part, these are non-zygote processes like Treble HALs, but it
// also includes zygote-derived processes that run in system UIDs, like bluetooth
// or potentially some mainline modules. The only process that should definitely
// not be compacted is system_server, since compacting system_server around the
// time of BOOT_COMPLETE could result in perceptible issues.
static void com_android_server_am_AppCompactor_compactSystem(JNIEnv *, jobject) {
    std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
    struct dirent* current;
    while ((current = readdir(proc.get()))) {
        if (current->d_type != DT_DIR) {
            continue;
        }
        // don't compact system_server, rely on persistent compaction during screen off
        // in order to avoid mmap_sem-related stalls
        if (atoi(current->d_name) == getpid()) {
            continue;
        }
        std::string status_name = StringPrintf("/proc/%s/status", current->d_name);
        struct stat status_info;
        if (stat(status_name.c_str(), &status_info) != 0) {
            // must be some other directory that isn't a pid
            continue;
        }
        // android.os.Process.FIRST_APPLICATION_UID
        if (status_info.st_uid >= 10000) {
            continue;
        }
        std::string reclaim_path = StringPrintf("/proc/%s/reclaim", current->d_name);
        WriteStringToFile(std::string("all"), reclaim_path); //写all
    }
}

FW逻辑总结:

  • 应用进程:
    一系列判断和赋值之后,在/proc/pid/reclaim文件中写入action,
    这里action主要看两种:
    some对应"file"
    full、presistent、bfgs都对应"all"

  • 非应用进程(除system_server):
    在/proc/pid/reclaim文件中写入”all"

FW最终写了节点,那么接下来看看kernel的逻辑:

kernel/msm-4.19/fs/proc/base.c
#ifdef CONFIG_PROCESS_RECLAIM
   REG("reclaim", 0200, proc_reclaim_operations),
#endif

注册节点执行proc_reclaim_operations

kernel/msm-4.19/fs/proc/task_mmu.c
const struct file_operations proc_reclaim_operations = {

   .write    = reclaim_write,

   .llseek       = noop_llseek,

};

在节点write操作后,对应会触发reclaim_write

static ssize_t reclaim_write(struct file *file, const char __user *buf,
            size_t count, loff_t *ppos)
{
   struct task_struct *task;//进程
   char buffer[200];
   struct mm_struct *mm;//内存
   struct vm_area_struct *vma;//虚拟内存区域
   enum reclaim_type type;//回收类型: RECLAIM_FILE, RECLAIM_ANON, RECLAIM_ALL, RECLAIM_RANGE,
   char *type_buf;
   struct mm_walk reclaim_walk = {};
   unsigned long start = 0;//起始
   unsigned long end = 0;//结尾
   struct reclaim_param rp;
   int ret;
   memset(buffer, 0, sizeof(buffer));//为新申请内存进行初始化
   if (count > sizeof(buffer) - 1)
      count = sizeof(buffer) - 1;
   if (copy_from_user(buffer, buf, count))
      return -EFAULT;
   //从FW传进来的内容看:目前只有file 和 all两种,这里就是解析对应的回收类型。
   type_buf = strstrip(buffer);
   if (!strcmp(type_buf, "file"))
      type = RECLAIM_FILE;//文件页
   else if (!strcmp(type_buf, "anon"))
      type = RECLAIM_ANON;//匿名页
   else if (!strcmp(type_buf, "all"))
      type = RECLAIM_ALL;
   else if (isdigit(*type_buf))
      type = RECLAIM_RANGE;
   else
      goto out_err;
   if (type == RECLAIM_RANGE) {
      char *token;
      unsigned long long len, len_in, tmp;
      token = strsep(&type_buf, " ");
      if (!token)
         goto out_err;
      tmp = memparse(token, &token);
      if (tmp & ~PAGE_MASK || tmp > ULONG_MAX)
         goto out_err;
      start = tmp;
      token = strsep(&type_buf, " ");
      if (!token)
         goto out_err;
      len_in = memparse(token, &token);
      len = (len_in + ~PAGE_MASK) & PAGE_MASK;
      if (len > ULONG_MAX)
         goto out_err;
      /*
       * Check to see whether len was rounded up from small -ve
       * to zero.
       */
      if (len_in && !len)
         goto out_err;
      end = start + len;
      if (end < start)
         goto out_err;
   }
   task = get_proc_task(file->f_path.dentry->d_inode);//获取进程
   if (!task)
      return -ESRCH;
   mm = get_task_mm(task);//获取进程的内存struct
   if (!mm)
      goto out;
   [reclaim_walk.mm](http://reclaim_walk.mm) = mm;
   reclaim_walk.pmd_entry = reclaim_pte_range;//最终触发回收
   rp.nr_to_reclaim = INT_MAX;
   rp.nr_reclaimed = 0;
   reclaim_walk.private = &rp;
   down_read(&mm->mmap_sem);//读信号量
   if (type == RECLAIM_RANGE) {
      vma = find_vma(mm, start);
      while (vma) {
         if (vma->vm_start > end)
            break;
         if (is_vm_hugetlb_page(vma))
            continue;
         rp.vma = vma;
         ret = walk_page_range(max(vma->vm_start, start),
               min(vma->vm_end, end),
               &reclaim_walk);
         if (ret)
            break;
         vma = vma->vm_next;
      }
   } else {
      //遍历当前进程所占用的虚拟地址
      for (vma = mm->mmap; vma; vma = vma->vm_next) {
         if (is_vm_hugetlb_page(vma))
            continue;
         if (type == RECLAIM_ANON && vma->vm_file)//anon对应回收匿名页
            continue;
         if (type == RECLAIM_FILE && !vma->vm_file)//file对应回收文件页
            continue;
         rp.vma = vma;
         //walk_page_range的功能就是遍历页表,并调用回调函数进行处理,回调函数都是定义在mm_walk中。
         ret = walk_page_range(vma->vm_start, vma->vm_end,
            &reclaim_walk);
         if (ret)
            break;
      }
   }
   flush_tlb_mm(mm);
   up_read(&mm->mmap_sem);
   mmput(mm);
out:
   put_task_struct(task);
   return count;
out_err:
   return -EINVAL;
}

walk_page_range最终会回调到:reclaim_walk.pmd_entry,而它会触发执行reclaim_pte_range

#ifdef CONFIG_PROCESS_RECLAIM
int reclaim_pte_range(pmd_t *pmd, unsigned long addr,
            unsigned long end, struct mm_walk *walk)
{
   struct reclaim_param *rp = walk->private;
   struct vm_area_struct *vma = rp->vma;
   pte_t *pte, ptent;
   spinlock_t *ptl;
   struct page *page;
   LIST_HEAD(page_list);
   int isolated;
   int reclaimed;
   split_huge_pmd(vma, addr, pmd);
   if (pmd_trans_unstable(pmd) || !rp->nr_to_reclaim)
      return 0;
cont:
   isolated = 0;
   pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
   for (; addr != end; pte++, addr += PAGE_SIZE) {
      ptent = *pte;
      if (!pte_present(ptent))
         continue;
      page = vm_normal_page(vma, addr, ptent);
      if (!page)
         continue;
      if (page_mapcount(page) != 1)
         continue;
      if (isolate_lru_page(page))
         continue;
      /* MADV_FREE clears pte dirty bit and then marks the page
       * lazyfree (clear SwapBacked). Inbetween if this lazyfreed page
       * is touched by user then it becomes dirty.  PPR in
       * shrink_page_list in try_to_unmap finds the page dirty, marks
       * it back as PageSwapBacked and skips reclaim. This can cause
       * isolated count mismatch.
       */
      if (PageAnon(page) && !PageSwapBacked(page)) {
         putback_lru_page(page);
         continue;
      }

      list_add(&page->lru, &page_list);
      inc_node_page_state(page, NR_ISOLATED_ANON +
            page_is_file_cache(page));
      isolated++;
      rp->nr_scanned++;
      if ((isolated >= SWAP_CLUSTER_MAX) || !rp->nr_to_reclaim)
         break;
   }
   pte_unmap_unlock(pte - 1, ptl);
   //最终调用reclaim_pages_from_list来触发回收
   reclaimed = reclaim_pages_from_list(&page_list, vma);
   rp->nr_reclaimed += reclaimed;
   rp->nr_to_reclaim -= reclaimed;
   if (rp->nr_to_reclaim < 0)
      rp->nr_to_reclaim = 0;
   if (rp->nr_to_reclaim && (addr != end))
      goto cont;
   cond_resched();
   return (rp->nr_to_reclaim == 0) ? -EPIPE : 0;
}

最终调用:

#ifdef CONFIG_PROCESS_RECLAIM
unsigned long reclaim_pages_from_list(struct list_head *page_list,
               struct vm_area_struct *vma)
{
   struct scan_control sc = {
      .gfp_mask = GFP_KERNEL,
      .priority = DEF_PRIORITY,
      .may_writepage = 1,
      .may_unmap = 1,
      .may_swap = 1,
      .target_vma = vma,
   };
   unsigned long nr_reclaimed;
   struct page *page;
   list_for_each_entry(page, page_list, lru)
      ClearPageActive(page);
   nr_reclaimed = shrink_page_list(page_list, NULL, &sc,
         TTU_IGNORE_ACCESS, NULL, true);
   while (!list_empty(page_list)) {
      page = lru_to_page(page_list);
      list_del(&page->lru);
      dec_node_page_state(page, NR_ISOLATED_ANON +
            page_is_file_cache(page));
      putback_lru_page(page);
   }
   return nr_reclaimed;
}
#endif

到shrink_page_list,这就很明显了,根据lru触发page页回收,后面就不继续跟了。

进程的内存管理

总结:
app compaction 是以进程为单位触发文件页、匿名页回收的内存优化策略,上层FW提供优化的进程依据。该策略在kernel很早就已经有了,只是到Android Q才开始在FW上层做逻辑,AOSP该功能目前是默认关闭的,并且策略稍显简单,可以研究下自定义一些策略。

对应kernel patch:
Michan Kim
https://lore.kernel.org/patchwork/patch/688100/

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

推荐阅读更多精彩内容