基于AndroidU进程冻结机制详解

打印 上一主题 下一主题

主题 848|帖子 848|积分 2544

1.背景概述

自Android问世以来,如何最大限度地优化进程占用的资源,同时不影响用户体验,一直是个困扰的题目。运行中的进程在制止与用户交互后会退到背景,此时虽然不再影响用户体验,但仍旧会占用部分CPU和内存等资源。
有人可能会问,为什么不直接杀掉这些背景进程?这是由于直接杀掉这些进程会影响用户的使用体验——每次用户重新启动这些进程时,都必要完全重新加载,从而导致启动速率变慢。
因此,我们必要一种机制,可以限定这些进程的资源占用而不直接杀掉它们。由此,“Freeze”冻结机制应运而生。
在Android 11及更高版本的原生系统中,支持对缓存应用的冻结。当应用切换到背景且没有其他动作后,系统会在一定时间后根据状态判断,通过cgroup对缓存应用进行冻结。此机制可以减少背景活泼应用的CPU资源消耗,从而到达省电和节省资源的目标。当用户必要再次使用应用时,应用会被“解冻”并快速恢复。
本文将基于Android U对冻结机制的流程和原理进行梳理和总结。
2.功能表明

2.1冻结的原理

Android将进程按照优先级从高到低分为:前台进程–>可感知进程–>服务进程–>Cache进程,Freeze通过冻结Cache进程来开释它们占用的系统资源。它们的优先级(adj)通过OomAdjuster来进行更新盘算。
2.2冻结的作用



  • 资源:冻结进程的CPU会被开释,这部分资源会被系统重新分配给其他必要实验的进程和服务
  • 电量:冻结的进程不会再背景运行,以是可以节省设备电量的消耗,到达省电的目标
  • 稳固性:通过冻结不活泼的进程,可以减少它们对系统资源的竞争,进步系统的稳固性
2.3怎样手动开启关闭


  • 开发者选项 “Suspend execution for cached apps”
    内里有三个选项,Device default, Enabled 和Disabled


  • 通过adb命令
    adb shell settings put global cached_apps_freezer <enabled|disabled|default>
    Android R上面的default等于功能关闭; Android S上面是默认开启。上面两种方式其实相同的,无论通过那种开启和关闭功能,都要重启才能见效
2.4怎样查看冻结的状态


  • 查看日志(一样平常日志会有相应寄义的字符)
  1. adb logcat -b all | grep -iE "freeze|froze"
  2. 08-06 05:21:28.866  1000  1467  1654 I am_unfreeze: [13839,com.google.android.gms,4]
  3. 08-06 05:21:48.952  1000  1467  1654 I am_freeze: [13839,com.google.android.gms]
  4. 08-06 05:21:55.171  1000  1467  1654 I am_unfreeze: [696,com.google.android.apps.messaging,6]
  5. 08-06 05:22:05.263  1000  1467  1654 I am_freeze: [696,com.google.android.apps.messaging]
  6. 08-06 05:27:05.417  1000  1467  1654 I am_unfreeze: [696,com.google.android.apps.messaging,6]
  7. 08-06 05:27:15.496  1000  1467  1654 I am_freeze: [696,com.google.android.apps.messaging]
  8. 08-06 05:33:29.112  1000  1467  1654 I am_unfreeze: [21895,com.google.android.apps.photos,6]
复制代码

  • 检查/sys/fs/cgroup/uid_xxx/cgroup.freeze文件
  1. adb shell cat /sys/fs/cgroup/uid_{应用UID}/cgroup.freeze
  2. 0代表没有被冻结。1代表被冻结
复制代码



  • PS查看进程状态和WCHAN
  1. adb shell ps -Ae |grep -i {PID}
  2. 当进程是S状态,且WCHAN 显示在do_freezer_trap 上等待,就表明进程被冻结
复制代码

  1. tips
  2. 1.在内核版本 4.14的时候,进程被冻结显示的是D状态, 高版本中已经不会对进程切到 D 状态,而是保留 S 态,然后在 S 态的基础上做 freeze 的处理
  3. 2.WCHAN 表示当前线程在内核上正在执行的函数名
复制代码
3.冻结的流程

3.1冻结的初始化

上面提到过冻结的原理,是根据进程的adj来判断是否是cache应用,然后对其进行冻结,以是Freeze初始化也是在OomAdjuster中
3.1.1 SystemServer.run

  1. private void run() {
  2.     ...
  3.     try {
  4.              t.traceBegin("StartServices");
  5.               startBootstrapServices(t); //初始化引导类服务,例如AMS PMS WMS等,这些是在Android系统系统启动过程中最先启动的服务
  6.               startCoreServices(t); //初始化系统的一写核心服务,列如health service,battery service,sernor service
  7.               startOtherServices(t); //这里启动的服务。一般在系统偏后阶段执行
  8.               startApexServices(t); //初始化apex系统服务,这些服务主要用于处理与 Apex 相关的功能,比如模块激活、更新监控等
  9.               updateWatchdogTimeout(t); //更新看门狗超时时间
  10.           }
  11.     ...
  12.      }
复制代码
3.1.2 SystemServer.startOtherServices

调用ContentProviderHelper.installSystemProviders来初始化SystemProviders
  1. private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
  2.      ...
  3.            t.traceBegin("InstallSystemProviders");
  4.               mActivityManagerService.getContentProviderHelper().installSystemProviders();
  5.               // Device configuration used to be part of System providers
  6.               mSystemServiceManager.startService(UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS);
  7.               // Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
  8.               SQLiteCompatibilityWalFlags.reset();
  9.               t.traceEnd();
  10.      ...        
  11. }
复制代码
3.1.3 ContentProviderHelper.installSystemProviders

调用OomAdjuster.initSettings来初始化OomAdjuster
  1. public final void installSystemProviders(){
  2.     ...
  3.     new DevelopmentSettingsObserver(); // init to observe developer settings enable/disable
  4.     SettingsToPropertiesMapper.start(mService.mContext.getContentResolver());
  5.     mService.mOomAdjuster.initSettings();
  6.     ...
  7. }
复制代码
3.1.4 OomAdjuster.initSettings

调用CachedAppOptimizer.init来初始化CachedAppOptimizer
  1. void initSettings() {
  2.     mCachedAppOptimizer.init();
  3.     mCacheOomRanker.init(ActivityThread.currentApplication().getMainExecutor());
  4.     ...
  5. }
复制代码
3.1.4 CachedAppOptimizer.init

会调用CachedAppOptimizer.updateUseFreezer来初始化进程冻结功能
  1.    ...
  2. @VisibleForTesting static final Uri CACHED_APP_FREEZER_ENABLED_URI = Settings.Global.getUriFor(
  3.                   Settings.Global.CACHED_APPS_FREEZER_ENABLED);
  4.    ...               
  5. public void init() {
  6.     ...
  7.      mAm.mContext.getContentResolver().registerContentObserver(
  8.                   CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver); //注册了一个观察者,当CACHED_APP_FREEZER_ENABLED_URI也就是
  9.                                                                              // Settings.Global.CACHED_APPS_FREEZER_ENABLED发生变化时收到通知
  10.                   
  11. synchronized (mPhenotypeFlagLock) {
  12.     ...
  13.               updateUseFreezer(); //更新是否使用冻结机制
  14.     ...         
  15.           }
  16.     ...      
  17. }
复制代码
3.1.5 CachedAppOptimizer.**updateUseFreezer

  1. @GuardedBy("mPhenotypeFlagLock")
  2.       private void updateUseFreezer() {
  3.                 final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),
  4. 1105                  Settings.Global.CACHED_APPS_FREEZER_ENABLED);    //首先获取CACHED_APPS_FREEZER_ENABLED数据库的值
  5. 1106  
  6. 1107          if ("disabled".equals(configOverride)) {  //如果是disabled,就不支持进程冻结
  7. 1108              mUseFreezer = false;
  8. 1109          } else if ("enabled".equals(configOverride)
  9. 1110                  || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
  10. 1111                      KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) { //如果CACHED_APPS_FREEZER_ENABLED数据库的值是enabled
  11.                                                                    //或者use_freezer对应的config的值是true
  12. 1112              mUseFreezer = isFreezerSupported(); //判断系统支不支持Freezer,也就是相关驱动
  13. 1113              updateFreezerDebounceTimeout(); //更新Debounce超时时间,也就是冻结机制的延迟超时时间
  14. 1114              updateFreezerExemptInstPkg();//更新冻结机制豁免安装包 ,指的是豁免被冻结的应用,拥有INSTALL_PACKAGES权限的应用能够豁免被冻结
  15. 1115          } else { //其余情况下也不支持被冻结
  16. 1116              mUseFreezer = false;
  17. 1117          }
  18. 1118  
  19. 1119          final boolean useFreezer = mUseFreezer;
  20. 1120          // enableFreezer() would need the global ActivityManagerService lock, post it.
  21. 1121          mAm.mHandler.post(() -> {
  22. 1122              if (useFreezer) { //如果系统支持Freeze
  23. 1123                  Slog.d(TAG_AM, "Freezer enabled");
  24. 1124                  enableFreezer(true);  启用进程冻结功能
  25. 1125  
  26. 1126                  if (!mCachedAppOptimizerThread.isAlive()) {
  27. 1127                      mCachedAppOptimizerThread.start(); //启动CachedAppOptimizerThread线程
  28. 1128                  }
  29. 1129  
  30. 1130                  if (mFreezeHandler == null) {
  31. 1131                      mFreezeHandler = new FreezeHandler();
  32. 1132                  }
  33. 1133  
  34. 1134                  Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
  35. 1135                          Process.THREAD_GROUP_SYSTEM);//设置CachedAppOptimizerThread线程的cgroup
  36. 1136              } else {
  37. 1137                  Slog.d(TAG_AM, "Freezer disabled");
  38. 1138                  enableFreezer(false);
  39. 1139              }
  40. 1140          });
  41. 1141      }
  42.       }
复制代码
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)
  1. /**
  2. 1055       * Determines whether the freezer is supported by this system
  3. 1056       */
  4. 1057      public static boolean isFreezerSupported() {
  5. 1058          boolean supported = false;
  6. 1059          FileReader fr = null;
  7. 1060  
  8. 1061          try {
  9. 1062              String path = getFreezerCheckPath();             // /sys/fs/cgroup/uid_0/pid_*/cgroup.freeze
  10. 1063              Slog.d(TAG_AM, "Checking cgroup freezer: " + path);
  11. 1064              fr = new FileReader(path);
  12. 1065              char state = (char) fr.read();
  13. 1066  
  14. 1067              if (state == '1' || state == '0') {
  15. 1068                  // Also check freezer binder ioctl
  16. 1069                  Slog.d(TAG_AM, "Checking binder freezer ioctl");
  17. 1070                  getBinderFreezeInfo(Process.myPid());     //判断驱动是否支持freezer
  18. 1071  
  19. 1072                  // Check if task_profiles.json contains invalid profiles
  20. 1073                  Slog.d(TAG_AM, "Checking freezer profiles");
  21. 1074                  supported = isFreezerProfileValid();    //检查task_profiles.json是否包含进程冻结相关的Profiles
  22. 1075              } else {
  23. 1076                  Slog.e(TAG_AM, "Unexpected value in cgroup.freeze");
  24. 1077              }
  25. 1078          } catch (java.io.FileNotFoundException e) {
  26. 1079              Slog.w(TAG_AM, "File cgroup.freeze not present");
  27. 1080          } catch (RuntimeException e) {
  28. 1081              Slog.w(TAG_AM, "Unable to read freezer info");
  29. 1082          } catch (Exception e) {
  30. 1083              Slog.w(TAG_AM, "Unable to read cgroup.freeze: " + e.toString());
  31. 1084          }
  32. 1085  
  33. 1086          if (fr != null) {
  34. 1087              try {
  35. 1088                  fr.close();
  36. 1089              } catch (java.io.IOException e) {
  37. 1090                  Slog.e(TAG_AM, "Exception closing cgroup.freeze: " + e.toString());
  38. 1091              }
  39. 1092          }
  40. 1093  
  41. 1094          Slog.d(TAG_AM, "Freezer supported: " + supported);
  42. 1095          return supported;
  43. 1096      }
复制代码
3.1.7 流程总结

流程总结如下:

3.2冻结的触发

当进程的优先级(adj)发生变化的时候,会去盘算判断,并实验冻结,adj会发生变化的原因一样平常如下:
  1. /**
  2. 173   * All of the code required to compute proc states and oom_adj values.
  3. 174   */
  4. 175  public class OomAdjuster {
  5. 176      static final String TAG = "OomAdjuster";
  6. 177
  7. 178      public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) {
  8. 179          switch (oomReason) {
  9. 180              case OOM_ADJ_REASON_NONE:
  10. 181                  return AppProtoEnums.OOM_ADJ_REASON_NONE;
  11. 182              case OOM_ADJ_REASON_ACTIVITY:
  12. 183                  return AppProtoEnums.OOM_ADJ_REASON_ACTIVITY;
  13. 184              case OOM_ADJ_REASON_FINISH_RECEIVER:
  14. 185                  return AppProtoEnums.OOM_ADJ_REASON_FINISH_RECEIVER;
  15. 186              case OOM_ADJ_REASON_START_RECEIVER:
  16. 187                  return AppProtoEnums.OOM_ADJ_REASON_START_RECEIVER;
  17. 188              case OOM_ADJ_REASON_BIND_SERVICE:
  18. 189                  return AppProtoEnums.OOM_ADJ_REASON_BIND_SERVICE;
  19. 190              case OOM_ADJ_REASON_UNBIND_SERVICE:
  20. 191                  return AppProtoEnums.OOM_ADJ_REASON_UNBIND_SERVICE;
  21. 192              case OOM_ADJ_REASON_START_SERVICE:
  22. 193                  return AppProtoEnums.OOM_ADJ_REASON_START_SERVICE;
  23. 194              case OOM_ADJ_REASON_GET_PROVIDER:
  24. 195                  return AppProtoEnums.OOM_ADJ_REASON_GET_PROVIDER;
  25. 196              case OOM_ADJ_REASON_REMOVE_PROVIDER:
  26. 197                  return AppProtoEnums.OOM_ADJ_REASON_REMOVE_PROVIDER;
  27. 198              case OOM_ADJ_REASON_UI_VISIBILITY:
  28. 199                  return AppProtoEnums.OOM_ADJ_REASON_UI_VISIBILITY;
  29. 200              case OOM_ADJ_REASON_ALLOWLIST:
  30. 201                  return AppProtoEnums.OOM_ADJ_REASON_ALLOWLIST;
  31. 202              case OOM_ADJ_REASON_PROCESS_BEGIN:
  32. 203                  return AppProtoEnums.OOM_ADJ_REASON_PROCESS_BEGIN;
  33. 204              case OOM_ADJ_REASON_PROCESS_END:
  34. 205                  return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
  35. 206              case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
  36. 207                  return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
  37. 208              case OOM_ADJ_REASON_SYSTEM_INIT:
  38. 209                  return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT;
  39. 210              case OOM_ADJ_REASON_BACKUP:
  40. 211                  return AppProtoEnums.OOM_ADJ_REASON_BACKUP;
  41. 212              case OOM_ADJ_REASON_SHELL:
  42. 213                  return AppProtoEnums.OOM_ADJ_REASON_SHELL;
  43. 214              case OOM_ADJ_REASON_REMOVE_TASK:
  44. 215                  return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK;
  45. 216              case OOM_ADJ_REASON_UID_IDLE:
  46. 217                  return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE;
  47. 218              case OOM_ADJ_REASON_STOP_SERVICE:
  48. 219                  return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE;
  49. 220              case OOM_ADJ_REASON_EXECUTING_SERVICE:
  50. 221                  return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE;
  51. 222              case OOM_ADJ_REASON_RESTRICTION_CHANGE:
  52. 223                  return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE;
  53. 224              case OOM_ADJ_REASON_COMPONENT_DISABLED:
  54. 225                  return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
  55. 226              default:
  56. 227                  return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
  57. 228          }
  58. 229      }
复制代码
从上我们可以看到当进程状态或者组件状态发生变化的时候,就会触发adj的更新,这里以Activity的状态变化为例:
3.2.1 ActivityRecord.setState

可以看到Activity的STARTED状态和DESTROYING状态调用了updateProcessInfo方法去更新进程状态,updateOomAdj都是true
  1. void setState(State state, String reason) {
  2.      ...
  3.       case STARTED:
  4.                   // Update process info while making an activity from invisible to visible, to make
  5.                   // sure the process state is updated to foreground.
  6.                   if (app != null) {
  7.                       app.updateProcessInfo(false /* updateServiceConnectionActivities */,
  8.                               true /* activityChange */, true /* updateOomAdj */,
  9.                               true /* addPendingTopUid */);
  10.                   }
  11.       ...      
  12.      case :
  13.                   if (app != null && !app.hasActivities()) {
  14.                       // Update any services we are bound to that might care about whether
  15.                       // their client may have activities.
  16.                       // No longer have activities, so update LRU list and oom adj.
  17.                       app.updateProcessInfo(true /* updateServiceConnectionActivities */,
  18.                               false /* activityChange */, true /* updateOomAdj */,
  19.                               false /* addPendingTopUid */);
  20.                   }
  21.       ...                  
  22. }
复制代码
  1. tips
  2. 其他状态,如 RESUMED、PAUSED、STOPPED 等,已经隐含或明确地处理了进程的优先级和状态管理,因此不需要在这些状态中调用 updateProcessInfo。
  3. RESUMED 状态:在 RESUMED 状态中,Activity 已经完全进入前台,并且通常在进入此状态时,进程已经被提升为前台优先级,因此不需要再次调用 updateProcessInfo。
  4. PAUSED 和 STOPPED 状态:这些状态下,Activity 逐渐变得不可见或不再与用户交互。通常系统会依赖其他机制来管理进程的优先级变化,而不需要在这两个状态中特别调用 updateProcessInfo。
复制代码
3.2.2 ProcessRecord.updateProcessInfo

生命周期变化后,会调用到updateOomAdjLocked来继续更新adj的流程
  1. public void updateProcessInfo(boolean updateServiceConnectionActivities, boolean activityChange,
  2.               boolean updateOomAdj) {
  3.               ...
  4.               mService.updateLruProcessLocked(this, activityChange, null /* client */);
  5.               if (updateOomAdj) {
  6.                   mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY);
  7.               }
  8.               ...           
  9.               }
复制代码
3.2.3 ActivityManagerService.updateOomAdjLocked

  1. @GuardedBy("this")  //这是一个线程安全注解,表示这个方法在调用时,必须在当前对象实例的锁,在这也就是am的锁
  2.   final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
  3.           mOomAdjuster.updateOomAdjLocked(oomAdjReason);
  4.       }
复制代码
3.2.4 OomAdjuster

  1.     * Update OomAdj for all processes in LRU list
  2.      */
  3.     @GuardedBy("mService")
  4.     void updateOomAdjLocked(String oomAdjReason) {
  5.         synchronized (mProcLock) {
  6.             updateOomAdjLSP(oomAdjReason);
  7.         }
  8.     }
  9.    
  10.     @GuardedBy({"mService", "mProcLock"})
  11.     private void updateOomAdjLSP(String oomAdjReason) {
  12.         ...
  13.             mOomAdjUpdateOngoing = true;
  14.             performUpdateOomAdjLSP(oomAdjReason);
  15.         ...
  16.     }
  17. @GuardedBy({"mService", "mProcLock"})
  18.     private void performUpdateOomAdjLSP(String oomAdjReason) {
  19.         ...
  20.         updateOomAdjInnerLSP(oomAdjReason, topApp , null, null, true, true);
  21.         ...
  22.     }
  23. @GuardedBy({"mService", "mProcLock"})
  24.     private void updateOomAdjInnerLSP(String oomAdjReason, final ProcessRecord topApp,
  25.             ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
  26.             boolean startProfiling) {
  27.                 ...
  28.                 computeOomAdjLSP(app, ProcessList.UNKNOWN_ADJ, topApp, fullUpdate, now, false,
  29.                         computeClients);   //计算进程的adj
  30.                  boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids);
  31.                 ...                 
  32.             }
  33.   @GuardedBy({"mService", "mProcLock"})
  34.     private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
  35.             final long oldTime, final ActiveUids activeUids) {
  36.             ...         
  37.             applyOomAdjLSP(app, true, now, nowElapsed);   
  38.             ...
  39.           }   
  40.    
  41.    @GuardedBy({"mService", "mProcLock"})
  42.     private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
  43.             long nowElapsed) {
  44.                 ...
  45.                 updateAppFreezeStateLSP(app);
  46.                 ...            
  47.             }            
复制代码
3.2.5 OomAdjuster.updateAppFreezeStateLSP

当adj发生变化,然后盘算,最终会去调用updateAppFreezeStateLSP更新app的冻结状态,在内里会去判断是否要用冻结机制,当前应用是否免疫冻结,当前是否已经被冻结等状态,然后和 ProcessList.CACHED_APP_MIN_ADJ(900)进行比较,当前当前adj大于等于它的时候,才归去调用冻结方法。
  1. @GuardedBy({"mService", "mProcLock"})
  2.     private void updateAppFreezeStateLSP(ProcessRecord app) {
  3.         if (!mCachedAppOptimizer.useFreezer()) {  // 是否使用冻结机制
  4.             return;
  5.         }
  6.         if (app.mOptRecord.isFreezeExempt()) { // 是否免冻结, 这里追溯过去,目前只有判断拥有INSTALL_PACKAGES权限的进程能够被豁免
  7.             return;
  8.         }
  9.         final ProcessCachedOptimizerRecord opt = app.mOptRecord;
  10.         // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
  11.         if (opt.isFrozen() && opt.shouldNotFreeze()) { //如果已经被冻结,并且不应该被冻结
  12.             mCachedAppOptimizer.unfreezeAppLSP(app);  //解除冻结
  13.             return;
  14.         }
  15.         final ProcessStateRecord state = app.mState;
  16.         // Use current adjustment when freezing, set adjustment when unfreezing.   
  17.         if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen()   //最小冻结adj 为public static final int CACHED_APP_MIN_ADJ = 900;
  18.                 && !opt.shouldNotFreeze()) {     //当前 adj 大于最小冻结 adj 并且没有被冻结并且应该被冻结  
  19.             mCachedAppOptimizer.freezeAppAsyncLSP(app);
  20.         } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
  21.             mCachedAppOptimizer.unfreezeAppLSP(app); //当前 adj 小于最小冻结 adj 应该解冻
  22.         }
  23.     }
  24. }
复制代码
3.2.6 CachedAppOptimizer.freezeAppAsyncLSP

freezeAppAsyncLSP内里会post一个10s的message在时间到了的时候调用freezeProcess去冻结进程(延时10s发送进程冻结的消息,在10s内如果收到进程解冻的消息,会把进程冻结消息移除,也就不会实验进程冻结的操作)
  1. @VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;  //默认超时时间,毫秒为单位,也就是10s
  2. @VisibleForTesting volatile long mFreezerDebounceTimeout = DEFAULT_FREEZER_DEBOUNCE_TIMEOUT;
  3.    @GuardedBy({"mAm", "mProcLock"})
  4.       void freezeAppAsyncLSP(ProcessRecord app) {
  5.           freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, mFreezerDebounceTimeout));
  6.       }
  7.   
  8.       @GuardedBy({"mAm", "mProcLock"})
  9.       private void freezeAppAsyncLSP(ProcessRecord app, @UptimeMillisLong long delayMillis) {
  10.           freezeAppAsyncInternalLSP(app, delayMillis, false);
  11.       }
  12.       
  13.        @GuardedBy({"mAm", "mProcLock"})
  14.     void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis,
  15.             boolean force) {
  16.                 ...
  17.                 mFreezeHandler.sendMessageDelayed(
  18.                 mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
  19.                 delayMillis);    //延迟10s发送Message
  20.                 ...      
  21.             }
  22.             
  23.        ...     
  24.        @Override
  25.         public void handleMessage(Message msg) {
  26.             switch (msg.what) {
  27.                 case SET_FROZEN_PROCESS_MSG: {
  28.                     ProcessRecord proc = (ProcessRecord) msg.obj;
  29.                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"freezeProcess");
  30.                     synchronized (mAm) {
  31.                         freezeProcess(proc);    //响应TRACE_TAG_ACTIVITY_MANAGER 调用freezeProcess
  32.                     }
  33.                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  34.                     if (proc.mOptRecord.isFrozen()) { //进程冻结成功
  35.                         onProcessFrozen(proc);  //进程被冻结后收到的回调,执行内存压缩的相关操作
  36.                         removeMessages(DEADLOCK_WATCHDOG_MSG);
  37.                         sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS); ///延时1s发送消息检查文件锁的持有情况,
  38.                                                                                                     //考虑到一些特殊场景下,进程在被冰冻的过程中拿住了文件锁,冰冻成功后还会再检查一次,发现持有锁就立刻解冻。
  39.                                                                                                     //去冻结的时候也会去检查他是否拿有文件锁
  40.                     }
  41.                 } break;      
  42.          ...      
复制代码
总结上面条件就是,通过判断冻结功能是否开启、应用是否属于宽免的应用、应用是否已经被冻结、应用是否不应该被冻结。当做完底子的判断之后,然后看应用当前的 adj 是否大于等于 900 (CACHE_APP) 来决定是否冻结应用,然后开启一个延迟10s,如果这个10s内状态都没有变化,就实验冻结流程
  1. tips
  2. 1.DEFAULT_FREEZER_DEBOUNCE_TIMEOUT在Android 14之前是十分钟,Android 14上面是十秒**
  3. 2.这个时间可以动态的修改: adb shell device_config put activity_manager_native_boot freeze_debounce_timeout 1000   这个是改成1s**
  4. 3.冻结的时候会去检查文件锁状态,这是为了防止冰冻进程持有文件锁引起死锁。考虑到一些特殊场景下,进程在被冰冻的过程中拿住了文件锁,冰冻成功后还会再检查一次,发现持有锁就立刻解冻。**
复制代码
3.2.7 CachedAppOptimizer.freezeProcess

最终都会调用到freezeProcess函数,Android 13以后针对binder 调用进行了优化,对进程的 binder 先辈行冻结,这一步禁掉该进程对同步binder请求的接收和处理惩罚,以及对异步binder请求的处理惩罚,由于之前如果随意的冻结应用,会导致一些应用背景的跨进程举动异常,比方在binder通信期间。
到了Android 13之后,binder驱动已经支持FREEZER状态,当应用调用一个被冻结binder进程后,会直接返回一个错误,如许子就不会阻塞调用方的进程。
  1. @GuardedBy({"mAm"})
  2.         private void freezeProcess(final ProcessRecord proc) {
  3.             ...
  4.              Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"freezeBinder:" + name);
  5.                     if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {   //对进程的 binder 先进行冻结
  6.                         handleBinderFreezerFailure(proc, "outstanding txns");
  7.                         return;
  8.                     }
  9.               ...
  10.             try {
  11.                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"setProcessFrozen:" + name);
  12.                     traceAppFreeze(proc.processName, pid, -1);
  13.                     Process.setProcessFrozen(pid, proc.uid, true);   //根据uid,pid对进程执行冻结
  14.                     
  15.               ...  
  16.                                        
  17.         }
复制代码
3.2.8 freezeBinder

freezeBinder最终调用到libbinder内里去,最终判断是否是Android设备,是的话就通过ioctl,传递BINDER_FREEZE,最终走到内核的 binder 驱动上
  1. /*
  2. frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
  3. */
  4.   public static native int freezeBinder(int pid, boolean freeze, int timeoutMs);
  5.   
  6. /*
  7. frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
  8.   */
  9.   static const JNINativeMethod sMethods[] = {
  10.         /* name, signature, funcPtr */
  11.        ...
  12.         {"freezeBinder", "(IZI)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
  13.        ...
  14.       
  15.   static jint com_android_server_am_CachedAppOptimizer_freezeBinder(JNIEnv* env, jobject clazz,
  16.                                                                   jint pid, jboolean freeze,
  17.                                                                   jint timeout_ms) {
  18.     jint retVal = IPCThreadState::freeze(pid, freeze, timeout_ms);
  19.     if (retVal != 0 && retVal != -EAGAIN) {
  20.         jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
  21.     }
  22.     return retVal;
  23. }
  24.    
  25. /*  
  26. frameworks/native/libs/binder/IPCThreadState.cpp
  27. */
  28. status_t IPCThreadState::freeze(pid_t pid, bool enable, uint32_t timeout_ms) {
  29.     struct binder_freeze_info info;
  30.     int ret = 0;
  31.     info.pid = pid;
  32.     info.enable = enable;
  33.     info.timeout_ms = timeout_ms;
  34. #if defined(__ANDROID__)   //判断是否是Android设备
  35.     if (ioctl(self()->mProcess->mDriverFD, BINDER_FREEZE, &info) < 0)  // 通过ioctl,传递BINDER_FREEZE,走到内核的 binder 驱动上
  36.         ret = -errno;
  37. #endif
复制代码
  1. tips
  2. ioctl(input/output control)是一个专用于设备输入输出操作的系统调用,该调用传入一个跟设备有关的请求码,系统调用的功能完全取决于请求码。
复制代码
3.2.9 setProcessFrozen

setProcessFrozen是一个native函数,会调用到android_util_Process的android_os_Process_setProcessFrozen函数,在此函数里会调用cgroup中间抽象层libprocessgroup的API,通过cgroup自己的freezer子系统来实现进程冻结功能
  1.   /*
  2.   frameworks/base/core/java/android/os/Process.java
  3.   */
  4.   /**
  5.      * Freeze or unfreeze the specified process.
  6.      *
  7.      * @param pid Identifier of the process to freeze or unfreeze.
  8.      * @param uid Identifier of the user the process is running under.
  9.      * @param frozen Specify whether to free (true) or unfreeze (false).
  10.      *
  11.      * @hide
  12.      */
  13.     public static final native void setProcessFrozen(int pid, int uid, boolean frozen);
  14.    
  15.     ...
  16.   /*  
  17.   frameworks/base/core/jni/android_util_Process.cpp
  18.   */
  19.     static const JNINativeMethod methods[] = {
  20.          {"setProcessFrozen", "(IIZ)V", (void*)android_os_Process_setProcessFrozen},   
  21.     }
  22.    
  23.     ....
  24.    
  25.     void android_os_Process_setProcessFrozen(
  26.         JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze)
  27. {
  28.     bool success = true;
  29.     if (freeze) {
  30.         success = SetProcessProfiles(uid, pid, {"Frozen"});
  31.     } else {
  32.         success = SetProcessProfiles(uid, pid, {"Unfrozen"});
  33.     }
  34.     if (!success) {
  35.         signalExceptionForGroupError(env, EINVAL, pid);
  36.     }
  37. }
复制代码
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的路径下
  1. {
  2.   "Cgroups": [
  3.     {
  4.       "Controller": "blkio",   
  5.       "Path": "/dev/blkio",      
  6.       "Mode": "0775",            
  7.       "UID": "system",
  8.       "GID": "system"                                                                                                                       
  9.     },
  10.     {
  11.       "Controller": "cpu",
  12.       "Path": "/dev/cpuctl",
  13.       "Mode": "0755",
  14.       "UID": "system",
  15.       "GID": "system"
  16.     },
  17.     {
  18.       "Controller": "cpuset",
  19.       "Path": "/dev/cpuset",
  20.       "Mode": "0755",
  21.       "UID": "system",
  22.       "GID": "system"
  23.     },
  24.     {
  25.       "Controller": "memory",
  26.       "Path": "/dev/memcg",
  27.       "Mode": "0700",
  28.       "UID": "root",
  29.       "GID": "system",
  30.       "Optional": true
  31.     }
  32.   ],
  33.   "Cgroups2": {
  34.     "Path": "/sys/fs/cgroup",
  35.     "Mode": "0775",
  36.     "UID": "system",
  37.     "GID": "system",
  38.     "Controllers": [
  39.       {
  40.         "Controller": "freezer",
  41.         "Path": "."
  42.       }
  43.     ]
  44.   }
复制代码
  1. tips
  2. cgroups文件可能不止有一个
  3. 一般有三类:
  4. /system/core/libprocessgroup/profiles/cgroups.json   //默认文件
  5. /system/core/libprocessgroup/profiles/cgroups_<API level>.json   //API级别的文件
  6. /vendor/xxx/cgroups.json   //自定义文件
  7. 三种文件加载顺序是:默认 -> 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,我们继续往下看调用的流程
  1. {
  2.   "Attributes": [
  3.     {
  4.       "Name": "LowCapacityCPUs",
  5.       "Controller": "cpuset",
  6.       "File": "background/cpus"
  7.     },
  8.     {
  9.       "Name": "HighCapacityCPUs",
  10.       "Controller": "cpuset",
  11.       "File": "foreground/cpus"
  12.     },
  13.     ....
  14.     {
  15.       "Name": "FreezerState",
  16.       "Controller": "freezer",
  17.       "File": "cgroup.freeze"
  18.     }
  19.   ],
  20.    "Profiles": [
  21.        ...
  22.        {
  23.       "Name": "Frozen",
  24.       "Actions": [
  25.         {
  26.           "Name": "SetAttribute",
  27.           "Params":
  28.           {
  29.             "Name": "FreezerState",
  30.             "Value": "1"
  31.           }
  32.         }
  33.       ]
  34.     },
  35.     {
  36.       "Name": "Unfrozen",
  37.       "Actions": [
  38.         {
  39.           "Name": "SetAttribute",
  40.           "Params":
  41.           {
  42.             "Name": "FreezerState",
  43.             "Value": "0"
  44.           }
  45.         }
  46.       ]
  47.     },
  48.     ...
  49.     "AggregateProfiles": [
  50.     ...
  51.     {
  52.       "Name": "SCHED_SP_BACKGROUND",
  53.       "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
  54.     },
  55.     ...
复制代码
3.3.3 processgroup.SetProcessProfiles

调用TaskProfiles::GetInstance()来获取单例对象,然后调用SetProcessProfiles方法
  1. bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
  2.     return TaskProfiles::GetInstance().SetProcessProfiles(
  3.             uid, pid, std::span<const std::string>(profiles), false);
  4. }
复制代码
3.3.4 TaskProfiles.SetProcessProfiles

首先获取我们要实验的这个profile,就是刚才上面写入的“Frozen”,然后ExecuteForProcess方法去实验
  1. bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid, std::span<const T> profiles,
  2.                                       bool use_fd_cache) {
  3.     bool success = true;
  4.     for (const auto& name : profiles) {
  5.         TaskProfile* profile = GetProfile(name);
  6.         if (profile != nullptr) {
  7.             if (use_fd_cache) {
  8.                 profile->EnableResourceCaching(ProfileAction::RCT_PROCESS);
  9.             }
  10.             if (!profile->ExecuteForProcess(uid, pid)) {        
  11.                 LOG(WARNING) << "Failed to apply " << name << " process profile";
  12.                 success = false;
  13.             }
  14.         } else {
  15.             LOG(WARNING) << "Failed to find " << name << " process profile";
  16.             success = false;
  17.         }
  18.     }
  19.     return success;
  20. }
复制代码
3.3.5 TaskProfiles.ExecuteForProcess

先调用GetPathForProcess获取对应profile对应的path,然后调用WriteValueToFile方法写入指定的文件,这个文件就是上面”Attributes“我们定义的File,Frozen对应的就是cgroup.freeze节点,
  1. bool SetAttributeAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
  2.     std::string path;
  3.     if (!attribute_->GetPathForProcess(uid, pid, &path)) {
  4.         LOG(ERROR) << "Failed to find cgroup for uid " << uid << " pid " << pid;
  5.         return false;
  6.     }
  7.     return WriteValueToFile(path);
  8. }
  9. bool SetAttributeAction::WriteValueToFile(const std::string& path) const {
  10.     if (!WriteStringToFile(value_, path)) {
  11.         if (access(path.c_str(), F_OK) < 0) {
  12.             if (optional_) {
  13.                 return true;
  14.             } else {
  15.                 LOG(ERROR) << "No such cgroup attribute: " << path;
  16.                 return false;
  17.             }
  18.         }
  19.         // The PLOG() statement below uses the error code stored in `errno` by
  20.         // WriteStringToFile() because access() only overwrites `errno` if it fails
  21.         // and because this code is only reached if the access() function returns 0.
  22.         PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
  23.         return false;
  24.     }
  25.     return true;
  26. }
复制代码
总结来说就是冻结的流程走到最后就是调用了"Frozen"这个profile,将/sys/fs/cgroup路径下面对应的cgroup.freeze节点,写值为1,反之解冻就是将值写为0.
这就是Cgroup 中间抽象层的作用,但是将这个节点写为1以后,怎样使用这个节点值,又是怎样实现进程的冻结,继续看kernel内里怎样使用这个节点。
3.3.6 cgroup.cgroup_base_files

当修改cgroup.freeze这个节点的值的时候,就会触发cgroup_freeze_write方法来进项处理惩罚
  1. androids/kernel-5.10/kernel/cgroup/cgroup.c
  2. /* cgroup core interface files for the default hierarchy */
  3. static struct cftype cgroup_base_files[] = {
  4. ....
  5. {
  6.                 .name = "cgroup.freeze",
  7.                 .flags = CFTYPE_NOT_ON_ROOT,
  8.                 .seq_show = cgroup_freeze_show, //读的时候会调用,例如cat cgroup.freeze,会调用这个方法来显示当前状态
  9.                 .write = cgroup_freeze_write, //写的时候调用,当写入新的值的时候,会触发此方法。例如 echo 1 >cgroup.freeze
  10.         },
  11. ...
  12. };
复制代码
3.3.7 cgroup.cgroup_freeze_write

调用cgroup_freeze方法
  1. static ssize_t cgroup_freeze_write(struct kernfs_open_file *of,
  2.                                    char *buf, size_t nbytes, loff_t off)
  3. {
  4.         struct cgroup *cgrp;
  5.         ssize_t ret;
  6.         int freeze;
  7.         ret = kstrtoint(strstrip(buf), 0, &freeze);
  8.         if (ret)
  9.                 return ret;
  10.         if (freeze < 0 || freeze > 1)   //如果写入的值不是0或者1  就返回异常
  11.                 return -ERANGE;
  12.         cgrp = cgroup_kn_lock_live(of->kn, false);
  13.         if (!cgrp)
  14.                 return -ENOENT;
  15.         cgroup_freeze(cgrp, freeze); //调用cgroup_freeze方法
  16.         cgroup_kn_unlock(of->kn);
  17.         return nbytes;
  18. }
复制代码
3.3.8 freezer.cgroup_freeze

会遍历当前cgroup的子cgroup,将父cgroup的状态传递到子cgroup中,如果父cgroup被冻结或者解冻,他的全部子cgroup也会被冻结或者解冻,然后调用cgroup_do_freeze去实验冻结动作
  1. void cgroup_freeze(struct cgroup *cgrp, bool freeze){
  2.     ...
  3.     /*
  4.          * Propagate changes downwards the cgroup tree.
  5.          */
  6.     css_for_each_descendant_pre(css, &cgrp->self) {    //将更改沿着 cgroup 树向下传播  这里用来遍历当前控制组(
  7.                                                        // cgrp)的所有后代控制组。它按先序遍历的顺序递归遍历树状结构中的控制组
  8.                 dsct = css->cgroup;
  9.      if (freeze) {                                    //如果freeze为真即请求冻结操作,代码会将后代控制组的
  10.                                                       //e_freeze 计数器递增。e_freeze 是一个计数器,用于跟踪控制组的冻结状态。
  11.                         dsct->freezer.e_freeze++;
  12.                         /*
  13.                          * Already frozen because of ancestor's settings?
  14.                          */
  15.                         if (dsct->freezer.e_freeze > 1)              //如果计数器在递增后大于1,表示该控制组已经因为其祖先的设置而被冻结,那么就继续处理下一个控制组。
  16.                                 continue;
  17.                 } else {                                        //反之就是解冻操作
  18.                         dsct->freezer.e_freeze--;
  19.                         /*
  20.                          * Still frozen because of ancestor's settings?
  21.                          */
  22.                         if (dsct->freezer.e_freeze > 0)
  23.                                 continue;
  24.                         WARN_ON_ONCE(dsct->freezer.e_freeze < 0);  //保证递减不会为负数
  25.                 }
  26.    ...
  27.    /*
  28.                  * Do change actual state: freeze or unfreeze.   
  29.                  */
  30.                 cgroup_do_freeze(dsct, freeze);     
  31.   ...
  32. }
复制代码
3.3.9 freezer.cgroup_do_freeze

判断是否是内核线程,如果是内核线程就跳出循环,如果不是就继续实验cgroup_freeze_task,目前cgroup冻结机制,没有对内核线程的处理惩罚
  1. static void cgroup_do_freeze(struct cgroup *cgrp, bool freeze){
  2.     ...
  3.             while ((task = css_task_iter_next(&it))) {
  4.                 /*
  5.                  * Ignore kernel threads here. Freezing cgroups containing
  6.                  * kthreads isn't supported.
  7.                  */
  8.                 if (task->flags & PF_KTHREAD)  //判断是否是内核线程,PF_KTHREAD 是内核线程的标志,表示这个任务是一个内核线程。
  9.                         continue;
  10.                 cgroup_freeze_task(task, freeze);
  11.   ...
  12.         }
  13. }
复制代码
3.3.10 freezer.cgroup_freeze_task

如果freeze为真,将jobctl中的标记位设置为 JOBCTL_TRAP_FREEZE。如果不是就扫除 JOBCTL_TRAP_FREEZE
jobctl 是一个与任务(线程)相关的控制字段,用于管理任务的控制状态。它包含多个标记位,用于不同的任务控制操作。
JOBCTL_TRAP_FREEZE是jobctl位掩码中的一个标记位,它用于控制任务的冻结状态。当该标记位被设置时,表示任务应当进入冻结状态。当该标记位被扫除时,任务将退出冻结状态。
  1. /*
  2. * Freeze or unfreeze the task by setting or clearing the JOBCTL_TRAP_FREEZE
  3. * jobctl bit.
  4. */
  5. static void cgroup_freeze_task(struct task_struct *task, bool freeze)
  6. {
  7.         unsigned long flags;
  8.         /* If the task is about to die, don't bother with freezing it. */
  9.         if (!lock_task_sighand(task, &flags))
  10.                 return;
  11.         if (freeze) {
  12.                 task->jobctl |= JOBCTL_TRAP_FREEZE;
  13.                 signal_wake_up(task, false);
  14.         } else {
  15.                 task->jobctl &= ~JOBCTL_TRAP_FREEZE;
  16.                 wake_up_process(task);
  17.         }
  18.         unlock_task_sighand(task, &flags);
  19. }
复制代码
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标记。这表示当火线程有挂起的信号必要处理惩罚,内核在调度时会检查这个标记
  1. static inline void signal_wake_up(struct task_struct *t, bool resume)
  2. {
  3.         signal_wake_up_state(t, resume ? TASK_WAKEKILL : 0);
  4. }
  5. /*
  6. * Tell a process that it has a new active signal..
  7. *
  8. * NOTE! we rely on the previous spin_lock to
  9. * lock interrupts for us! We can only be called with
  10. * "siglock" held, and the local interrupt must
  11. * have been disabled when that got acquired!
  12. *
  13. * No need to set need_resched since signal event passing
  14. * goes through ->blocked
  15. */
  16. void signal_wake_up_state(struct task_struct *t, unsigned int state)
  17. {
  18.         set_tsk_thread_flag(t, TIF_SIGPENDING);
  19.         /*
  20.          * TASK_WAKEKILL also means wake it up in the stopped/traced/killable
  21.          * case. We don't check t->state here because there is a race with it
  22.          * executing another processor and just now entering stopped state.
  23.          * By using wake_up_state, we ensure the process will wake up and
  24.          * handle its death signal.
  25.          */
  26.         if (!wake_up_state(t, state | TASK_INTERRUPTIBLE))  //TASK_INTERRUPTIBLE 表示该任务是可被中断的,因此可以被信号唤醒。
  27.                                                      //如果 wake_up_state 返回 false(即任务未被唤醒),则调用 kick_process(t); 来确保任务被唤醒并能够处理信号。
  28.                 kick_process(t);
  29. }
复制代码
综上,全部要被冻结的任务,都将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。
  1. void do_notify_resume(struct pt_regs *regs, unsigned long thread_info_flags)
  2. {
  3. .....
  4.         if (thread_info_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL)) {
  5.                 BUG_ON(regs != current->thread.regs);
  6.                 do_signal(current); //如果有_TIF_SIGPENDING状态,会去执行do_signal
  7.         }
  8. .....
  9. }
复制代码
3.3.13 signal.do_signal

通过get_signal函数,检测是否有信号待处理惩罚,
  1. static void do_signal(struct task_struct *tsk){
  2.     struct ksignal ksig = { .sig = 0 };    // 初始化一个 ksignal 结构体,初始信号值为 0
  3.     ...
  4.     get_signal(&ksig); // 检查是否有信号待处理,如果有,将其存储在 ksig 中
  5.     ...
  6. }
复制代码
3.3.14 signal.get_signal

在信号处理惩罚流程中检查task中的jobctl标记位是否是JOBCTL_TRAP_FREEZ,如果是的话,就实验冻结操作,当前这个函数也和我们最开始通过PS判断进程冻结状态时,看到进程的WCHAN是在do_freezer_trap上面对应了起来,最终在内核上面实验的冻结函数就是do_freezer_trap
  1. bool get_signal(struct ksignal *ksig)
  2. {
  3. ...
  4. if (unlikely(current->jobctl &
  5.                              (JOBCTL_TRAP_MASK | JOBCTL_TRAP_FREEZE))) {
  6.                         if (current->jobctl & JOBCTL_TRAP_MASK) {
  7.                                 do_jobctl_trap();
  8.                                 spin_unlock_irq(&sighand->siglock);
  9.                         } else if (current->jobctl & JOBCTL_TRAP_FREEZE)
  10.                                 do_freezer_trap();                       //如果jobctl中的标志位为 JOBCTL_TRAP_FREEZE,开始执行真正的冻结操作
  11.                         goto relock;
  12.                 }
  13. ...
复制代码
3.3.15 signal.do_freezer_trap

在冻结方法里会走到freezable_schedule()冻结进程调度方法内里去,然后调用schedule()方法,具体内核的调度战略就不细谈,重要相识这里的schedule() 是 Linux 内核中管理任务调度的关键接口。它负责将当进步程的CPU时间让给其他进程,并确保系统能够根据调度战略高效运行。
  1. /**
  2. * do_freezer_trap - handle the freezer jobctl trap
  3. *
  4. * Puts the task into frozen state, if only the task is not about to quit.
  5. * In this case it drops JOBCTL_TRAP_FREEZE.
  6. *
  7. * CONTEXT:
  8. * Must be called with @current->sighand->siglock held,
  9. * which is always released before returning.
  10. */
  11. static void do_freezer_trap(void)
  12.         __releases(&current->sighand->siglock)
  13. {
  14. .....
  15.         __set_current_state(TASK_INTERRUPTIBLE); //当前进程的状态设置为 TASK_INTERRUPTIBLE。在这个状态下,进程是可中断的,等待某些事件(如信号或定时器)时进入睡眠
  16.         clear_thread_flag(TIF_SIGPENDING); 、//清除 TIF_SIGPENDING 线程标志,表明此时线程认为已经处理完所有未决的信号   
  17.         spin_unlock_irq(&current->sighand->siglock);
  18.         cgroup_enter_frozen(); //表明cgroup进入冻结状态
  19.         freezable_schedule(); //进程调度挂起,让出CPU 。-------------->这里就是进程冻结真正的逻辑
  20. }
  21. /kernel-5.10/include/linux/freezer.h
  22. static inline void freezable_schedule(void)
  23. {
  24.    
  25.         freezer_do_not_count();   // 通知冻结管理机制,当前任务不应被计入活跃任务计数。通常,内核中有一个计数器,用于跟踪有多少任务仍在运行。
  26.                            //调用 freezer_do_not_count() 意味着当前任务在进入 schedule() 时将不被视为活跃的,从而允许系统在冻结期间忽略该任务
  27.                            
  28.         schedule();             //标准的调度调用,当前任务会进入睡眠状态,并且系统会调度其他任务运行。此时,任务进入了可中断的睡眠状态,等待被唤醒或其他事件发生
  29.         freezer_count();       //与freezer_do_not_count相反通知冻结管理机制任务已经恢复活跃状态,应该再次计入活跃任务计数。这通常在任务从睡眠状态恢复后调用。
  30. }
复制代码
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来整理
  1. frameworks/base/services/core/java/com/android/server/am/AppProfiler.java
  2. @GuardedBy({"mService", "mProcLock"})
  3.     private void trimMemoryUiHiddenIfNecessaryLSP(ProcessRecord app) {
  4.         if ((app.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
  5.                 || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) {
  6.             // If this application is now in the background and it
  7.             // had done UI, then give it the special trim level to
  8.             // have it free UI resources.
  9.             scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,
  10.                     "Trimming memory of bg-ui ");
  11.             app.mProfile.setPendingUiClean(false);
  12.         }
  13.     }
  14.    
  15.     @GuardedBy({"mService", "mProcLock"})
  16.     private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) {
  17.         IApplicationThread thread;
  18.         if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
  19.             try {
  20.                 if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
  21.                     Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level);
  22.                 }
  23.                 mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
  24.                         CachedAppOptimizer.UNFREEZE_REASON_TRIM_MEMORY);
  25.                 thread.scheduleTrimMemory(level);
  26.             } catch (RemoteException e) {
  27.             }
  28.         }
  29.     }
复制代码
在scheduleTrimMemoryLSP方法内里,会调用unfreezeTemporarily来临时解冻应用,然后走到unfreezeAppLSP,在解冻完成以后,会继续调用freezeAppAsyncLSP去冻结,上面讲过,这个冻结是有延时的
  1. @GuardedBy("mAm")
  2.     void unfreezeTemporarily(ProcessRecord app, @UnfreezeReason int reason, long delayMillis) {
  3.         if (mUseFreezer) {
  4.             synchronized (mProcLock) {
  5.                 // Move the earliest freezable time further, if necessary
  6.                 final long delay = updateEarliestFreezableTime(app, delayMillis);
  7.                 if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
  8.                     unfreezeAppLSP(app, reason);
  9.                     freezeAppAsyncLSP(app, delay);
  10.                 }
  11.             }
  12.         }
  13.     }
复制代码
5.1.2 dump进程信息时解冻

在进行dump时,可以看到在类似的dump方法内里会调用enableFreezer方法,传递的参数是fasle,代表当前禁止冻结,这个时候如果判断进程被冻结了,就会进行解冻,然后再dump实验完以后,在finally内里再去允许冻结
  1. frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
  2.   @Override
  3.         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
  4.             try {
  5.                 mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);
  6.                 if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
  7.                         "meminfo", pw)) return;
  8.                 PriorityDump.dump(mPriorityDumper, fd, pw, args);
  9.             } finally {
  10.                 mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
  11.             }
  12.         }
  13.     }
  14.    
  15.    frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
  16.       /**
  17.      * Enables or disabled the app freezer.
  18.      * @param enable Enables the freezer if true, disables it if false.
  19.      * @return true if the operation completed successfully, false otherwise.
  20.      */
  21.     public synchronized boolean enableFreezer(boolean enable) {
  22.         ...
  23.           if (!enable && opt.isFrozen()) {
  24.                         unfreezeAppLSP(process, UNFREEZE_REASON_FEATURE_FLAGS);
  25.         ...   
  26.     }
复制代码
5.1.3 发送和接收广播临时解冻

担当广播时,临时解冻
  1. frameworks/base/services/core/java/com/android/server/am/BroadcastQueueImpl.java
  2. private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
  3.             BroadcastFilter filter, boolean ordered, int index) {
  4.    ...
  5. if (ordered) { //有序广播
  6.             r.curFilter = filter;
  7.             filter.receiverList.curBroadcast = r;
  8.             r.state = BroadcastRecord.CALL_IN_RECEIVE;
  9.             if (filter.receiverList.app != null) {
  10.                 // Bump hosting application to no longer be in background
  11.                 // scheduling class.  Note that we can't do that if there
  12.                 // isn't an app...  but we can only be in that case for
  13.                 // things that directly call the IActivityManager API, which
  14.                 // are already core system stuff so don't matter for this.
  15.                 r.curApp = filter.receiverList.app;
  16.                 r.curAppLastProcessState = r.curApp.mState.getCurProcState();
  17.                 filter.receiverList.app.mReceivers.addCurReceiver(r);
  18.                 mService.enqueueOomAdjTargetLocked(r.curApp);
  19.                 mService.updateOomAdjPendingTargetsLocked(
  20.                         OOM_ADJ_REASON_START_RECEIVER);
  21.             }
  22.         } else if (filter.receiverList.app != null) {
  23.             mService.mOomAdjuster.unfreezeTemporarily(filter.receiverList.app,
  24.                     CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
  25.         }
  26.      ...
  27.     }   
复制代码
发送广播时,临时解冻
  1. frameworks/base/services/core/java/com/android/server/am/BroadcastQueueImpl.java
  2. public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
  3.     ...
  4.     if (sendResult) {
  5.                         if (r.callerApp != null) {
  6.                             mService.mOomAdjuster.unfreezeTemporarily(
  7.                                     r.callerApp,
  8.                                     CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
  9.                         }
  10.      ...                  
  11. }
复制代码
5.1.4 持有文件锁解冻

为了防止冰冻进程持有文件锁引起死锁。或者思量到一些特别场景下,进程在被冰冻的过程中拿住了文件锁,发现持有锁就立刻解冻。
  1. frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java
  2. @GuardedBy({"mAm"})
  3.         @Override
  4.         public void onBlockingFileLock(IntArray pids) {
  5.             if (DEBUG_FREEZER) {
  6.                 Slog.d(TAG_AM, "Blocking file lock found: " + pids);
  7.             }
  8.             synchronized (mAm) {
  9.                 synchronized (mProcLock) {
  10.             ....
  11.                     if (app != null) {
  12.                         for (int i = 1; i < pids.size(); i++) {
  13.                             int blocked = pids.get(i);
  14.                             synchronized (mAm.mPidsSelfLocked) {
  15.                                 pr = mAm.mPidsSelfLocked.get(blocked);
  16.                             }
  17.                             if (pr != null
  18.                                     && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
  19.                                 Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
  20.                                         + pr.processName + " (" + blocked + ")");
  21.                                 // Found at least one blocked non-cached process
  22.                                 unfreezeAppLSP(app, UNFREEZE_REASON_FILE_LOCKS);   //解冻原因是因为文件锁
  23.                                 break;
  24.                             }
  25.                         }
  26.             ....
  27.         }
  28.     }
复制代码
5.1.5 留意

向冻住的进程发送同步binder请求会立刻收到错误码BR_FROZEN_REPLY。
在进程进行解冻的时候,会去检查,如果在冻结期间有收到同步binder的请求,就会kill掉这个进程
  1. void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
  2.      ...
  3.    try {
  4.             int freezeInfo = getBinderFreezeInfo(pid);
  5.             if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
  6.                 Slog.d(TAG_AM, "pid " + pid + " " + app.processName
  7.                         + " received sync transactions while frozen, killing");
  8.                 app.killLocked("Sync transaction while in frozen state",
  9.                         ApplicationExitInfo.REASON_FREEZER,
  10.                         ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION, true);
  11.                 processKilled = true;
  12.             }
  13.             if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {
  14.                 Slog.d(TAG_AM, "pid " + pid + " " + app.processName
  15.                         + " received async transactions while frozen");
  16.             }
  17.          ...
  18.     }
复制代码
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企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

科技颠覆者

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表