科技颠覆者 发表于 2024-11-24 03:50:11

基于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(&current->sighand->siglock)
{
.....
        __set_current_state(TASK_INTERRUPTIBLE); //当前进程的状态设置为 TASK_INTERRUPTIBLE。在这个状态下,进程是可中断的,等待某些事件(如信号或定时器)时进入睡眠
        clear_thread_flag(TIF_SIGPENDING); 、//清除 TIF_SIGPENDING 线程标志,表明此时线程认为已经处理完所有未决的信号   
        spin_unlock_irq(&current->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]
查看完整版本: 基于AndroidU进程冻结机制详解