基于版本:Android R
0. 前言
在之前的两篇博文《Android 中app内存回收优化(一)》和 《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理,为什么叫这个名字,而不叫 AppCompact 等?在之前的两篇博文中也提到了,因为该类中还管理了一个重要功能:freezer,一个针对应用进程长期处于 Cached 状态的优化。
本文将继续分析 CachedAppOptimizer 类另一个功能 freezer。
1. Freezer 触发
在《Android oom_adj 更新原理(二)》中详细剖析了 OomAdjuster.applyOomAdjLocked() 函数,在 oom_adj 发生变化之后会重新 compute 然后在 apply, 在该函数中就是通过调用 updateAppFreezeStateLocked(app) 来确认是否冻结进程。
1.1 updateAppFreezeStateLocked()
frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java void updateAppFreezeStateLocked(ProcessRecord app) { // 确定该功能是使能的 if (!mCachedAppOptimizer.useFreezer()) { return; } // 如果该进程处于 frozen状态,但sholudNoFreeze变为true,需要解冻 if (app.frozen && app.shouldNotFreeze) { mCachedAppOptimizer.unfreezeAppLocked(app); } // 如果该进程的 adj处于 CACHED,并且可以冻结,则调用 freezeAppAsync() 冻结 // 如果该进程的 adj离开 CACHED,则解冻 if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen && !app.shouldNotFreeze) { mCachedAppOptimizer.freezeAppAsync(app); } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.frozen) { mCachedAppOptimizer.unfreezeAppLocked(app); } }
在开始分析 freezer 之前,首先通过触发的代码了解 freezer 会有哪些状态,以及发生freeze、unfreeze 的情况。
这里主要分三个部分:
- 确认是否使能了 freezer;
- 进程状态是否已经发生变化,已经被冻结,但进程状态改成 shouldNotFreeze,此时需要解冻。但这里的逻辑显然有些难以理解,已经进行了 unfreeze,为何要继续下面的流程?好在Android S 中解决了这个逻辑问题;
- 根据 app 现在adj 是否处于 Cached,以及 frozen 的情况,确定是进入 freeze 流程,还是 unfreeze 流程;
2. CachedAppOptimizer.init()
对于CachedAppOptimizer 的构造调用,以及 init() 函数的触发流程,可以参考《Android 中app内存回收优化(一)》 一文第 1 节 和 第 2 节。
frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java public void init() { ... synchronized (mPhenotypeFlagLock) { ... updateUseFreezer(); } }
2.1 updateUseFreezer()
private void updateUseFreezer() { // 获取settings 中属性 cached_apps_freezer 的值,根据属性值初始化变量mUseFreezer 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(); } if (mUseFreezer && mFreezeHandler == null) { Slog.d(TAG_AM, "Freezer enabled"); enableFreezer(true); if (!mCachedAppOptimizerThread.isAlive()) { mCachedAppOptimizerThread.start(); } mFreezeHandler = new FreezeHandler(); Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(), Process.THREAD_GROUP_SYSTEM); } else { enableFreezer(false); } }
首先确认 freezer 功能是否使能,用个流程图来说明比较清晰:
当 freezer 使能,就会:
- 调用 enableFreezer() 进行使能,详细的流程可以查看第 4 节;
- 如果 CachedAppOptimizer 中的 ServiceThread 没有启动,则启动;
- 创建 free handler,用以处理 freezer 相关消息;
- 设置 ServiceThread 的优先级为 THREAD_GROUP_SYSTEM;
3. cgroups 简介
cgroups (全称:control groups) 是 Linux 内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 CPU、memory 等资源实现精细化的控制。目前越来越活的轻量级容器 Docker 就使用了 cgroups 提供的资源限制能力来完成 CPU、memory 等部门的资源控制。
cgroups 为每种可以控制的资源定义了一个子系统,典型的子系统如下:
- cpu:主要限制进程的 cpu 使用率;
- cpuaat:可以统计 cgroups 中的进程的 cpu 使用报告;
- cpuset:可以为 cgroups 中的进程分配单独的 cpu 节点或内存节点;
- memory:可以限制进程的 memory 使用量;
- blkio:可以限制进程的块设备 io;
- devices:可以控制进程能够访问某些设备;
- freezer:可以挂起或恢复 cgroups 中的进程;
- net_cls:可以标记 cgroups 中进程的网络数据包,然后可以使用 tc (traffic control)模块对数据包进行控制;
- ns:可以使不同的 cgroups 下面的进程使用不同的 namespace;
在 Android Q 或更高版本通过 task profiles 使用 cgroup 抽象层,task profiles 可以用来描述应用于某个线程或进程的一个set 或 sets 的限制。系统依照 task profiles 的规定选择一个或多个适当的 cgroups。通过这种限制,可以对底层的 cgroup 功能集进行更改,而不会影响较高的软件层。
具体的细节可以查看博文:《Android 中 cgroup抽象层详解》
本文的很多重要特性都是通过 profile 的方式完成。
4. enableFreezer()
主要是调用 enableFreezerInternal() 函数:
private static native void enableFreezerInternal(boolean enable);
frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cppstatic void com_android_server_am_CachedAppOptimizer_enableFreezerInternal( JNIEnv *env, jobject clazz, jboolean enable) { bool success = true; if (enable) { success = SetTaskProfiles(0, {"FreezerEnabled"}, true); } else { success = SetTaskProfiles(0, {"FreezerDisabled"}, true); } if (!success) { jniThrowException(env, "java/lang/RuntimeException", "Unknown error"); }}
通过接口 SetTaskProfiles() 往对应的节点写入特定的 value 值,详细可以查看博文:《Android 中 cgroup抽象层详解》
5. freezeAppAsync()
参数为 ProcessRecord 类型,也就是对应的进程。
void freezeAppAsync(ProcessRecord app) { mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app); mFreezeHandler.sendMessageDelayed( mFreezeHandler.obtainMessage( SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app), FREEZE_TIMEOUT_MS); }
注意,
- 如果该进程已经发送 freeze 请求,再次发送请求时,先取消原来的消息;
- 发送消息 SET_FROZEN_PROCESS_MSG,请求 freeze;
- 消息处理的延时时长为 FREEZE_TIMEOUT_MS(10 min),如果10 分钟之后,冻结该进程的消息还没有被取消,则进入冻结进程的流程;
5.1 freeze 消息处理
public void handleMessage(Message msg) { switch (msg.what) { case SET_FROZEN_PROCESS_MSG: 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()
private void freezeProcess(ProcessRecord proc) { final int pid = proc.pid; final String name = proc.processName; final long unfrozenDuration; final boolean frozen; try { // 确认进程是否存在任意的文件锁,避免不必要的 free/unfreeze操作 // 这是为了防止冻结进程持有文件锁,从而引起死锁 // 冻结成功之后,还会再次确认文件锁,如果有锁,则立即解冻 if (Process.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; } // 使用ASM做锁,在S 版本中换成专一的锁 synchronized (mAm) { // 如果进程没有变成Cached或者不能freeze,则退出此次freeze操作 if (proc.curAdj < ProcessList.CACHED_APP_MIN_ADJ || proc.shouldNotFreeze) { return; } // 已经处于frozen,或者不是一个应用进程,则退出此次 freeze操作 // pid为0,有可能进程还没有launch完成,或者进程被kill了 if (pid == 0 || proc.frozen) { return; } long unfreezeTime = proc.freezeUnfreezeTime; // 核心函数setProcessFrozen(),同步冻结进程 try { Process.setProcessFrozen(pid, proc.uid, true); proc.freezeUnfreezeTime = SystemClock.uptimeMillis(); proc.frozen = true; } catch (Exception e) { Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name); } unfrozenDuration = proc.freezeUnfreezeTime - unfreezeTime; frozen = proc.frozen; } if (!frozen) { return; } //此次freeze记录到event log EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name); // 这里逻辑应该是颠倒了,先冻结进程,再去冻结binder,逻辑上不通 // 在 S 版本中已经将freeBinder提前到 process冻结之前 try { freezeBinder(pid, true); } catch (RuntimeException e) { Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name); proc.kill("Unable to freeze binder interface", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_INVALID_STATE, true); } // 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); } try { // 再次check文件锁,如果该冻结进程持有文件锁,立即unfreeze if (Process.hasFileLocks(pid)) { synchronized (mAm) { unfreezeAppLocked(proc); } } } catch (Exception e) { Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e); synchronized (mAm) { unfreezeAppLocked(proc); } } }
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.cppvoid 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() 精细是 SetCgroupAction 类型的profile,最终调用 ExecuteForProcess():
system/core/libprocessgroup/task_profiles.cpp bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const { std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid); unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC))); if (tmp_fd < 0) { PLOG(WARNING) << "Failed to open " << procs_path; return false; } if (!AddTidToCgroup(pid, tmp_fd)) { LOG(ERROR) << "Failed to add task into cgroup"; return false; } return true;}
通过函数,先通过 Controller 的 GetProcsFilePath() 接口获取该profile 需要修改的path,参数为该 profile 配置的 Path:
system/core/libprocessgroup/cgroup_map.cppstd::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid, pid_t pid) const { std::string proc_path(path()); proc_path.append("/").append(rel_path); proc_path = regex_replace(proc_path, std::regex(""), std::to_string(uid)); proc_path = regex_replace(proc_path, std::regex(""), std::to_string(pid)); return proc_path.append(CGROUP_PROCS_FILE);}
最终写的文件就是 CGROUP_PROCS_FILE,也就是 cgroup.procs 文件。
6. unfreezeAppLocked()
void unfreezeAppLocked(ProcessRecord app) { // 首先,取消之前该进程的冻结请求 mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app); // 如果进程还没有冻结,则无需做解冻处理 if (!app.frozen) { return; } boolean processKilled = false; // 冻住的进程可以接收异步binder请求,但是不会处理,只是放入binder buffer, 过多的请求会导致buffer耗尽; // 这里需要确认下该进程在解冻之前,进程是否在冰冻期间收到同步的binder 请求,有则kill该进程 try { int freezeInfo = getBinderFreezeInfo(app.pid); if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) { Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " " + " received sync transactions while frozen, killing"); app.kill("Sync transaction while in frozen state", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_INVALID_STATE, true); processKilled = true; } if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) { Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " " + " received async transactions while frozen"); } } catch (Exception e) { Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " " + app.processName + ". Killing it. Exception: " + e); app.kill("Unable to query binder frozen stats", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_INVALID_STATE, true); processKilled = true; } //进程被kill 了,退出unfreeze if (processKilled) { return; } // app.freezeUnfreezeTime记录的是上次free、unfreeze的时间 long freezeTime = app.freezeUnfreezeTime; try { freezeBinder(app.pid, false); } catch (RuntimeException e) { Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName + ". Killing it"); app.kill("Unable to unfreeze", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_INVALID_STATE, true); return; } try { Process.setProcessFrozen(app.pid, app.uid, false); app.freezeUnfreezeTime = SystemClock.uptimeMillis(); app.frozen = false; } catch (Exception e) { Slog.e(TAG_AM, "Unable to unfreeze " + app.pid + " " + app.processName + ". This might cause inconsistency or UI hangs."); } if (!app.frozen) { if (DEBUG_FREEZER) { Slog.d(TAG_AM, "sync unfroze " + app.pid + " " + app.processName); } mFreezeHandler.sendMessage( mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG, app.pid, (int) Math.min(app.freezeUnfreezeTime - freezeTime, Integer.MAX_VALUE), app.processName)); } }
核心处理函数是 setProcessFrozen(),详细的流程在上面第 5.2.1 节中已经分析过。主要需要注意的是 unfrozen 在 task_profiles.json 中:
{ "Name": "Frozen", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "freezer", "Path": "" } } ] }, { "Name": "Unfrozen", "Actions": [ { "Name": "JoinCgroup", "Params": { "Controller": "freezer", "Path": "../" } } ] },
与Frozen 不同,Unfrozen 的Path 为上一级目录,即最终修改的是 sys/fs/cgroup/cgroup.procs,而frozen 修改的是 sys/fs/cgroup/freezer/cgroup.procs
至此,Android R 版本中关于 freezer这里就全部完成了。
从本文的逻辑分析来看Android R 上还有些逻辑问题,通过本文我们大致了解了 freezer 大致流程,以及使用 cgroup 抽象层的接口,但因为版本不稳定,这里就不去过多分析Kernel 中的实现。
最后,附上一个 app 冻结的旅程图:
相关博文:
https://justinwei.blog.csdn.net/article/details/131974011
https://justinwei.blog.csdn.net/article/details/131854291
https://justinwei.blog.csdn.net/article/details/131769953
https://justinwei.blog.csdn.net/article/details/131591931
https://justinwei.blog.csdn.net/article/details/131717028
https://justinwei.blog.csdn.net/article/details/131685304
https://justinwei.blog.csdn.net/article/details/131595511
来源地址:https://blog.csdn.net/jingerppp/article/details/131845360