基于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怎样手动开启关闭

  1. 开发者选项 “Suspend execution for cached apps”

    里面有三个选项,Device default, Enabled 和Disabled

  1. 通过adb命令

    adb shell settings put global cached_apps_freezer <enabled|disabled|default>

    Android R上面的default等于功能关闭; Android S上面是默认开启。上面两种方式其实相同的,无论通过那种开启和关闭功能,都要重启才能生效

2.4怎样查看冻结的状态

  1. 查看日志(一般日志会有相应含义的字符)
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]
  1. 检查/sys/fs/cgroup/uid_xxx/cgroup.freeze文件
adb shell cat /sys/fs/cgroup/uid_{应用UID}/cgroup.freeze
0代表没有被冻结。1代表被冻结

在这里插入图片描述
在这里插入图片描述

  1. PS查看进程状态和WCHAN
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(&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.简单图解

在这里插入图片描述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值