基于AndroidU进程冻结机制详解
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
https://i-blog.csdnimg.cn/direct/94157a240e794e11a9b4ecd7cb2ff98a.png
[*] 通过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.866100014671654 I am_unfreeze:
08-06 05:21:48.952100014671654 I am_freeze:
08-06 05:21:55.171100014671654 I am_unfreeze:
08-06 05:22:05.263100014671654 I am_freeze:
08-06 05:27:05.417100014671654 I am_unfreeze:
08-06 05:27:15.496100014671654 I am_freeze:
08-06 05:33:29.112100014671654 I am_unfreeze:
[*]检查/sys/fs/cgroup/uid_xxx/cgroup.freeze文件
adb shell cat /sys/fs/cgroup/uid_{应用UID}/cgroup.freeze
0代表没有被冻结。1代表被冻结
https://i-blog.csdnimg.cn/direct/0cedd339a2884a92b4ffdc26cd0cb3bd.png
https://i-blog.csdnimg.cn/direct/49f9c23961ad4923995851b866fe37dc.png
[*]PS查看进程状态和WCHAN
adb shell ps -Ae |grep -i {PID}
当进程是S状态,且WCHAN 显示在do_freezer_trap 上等待,就表明进程被冻结
https://i-blog.csdnimg.cn/direct/b340733a706a451a8d7b0e37dcd01b88.png
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 流程总结
流程总结如下:
https://i-blog.csdnimg.cn/direct/a90e7a6eee5f4b07b117f6b3b9ec2414.png
3.2冻结的触发
当进程的优先级(adj)发生变化的时候,会去盘算判断,并实验冻结,adj会发生变化的原因一样平常如下:
/**
173 * All of the code required to compute proc states and oom_adj values.
174 */
175public 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 实现进程的冻结功能。
https://i-blog.csdnimg.cn/direct/d34934235a734bf4b630dde3bc620ae4.png
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.简朴图解
https://i-blog.csdnimg.cn/direct/9f006d12154c4c619a6dd17f3be3da7d.png
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企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]