基于版本:Android S
0. 前言
在之前的两篇博文《Android 中app内存回收优化(一)》和 《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理,为什么叫这个名字,而不叫 AppCompact 等?在之前的两篇博文中也提到了,因为该类中还管理了一个重要功能:freezer,一个针对应用进程长期处于 Cached 状态的优化。
在之前博文《app freezer 原理 R 版本》中简单的剖析了app freeze / unfreeze 的流程,但从代码逻辑上来看,笔者觉得 R 版本上有些逻辑是存在问题的,好在S 版本中都修复了。
本文将以 R 版本的原理为基础,对 S 版本进行对比分析。
1. freezer 触发
同 R 版本中,S 版本的触发也是在 applyOomAdjLSP() 函数中调用 updateAppFreezeStateLSP() 来确认是否冻结进程:
frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
private void updateAppFreezeStateLSP(ProcessRecord app) {
// 确认该功能是否使能,如果没有使能则返回
if (!mCachedAppOptimizer.useFreezer()) {
return;
}
// S 版本新加的,确认应用是否可以豁免
if (app.mOptRecord.isFreezeExempt()) {
return;
}
// S 版本中cached进程优化相关的属性,都放到了mOptRecord中管理
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
// 如果进程处于frozen状态,但shouldNotFreeze变成true,需要解冻
if (opt.isFrozen() && opt.shouldNotFreeze()) {
mCachedAppOptimizer.unfreezeAppLSP(app);
return;
}
// 确定adj,是进入freeze 还是 unfreeze 流程
final ProcessStateRecord state = app.mState;
if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen()
&& !opt.shouldNotFreeze()) {
mCachedAppOptimizer.freezeAppAsyncLSP(app);
} else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
mCachedAppOptimizer.unfreezeAppLSP(app);
}
}
该函数与 R 版本基本逻辑差不多,稍微有些差异:
- 进程有了是否freeze 豁免的功能,当应用设置了 INSTALL_PACKAGES 的权限之后,该应用处于豁免状态,详细看 ProcessList.startProcessLocked() 函数;
- S 版本中cached进程的一些状态,统一由ProcessRecord 中的 mOptRecord 维护;
- 最后再unfreeze 调用时,不再判断app 是否处于 frozen,因为这部分逻辑判断会在 unfreeze 函数中判定,这让代码更简介,R 版本中有些多余;
2. CachedAppOptimizer.init()
对于CachedAppOptimizer 的构造调用,以及 init() 函数的触发流程,可以参考《Android 中app内存回收优化(二)》 一文第 1 节 和 第 2 节。
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
public void init() {
...
DeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
ActivityThread.currentApplication().getMainExecutor(),
mOnNativeBootFlagsChangedListener);
mAm.mContext.getContentResolver().registerContentObserver(
CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver);
synchronized (mPhenotypeFlagLock) {
...
updateUseFreezer();
...
}
}
相比较与 R 版本,这里多了两个功能:
- 新加一个 freeze_debounce_timeout 属性发生变化的 listener,当该属性变化时,会调用 updateFreezerDebounceTimeout() 进行更新;
- 新加了 cached_apps_freezer 属性值发生变化的 observer;
在 R 版本中,freeze timeout 是10min,使用的是常量。而在 S 版本中,将该值设计为可变的,用户可以通过 DeviceConfig 进行修改。当发生变化时,会调用 updateFreezerDebounceTimeout()进行更新。
另外,在 S 版本中,对 cached_apps_freezer 的值做了一个observer,及时控制 freezer 的使能。
2.1 updateUseFreezer()
private void updateUseFreezer() {
// 获取 settings中属性 cached_apps_freezer的值,同R 版本
final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),
Settings.Global.CACHED_APPS_FREEZER_ENABLED);
if ("disabled".equals(configOverride)) {
mUseFreezer = false;
} else if ("enabled".equals(configOverride)
|| DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
mUseFreezer = isFreezerSupported();
updateFreezerDebounceTimeout();
} else {
mUseFreezer = false;
}
final boolean useFreezer = mUseFreezer;
// enableFreezer() would need the global ActivityManagerService lock, post it.
mAm.mHandler.post(() -> {
if (useFreezer) {
Slog.d(TAG_AM, "Freezer enabled");
enableFreezer(true);
if (!mCachedAppOptimizerThread.isAlive()) {
mCachedAppOptimizerThread.start();
}
if (mFreezeHandler == null) {
mFreezeHandler = new FreezeHandler();
}
Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
mCompactionPriority);
} else {
Slog.d(TAG_AM, "Freezer disabled");
enableFreezer(false);
}
});
}
针对 R 版本也做了个优化,将 enableFreezer() 的处理进行异步处理,因为在 S 版本中做了一个很大的调整。详细查看下文第 4 节。
freezer 功能是否使能,用流程图说明比较清晰:
需要注意的是,在获取freezer 节点的时候,与R 版本有所不同:
- R 版本是,直接指定节点 /sys/fs/cgroup/freezer/cgroup.freeze;
- S 版本是,通过函数 getFreezerCheckPath() 向libprocessgroup 中查找 pid 所对应的节点;
3. cgroups 简介
这里不再补充,详细可以查看 R 版本或《Android 中 cgroup抽象层详解》
这里需要注意的是,R 版本中的 freezer 中,通过 /sys/fs/cgroup/freezer/cgroup.freeze 来确定是否使能 freezer,通过 /sys/fs/cgroup/freezer/cgroup.procs 来进行 frozen / unfrozen 操作。
而在 S 版本中,frozen/unfrozen 的操作通过 /sys/fs/cgroup/uid_xxx/pid_xxx/cgroup.freeze 节点。
在 S 版本中 cgroups.json 有所不同:
"Cgroups2": {
"Path": "/sys/fs/cgroup",
"Mode": "0755",
"UID": "system",
"GID": "system",
"Controllers": [
{
"Controller": "freezer",
"Path": ".",
"Mode": "0755",
"UID": "system",
"GID": "system"
}
]
}
不再出现 freezer 目录,而是直接在 /sys/fs/cgroup 目录下创建 uid_xxx/pid_xxx 目录。
详细看下面第 5.2.1 节。
4. enableFreezer()
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
public synchronized boolean enableFreezer(boolean enable) {
if (!mUseFreezer) {
return false;
}
if (enable) {
mFreezerDisableCount--;
if (mFreezerDisableCount > 0) {
return true;
} else if (mFreezerDisableCount < 0) {
Slog.e(TAG_AM, "unbalanced call to enableFreezer, ignoring");
mFreezerDisableCount = 0;
return false;
}
} else {
mFreezerDisableCount++;
if (mFreezerDisableCount > 1) {
return true;
}
}
// Override is applied immediately, restore is delayed
synchronized (mAm) {
synchronized (mProcLock) {
mFreezerOverride = !enable;
Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride);
mAm.mProcessList.forEachLruProcessesLOSP(true, process -> {
if (process == null) {
return;
}
final ProcessCachedOptimizerRecord opt = process.mOptRecord;
if (enable && opt.hasFreezerOverride()) {
freezeAppAsyncLSP(process);
opt.setFreezerOverride(false);
}
if (!enable && opt.isFrozen()) {
unfreezeAppLSP(process);
// Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
opt.setFreezerOverride(true);
}
});
}
}
return true;
}
代码与 R 版本 不同,这里通过 mAm.mProcessList.forEachLruProcessesLOSP() 对每个LRU 中的进程进行确认。
进程中引入了 mOptRecord.mFreezerOverride 属性,用以标记对某个进程是否进行 disable freezer 操作。若该值为 true,则表示进程被 disable freezer 过。此时如果再次 enable,需对进程进行 freeze 请求。
当然,CachedAppOptimizer 类中也有这样的成员变量 mFreezerOverride,这个用以控制从外部调用的 freezeAppAsyncLSP()。如果该值为 true,则表示disable freezer了,外部如果有调用 freezeAppAsyncLSP(),则不需要去处理。
5. freezeAppAsyncLSP()
void freezeAppAsyncLSP(ProcessRecord app) {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
if (opt.isPendingFreeze()) {
// Skip redundant DO_FREEZE message
return;
}
mFreezeHandler.sendMessageDelayed(
mFreezeHandler.obtainMessage(
SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
mFreezerDebounceTimeout);
opt.setPendingFreeze(true);
}
相比较 R 版本,这里做了一个保护,防止反复进行 freeze 请求。
另外,timeout 从 R 版本中的常量 FREEZE_TIMEOUT_MS 改成可变的 debounce timeout。
注意:冻结时异步操作,使用 CachedAppOptimizer类中定义的 ServiceThread 进行,而解冻是东部操作,没有通过 ServiceThread。
5.1 freeze 消息处理
public void handleMessage(Message msg) {
switch (msg.what) {
case SET_FROZEN_PROCESS_MSG:
synchronized (mAm) {
freezeProcess((ProcessRecord) msg.obj);
}
break;
case REPORT_UNFREEZE_MSG:
int pid = msg.arg1;
int frozenDuration = msg.arg2;
String processName = (String) msg.obj;
reportUnfreeze(pid, frozenDuration, processName);
break;
default:
return;
}
}
对于 freezer 一共有两个消息,REPORT_UNFREEZE_MSG 这个消息是在 unfreeze 之后进行记录的。
本文重点来看下 freeze 的消息处理,这里看到最终调用的是 freezeProcess() 函数,详细查看下一小节
5.2 freezeProcess()
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
private void freezeProcess(final ProcessRecord proc) {
int pid = proc.getPid(); // Unlocked intentionally
final String name = proc.processName;
final long unfrozenDuration;
final boolean frozen;
final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
opt.setPendingFreeze(false);
try {
// 确认进程是否存在任意的文件锁,避免不必要的 free/unfreeze操作
// 这是为了防止冻结进程持有文件锁,从而引起死锁
// 冻结成功之后,还会再次确认文件锁,如果有锁,则立即解冻
if (mProcLocksReader.hasFileLocks(pid)) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
}
return;
}
} catch (Exception e) {
Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
+ "): " + e);
return;
}
synchronized (mProcLock) {
pid = proc.getPid();
// 如果进程没有变成Cached或者不能freeze,则退出此次freeze操作
if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ
|| opt.shouldNotFreeze()) {
return;
}
// 如果freezer 处于disable状态,返回,并告知该进程
if (mFreezerOverride) {
opt.setFreezerOverride(true);
return;
}
// 已经处于frozen,或者不是一个应用进程,则退出此次 freeze操作
// pid为0,有可能进程还没有launch完成,或者进程被kill了
if (pid == 0 || opt.isFrozen()) {
// Already frozen or not a real process, either one being
// launched or one being killed
return;
}
Slog.d(TAG_AM, "freezing " + pid + " " + name);
// 在S版本中,将这部分功能提前,也是正确的行为
// 冻结 binder
// 1.如果freezer是使能,将同步发送所有的pending 交互给指定的pid;
// 2.该函数调用后,所有的binder 请求,都会被block,并返回error给发送请求的进程;
try {
if (freezeBinder(pid, true) != 0) {
rescheduleFreeze(proc, "outstanding txns");
return;
}
} catch (RuntimeException e) { //如果调用失败,则直接kill该进程
Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
mFreezeHandler.post(() -> {
synchronized (mAm) {
proc.killLocked("Unable to freeze binder interface",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
}
});
}
long unfreezeTime = opt.getFreezeUnfreezeTime();
//核心函数 setProcessFrozen(),同步冻结进程
try {
Process.setProcessFrozen(pid, proc.uid, true);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(true);
} catch (Exception e) {
Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
}
unfrozenDuration = opt.getFreezeUnfreezeTime() - unfreezeTime;
frozen = opt.isFrozen();
}
if (!frozen) {
return;
}
Slog.d(TAG_AM, "froze " + pid + " " + name);
// 将此次 freeze 记录到 event log 中
EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
// See above for why we're not taking mPhenotypeFlagLock here
if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
pid,
name,
unfrozenDuration);
}
// 确认在冻结的时候,是否收到了 TXNS_PENDING_WHILE_FROZEN的binder请求,
// 如果有有收到请求,则重新freeze 该进程(unfreeze + freeze)
try {
// post-check to prevent races
int freezeInfo = getBinderFreezeInfo(pid);
if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
synchronized (mProcLock) {
rescheduleFreeze(proc, "new pending txns");
}
return;
}
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
mFreezeHandler.post(() -> {
synchronized (mAm) {
proc.killLocked("Unable to freeze binder interface",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
}
});
}
try {
// 再次check文件锁,如果该冻结进程持有文件锁,立即unfreeze
if (mProcLocksReader.hasFileLocks(pid)) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
}
unfreezeAppLSP(proc);
}
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
unfreezeAppLSP(proc);
}
}
逻辑与R 版本有很大的不同:
- 在冻结进程之前,调用 freezeBinder(),用以冻结binder 通信;
- 在冻结进程之后,调用 getBinderFreezeInfo(),确认是否在冻结的时候有 TXNS_PENDING_WHILE_FROZEN 的binder 请求,如果有该请求,则重新freeze 该进程(unfreeze + freeze);
弄个流程图理解冻结过程:
5.2.1 setProcessFrozen()
frameworks/base/core/java/android/os/Process.java
public static final native void setProcessFrozen(int pid, int uid, boolean frozen);
frameworks/base/core/jni/android_util_Process.cpp
void android_os_Process_setProcessFrozen(
JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze)
{
bool success = true;
if (freeze) {
success = SetProcessProfiles(uid, pid, {"Frozen"});
} else {
success = SetProcessProfiles(uid, pid, {"Unfrozen"});
}
if (!success) {
signalExceptionForGroupError(env, EINVAL, pid);
}
}
此处的调用在博文《cgroup抽象层》中已经分析过,通过接口 SetProcessProfiles() 精细是 SetAttributeAction 类型的profile,最终调用 ExecuteForProcess():
system/core/libprocesscgroup/task_profiles.cpp
bool SetAttributeAction::ExecuteForProcess(uid_t, pid_t pid) const {
return ExecuteForTask(pid);
}
bool SetAttributeAction::ExecuteForTask(int tid) const {
std::string path;
if (!attribute_->GetPathForTask(tid, &path)) {
LOG(ERROR) << "Failed to find cgroup for tid " << tid;
return false;
}
if (!WriteStringToFile(value_, path)) {
PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
return false;
}
return true;
}
通过代码需要确定 attribute_->GetPathForTask():
system/core/libprocessgroup/task_profiles.cpp
bool ProfileAttribute::GetPathForTask(int tid, std::string* path) const {
std::string subgroup;
if (!controller()->GetTaskGroup(tid, &subgroup)) {
return false;
}
if (path == nullptr) {
return true;
}
if (subgroup.empty()) {
*path = StringPrintf("%s/%s", controller()->path(), file_name_.c_str());
} else {
*path = StringPrintf("%s/%s/%s", controller()->path(), subgroup.c_str(),
file_name_.c_str());
}
return true;
}
有两部分,一个是通过 controller 获取subgroup,二是进行最终path 的拼接。
下面来看下 congtroller 指向的 GetTaskGroup():
system/core/libprocessgroup/cgroup_map.cpp
bool CgroupController::GetTaskGroup(int tid, std::string* group) const {
std::string file_name = StringPrintf("/proc/%d/cgroup", tid);
std::string content;
if (!android::base::ReadFileToString(file_name, &content)) {
PLOG(ERROR) << "Failed to read " << file_name;
return false;
}
// if group is null and tid exists return early because
// user is not interested in cgroup membership
if (group == nullptr) {
return true;
}
std::string cg_tag;
if (version() == 2) {
cg_tag = "0::";
} else {
cg_tag = StringPrintf(":%s:", name());
}
size_t start_pos = content.find(cg_tag);
if (start_pos == std::string::npos) {
return false;
}
start_pos += cg_tag.length() + 1; // skip '/'
size_t end_pos = content.find('\n', start_pos);
if (end_pos == std::string::npos) {
*group = content.substr(start_pos, std::string::npos);
} else {
*group = content.substr(start_pos, end_pos - start_pos);
}
return true;
}
读取 /proc/PID/cgroup 中信息,对于 cgroup2,读取是 0:: 所在信息,在 S 版本中指定的是uid和pid 拼接,例如:
130|shift:/sys/fs/cgroup # cat /proc/2119/cgroup
4:memory:/
3:cpuset:/restricted
2:cpu:/foreground
1:blkio:/foreground
0::/uid_10083/pid_2119
最终写的是 /sys/fs/cgroup/uid_xxx/pid_xxx/cgroup.freeze 文件。
某个进程被冻结的旅程图:
6. unfreezeAppLSP()
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
void unfreezeAppLSP(ProcessRecord app) {
final int pid = app.getPid();
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
// 进程已经处于peding freeze中,移除冻结消息
if (opt.isPendingFreeze()) {
// Remove pending DO_FREEZE message
mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
opt.setPendingFreeze(false);
}
opt.setFreezerOverride(false);
// 如果进程还没有冻结,则无需做解冻处理
if (!opt.isFrozen()) {
return;
}
// 冻住的进程可以接收异步binder请求,但是不会处理,只是放入binder buffer, 过多的请求会导致buffer耗尽;
// 这里需要确认下该进程在解冻之前,进程是否在冰冻期间收到同步的binder 请求,有则kill该进程
boolean processKilled = false;
try {
int freezeInfo = getBinderFreezeInfo(pid);
if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
Slog.d(TAG_AM, "pid " + pid + " " + app.processName
+ " received sync transactions while frozen, killing");
app.killLocked("Sync transaction while in frozen state",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION, true);
processKilled = true;
}
if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {
Slog.d(TAG_AM, "pid " + pid + " " + app.processName
+ " received async transactions while frozen");
}
} catch (Exception e) {
Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + pid + " "
+ app.processName + ". Killing it. Exception: " + e);
app.killLocked("Unable to query binder frozen stats",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
processKilled = true;
}
//进程被kill 了,无需 unfreeze
if (processKilled) {
return;
}
// app.freezeUnfreezeTime记录的是上次free、unfreeze的时间
long freezeTime = opt.getFreezeUnfreezeTime();
try {
freezeBinder(pid, false);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
+ ". Killing it");
app.killLocked("Unable to unfreeze",
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
return;
}
try {
Process.setProcessFrozen(pid, app.uid, false);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(false);
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to unfreeze " + pid + " " + app.processName
+ ". This might cause inconsistency or UI hangs.");
}
if (!opt.isFrozen()) {
Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);
mFreezeHandler.sendMessage(
mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
pid,
(int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
app.processName));
}
}
逻辑比较清晰,核心处理函数是 setProcessFrozen(),详细的流程在上面第 5.2.1 节中已经分析过。
7. kernel 处理
kernel/cgroup/cgroup.c
static struct cftype cgroup_base_files[] = {
...
{
.name = "cgroup.freeze",
.flags = CFTYPE_NOT_ON_ROOT,
.seq_show = cgroup_freeze_show,
.write = cgroup_freeze_write,
},
...
};
对 /sys/fs/cgroup/uid_xxx/pid_xxx/cgroup.freeze 文件的写入触发回调函数 cgroup_freeze_write() 函数,通过当前的 kernfs 找到对应的 cgroup,将这个 cgroup 下所有进程以及子进程都 freeze。
详细的 cgroup_freeze_write() 函数,后续将补充剖析。