原创文章,谢绝转载!
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/