1.背景概述
自Android问世以来,如何最大限度地优化进程占用的资源,同时不影响用户体验,一直是个困扰的题目。运行中的进程在制止与用户交互后会退到背景,此时虽然不再影响用户体验,但仍旧会占用部分CPU和内存等资源。
有人可能会问,为什么不直接杀掉这些背景进程?这是由于直接杀掉这些进程会影响用户的使用体验——每次用户重新启动这些进程时,都必要完全重新加载,从而导致启动速率变慢。
因此,我们必要一种机制,可以限定这些进程的资源占用而不直接杀掉它们。由此,“Freeze”冻结机制应运而生。
在Android 11及更高版本的原生系统中,支持对缓存应用的冻结。当应用切换到背景且没有其他动作后,系统会在一定时间后根据状态判断,通过cgroup对缓存应用进行冻结。此机制可以减少背景活泼应用的CPU资源消耗,从而到达省电和节省资源的目标。当用户必要再次使用应用时,应用会被“解冻”并快速恢复。
本文将基于Android U对冻结机制的流程和原理进行梳理和总结。
2.功能表明
2.1冻结的原理
Android将进程按照优先级从高到低分为:前台进程–>可感知进程–>服务进程–>Cache进程,Freeze通过冻结Cache进程来开释它们占用的系统资源。它们的优先级(adj)通过OomAdjuster来进行更新盘算。
2.2冻结的作用
- 资源:冻结进程的CPU会被开释,这部分资源会被系统重新分配给其他必要实验的进程和服务
- 电量:冻结的进程不会再背景运行,以是可以节省设备电量的消耗,到达省电的目标
- 稳固性:通过冻结不活泼的进程,可以减少它们对系统资源的竞争,进步系统的稳固性
2.3怎样手动开启关闭
- 开发者选项 “Suspend execution for cached apps”
内里有三个选项,Device default, Enabled 和Disabled
- 通过adb命令
adb shell settings put global cached_apps_freezer <enabled|disabled|default>
Android R上面的default等于功能关闭; Android S上面是默认开启。上面两种方式其实相同的,无论通过那种开启和关闭功能,都要重启才能见效
2.4怎样查看冻结的状态
- adb logcat -b all | grep -iE "freeze|froze"
- 08-06 05:21:28.866 1000 1467 1654 I am_unfreeze: [13839,com.google.android.gms,4]
- 08-06 05:21:48.952 1000 1467 1654 I am_freeze: [13839,com.google.android.gms]
- 08-06 05:21:55.171 1000 1467 1654 I am_unfreeze: [696,com.google.android.apps.messaging,6]
- 08-06 05:22:05.263 1000 1467 1654 I am_freeze: [696,com.google.android.apps.messaging]
- 08-06 05:27:05.417 1000 1467 1654 I am_unfreeze: [696,com.google.android.apps.messaging,6]
- 08-06 05:27:15.496 1000 1467 1654 I am_freeze: [696,com.google.android.apps.messaging]
- 08-06 05:33:29.112 1000 1467 1654 I am_unfreeze: [21895,com.google.android.apps.photos,6]
复制代码
- 检查/sys/fs/cgroup/uid_xxx/cgroup.freeze文件
- adb shell cat /sys/fs/cgroup/uid_{应用UID}/cgroup.freeze
- 0代表没有被冻结。1代表被冻结
复制代码
- adb shell ps -Ae |grep -i {PID}
- 当进程是S状态,且WCHAN 显示在do_freezer_trap 上等待,就表明进程被冻结
复制代码
- tips
- 1.在内核版本 4.14的时候,进程被冻结显示的是D状态, 高版本中已经不会对进程切到 D 状态,而是保留 S 态,然后在 S 态的基础上做 freeze 的处理
- 2.WCHAN 表示当前线程在内核上正在执行的函数名
复制代码 3.冻结的流程
3.1冻结的初始化
上面提到过冻结的原理,是根据进程的adj来判断是否是cache应用,然后对其进行冻结,以是Freeze初始化也是在OomAdjuster中
3.1.1 SystemServer.run
- private void run() {
- ...
- try {
- t.traceBegin("StartServices");
- startBootstrapServices(t); //初始化引导类服务,例如AMS PMS WMS等,这些是在Android系统系统启动过程中最先启动的服务
- startCoreServices(t); //初始化系统的一写核心服务,列如health service,battery service,sernor service
- startOtherServices(t); //这里启动的服务。一般在系统偏后阶段执行
- startApexServices(t); //初始化apex系统服务,这些服务主要用于处理与 Apex 相关的功能,比如模块激活、更新监控等
- updateWatchdogTimeout(t); //更新看门狗超时时间
- }
- ...
- }
复制代码 3.1.2 SystemServer.startOtherServices
调用ContentProviderHelper.installSystemProviders来初始化SystemProviders
- private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
- ...
- t.traceBegin("InstallSystemProviders");
- mActivityManagerService.getContentProviderHelper().installSystemProviders();
- // Device configuration used to be part of System providers
- mSystemServiceManager.startService(UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS);
- // Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
- SQLiteCompatibilityWalFlags.reset();
- t.traceEnd();
- ...
- }
复制代码 3.1.3 ContentProviderHelper.installSystemProviders
调用OomAdjuster.initSettings来初始化OomAdjuster
- public final void installSystemProviders(){
- ...
- new DevelopmentSettingsObserver(); // init to observe developer settings enable/disable
- SettingsToPropertiesMapper.start(mService.mContext.getContentResolver());
- mService.mOomAdjuster.initSettings();
- ...
- }
复制代码 3.1.4 OomAdjuster.initSettings
调用CachedAppOptimizer.init来初始化CachedAppOptimizer
- void initSettings() {
- mCachedAppOptimizer.init();
- mCacheOomRanker.init(ActivityThread.currentApplication().getMainExecutor());
- ...
- }
复制代码 3.1.4 CachedAppOptimizer.init
会调用CachedAppOptimizer.updateUseFreezer来初始化进程冻结功能
- ...
- @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
- Settings.Global.CACHED_APPS_FREEZER_ENABLED);
- ...
- public void init() {
- ...
- mAm.mContext.getContentResolver().registerContentObserver(
- CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver); //注册了一个观察者,当CACHED_APP_FREEZER_ENABLED_URI也就是
- // Settings.Global.CACHED_APPS_FREEZER_ENABLED发生变化时收到通知
-
- synchronized (mPhenotypeFlagLock) {
- ...
- updateUseFreezer(); //更新是否使用冻结机制
- ...
- }
- ...
- }
复制代码 3.1.5 CachedAppOptimizer.**updateUseFreezer
- @GuardedBy("mPhenotypeFlagLock")
- private void updateUseFreezer() {
- final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),
- 1105 Settings.Global.CACHED_APPS_FREEZER_ENABLED); //首先获取CACHED_APPS_FREEZER_ENABLED数据库的值
- 1106
- 1107 if ("disabled".equals(configOverride)) { //如果是disabled,就不支持进程冻结
- 1108 mUseFreezer = false;
- 1109 } else if ("enabled".equals(configOverride)
- 1110 || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
- 1111 KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) { //如果CACHED_APPS_FREEZER_ENABLED数据库的值是enabled
- //或者use_freezer对应的config的值是true
- 1112 mUseFreezer = isFreezerSupported(); //判断系统支不支持Freezer,也就是相关驱动
- 1113 updateFreezerDebounceTimeout(); //更新Debounce超时时间,也就是冻结机制的延迟超时时间
- 1114 updateFreezerExemptInstPkg();//更新冻结机制豁免安装包 ,指的是豁免被冻结的应用,拥有INSTALL_PACKAGES权限的应用能够豁免被冻结
- 1115 } else { //其余情况下也不支持被冻结
- 1116 mUseFreezer = false;
- 1117 }
- 1118
- 1119 final boolean useFreezer = mUseFreezer;
- 1120 // enableFreezer() would need the global ActivityManagerService lock, post it.
- 1121 mAm.mHandler.post(() -> {
- 1122 if (useFreezer) { //如果系统支持Freeze
- 1123 Slog.d(TAG_AM, "Freezer enabled");
- 1124 enableFreezer(true); 启用进程冻结功能
- 1125
- 1126 if (!mCachedAppOptimizerThread.isAlive()) {
- 1127 mCachedAppOptimizerThread.start(); //启动CachedAppOptimizerThread线程
- 1128 }
- 1129
- 1130 if (mFreezeHandler == null) {
- 1131 mFreezeHandler = new FreezeHandler();
- 1132 }
- 1133
- 1134 Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
- 1135 Process.THREAD_GROUP_SYSTEM);//设置CachedAppOptimizerThread线程的cgroup
- 1136 } else {
- 1137 Slog.d(TAG_AM, "Freezer disabled");
- 1138 enableFreezer(false);
- 1139 }
- 1140 });
- 1141 }
- }
复制代码 3.1.6 CachedAppOptimizer.**isFreezerSupported
通过数据库开关启动Freeze,还必要判断系统是否支持Freeze才能乐成启动
读取/sys/fs/cgroup/uid_0/pid_*/cgroup.freeze节点下的值,如果节点的值为0或者1,然后调用getBinderFreezeInfo判断驱动是否支持freezer(kernel版本较低的话,默认不会实现BINDER_GET_FROZEN_INFO,就不支持freezer),接着调用isFreezerProfileValid判断知否加载了task_profiles.json文件(进程冻结相关的profiles)
- /**
- 1055 * Determines whether the freezer is supported by this system
- 1056 */
- 1057 public static boolean isFreezerSupported() {
- 1058 boolean supported = false;
- 1059 FileReader fr = null;
- 1060
- 1061 try {
- 1062 String path = getFreezerCheckPath(); // /sys/fs/cgroup/uid_0/pid_*/cgroup.freeze
- 1063 Slog.d(TAG_AM, "Checking cgroup freezer: " + path);
- 1064 fr = new FileReader(path);
- 1065 char state = (char) fr.read();
- 1066
- 1067 if (state == '1' || state == '0') {
- 1068 // Also check freezer binder ioctl
- 1069 Slog.d(TAG_AM, "Checking binder freezer ioctl");
- 1070 getBinderFreezeInfo(Process.myPid()); //判断驱动是否支持freezer
- 1071
- 1072 // Check if task_profiles.json contains invalid profiles
- 1073 Slog.d(TAG_AM, "Checking freezer profiles");
- 1074 supported = isFreezerProfileValid(); //检查task_profiles.json是否包含进程冻结相关的Profiles
- 1075 } else {
- 1076 Slog.e(TAG_AM, "Unexpected value in cgroup.freeze");
- 1077 }
- 1078 } catch (java.io.FileNotFoundException e) {
- 1079 Slog.w(TAG_AM, "File cgroup.freeze not present");
- 1080 } catch (RuntimeException e) {
- 1081 Slog.w(TAG_AM, "Unable to read freezer info");
- 1082 } catch (Exception e) {
- 1083 Slog.w(TAG_AM, "Unable to read cgroup.freeze: " + e.toString());
- 1084 }
- 1085
- 1086 if (fr != null) {
- 1087 try {
- 1088 fr.close();
- 1089 } catch (java.io.IOException e) {
- 1090 Slog.e(TAG_AM, "Exception closing cgroup.freeze: " + e.toString());
- 1091 }
- 1092 }
- 1093
- 1094 Slog.d(TAG_AM, "Freezer supported: " + supported);
- 1095 return supported;
- 1096 }
复制代码 3.1.7 流程总结
流程总结如下:
3.2冻结的触发
当进程的优先级(adj)发生变化的时候,会去盘算判断,并实验冻结,adj会发生变化的原因一样平常如下:
- /**
- 173 * All of the code required to compute proc states and oom_adj values.
- 174 */
- 175 public class OomAdjuster {
- 176 static final String TAG = "OomAdjuster";
- 177
- 178 public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) {
- 179 switch (oomReason) {
- 180 case OOM_ADJ_REASON_NONE:
- 181 return AppProtoEnums.OOM_ADJ_REASON_NONE;
- 182 case OOM_ADJ_REASON_ACTIVITY:
- 183 return AppProtoEnums.OOM_ADJ_REASON_ACTIVITY;
- 184 case OOM_ADJ_REASON_FINISH_RECEIVER:
- 185 return AppProtoEnums.OOM_ADJ_REASON_FINISH_RECEIVER;
- 186 case OOM_ADJ_REASON_START_RECEIVER:
- 187 return AppProtoEnums.OOM_ADJ_REASON_START_RECEIVER;
- 188 case OOM_ADJ_REASON_BIND_SERVICE:
- 189 return AppProtoEnums.OOM_ADJ_REASON_BIND_SERVICE;
- 190 case OOM_ADJ_REASON_UNBIND_SERVICE:
- 191 return AppProtoEnums.OOM_ADJ_REASON_UNBIND_SERVICE;
- 192 case OOM_ADJ_REASON_START_SERVICE:
- 193 return AppProtoEnums.OOM_ADJ_REASON_START_SERVICE;
- 194 case OOM_ADJ_REASON_GET_PROVIDER:
- 195 return AppProtoEnums.OOM_ADJ_REASON_GET_PROVIDER;
- 196 case OOM_ADJ_REASON_REMOVE_PROVIDER:
- 197 return AppProtoEnums.OOM_ADJ_REASON_REMOVE_PROVIDER;
- 198 case OOM_ADJ_REASON_UI_VISIBILITY:
- 199 return AppProtoEnums.OOM_ADJ_REASON_UI_VISIBILITY;
- 200 case OOM_ADJ_REASON_ALLOWLIST:
- 201 return AppProtoEnums.OOM_ADJ_REASON_ALLOWLIST;
- 202 case OOM_ADJ_REASON_PROCESS_BEGIN:
- 203 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_BEGIN;
- 204 case OOM_ADJ_REASON_PROCESS_END:
- 205 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
- 206 case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
- 207 return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
- 208 case OOM_ADJ_REASON_SYSTEM_INIT:
- 209 return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT;
- 210 case OOM_ADJ_REASON_BACKUP:
- 211 return AppProtoEnums.OOM_ADJ_REASON_BACKUP;
- 212 case OOM_ADJ_REASON_SHELL:
- 213 return AppProtoEnums.OOM_ADJ_REASON_SHELL;
- 214 case OOM_ADJ_REASON_REMOVE_TASK:
- 215 return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK;
- 216 case OOM_ADJ_REASON_UID_IDLE:
- 217 return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE;
- 218 case OOM_ADJ_REASON_STOP_SERVICE:
- 219 return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE;
- 220 case OOM_ADJ_REASON_EXECUTING_SERVICE:
- 221 return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE;
- 222 case OOM_ADJ_REASON_RESTRICTION_CHANGE:
- 223 return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE;
- 224 case OOM_ADJ_REASON_COMPONENT_DISABLED:
- 225 return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
- 226 default:
- 227 return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
- 228 }
- 229 }
复制代码 从上我们可以看到当进程状态或者组件状态发生变化的时候,就会触发adj的更新,这里以Activity的状态变化为例:
3.2.1 ActivityRecord.setState
可以看到Activity的STARTED状态和DESTROYING状态调用了updateProcessInfo方法去更新进程状态,updateOomAdj都是true
- void setState(State state, String reason) {
- ...
- case STARTED:
- // Update process info while making an activity from invisible to visible, to make
- // sure the process state is updated to foreground.
- if (app != null) {
- app.updateProcessInfo(false /* updateServiceConnectionActivities */,
- true /* activityChange */, true /* updateOomAdj */,
- true /* addPendingTopUid */);
- }
- ...
- case :
- if (app != null && !app.hasActivities()) {
- // Update any services we are bound to that might care about whether
- // their client may have activities.
- // No longer have activities, so update LRU list and oom adj.
- app.updateProcessInfo(true /* updateServiceConnectionActivities */,
- false /* activityChange */, true /* updateOomAdj */,
- false /* addPendingTopUid */);
- }
- ...
- }
复制代码- tips
- 其他状态,如 RESUMED、PAUSED、STOPPED 等,已经隐含或明确地处理了进程的优先级和状态管理,因此不需要在这些状态中调用 updateProcessInfo。
- RESUMED 状态:在 RESUMED 状态中,Activity 已经完全进入前台,并且通常在进入此状态时,进程已经被提升为前台优先级,因此不需要再次调用 updateProcessInfo。
- PAUSED 和 STOPPED 状态:这些状态下,Activity 逐渐变得不可见或不再与用户交互。通常系统会依赖其他机制来管理进程的优先级变化,而不需要在这两个状态中特别调用 updateProcessInfo。
复制代码 3.2.2 ProcessRecord.updateProcessInfo
生命周期变化后,会调用到updateOomAdjLocked来继续更新adj的流程
- public void updateProcessInfo(boolean updateServiceConnectionActivities, boolean activityChange,
- boolean updateOomAdj) {
- ...
- mService.updateLruProcessLocked(this, activityChange, null /* client */);
- if (updateOomAdj) {
- mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY);
- }
- ...
- }
复制代码 3.2.3 ActivityManagerService.updateOomAdjLocked
- @GuardedBy("this") //这是一个线程安全注解,表示这个方法在调用时,必须在当前对象实例的锁,在这也就是am的锁
- final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
- mOomAdjuster.updateOomAdjLocked(oomAdjReason);
- }
复制代码 3.2.4 OomAdjuster
- * Update OomAdj for all processes in LRU list
- */
- @GuardedBy("mService")
- void updateOomAdjLocked(String oomAdjReason) {
- synchronized (mProcLock) {
- updateOomAdjLSP(oomAdjReason);
- }
- }
-
- @GuardedBy({"mService", "mProcLock"})
- private void updateOomAdjLSP(String oomAdjReason) {
- ...
- mOomAdjUpdateOngoing = true;
- performUpdateOomAdjLSP(oomAdjReason);
- ...
- }
- @GuardedBy({"mService", "mProcLock"})
- private void performUpdateOomAdjLSP(String oomAdjReason) {
- ...
- updateOomAdjInnerLSP(oomAdjReason, topApp , null, null, true, true);
- ...
- }
- @GuardedBy({"mService", "mProcLock"})
- private void updateOomAdjInnerLSP(String oomAdjReason, final ProcessRecord topApp,
- ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
- boolean startProfiling) {
- ...
- computeOomAdjLSP(app, ProcessList.UNKNOWN_ADJ, topApp, fullUpdate, now, false,
- computeClients); //计算进程的adj
- boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids);
- ...
- }
- @GuardedBy({"mService", "mProcLock"})
- private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
- final long oldTime, final ActiveUids activeUids) {
- ...
- applyOomAdjLSP(app, true, now, nowElapsed);
- ...
- }
-
- @GuardedBy({"mService", "mProcLock"})
- private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
- long nowElapsed) {
- ...
- updateAppFreezeStateLSP(app);
- ...
- }
复制代码 3.2.5 OomAdjuster.updateAppFreezeStateLSP
当adj发生变化,然后盘算,最终会去调用updateAppFreezeStateLSP更新app的冻结状态,在内里会去判断是否要用冻结机制,当前应用是否免疫冻结,当前是否已经被冻结等状态,然后和 ProcessList.CACHED_APP_MIN_ADJ(900)进行比较,当前当前adj大于等于它的时候,才归去调用冻结方法。
- @GuardedBy({"mService", "mProcLock"})
- private void updateAppFreezeStateLSP(ProcessRecord app) {
- if (!mCachedAppOptimizer.useFreezer()) { // 是否使用冻结机制
- return;
- }
- if (app.mOptRecord.isFreezeExempt()) { // 是否免冻结, 这里追溯过去,目前只有判断拥有INSTALL_PACKAGES权限的进程能够被豁免
- return;
- }
- final ProcessCachedOptimizerRecord opt = app.mOptRecord;
- // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
- if (opt.isFrozen() && opt.shouldNotFreeze()) { //如果已经被冻结,并且不应该被冻结
- mCachedAppOptimizer.unfreezeAppLSP(app); //解除冻结
- return;
- }
- final ProcessStateRecord state = app.mState;
- // Use current adjustment when freezing, set adjustment when unfreezing.
- if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen() //最小冻结adj 为public static final int CACHED_APP_MIN_ADJ = 900;
- && !opt.shouldNotFreeze()) { //当前 adj 大于最小冻结 adj 并且没有被冻结并且应该被冻结
- mCachedAppOptimizer.freezeAppAsyncLSP(app);
- } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
- mCachedAppOptimizer.unfreezeAppLSP(app); //当前 adj 小于最小冻结 adj 应该解冻
- }
- }
- }
复制代码 3.2.6 CachedAppOptimizer.freezeAppAsyncLSP
freezeAppAsyncLSP内里会post一个10s的message在时间到了的时候调用freezeProcess去冻结进程(延时10s发送进程冻结的消息,在10s内如果收到进程解冻的消息,会把进程冻结消息移除,也就不会实验进程冻结的操作)
- @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L; //默认超时时间,毫秒为单位,也就是10s
- @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
-
- @GuardedBy({"mAm", "mProcLock"})
- void freezeAppAsyncLSP(ProcessRecord app) {
- freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, mFreezerDebounceTimeout));
- }
-
- @GuardedBy({"mAm", "mProcLock"})
- private void freezeAppAsyncLSP(ProcessRecord app, @UptimeMillisLong long delayMillis) {
- freezeAppAsyncInternalLSP(app, delayMillis, false);
- }
-
- @GuardedBy({"mAm", "mProcLock"})
- void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis,
- boolean force) {
- ...
- mFreezeHandler.sendMessageDelayed(
- mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
- delayMillis); //延迟10s发送Message
- ...
- }
-
- ...
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SET_FROZEN_PROCESS_MSG: {
- ProcessRecord proc = (ProcessRecord) msg.obj;
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"freezeProcess");
- synchronized (mAm) {
- freezeProcess(proc); //响应TRACE_TAG_ACTIVITY_MANAGER 调用freezeProcess
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- if (proc.mOptRecord.isFrozen()) { //进程冻结成功
- onProcessFrozen(proc); //进程被冻结后收到的回调,执行内存压缩的相关操作
- removeMessages(DEADLOCK_WATCHDOG_MSG);
- sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS); ///延时1s发送消息检查文件锁的持有情况,
- //考虑到一些特殊场景下,进程在被冰冻的过程中拿住了文件锁,冰冻成功后还会再检查一次,发现持有锁就立刻解冻。
- //去冻结的时候也会去检查他是否拿有文件锁
- }
- } break;
- ...
复制代码 总结上面条件就是,通过判断冻结功能是否开启、应用是否属于宽免的应用、应用是否已经被冻结、应用是否不应该被冻结。当做完底子的判断之后,然后看应用当前的 adj 是否大于等于 900 (CACHE_APP) 来决定是否冻结应用,然后开启一个延迟10s,如果这个10s内状态都没有变化,就实验冻结流程
- tips
- 1.DEFAULT_FREEZER_DEBOUNCE_TIMEOUT在Android 14之前是十分钟,Android 14上面是十秒**
- 2.这个时间可以动态的修改: adb shell device_config put activity_manager_native_boot freeze_debounce_timeout 1000 这个是改成1s**
- 3.冻结的时候会去检查文件锁状态,这是为了防止冰冻进程持有文件锁引起死锁。考虑到一些特殊场景下,进程在被冰冻的过程中拿住了文件锁,冰冻成功后还会再检查一次,发现持有锁就立刻解冻。**
复制代码 3.2.7 CachedAppOptimizer.freezeProcess
最终都会调用到freezeProcess函数,Android 13以后针对binder 调用进行了优化,对进程的 binder 先辈行冻结,这一步禁掉该进程对同步binder请求的接收和处理惩罚,以及对异步binder请求的处理惩罚,由于之前如果随意的冻结应用,会导致一些应用背景的跨进程举动异常,比方在binder通信期间。
到了Android 13之后,binder驱动已经支持FREEZER状态,当应用调用一个被冻结binder进程后,会直接返回一个错误,如许子就不会阻塞调用方的进程。
- @GuardedBy({"mAm"})
- private void freezeProcess(final ProcessRecord proc) {
- ...
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"freezeBinder:" + name);
- if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) { //对进程的 binder 先进行冻结
- handleBinderFreezerFailure(proc, "outstanding txns");
- return;
- }
- ...
- try {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"setProcessFrozen:" + name);
- traceAppFreeze(proc.processName, pid, -1);
- Process.setProcessFrozen(pid, proc.uid, true); //根据uid,pid对进程执行冻结
-
- ...
-
- }
复制代码 3.2.8 freezeBinder
freezeBinder最终调用到libbinder内里去,最终判断是否是Android设备,是的话就通过ioctl,传递BINDER_FREEZE,最终走到内核的 binder 驱动上
- /*
- frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
- */
- public static native int freezeBinder(int pid, boolean freeze, int timeoutMs);
-
- /*
- frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
- */
- static const JNINativeMethod sMethods[] = {
- /* name, signature, funcPtr */
- ...
- {"freezeBinder", "(IZI)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
- ...
-
- static jint com_android_server_am_CachedAppOptimizer_freezeBinder(JNIEnv* env, jobject clazz,
- jint pid, jboolean freeze,
- jint timeout_ms) {
- jint retVal = IPCThreadState::freeze(pid, freeze, timeout_ms);
- if (retVal != 0 && retVal != -EAGAIN) {
- jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
- }
- return retVal;
- }
-
- /*
- frameworks/native/libs/binder/IPCThreadState.cpp
- */
- status_t IPCThreadState::freeze(pid_t pid, bool enable, uint32_t timeout_ms) {
- struct binder_freeze_info info;
- int ret = 0;
- info.pid = pid;
- info.enable = enable;
- info.timeout_ms = timeout_ms;
- #if defined(__ANDROID__) //判断是否是Android设备
- if (ioctl(self()->mProcess->mDriverFD, BINDER_FREEZE, &info) < 0) // 通过ioctl,传递BINDER_FREEZE,走到内核的 binder 驱动上
- ret = -errno;
- #endif
复制代码- tips
- ioctl(input/output control)是一个专用于设备输入输出操作的系统调用,该调用传入一个跟设备有关的请求码,系统调用的功能完全取决于请求码。
复制代码 3.2.9 setProcessFrozen
setProcessFrozen是一个native函数,会调用到android_util_Process的android_os_Process_setProcessFrozen函数,在此函数里会调用cgroup中间抽象层libprocessgroup的API,通过cgroup自己的freezer子系统来实现进程冻结功能
- /*
- frameworks/base/core/java/android/os/Process.java
- */
- /**
- * Freeze or unfreeze the specified process.
- *
- * @param pid Identifier of the process to freeze or unfreeze.
- * @param uid Identifier of the user the process is running under.
- * @param frozen Specify whether to free (true) or unfreeze (false).
- *
- * @hide
- */
- public static final native void setProcessFrozen(int pid, int uid, boolean frozen);
-
- ...
- /*
- frameworks/base/core/jni/android_util_Process.cpp
- */
- static const JNINativeMethod methods[] = {
- {"setProcessFrozen", "(IIZ)V", (void*)android_os_Process_setProcessFrozen},
- }
-
- ....
-
- 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);
- }
- }
复制代码 3.2.10 总结
通过上面的触发流程可以看出,系统通过
adj的变化来触发冻结状态的更新。经过一系列判断后,从 Java 层到 Native 层,最终调用到 Cgroup 中间抽象层的 API,进而通过 Cgroup 实现进程的冻结功能。
3.3冻结的实现
由触发的流程可知,我们最后是通过调用Cgroup 中间抽象层的 API,进而通过 Cgroup 实现进程的冻结功能,那么Cgroup 中间抽象层是什么,Cgroup又是什么呢?
Cgroup:
“cgroup”指的是 Control Group(控制组),这是 Linux 内核中的一种资源管理机制。cgroup 提供一种机制,可将任务集(包罗进程、线程及其全部将来的子级)聚归并分区到具有专门举动的层级组中,它就像给进程分组并订定资源使用规则的“管理员”,通过它的各种子系统,好比这里提到的 freezer 子系统,能实现对进程资源使用的控制和特别操作,像在进程无感知的环境下将其冻结。
Cgroup 中间抽象层:
Android 10 及更高版本将对照组 (cgroup) 抽象层和任务配置文件搭配使用,让开发者能够使用它们来描述应用于某个线程或进程的一组(或多组)限定。
然后,系统按照任务配置文件的规定操作来选择一个或多个适当的 cgroup;通过这种方式,系统可以应用各种限定,并对底层 cgroup 功能集进行更改,而不会影响较高的软件层
简而言之,Cgroup 中间抽象层就像是对Cgroup 机制进行的一种更高层次的封装或概括,以方便上层管理和使用Cgroup的功能,cgroup 自己就像是一堆复杂的零部件,而 Cgroup抽象层则是把这些零部件组装成一个易用的工具,让开发者不必要深入相识Cgroup的底层细节,就能直接使用它提供的一些常见功能。
那么针对我们的系统来讲,从Android10及更高版本上,cgroup配置就是cgroups.json,任务配置文件就是task_profiles.json,都在libprocessgroup下,在libprocessgroup中,在启动阶段,会根据cgroups.json 来装载具体的cgroup,然后根据task_profiles.json定义具体对cgroup的操作和参数。
两者搭配起来使用libprocessgroup也就是说的Cgroup 中间抽象层。
3.3.1 cgroups.json
在cgroups.json文件中描述cgroups配置,以此定义cgroups组以及他们的挂载点和attibutes。全部的cgroups将在early-init阶段被挂载上,Cgroups和Cgroups2的两个版本 ,V1和V2,这两个版本是共存的,可以同时使用
Controller:指定 cgroups 子系统名称,之后 task profiles 中设定必要依赖该名称
Path:指定挂载的路径,有了该路径 task profiles 下才可以指定文件名;
Mode: 用于指定Path 目次下文件的实验 mode
如freezer,定义了Controller为freezer,会在Path为/sys/fs/cgroup的路径下
- {
- "Cgroups": [
- {
- "Controller": "blkio",
- "Path": "/dev/blkio",
- "Mode": "0775",
- "UID": "system",
- "GID": "system"
- },
- {
- "Controller": "cpu",
- "Path": "/dev/cpuctl",
- "Mode": "0755",
- "UID": "system",
- "GID": "system"
- },
- {
- "Controller": "cpuset",
- "Path": "/dev/cpuset",
- "Mode": "0755",
- "UID": "system",
- "GID": "system"
- },
- {
- "Controller": "memory",
- "Path": "/dev/memcg",
- "Mode": "0700",
- "UID": "root",
- "GID": "system",
- "Optional": true
- }
- ],
- "Cgroups2": {
- "Path": "/sys/fs/cgroup",
- "Mode": "0775",
- "UID": "system",
- "GID": "system",
- "Controllers": [
- {
- "Controller": "freezer",
- "Path": "."
- }
- ]
- }
复制代码- tips
- cgroups文件可能不止有一个
- 一般有三类:
- /system/core/libprocessgroup/profiles/cgroups.json //默认文件
- /system/core/libprocessgroup/profiles/cgroups_<API level>.json //API级别的文件
- /vendor/xxx/cgroups.json //自定义文件
- 三种文件加载顺序是:默认 -> API 级别 -> vendor,所以这三个是存在一个覆盖关系的,只要后面的文件中定义的 Controller 值与前面的相同,就会覆盖前者的定义
复制代码 3.3.2 task_profiles.json
task_profiles.json定义具体对cgroup的操作和参数,描述要应用于进程或线程的一组特定操作,重要是三个字段"Attributes",“Profiles”,“AggregateProfiles”
Attributes:
Name定义Profiles操作时的名称
Controller是引用cgroups.json对应的控制器
File是对应的节点文件
如这里定义的冻结相关的 name是"FreezerState",对应操作属性就是FreezerState,Controller是"freezer",就是前面cgroups.json内里定义的控制器,File 是"cgroup.freeze",最终写的节点为"cgroup.freeze"。
Profiles:
Name定义对应的操作名字
Actions定义这个Profiles被调用时,应该实验的操作聚集,再内里的Name和Params就是对应的操作类别和参数
以冻结为例,就是当调用Frozen这个Profile的时候,实验的action是SetAttribute,设置属性,对应的参数是FreezerState,value是1
同cgroups.json,它也是可能不止一个文件,加载次序和覆盖关系也是和cgroups.json一样
AggregateProfiles:
Name定义对应的操作名字
Profiles定义是上面Profile一个聚集,相称于是组合使用不同的Profile
从这个文件我们就能联想到,在上面setProcessFrozen方法,最后去写入的值,就是去调用了对应的Profiles —>Frozen,我们继续往下看调用的流程
- {
- "Attributes": [
- {
- "Name": "LowCapacityCPUs",
- "Controller": "cpuset",
- "File": "background/cpus"
- },
- {
- "Name": "HighCapacityCPUs",
- "Controller": "cpuset",
- "File": "foreground/cpus"
- },
- ....
- {
- "Name": "FreezerState",
- "Controller": "freezer",
- "File": "cgroup.freeze"
- }
- ],
- "Profiles": [
- ...
- {
- "Name": "Frozen",
- "Actions": [
- {
- "Name": "SetAttribute",
- "Params":
- {
- "Name": "FreezerState",
- "Value": "1"
- }
- }
- ]
- },
- {
- "Name": "Unfrozen",
- "Actions": [
- {
- "Name": "SetAttribute",
- "Params":
- {
- "Name": "FreezerState",
- "Value": "0"
- }
- }
- ]
- },
- ...
- "AggregateProfiles": [
- ...
- {
- "Name": "SCHED_SP_BACKGROUND",
- "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
- },
- ...
复制代码 3.3.3 processgroup.SetProcessProfiles
调用TaskProfiles::GetInstance()来获取单例对象,然后调用SetProcessProfiles方法
- bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
- return TaskProfiles::GetInstance().SetProcessProfiles(
- uid, pid, std::span<const std::string>(profiles), false);
- }
复制代码 3.3.4 TaskProfiles.SetProcessProfiles
首先获取我们要实验的这个profile,就是刚才上面写入的“Frozen”,然后ExecuteForProcess方法去实验
- bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid, std::span<const T> profiles,
- bool use_fd_cache) {
- bool success = true;
- for (const auto& name : profiles) {
- TaskProfile* profile = GetProfile(name);
- if (profile != nullptr) {
- if (use_fd_cache) {
- profile->EnableResourceCaching(ProfileAction::RCT_PROCESS);
- }
- if (!profile->ExecuteForProcess(uid, pid)) {
- LOG(WARNING) << "Failed to apply " << name << " process profile";
- success = false;
- }
- } else {
- LOG(WARNING) << "Failed to find " << name << " process profile";
- success = false;
- }
- }
- return success;
- }
复制代码 3.3.5 TaskProfiles.ExecuteForProcess
先调用GetPathForProcess获取对应profile对应的path,然后调用WriteValueToFile方法写入指定的文件,这个文件就是上面”Attributes“我们定义的File,Frozen对应的就是cgroup.freeze节点,
- bool SetAttributeAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
- std::string path;
- if (!attribute_->GetPathForProcess(uid, pid, &path)) {
- LOG(ERROR) << "Failed to find cgroup for uid " << uid << " pid " << pid;
- return false;
- }
- return WriteValueToFile(path);
- }
- bool SetAttributeAction::WriteValueToFile(const std::string& path) const {
- if (!WriteStringToFile(value_, path)) {
- if (access(path.c_str(), F_OK) < 0) {
- if (optional_) {
- return true;
- } else {
- LOG(ERROR) << "No such cgroup attribute: " << path;
- return false;
- }
- }
- // The PLOG() statement below uses the error code stored in `errno` by
- // WriteStringToFile() because access() only overwrites `errno` if it fails
- // and because this code is only reached if the access() function returns 0.
- PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
- return false;
- }
- return true;
- }
复制代码 总结来说就是冻结的流程走到最后就是调用了"Frozen"这个profile,将/sys/fs/cgroup路径下面对应的cgroup.freeze节点,写值为1,反之解冻就是将值写为0.
这就是Cgroup 中间抽象层的作用,但是将这个节点写为1以后,怎样使用这个节点值,又是怎样实现进程的冻结,继续看kernel内里怎样使用这个节点。
3.3.6 cgroup.cgroup_base_files
当修改cgroup.freeze这个节点的值的时候,就会触发cgroup_freeze_write方法来进项处理惩罚
- androids/kernel-5.10/kernel/cgroup/cgroup.c
- /* cgroup core interface files for the default hierarchy */
- static struct cftype cgroup_base_files[] = {
- ....
- {
- .name = "cgroup.freeze",
- .flags = CFTYPE_NOT_ON_ROOT,
- .seq_show = cgroup_freeze_show, //读的时候会调用,例如cat cgroup.freeze,会调用这个方法来显示当前状态
- .write = cgroup_freeze_write, //写的时候调用,当写入新的值的时候,会触发此方法。例如 echo 1 >cgroup.freeze
- },
- ...
- };
复制代码 3.3.7 cgroup.cgroup_freeze_write
调用cgroup_freeze方法
- static ssize_t cgroup_freeze_write(struct kernfs_open_file *of,
- char *buf, size_t nbytes, loff_t off)
- {
- struct cgroup *cgrp;
- ssize_t ret;
- int freeze;
- ret = kstrtoint(strstrip(buf), 0, &freeze);
- if (ret)
- return ret;
- if (freeze < 0 || freeze > 1) //如果写入的值不是0或者1 就返回异常
- return -ERANGE;
- cgrp = cgroup_kn_lock_live(of->kn, false);
- if (!cgrp)
- return -ENOENT;
- cgroup_freeze(cgrp, freeze); //调用cgroup_freeze方法
- cgroup_kn_unlock(of->kn);
- return nbytes;
- }
复制代码 3.3.8 freezer.cgroup_freeze
会遍历当前cgroup的子cgroup,将父cgroup的状态传递到子cgroup中,如果父cgroup被冻结或者解冻,他的全部子cgroup也会被冻结或者解冻,然后调用cgroup_do_freeze去实验冻结动作
- void cgroup_freeze(struct cgroup *cgrp, bool freeze){
- ...
- /*
- * Propagate changes downwards the cgroup tree.
- */
- css_for_each_descendant_pre(css, &cgrp->self) { //将更改沿着 cgroup 树向下传播 这里用来遍历当前控制组(
- // cgrp)的所有后代控制组。它按先序遍历的顺序递归遍历树状结构中的控制组
- dsct = css->cgroup;
- if (freeze) { //如果freeze为真即请求冻结操作,代码会将后代控制组的
- //e_freeze 计数器递增。e_freeze 是一个计数器,用于跟踪控制组的冻结状态。
- dsct->freezer.e_freeze++;
- /*
- * Already frozen because of ancestor's settings?
- */
- if (dsct->freezer.e_freeze > 1) //如果计数器在递增后大于1,表示该控制组已经因为其祖先的设置而被冻结,那么就继续处理下一个控制组。
- continue;
- } else { //反之就是解冻操作
- dsct->freezer.e_freeze--;
- /*
- * Still frozen because of ancestor's settings?
- */
- if (dsct->freezer.e_freeze > 0)
- continue;
- WARN_ON_ONCE(dsct->freezer.e_freeze < 0); //保证递减不会为负数
- }
- ...
- /*
- * Do change actual state: freeze or unfreeze.
- */
- cgroup_do_freeze(dsct, freeze);
- ...
- }
复制代码 3.3.9 freezer.cgroup_do_freeze
判断是否是内核线程,如果是内核线程就跳出循环,如果不是就继续实验cgroup_freeze_task,目前cgroup冻结机制,没有对内核线程的处理惩罚
- static void cgroup_do_freeze(struct cgroup *cgrp, bool freeze){
- ...
- while ((task = css_task_iter_next(&it))) {
- /*
- * Ignore kernel threads here. Freezing cgroups containing
- * kthreads isn't supported.
- */
- if (task->flags & PF_KTHREAD) //判断是否是内核线程,PF_KTHREAD 是内核线程的标志,表示这个任务是一个内核线程。
- continue;
- cgroup_freeze_task(task, freeze);
- ...
- }
- }
复制代码 3.3.10 freezer.cgroup_freeze_task
如果freeze为真,将jobctl中的标记位设置为 JOBCTL_TRAP_FREEZE。如果不是就扫除 JOBCTL_TRAP_FREEZE
jobctl 是一个与任务(线程)相关的控制字段,用于管理任务的控制状态。它包含多个标记位,用于不同的任务控制操作。
JOBCTL_TRAP_FREEZE是jobctl位掩码中的一个标记位,它用于控制任务的冻结状态。当该标记位被设置时,表示任务应当进入冻结状态。当该标记位被扫除时,任务将退出冻结状态。
- /*
- * Freeze or unfreeze the task by setting or clearing the JOBCTL_TRAP_FREEZE
- * jobctl bit.
- */
- static void cgroup_freeze_task(struct task_struct *task, bool freeze)
- {
- unsigned long flags;
- /* If the task is about to die, don't bother with freezing it. */
- if (!lock_task_sighand(task, &flags))
- return;
- if (freeze) {
- task->jobctl |= JOBCTL_TRAP_FREEZE;
- signal_wake_up(task, false);
- } else {
- task->jobctl &= ~JOBCTL_TRAP_FREEZE;
- wake_up_process(task);
- }
- unlock_task_sighand(task, &flags);
- }
复制代码 3.3.11 freezer.signal_wake_up_state
signal_wake_up方法会调用signal_wake_up_state方法将线程状态置为TIF_SIGPENDING,表明有一个挂起的信号必要处理惩罚。
TIF_SIGPENDING是Linux内核中的一个线程标记(Thread Information Flag),用于表示当火线程有未处理惩罚的挂起信号。它是内核用于管理信号处理惩罚机制的一部分
当一个线程收到信号后,但还未开始处理惩罚这些信号时,内核会设置TIF_SIGPENDING标记。这表示当火线程有挂起的信号必要处理惩罚,内核在调度时会检查这个标记
- static inline void signal_wake_up(struct task_struct *t, bool resume)
- {
- signal_wake_up_state(t, resume ? TASK_WAKEKILL : 0);
- }
- /*
- * Tell a process that it has a new active signal..
- *
- * NOTE! we rely on the previous spin_lock to
- * lock interrupts for us! We can only be called with
- * "siglock" held, and the local interrupt must
- * have been disabled when that got acquired!
- *
- * No need to set need_resched since signal event passing
- * goes through ->blocked
- */
- void signal_wake_up_state(struct task_struct *t, unsigned int state)
- {
- set_tsk_thread_flag(t, TIF_SIGPENDING);
- /*
- * TASK_WAKEKILL also means wake it up in the stopped/traced/killable
- * case. We don't check t->state here because there is a race with it
- * executing another processor and just now entering stopped state.
- * By using wake_up_state, we ensure the process will wake up and
- * handle its death signal.
- */
- if (!wake_up_state(t, state | TASK_INTERRUPTIBLE)) //TASK_INTERRUPTIBLE 表示该任务是可被中断的,因此可以被信号唤醒。
- //如果 wake_up_state 返回 false(即任务未被唤醒),则调用 kick_process(t); 来确保任务被唤醒并能够处理信号。
- kick_process(t);
- }
复制代码 综上,全部要被冻结的任务,都将jobctl中的标记位设置为 JOBCTL_TRAP_FREEZE,然后处于TIF_SIGPENDING的状态,而从处于TIF_SIGPENDING状态我们可以得知,最后的处理惩罚是在signal信号处理惩罚机制内里行止理惩罚的。这个时候我们也能知晓在前面,为什么判断是内核线程的时候会退出,由于在Linux的设计上,信号机制重要是针对用户态(用户空间进程)进行处理惩罚的,
3.3.12 signal.do_notify_resume
从上面得知,最后会走到信号处理惩罚内里行止理惩罚,处于TIF_SIGPENDING状态,表明有信号等待处理惩罚,这时候会走到信号处理惩罚流程,此中关于信号处理惩罚机制就不详细表明了
直接从逻辑上讲,如果有挂起的信号(由 TIF_SIGPENDING 标记指示),do_notify_resume将调用信号处理惩罚函数来处理惩罚这些信号,也就是说归去实验do_notify_resume方法,然后如果有_TIF_SIGPENDING状态,会去实验do_signal。
- void do_notify_resume(struct pt_regs *regs, unsigned long thread_info_flags)
- {
- .....
- if (thread_info_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL)) {
- BUG_ON(regs != current->thread.regs);
- do_signal(current); //如果有_TIF_SIGPENDING状态,会去执行do_signal
- }
- .....
- }
复制代码 3.3.13 signal.do_signal
通过get_signal函数,检测是否有信号待处理惩罚,
- static void do_signal(struct task_struct *tsk){
- struct ksignal ksig = { .sig = 0 }; // 初始化一个 ksignal 结构体,初始信号值为 0
- ...
- get_signal(&ksig); // 检查是否有信号待处理,如果有,将其存储在 ksig 中
- ...
- }
复制代码 3.3.14 signal.get_signal
在信号处理惩罚流程中检查task中的jobctl标记位是否是JOBCTL_TRAP_FREEZ,如果是的话,就实验冻结操作,当前这个函数也和我们最开始通过PS判断进程冻结状态时,看到进程的WCHAN是在do_freezer_trap上面对应了起来,最终在内核上面实验的冻结函数就是do_freezer_trap
- bool get_signal(struct ksignal *ksig)
- {
- ...
- if (unlikely(current->jobctl &
- (JOBCTL_TRAP_MASK | JOBCTL_TRAP_FREEZE))) {
- if (current->jobctl & JOBCTL_TRAP_MASK) {
- do_jobctl_trap();
- spin_unlock_irq(&sighand->siglock);
- } else if (current->jobctl & JOBCTL_TRAP_FREEZE)
- do_freezer_trap(); //如果jobctl中的标志位为 JOBCTL_TRAP_FREEZE,开始执行真正的冻结操作
- goto relock;
- }
- ...
复制代码 3.3.15 signal.do_freezer_trap
在冻结方法里会走到freezable_schedule()冻结进程调度方法内里去,然后调用schedule()方法,具体内核的调度战略就不细谈,重要相识这里的schedule() 是 Linux 内核中管理任务调度的关键接口。它负责将当进步程的CPU时间让给其他进程,并确保系统能够根据调度战略高效运行。
- /**
- * do_freezer_trap - handle the freezer jobctl trap
- *
- * Puts the task into frozen state, if only the task is not about to quit.
- * In this case it drops JOBCTL_TRAP_FREEZE.
- *
- * CONTEXT:
- * Must be called with @current->sighand->siglock held,
- * which is always released before returning.
- */
- static void do_freezer_trap(void)
- __releases(¤t->sighand->siglock)
- {
- .....
- __set_current_state(TASK_INTERRUPTIBLE); //当前进程的状态设置为 TASK_INTERRUPTIBLE。在这个状态下,进程是可中断的,等待某些事件(如信号或定时器)时进入睡眠
- clear_thread_flag(TIF_SIGPENDING); 、//清除 TIF_SIGPENDING 线程标志,表明此时线程认为已经处理完所有未决的信号
- spin_unlock_irq(¤t->sighand->siglock);
- cgroup_enter_frozen(); //表明cgroup进入冻结状态
- freezable_schedule(); //进程调度挂起,让出CPU 。-------------->这里就是进程冻结真正的逻辑
- }
- /kernel-5.10/include/linux/freezer.h
- static inline void freezable_schedule(void)
- {
-
- freezer_do_not_count(); // 通知冻结管理机制,当前任务不应被计入活跃任务计数。通常,内核中有一个计数器,用于跟踪有多少任务仍在运行。
- //调用 freezer_do_not_count() 意味着当前任务在进入 schedule() 时将不被视为活跃的,从而允许系统在冻结期间忽略该任务
-
- schedule(); //标准的调度调用,当前任务会进入睡眠状态,并且系统会调度其他任务运行。此时,任务进入了可中断的睡眠状态,等待被唤醒或其他事件发生
- freezer_count(); //与freezer_do_not_count相反通知冻结管理机制任务已经恢复活跃状态,应该再次计入活跃任务计数。这通常在任务从睡眠状态恢复后调用。
- }
复制代码 3.3.16 总结
综上,我们可以对冻结的实现做个简朴总结,上层通过Cgroup 中间抽象层将cgroup.freeze节点,写值为1,然后内核监控这个节点的值,对写了这个节点的进程,将jobctl中的标记位设置为 JOBCTL_TRAP_FREEZE,然后处于TIF_SIGPENDING的状态(有待处理惩罚的信号),信号处理惩罚机制监控到以后,调用内核的调度方法,将进程自动挂起,将当进步程的CPU时间让给其他进程,从而实现了进程的冻结。
4.简朴图解
1.判断冻结功能是否开启、应用是否属于宽免的应用、应用是否已经被冻结、应用是否不应该被冻结。当做完底子的判断之后,然后看应用当前的 adj 是否大于等于 900 (CACHE_APP) 来决定是否冻结应用,然后开启一个延迟10s,如果这个10s内状态都没有变化,就实验冻结流程,调用Cgrop中间层
2.Cgrop中间层判断实验冻结,将/sys/fs/cgroup路径下面对应的cgroup.freeze节点,写值为1,反之解冻就是将值写为0
3.内核监控cgroup.freeze节点,如值为1,将jobctl中的标记位设置为 JOBCTL_TRAP_FREEZE,然后处于TIF_SIGPENDING的状态
4.信号机制监控到TIF_SIGPENDING状态任务,判断标记位是否是JOBCTL_TRAP_FREEZE,调用内核的调度方法,将进程自动挂起,让出CPU资源
5.解冻的场景
上面梳理了从上层到内核的冻结实现逻辑,任务冻结以后自然也是时候会去解冻,下面就来大致看看几个解冻场景
5.1.1 低内存内存整理时解冻
在系统内存告急时,会调用trimMemoryUiHiddenIfNecessaryLSP进行判断,是否必要清算不再可见的UI元向来开释内存,如果是,通过scheduleTrimMemoryLSP来整理
- frameworks/base/services/core/java/com/android/server/am/AppProfiler.java
- @GuardedBy({"mService", "mProcLock"})
- private void trimMemoryUiHiddenIfNecessaryLSP(ProcessRecord app) {
- if ((app.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
- || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) {
- // If this application is now in the background and it
- // had done UI, then give it the special trim level to
- // have it free UI resources.
- scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,
- "Trimming memory of bg-ui ");
- app.mProfile.setPendingUiClean(false);
- }
- }
-
- @GuardedBy({"mService", "mProcLock"})
- private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) {
- IApplicationThread thread;
- if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
- try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
- Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level);
- }
- mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
- CachedAppOptimizer.UNFREEZE_REASON_TRIM_MEMORY);
- thread.scheduleTrimMemory(level);
- } catch (RemoteException e) {
- }
- }
- }
复制代码 在scheduleTrimMemoryLSP方法内里,会调用unfreezeTemporarily来临时解冻应用,然后走到unfreezeAppLSP,在解冻完成以后,会继续调用freezeAppAsyncLSP去冻结,上面讲过,这个冻结是有延时的
- @GuardedBy("mAm")
- void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis) {
- if (mUseFreezer) {
- synchronized (mProcLock) {
- // Move the earliest freezable time further, if necessary
- final long delay = updateEarliestFreezableTime(app, delayMillis);
- if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
- unfreezeAppLSP(app, reason);
- freezeAppAsyncLSP(app, delay);
- }
- }
- }
- }
复制代码 5.1.2 dump进程信息时解冻
在进行dump时,可以看到在类似的dump方法内里会调用enableFreezer方法,传递的参数是fasle,代表当前禁止冻结,这个时候如果判断进程被冻结了,就会进行解冻,然后再dump实验完以后,在finally内里再去允许冻结
- frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- try {
- mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
- "meminfo", pw)) return;
- PriorityDump.dump(mPriorityDumper, fd, pw, args);
- } finally {
- mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
- }
- }
- }
-
- frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
- /**
- * Enables or disabled the app freezer.
- * @param enable Enables the freezer if true, disables it if false.
- * @return true if the operation completed successfully, false otherwise.
- */
- public synchronized boolean enableFreezer(boolean enable) {
- ...
- if (!enable && opt.isFrozen()) {
- unfreezeAppLSP(process, UNFREEZE_REASON_FEATURE_FLAGS);
- ...
- }
复制代码 5.1.3 发送和接收广播临时解冻
担当广播时,临时解冻
- frameworks/base/services/core/java/com/android/server/am/BroadcastQueueImpl.java
- private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
- BroadcastFilter filter, boolean ordered, int index) {
- ...
- if (ordered) { //有序广播
- r.curFilter = filter;
- filter.receiverList.curBroadcast = r;
- r.state = BroadcastRecord.CALL_IN_RECEIVE;
- if (filter.receiverList.app != null) {
- // Bump hosting application to no longer be in background
- // scheduling class. Note that we can't do that if there
- // isn't an app... but we can only be in that case for
- // things that directly call the IActivityManager API, which
- // are already core system stuff so don't matter for this.
- r.curApp = filter.receiverList.app;
- r.curAppLastProcessState = r.curApp.mState.getCurProcState();
- filter.receiverList.app.mReceivers.addCurReceiver(r);
- mService.enqueueOomAdjTargetLocked(r.curApp);
- mService.updateOomAdjPendingTargetsLocked(
- OOM_ADJ_REASON_START_RECEIVER);
- }
- } else if (filter.receiverList.app != null) {
- mService.mOomAdjuster.unfreezeTemporarily(filter.receiverList.app,
- CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
- }
- ...
- }
复制代码 发送广播时,临时解冻
- frameworks/base/services/core/java/com/android/server/am/BroadcastQueueImpl.java
- public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
- ...
- if (sendResult) {
- if (r.callerApp != null) {
- mService.mOomAdjuster.unfreezeTemporarily(
- r.callerApp,
- CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
- }
- ...
- }
复制代码 5.1.4 持有文件锁解冻
为了防止冰冻进程持有文件锁引起死锁。或者思量到一些特别场景下,进程在被冰冻的过程中拿住了文件锁,发现持有锁就立刻解冻。
- frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
- @GuardedBy({"mAm"})
- @Override
- public void onBlockingFileLock(IntArray pids) {
- if (DEBUG_FREEZER) {
- Slog.d(TAG_AM, "Blocking file lock found: " + pids);
- }
- synchronized (mAm) {
- synchronized (mProcLock) {
- ....
- if (app != null) {
- for (int i = 1; i < pids.size(); i++) {
- int blocked = pids.get(i);
- synchronized (mAm.mPidsSelfLocked) {
- pr = mAm.mPidsSelfLocked.get(blocked);
- }
- if (pr != null
- && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
- Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
- + pr.processName + " (" + blocked + ")");
- // Found at least one blocked non-cached process
- unfreezeAppLSP(app, UNFREEZE_REASON_FILE_LOCKS); //解冻原因是因为文件锁
- break;
- }
- }
- ....
- }
- }
复制代码 5.1.5 留意
向冻住的进程发送同步binder请求会立刻收到错误码BR_FROZEN_REPLY。
在进程进行解冻的时候,会去检查,如果在冻结期间有收到同步binder的请求,就会kill掉这个进程
- void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
- ...
- 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_FREEZER,
- 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");
- }
- ...
- }
复制代码 6.小结
1.进程的冻结是通过cgroup的Freezer子系统来实现的,最终的原理是调度进程,不去申请cpu资源。
2.冻结的进程不会开释内存,由于当解冻时,要快速恢复,而无需重新加载或启动
3.冻结机制是针对cache进程
4.正常当进程变为非cache进程时候,就会解冻
5.Android 13以后针对binder 调用进行了优化,对进程的binder先辈行冻结,如果向冻住的进程发送同步binder请求会立刻收到错误码BR_FROZEN_REPLY
6.进程进行解冻的时候,会去检查,如果在冻结期间有收到同步binder的请求,就会kill掉这个进程
7.拥有INSTALL_PACKAGES权限的应用,能够宽免被冻结
参考文档:
https://juejin.cn/post/7264949719275880482#heading-13
https://zhuanlan.zhihu.com/p/487695624
https://blog.csdn.net/gary_qing/article/details/136243769
https://kernel.meizu.com/2024/07/12/sub-system-cgroup-freezer-in-Linux-kernel/#%E4%BA%8C%E3%80%81cgroup%E7%9B%B8%E5%85%B3%E7%BB%84%E4%BB%B6
u.com/p/487695624](https://zhuanlan.zhihu.com/p/487695624)
https://blog.csdn.net/gary_qing/article/details/136243769
https://kernel.meizu.com/2024/07/12/sub-system-cgroup-freezer-in-Linux-kernel/#%E4%BA%8C%E3%80%81cgroup%E7%9B%B8%E5%85%B3%E7%BB%84%E4%BB%B6
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |