当前位置: 首页 > 工具软件 > Awake.app > 使用案例 >

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

百里成仁
2023-12-01

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/

原文地址:https://www.jianshu.com/p/ae4ca096201a

 类似资料: